Building a hybrid Meteor Cordova app: share experiences?

Hi guys, I’m trying to build a (maybe quite sophisticated?) hybrid Meteor Cordova app.

The idea is to use native code where it seems to be appropriate, i.e. where I’m not able to accomplish my goals using HTML5 only. As I am a single developer with limited time, I want to focus these UI improvements on iOS first (since I really like Swift, but don’t like Java that much), and rely on the pure HTML5 variant for Android.

For iOS, I started with the Xcode template generated by Meteor and added my Swift extensions there. For performance and user experience reasons, I decided to implement my own startup pages (as standard Meteor Cordova did way too long to load), and I’m loading the Meteor app in its own Webview in the background in the meantime.

This works suprisingly well, so I am quite happy with what I’ve achieved so far. Yet, I’m still struggling with a lot of quirks regarding the hot-code-push functionality (sometimes happens to break the app completely). According to issues on GitHub, I’m quite confident that these problems are not related to my own view controllers (which I derived from Cordova’s, just to be sure I’m not missing something), but of course I cannot be 100% sure.

I also find Meteor’s Cordova documentation quite unsatisfying, as it lacks a lot of information about how to call Cordova plugins correctly, and there is no information about how collaboration with native code is set-up in a best-practice way. Don’t want to blame anyone, it’s still a young project, but I’m getting lots of gray hair due to these issues.

So I am wondering if there are any projects / people out there who would like to share experiences / code samples with me? It would be great to see how other people are using Meteor Cordova in a “non-out-of-the-box” way. So far, I only found this page:

https://www.discovermeteor.com/blog/meteor-cordova-famous-the-chill-way-to-build-apps/

It sounds promising, but unfortunately the guys do not mention how they solved the integration problems they were facing. And I gues they also would not want to disclose their code…

KR, Tom aka Waldgeist

7 Likes

I find really interesting what you have achieved. I’ve just finished an hybrid App for Android and iOS, but I could use HTML5 for all the features I needed (including push notifications, photos and maps). In any case I was wondering about the possibilities of using the Xcode and Android projects as the foundations for native app in the same way you are using SWIFT.

I agree with you about the Cordova Documentation, we had to dig a bit in order to find out how to use our own Cordova Plugins.

I will really appreciate if you can share your experience with us, focusing in the technical side :smile:

1 Like

Hi poliuk,

thanks for your reply.

Yes, I also try to use HTML5 functionality as much as possible, but there are some reasons why I want to have native code inside as well:

First of all, my (not yet very complex) Meteor webapp took about 20 seconds to load inside the app on my iPad 3. At least for me, this time delay was unacceptable for a B2C app, where people rush away quickly. I already had implemented the launch page in Swift, and it showed app in 3 seconds max, which makes a huge difference. So I was nearly discarding the Cordova idea completely, and instead program everything in Swift, using the Meteor DDP bridge to talk to the webapp backend. But of course, this would have doubled the work for the webapp and the native app, plus I would also be restricted on iOS only. So I tried to figure out if I could combine both approaches in a more “hybrid” way.

The second reason why I am heading into that direction is that I realized that some Cordova plugins aren’t implemented very well on the native side. For instance, I’m using a plugin that has a “Cancel” button to return to the calling page, but this Cancel button only works on Android, on iOS it is just not reacting. I did not find any work-around for this. So I would like to be able to replace native functionality (which my app will have to rely on quite a lot) by my own implementations if I need it - at least on iOS, where I feel a bit more comfortable than on the Android Java side.

Regarding the technical side, please feel free to ask my any questions about my code; I would also be very happy to share experiences with another developer trying to achieve the same things.

