Different interfaces based on devices?

Hey community,

due to lack of a good platform i moved this question back though i’m thinking about it for quite some while. As i lost patience a few days ago i already asked it on Quora but didn’t gain so much attraction there.

My question is: What is your best way to have completely different interfaces on for example smartphones compared to the interface you serve to the desktop browsers.

I mean of course we could do Responsive design which is great for websites. This works most of the time for iPads but especially as we have the wonderful Cordova - that helps a lot to come very close to native apps - having a more native-style interface would be helpful. Therefore Famo.us or Ionic are great starters but then you still want to have like for example a completely self built or bootstrap/semantic-ui based classical website.

DId you do such thing already? What is the best way to approach this? Have two Meteor apps connect to the same database is probably a thing that could work but then you would have to double your complete application logic. Only using Meteor.is_Cordova in conditionals would probably clutter your code? As far as i know so far there’s no folder-based solution like with /client and /server, right?

So what would you guys say would be the way to go? I’d especially keen to hear what @sashko and @slava would throw in here but of course also the rest of the community.

Thanks in advance for your input.

Frank

9 Likes

My first approach was to have everything in one app and decide to send different javascript/css to the client based on the platform. I did this via Iron:Router layout templates and CSS links in the different templates. The rendered function of the layout template would then load in additional javascript, based on the platform.

This works, but is convoluted and subverts some of the Meteor magic (all in one script, css compilation, etc).

Lately I have been checking into DDP.connect and have made some wonderful progress. Basically, you can write an app without the server and use the following code snippet to connect it to an existing app. In my case I connect the mobile version to the desktop version.

I just call this function at the beginning of my mobile app (not in Meteor.startup though)

connectToExistingBackend = function(url) {
  //
  // make a remote connection and set the global connection object to it
  Meteor.connection = DDP.connect(url);
  // make sure Accounts uses that connection
  Accounts.connection = Meteor.connection;
  //
  // this is copied from ddp/*/web.browser/packages/ddp.js
  // it makes sure all method calls are done with the correct connection
  //
  _.each(['subscribe', 'methods', 'call', 'apply', 'status', 'reconnect',                                         // 52
          'disconnect'],                                                                                          // 53
         function (name) {                                                                                        // 54
           Meteor[name] = _.bind(Meteor.connection[name], Meteor.connection);                                     // 55
         });                                                                                                      // 56
  //
  // we need re-declare the users collection so Meteor knows to use the remote one
  //
  Meteor.users = new Meteor.Collection('users');
  //
  // now that we have our act together, try to re-login
  // unfortunately Accounts seems to have already run before
  // we did the Meteor.connection = DDP.connect part, so we manually
  // need to re-check the loginToken or hot code pushes log us out every time
  //
  var token = Accounts._storedLoginToken();
  if(token)
  {
    Meteor.loginWithToken(token, function(err){
      // this is going to throw error if we logged out
      if(err) console.log(err);else console.log('loginWithToken');
    });
  }
}

With this little snippet I was able to write a mobile web.browser as well as web.cordova app and have all the Meteor magic.

8 Likes

Could you elaborate why, not to call it inside Meteor.startup?

@davethe0nly because it wouldn’t work. Whenever I called it in startup, my subscriptions would be empty. Worthwhile finding out why, but for now I am just happy that this works.

This is clearly a hack, and as I found out at the DevShop yesterday not 100% supported. I am about to file a github issue on the meteor project about this.

Microservices in https://github.com/meteorhacks/cluster provide another approach to this, and IMO would be a better long term strategy for greater scale when it makes sense to break the backend up into separate apps.

2 Likes

Juggll is a Meteor app. I know they render different templates for different devices. They run a Meteor app where everything is shared except template files. I’m not 100% sure how they’re doing it. I guess it’s somewhere around the router.

@davethe0nly I have been looking into this some more. As per meteor api docs

On a server, the function will run as soon as the server process is finished starting. On a client, the function will run as soon as the DOM is ready.

The startup callbacks are called in the same order as the calls to Meteor.startup were made.

On a client, startup callbacks from packages will be called first, followed by templates from your .html files, followed by your application code.

