This step of the tutorial teaches us how to add mobile support for iOS and Android and how to elegantly reuse code using the es2015 modules.
In this tutorial's example we will differentiate the login part of the project: in the browser users will login using email and password and in the mobile app users will login with SMS verification.
To add mobile support, select the platform(s) you want and run the following command:
$ meteor add-platform ios
# OR / AND
$ meteor add-platform android
And now to run in the emulator, run:
$ meteor run ios
# OR
$ meteor run android
You can also run in a real mobile device, for more instructions, read the "Mobile" chapter of the Official Meteor Guide.
We're going to keep the view and the controller for the web under web.html
and web.js
and doing the same for mobile.html
and mobile.js
.
First thing to do is to move the content of login.html
to web.html
file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<md-content layout="row" layout-align="center start" layout-fill layout-margin>
<md-whiteframe layout="column" flex flex-md="50" flex-lg="50" flex-gt-lg="33" class="md-whiteframe-z2" layout-fill>
<md-toolbar class="md-primary md-tall" layout="column" layout-align="end" layout-fill>
<div layout="row" class="md-toolbar-tools md-toolbar-tools-bottom">
<h3 class="md-display-1">
Sign in
</h3>
</div>
</md-toolbar>
<div layout="column" layout-fill layout-margin layout-padding>
<div layout="row" layout-fill layout-margin>
<p class="md-body-2">
Use existing account</p>
</div>
<div layout="row" layout-fill layout-margin layout-padding layout-wrap>
<md-button class="md-raised">
<md-icon md-svg-icon="social:ic_google_24px" style="color: #DC4A38;"></md-icon>
<span>Google</span>
</md-button>
<md-button class="md-raised">
<md-icon md-svg-icon="social:ic_facebook_24px" style="color: #3F62B4;"></md-icon>
<span>Facebook</span>
</md-button>
<md-button class="md-raised">
<md-icon md-svg-icon="social:ic_twitter_24px" style="color: #27AAE2;"></md-icon>
<span>Twitter</span>
</md-button>
</div>
<md-divider class="inset"></md-divider>
<div layout="row" layout-fill layout-margin>
<p class="md-body-2">
Sign in with your email</p>
</div>
<form name="loginForm" layout="column" layout-fill layout-padding layout-margin>
<md-input-container>
<label>
</label>
<input type="text" ng-model="login.credentials.email" aria-label="email" required/>
</md-input-container>
<md-input-container>
<label>
Password
</label>
<input type="password" ng-model="login.credentials.password" aria-label="password" required/>
</md-input-container>
<div layout="row" layout-align="space-between center">
<a class="md-button" href="/password">Forgot password?</a>
<md-button class="md-raised md-primary" ng-click="login.login()" aria-label="login" ng-disabled="login.loginForm.$invalid()">Sign In
</md-button>
</div>
</form>
<md-toolbar ng-show="login.error" class="md-warn" layout="row" layout-fill layout-padding layout-margin>
<p class="md-body-1">{{ login.error }}</p>
</md-toolbar>
<md-divider></md-divider>
<div layout="row" layout-align="center">
<a class="md-button" href="/register">Need an account?</a>
</div>
</div>
</md-whiteframe>
</md-content>
Now we can create a view for a mobile version:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<md-content layout="row" layout-align="center start" layout-fill layout-margin>
<md-whiteframe layout="column" flex flex-md="50" flex-lg="50" flex-gt-lg="33" class="md-whiteframe-z2" layout-fill>
<!-- header -->
<md-toolbar class="md-primary md-tall" layout="column" layout-align="end" layout-fill>
<div layout="row" class="md-toolbar-tools md-toolbar-tools-bottom">
<h3 class="md-display-1">
Login
</h3>
</div>
</md-toolbar>
<!-- content -->
<div layout="column" layout-fill layout-margin layout-padding>
<!-- display an error -->
<md-toolbar ng-show="login.error" class="md-warn" layout="row" layout-fill layout-padding layout-margin>
<p class="md-body-1">{{ login.error }}</p>
</md-toolbar>
<md-divider></md-divider>
<!-- other actions -->
<div layout="row" layout-align="center">
<a class="md-button" href="/password">Forgot password?</a>
</div>
</div>
</md-whiteframe>
</md-content>
As you can see for now there are only three elements:
Now we have views for both versions. Let's take Login
class which is component's controller and move it to web.js
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { Meteor } from 'meteor/meteor';
export class Login {
constructor($scope, $reactive, $state) {
'ngInject';
this.$state = $state;
$reactive(this).attach($scope);
this.credentials = {
email: '',
password: ''
};
this.error = '';
}
login() {
Meteor.loginWithPassword(this.credentials.email, this.credentials.password,
this.$bindToContext((err) => {
if (err) {
this.error = err;
} else {
this.$state.go('parties');
}
})
);
}
}
Let's create an empty class with the same name but for mobile inside mobile.js
file:
1
export class Login {}
We have two modules: web and mobile. Now let's do few things with login.js
:
login.html
view.Login
class.Login
classes from mobile
and web
modules.controller
and template
variables so they can be used in component definition.Meteor.isCordova
inside conditional statement to set proper value for these variables.login.js
file should look like this:
4
5
6
7
8
9
10
11
12
13
14
15
16
17
20
21
22
23
24
25
26
27
import { Meteor } from 'meteor/meteor';
import webTemplate from './web.html';
import { Login as LoginWeb } from './web';
import mobileTemplate from './mobile.html';
import { Login as LoginMobile } from './mobile';
const name = 'login';
const template = Meteor.isCordova ? mobileTemplate : webTemplate;
const controller = Meteor.isCordova ? LoginMobile : LoginWeb;
// create a module
export default angular.module(name, [
...some lines skipped...
])
.component(name, {
template,
controller,
controllerAs: name
})
.config(config);
As I mentioned before, we're going to use SMS verification to log in a user on the mobile application.
There is a package for that!
We will use an external package that extends Meteor's Accounts, called accounts-phone that verifies phone number with SMS message, so let's add it:
$ meteor add okland:accounts-phone
Note that in development mode - the SMS will not be sent - and the verification code will be printed to the Meteor log.
Latest release of accounts-phone won't work with Meteor 1.3. Until this changes you can use
mys:accounts-phone
which fixes that issue.
This is a two-step process so let's implement the first one:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { Accounts } from 'meteor/accounts-base';
export class Login {
constructor($scope, $reactive) {
'ngInject';
$reactive(this).attach($scope);
this.isStepTwo = false;
this.phoneNumber = '';
this.verificationCode = '';
this.error = '';
}
verifyPhone() {
Accounts.requestPhoneVerification(this.phoneNumber, this.$bindToContext((err) => {
if (err) {
// display also reason of Meteor.Error
this.error = err.reason || err;
} else {
this.error = '';
// move to code verification
this.isStepTwo = true;
}
}));
}
}
Let me explain what happened:
isStepTwo
variable to check in which step we currently are.verificationCode
will be used in the second step.Accounts.requestPhoneVerification
is to verify a phone number.this.isStepTwo = true;
is to mark that we moved to the second stepNow we can implement it in the mobile.html
:
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<md-toolbar class="md-primary md-tall" layout="column" layout-align="end" layout-fill>
<div layout="row" class="md-toolbar-tools md-toolbar-tools-bottom">
<h3 class="md-display-1">
Login with SMS
</h3>
</div>
</md-toolbar>
<!-- content -->
<div layout="column" layout-fill layout-margin layout-padding>
<!-- step 1: phone verification -->
<form ng-show="!login.isStepTwo" layout="column" layout-fill layout-padding layout-margin>
<md-input-container>
<input type="text" ng-model="login.phoneNumber" placeholder="phone"/>
</md-input-container>
<md-button class="md-raised md-primary" ng-click="login.verifyPhone()" aria-label="login" ng-disabled="!login.phoneNumber">Send SMS
</md-button>
</form>
<!-- display an error -->
<md-toolbar ng-show="login.error" class="md-warn" layout="row" layout-fill layout-padding layout-margin>
<p class="md-body-1">{{ login.error }}</p>
Let's move to second step which is the code verification. We have to create a method for this:
1
2
3
4
5
6
7
8
9
10
11
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import { Accounts } from 'meteor/accounts-base';
export class Login {
constructor($scope, $reactive, $state) {
'ngInject';
this.$state = $state;
$reactive(this).attach($scope);
this.isStepTwo = false;
...some lines skipped...
}
}));
}
verifyCode() {
Accounts.verifyPhone(this.phoneNumber, this.verificationCode, this.$bindToContext((err) => {
if (err) {
this.error = err.reason || err;
} else {
// redirect to parties list
this.$state.go('parties');
}
}));
}
}
$state
service to redirect user to parties
after successful verification.Accounts.verifyPhone
is to verify a code with a phone number.Add it to the mobile.html
:
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<md-button class="md-raised md-primary" ng-click="login.verifyPhone()" aria-label="login" ng-disabled="!login.phoneNumber">Send SMS
</md-button>
</form>
<!-- step 2: code verification -->
<form ng-show="login.isStepTwo" layout="column" layout-fill layout-padding layout-margin>
<md-input-container>
<input type="text" ng-model="login.verificationCode" placeholder="code"/>
</md-input-container>
<md-button class="md-raised md-primary" ng-click="login.verifyCode()" aria-label="login" ng-disabled="!login.verificationCode">Verify Code
</md-button>
</form>
<!-- display an error -->
<md-toolbar ng-show="login.error" class="md-warn" layout="row" layout-fill layout-padding layout-margin>
<p class="md-body-1">{{ login.error }}</p>
And that's it! We've got a the same component that behaves differently for each platform!
In this tutorial we showed how to make our code behave differently in mobile and web platforms. We did this by creating separate es2015 modules with specific code for mobile and web, and using them based on the platform that runs the application.