To give a first summary about what I’ve done so far:

  • Implemented my own launch page using Xcode’s native UI elements (storyboards, Swift view controllers etc.). This launch page already has enough functionality that the end-user needs for performing the first steps inside the application. This helps to “cover” the fact that the webpp won’t be ready during the first 20 secs or so.
  • Built a container view around Cordova’s webapp view, since I want to display native navigation elements on the very same page, i.e. create a mixture of native elements and Cordova, like it has been described in the article I linked in my initial post.
  • On iOS, I’ve used a “ContainerView” for this, the actual Cordova view is embedded in its child view and uses its own view controller. This also allows me to re-use the Cordova view in multiple native views.
  • Since I did not want to break the standard Cordova code (and introduce quirky side-effects), I directly direved my own view controller from Cordova’s MainViewController, just to be sure nothing essential is missing. The same I did for the AppDelegate, which (on iOS) is the central entry point for the application (don’t know what the Android counterpart looks like). From the standard AppDelegate, I only overrode the method that constructs Cordova’s standard (full-screen) web-view, since I wanted to embed this view in my container. I tried to carefully respect that everything is setup in a way that it won’t break anything (and still hope I did succeed in this :smile:)
  • To speed up user experience even more, I am loading the Cordova web view already on the (native) launch page. It is embedded there using the same container view approach, but hidden from the user. So when the user switches to the actual web view later, there is good chance that it is already up and running.
  • To make this work, I am instantiating the web view only once, in a kind of singleton view controller bound directly to my AppDelegate. Otherwise, Cordova would reload every time the user switches to the web view again from another native view. This worked surprisingly well. Even if the user switches between native and embedded web view during Meteor’s startup, the startup won’t fail and continue as it should. I hope this will still hold if I’m adding more native functionality, since I do not know how iOS handles web views that are not visible at the moment. But as I’ve bound it to the central AppDelegate, it should at least not be disposed by the garbage collector. And I also hope that this approach won’t introduce any memory leaks or something, so I am carefully monitoring memory consumption in Xcode. It’s quite high sometimes, but at least it does not seem to leak so far.

At the moment, I’m struggling a bit at implementing the handover of data between native and webapp in a reliable way. Since my webapp will startup silently in the background, it can happen that the user is too quick and executes an action on the native side, that will result in an handleOpenURL() call before the Meteor app has finished loading. This does not break the app, but the action is being discarded. Hence, I’m trying to figure out if Cordova has already implemented a kind of message queue that can be triggered in a “reliable way”. Or, to put it in other words: I’m looking for a native counterpart to “deviceready” in JS; a kind of event hook that is triggered on the native side as soon as Cordova’s web view is ready to accept handleOpenURL() calls. But I could not find anything like this so far. And Meteor’s documentation on the native side is just not existent.

4 Likes

@waldgeist @loren:

I’m sorry I haven’t responded to this earlier, I’m a bit preoccupied with work for the next release at the moment.

But I would definitely be interested in continuing this conversation. Hybrid apps are something I’m excited about myself (being primarily a native mobile developer), and I would like to hear more about your approach and experiences. I realize the current state of Cordova and of our integration isn’t always ideal for these purposes, so let’s talk about ways to improve this.

1 Like

How big is your app? I’m seeing just a couple of seconds when launching my app

No worries! Looking forward to your next release :smile:

I’ve been working on a single cordova app since the Meteor Cordova integration came out. Came across a lot of small things, but the improvements I think would be most impactful are:

  • render speed: using WKWebView and Crosswalk by default, or making sure the packages work well and are pointed to in the docs.
  • working native accounts: eg there are a number of facebook+cordova meteor packages, and afaict all of them have bugs, and oauth is hard to debug.
  • hot code push downloaded in background thread, and next time app is opened, it immediately loads with the new code (versus the current reload-on-resume behavior of the page refreshing after you open and see the old page). and isDownloadingUpdate var. https://github.com/meteor/meteor/issues/4876

The thread-related suggestion was to provide an API (or if it already exists, explain in docs) to change more things in cordova build output:

@benjick Mine is 20k nodes. Initial load is just a few sec, but rendering new templates + removing templates from DOM (page navigation and infinite scroll pagination) can take a couple seconds.

Hi Martijn,

nice to hear from you, looking forward to continuing this conversation with you!

Regarding ways to improve the Cordova integration: One thing that springs to my mind first is a better way of controlling the hot code push feature. Of course, this is a nice thing, but as it may have side-effects on the user’s experience, I would prefer being able to give the user the choice when to update an app. It would be awesome if I could implement a feature that informs the user actively (e.g. using a kind of notification bar): “There’s a new version of this app available. Would you like to update?” And only if the user clicks on “yes”, a hot code push happens. Of course, this could be combined with a “silent hot code push” happening only when the user opens the app. But the latter should also be controllable.