so all bootstrap actions that depend on the connection have already run (my explanation). The best way to actually get this done was if it was possible to set

__meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL

to the desired value, but I have not yet been able to figure out how to do that.

Just filed an issue with the meteor project

2 Likes

Thanks for fighting this out.

You approach is very interesting and i’m watching this closely.

This is exactly my question here:

Please let us know what you figure out. Thanks!

I just updated my issue filed on github meteor project about this.

The “solution” (using this loosely since I don’t know yet if there are other ramifications) is to remove autoupdate package as suggested in another post, and start the server for the mobile app with DDP_DEFAULT_CONNECTION_URL set to the desktop app.

Now everything works (except hot code pushes

UPDATE: and Cordova does not work at all, so this really isn’t a solution :frowning:

My company does this to some extent, and I accomplish this using a package I built based on bootstrap’s responsive helpers:

https://atmospherejs.com/plusmore/reactive-responsive-helpers

You can then do something like the following:

<template name="AppLayout">
    {{#visibleXs}}
        {{> mobileLayout}}
    {{/visibleXs}}
    {{#hiddenXs}}
        {{> largerLayout}}
    {{/hiddenXs}}
</template>

This works really well for us because we still have a lot of shared templates.

2 Likes

I am currently doing something similar, but that still means all the templates are being sent to the mobile device. And as my app has grown so has the size of the deployment bundle. Additionally this does not solve the problem with differing CSS/js libraries for mobile/desktop.

True.

Not an issue in our case. We use a heavily customized boostrap instead of libs for specific sizes (semantic-ui would work wonderfully as well) and I’d say at least 95% of the templates are the same. Really just a different menu template and different layout template. All content templates are responsive so I use mostly the same templates for both versions. I’m ok with people downloading an extra couple templates they won’t use to save the time of building an entirely separate app.

Most of our real logic is in microservices, so the client is pretty thin.

Obviously not a solution if you’re trying to get every ounce of performance you can, but for those willing to make a very small sacrifice to save a ton of time, it’s an option.

Two Meteor apps … use --server--mobile-server … problem solved.

@andrewreedy that is what I am playing with right now, but code reload is not happening on my cordova app. Once I start it with --mobile-server it no longer picks up new code …

OK, the only way I got it to work reliably was to explicitly DDP.connect and use the connection for my remote collections.

Meteor.remoteConnection = DDP.connect( <remote url> );
// make sure Accounts uses this so I can login
Accounts.connection = Meteor.remoteConnection;
// redeclare so it uses remote users
Meteor.users = new Meteor.Collection('users',{connection: Meteor.remoteConnection});
// now instantiate my collections and subscriptions 
Collection = new Meteor.Collection('collection,{connection: Meteor.remoteConnection});
Meteor.remoteConnection.subscribe('collection');

this way I can run my app as

meteor run ios

and still connect to the same app via web browser, and autoupdate works on the web and in Cordova.

4 Likes

Could you please elaborate on that? What are the differences between --server and --mobile-server?

As far as I understand --mobile-server is for Cordova apps to tell them to connect to a specific back-end, otherwise it would always connect to where it originally got the code from.

I don’t know what --server is, because none of the meteor options seem to support it.

Hey guys, I had this question for a long time, and tried many different things to try to accomplish it, but in the end it all becomes a lot of trouble and confusion.

Using Iron:Router and Meteor.isCordova is good until a certain point, afterwards it just gets more and more troublesome: Different styles, different layouts, and if you want to use bootstrap in your web app and want to use Ionic in your cordova app? lots of extra css and things here and there. So this was a major headache for me… until now…
just solved the issue in this thread today. Check it here: https://github.com/danielfbm/meteor-cordova-web-example

It is a very different way to create a meteor app, but I really like it, and according to this slideshow here this should be the way to go.

PS: Maybe for the first time you run the app you will get some error regarding the missing files in the .scss file. Just comment that and run it again. Afterwards those files will be present and then you can uncomment the @imports again

BTW: @jamgold your solution is also fantastic. Thank you very much for sharing!
Learned a lot reading!

4 Likes

that is pretty slick, I have to say