The second improvement I would love was a kind of “best practice implementation” for a hybrid app using its own iOS storyboards that embeds Cordova via a WebView at some place. I tried to implement this myself, as mentioned above, but I’m still facing problems that I cannot really explain. For instance, sometimes my app crashes on startup. The crash can be tracked down to a “BAD ACCESS” error. Obviously, a message is sent to an object that has already been disposed by memory management. After turning on the “Zombie objects detection feature” of Xcode, I was able to track this error down to a call that seems to be related to an animation effect. I assumed it has something to do with the slow fade-out of the lauch screen, but even after disabling this feature the crash sometimes happens. I have absolutely no clue why this happens, and it is happening in a non-deterministic way.

So even though I tried to mimic the initialization of Cordova’s own view controllers as best as I could, there might be something I’m still missing (or, there is a bug in Cordova itself, I just don’t know). A kind of “reference implementation” for a hybrid app would help a lot here.

I’ve also noticed that Cordova’s standard implementation of the App Delegate is missing a lot of protocols that the standard Swift implementation is using. And I’m wondering if this will cause side-effects when implementing other things in the future. To prevent any potential problems, I’ve derived my own App Delegate directly from Cordova’s, but I’m not having a good feeling with this, due to the missing standard protocols.

Another enhancement that would be very welcome is a kind of “realiable messaging system” that passes data between the native app and the embedded Meteor application. Messages sent before the webapp side is ready should be cashed, and the messages should also be able to “survive” a hot code push.

BTW: I think I’ve found a major bug in Cordova’s implementation of the handleOpenURL() method call in iOS which also effects Meteor: https://issues.apache.org/jira/browse/CB-9485. In essence, this bug means that long URLs are being truncated due to a very strange behaviour of Apple’s NSURL implementation. This bug can also be found in the latest Cordova version, so I had to patch this manually to make things work.

KR, Tom

1 Like

@benjick: Strange. Mine is not that big at the moment, as it is still in an early development stage. Maybe it’s loading slower on older iOS devices, I’m using an iPad 3?

Hi there.
I am also trying to find a accurate way to mix a meteor/cordova view with a native MapboxGL view (for smooth usage and offline mode).
In short this is a tourism app mixing a map view, some items list/details views and user account.

The big issue is that I am completely new to Xcode development but not so bad in JS and OOP. So diving into Swift is an exciting opportunity.

My first idea is to let Meteor handle 80% of the app (Menus, datas, paginations, user) and let the 20% (the map) to a native view.

I first tried this tutorial but I am stuck to the first build with a black screen. As the tutorial use Xcode 5.1.1 and a old Cordova version I guess there is some too old stuff in there.

Have you got any barebone project to dive into? Or maybe an online ressource which could teach me how to achieve this?

Hi vikti,

so you are trying to set-up the same thing as I do, that’s great. Would be happy to share experiences with you.

Regarding the Devgirl tutorial: Yes, that’s one thing I also tried, but I did not suceed in making my app to run in combination with Meteor. I had a lot of problems and ran into build issues caused by weird compiler settings I did not really understand. In the end, I used the standard Cordova project created by Meteor and compared each and every compiler setting until I found the one that was causing my problems (“No Common Blocks” had to be set to “No”). But I don’t think that’s your problem right now if you already managed to build the project but are facing a black screen.

If you start your application, what do you see in your Xcode log? Does Meteor try to start up (this will cause a long log of messages)? And the black screen you’re seeing: Is this a full-black screen, or is it the Meteor launch page?

Hi, thanks for your reply. I have just tried the Devgirl tutorial with a blank Cordova project, not with Meteor. For the moment I am stuck with the Crodova loading screen, then a black screen. The Xcode log says:

2015-10-08 14:44:39.753 MapboxGLNativeCordova[19419:442260] DiskCookieStorage changing policy from 2 to 0, cookie file: file:///Users/vikti/Library/Developer/CoreSimulator/Devices/0DF7011F-0444-4DC9-9344-654CDF273551/data/Containers/Data/Application/C479C79F-0247-4244-ACCB-2234EBAB9398/Library/Cookies/io.cordova.example.binarycookies
2015-10-08 14:44:40.296 MapboxGLNativeCordova[19419:442260] Failed to instantiate the default view controller for UIMainStoryboardFile 'Main' - perhaps the designated entry point is not set?

However, I think your method is smarter. Indeed, the Xcode Cordova project built by Meteor will be refreshed very frequently. It is better to have a more constant project embedding some changing ones.
You seam to have the same setup than Mapbox for their React Native component.

I’m not an Xcode expert myself, but it logs like if your storyboard entry point is missing (the arrow pointing to your first view controller). So iOS does not know which view to show. In the treeview on the left side of the storyboard this shows up as “Storyboard Entry Point” at the view controller where it is attached to.

Regarding the overall Xcode project setup, here’s how I did it:

  1. Run a meteor build with the --directory option to export a complete build to some directory.
  2. Copy the iOS part of this export to a directory where you want to maintain your Xcode project in the future…
  3. In this copy, delete the www/ folder and the config.xml file
  4. Run meteor run -ios in the Meteor folder to create a local development build under the .meteor folder.
  5. In the folder created in step 2, create symlinks for the config.xml and the www/ folders that point to the corresponding development build folders

This symlinking allows me to always have the latest version of the Meteor app when I build the app in Xcode. I did not symlink the Cordova plugin folders as well, because I wanted to have full control over what is actually installed in my app. So each time I add a new plugin, I have to update the corresponding Cordova files in my copy. But of course, you could symlink these files as well.

(I’m not really sure if step 1 could be replaced by directly copying the result of the meteor run -ios-device instead, but I wanted to make sure that I get the build version of everything for my app, just in case Meteor patches something in the final build phase.)

2 Likes

Thank you very much for the steps. It works. Now I have to learn a bit of Xcode before diving into the ContainerView.

@waldgeist We’ve released our messenger in Google Play Store a few days ago. We’ve used Meteor in combination with Framework7 (http://www.idangero.us/framework7/). Both work great together and the only bad thing I’ve discovered yet were some performance issues, that most of the Cordova apps have to deal with (like “lagging” if your result list is too long or problems with a slow keyboard).

Could you elaborate how you use Framework7 with meteor? My last info was, that framework7 had/has issues with meteors routing / reactivity…?

In the process of assessing Meteor + React + Framework 7

@dinos Well, we’ve used F7 in combination with Iron Router and Blaze. You have to initialize a F7 instance on every Template.xxx.onRendered(). Here an example how we did it:

A simple template:
Template.profile.onRendered(function() { initLayout(); });

Our initial function (initLayout.js):

myApp = null; //global
$$ = Dom7; //global


initLayout = function()
{
if(myApp == null)
{
    myApp = new Framework7({
        material: true, //Android
        materialRipple: false, //having some performance problems....
        showBarsOnPageScrollEnd: false
    });

}
else
{
 myApp.init();
}
}

I didn’t experience any issues with Meteors reactivity, yet. The only thing you have to deal with is to initialize some plugins if you use them on dynamically added items (f.e. a card that uses the F7 Swiper Slider inside).

In this case, you have to initialize each slider after it has been added to the DOM (we use Meteor ._uihooks to check for new elements). But that issue you’ll have with third party jQuery plugins too, so I think that isn’t a problem.

BTW: We’ve used the nobutakaoshiro:framework7-ios-material package.

1 Like

Thanks for the quick reply! So the page transitions / animations still work this way for you guys?

@dinos For page transitions we use ccorcos:transitioner, to have a better control of our Iron Router routes. Other animations like the F7 popup / animated tab bar or the hiding navbar are working fine.

2 Likes

@XTA: Framework7 looks promising. But I read on the project home-page that it loads files via AJAX. Doesn’t that conflict with Blaze? How did you use this?

@waldgeist You can disable the automatic ajax linking during initialization (http://www.idangero.us/framework7/docs/init-app.html), f.e.:

ajaxLinks:‘a.forceajax’

Also you can disable the F7 router to use, f.e., Iron Router.