MobileFirst Foundation 8.0 Developer Labs


Pre-requisites:
Follow the Setup section to install an Ubuntu image, install the MobileFirst Developer Kit, archetypes and CLI and register at IBM Cloud.
You can download videos for offline usage from from Dropbox or OneDrive (2.62 - 2.66)
Setup
The recommended way to setup your workstation for these labs is to download the prepared VMware image that has all required software installed and ready to be used. However, if you want to do this on your own you will need to setup the dependencies listed below.
- Google Chrome CORS extension
- Visual Studio Code
- Oracle JDK 7
- Android SDK
- Google Play Services
- Android Support repository
- Google Repository
- API v23
- Build tools v23
- Intel x86 Atom_64 image v23
- curl
- nodejs
- npm
- bower
- gulp
- cordova
- ionic@2.0.0-beta.32
- cdvlive
- maven
- git
- python
If you will be using ubuntu-based x64 distro, additional packages are required: lib32z1, lib32ncurses5, lib32bz2-1.0, and lib32stdc++6.
Finally, do not forget to set JAVA_HOME, ANDROID_HOME, MAVEN_HOME and M2_HOME and add them to your PATH.
Alternatively to watching labs on YouTube, you can download offline version from Dropbox or OneDrive. The videos are located inside the "labs_en" folder and have "webm" extension. VLC, same as most of media players and browser will be able to play them.
Download and setup the VM image
- Download the VMware image archive from Dropbox or OneDrive (7.7 GB)
- Import the downloaded .ova file into the latest version of VMware Player / VMware Fusion / VMware Workstation
- Set the VM settings to at least 2 CPU cores and 4GB of RAM; Enable VT-x/EPT support (may also be required to enable it in the computer BIOS as well)
- Start the VM image
- Use user "ibm" and password: “QQqq1234″ to log in. Same password is set for sudo
Download and install the MobileFirst Developer Kit
- Follow the MobileFirst Developer Kit setup instructions and install devkit into /home/ibm/dev/server/ folder
- Install the MobileFirst CLI by running from Terminal:
npm install -g mfpdev-cli
- If you expect to not have an Internet connection available, install the adapter archetypes from the Download Center in the MobileFirst Operations Console.
Register on IBM Cloud
- Navigate to registration page, fill required info and register.
- Check your mailbox and verify email
- Make sure that you are able to login inside IBM Cloud console
Part 1
Lab highlights
- Moving existing app to Mobile Foundation 8.0
- Uses Ionic v2 beta with Typescript
- Development using CLI and MS Visual Studio Code
- Using Java and Javascript adapters for backend calls
- User authentication
- Push notifications messaging
- JSONStore
- Uses Google Maps Distance Matrix API
- Uses Node.js as mock server
Time to complete: up to 4 hours
Lab steps
Time to complete: 30 minutes
Lab number for offline usage: 2.62
Lab helpers:
Repository URL
https://github.com/andriivasylchenko/advancedMessenger
Catching events
renderer.listenGlobal('document', 'mfpjsloaded', () => {
console.log('--> MobileFirst API init complete');
this.MFPInitComplete();
})
Init complete function
MFPInitComplete(){
console.log('--> MFPInitComplete function called')
this.rootPage = TabsPage;
}
Link to completed lab github repository branch
Time to complete: 40 minutes
Lab number for offline usage: 2.63
Lab helpers:
Adapters examples repository URL
https://github.com/MobileFirst-Platform-Developer-Center/Adapters/tree/release80
Employee Javascript adapter getRating()
function getRating() {
var endpoint = MFP.Server.getPropertyValue("endpoint");
var input = {
method : 'get',
returnedContentType : 'json',
path : endpoint
};
return MFP.Server.invokeHttp(input);
}
News Java adapter JSONObject parsing
JSONObject result = JSONObject.parse(JSONResponse.getEntity().getContent());
String json = result.toString();
Employee provider load function
load() {
console.log('---> called EmployeeProvider load');
if (this.data) {
return Promise.resolve(this.data);
}
return new Promise(resolve => {
let dataRequest = new WLResourceRequest("/adapters/employeeAdapter/getRating", WLResourceRequest.GET);
dataRequest.send().then((response) => {
console.log('--> data loaded from adapter', response);
this.data = response.responseJSON.results;
resolve(this.data)
}, (failure) => {
console.log('--> failed to load data', failure);
resolve('error')
})
});
}
News provider load function
load() {
console.log('---> called NewsProvider load');
if (this.data) {
return Promise.resolve(this.data);
}
return new Promise(resolve => {
let dataRequest = new WLResourceRequest("/adapters/JavaHTTP/", WLResourceRequest.GET);
dataRequest.send().then((response) => {
console.log('--> data loaded from adapter', response);
this.data = response.responseJSON.news;
resolve(this.data)
}, (failure) => {
console.log('--> failed to load data', failure);
resolve('error')
})
});
}
Link to completed lab github repository branch
Time to complete: 50 minutes
Lab number for offline usage: 2.64
Lab helpers:
AuthInit function
AuthInit(){
this.AuthHandler = WL.Client.createSecurityCheckChallengeHandler("UserLogin");
this.AuthHandler.handleChallenge = ((response) => {
console.log('--> inside handleChallenge');
if(response.errorMsg){
var msg = response.errorMsg + '<br>';
msg += 'Remaining attempts: ' + response.remainingAttempts;
}
this.displayLogin(msg);
})
}
displayLogin function
displayLogin(msg) {
let prompt = Alert.create({
title: 'Login',
message: msg,
inputs: [
{
name: 'username',
placeholder: 'Username'
},
{
name: 'password',
placeholder: 'Password',
type: 'password'
},
],
buttons: [
{
text: 'Login',
handler: data => {
console.log('--> Trying to auth with user', data.username);
this.AuthHandler.submitChallengeAnswer(data);
}
}
]
});
this.nav.present(prompt);
}
Getting active nav
ngAfterViewInit(){
this.nav = this.app.getActiveNav();
}
Link to completed lab github repository branch
Time to complete: 30 minutes
Lab number for offline usage: 2.65
Lab helpers:
Push provider
import {Injectable} from '@angular/core';
@Injectable()
export class PushProvider {
data: any = null;
constructor() {}
init() {
console.log('--> PushProvider init called');
MFPPush.initialize(
function(success){
console.log('--> Push init success');
MFPPush.registerNotificationsCallback(pushNotificationReceived);
var options = {"phoneNumber": ""};
MFPPush.registerDevice(
options,
function(success){
console.log('--> Push registration success');
var tag = ['am'];
MFPPush.subscribe(
tag,
function(success){
console.log('--> Push subscribe success');
},
function(failure){
console.log('--> Push subscribe failure', failure);
}
)
},
function(failure){
console.log('--> Push registration failure', failure);
}
)
}, function(failure){
console.log('--> Push init failure', failure);
})
function pushNotificationReceived(message){
console.log('--> Push received', message);
alert(message.alert);
}
}
}
Link to completed lab github repository branch
Time to complete: 45 minutes
Lab number for offline usage: 2.66
Lab helpers:
Storage provider
import {Injectable} from '@angular/core';
@Injectable()
export class StorageProvider {
data: any = null;
constructor() {}
init() {
console.log('--> JSONStore init function called');
let collections = {
news: {
searchFields: {text: 'string', date: 'string'}
}
}
WL.JSONStore.init(collections).then((success) => {
console.log('-->JSONStore init success')
}, (failure) => {
console.log('-->JSONStore init failed', failure)
})
}
put(data){
console.log('--> JSONStore put function called');
let collectionName = 'news';
let options = {
replaceCriteria: ['text', 'date'],
addNew: true,
markDirty: false
};
WL.JSONStore.get(collectionName).change(data, options).then((success) => {
console.log('--> JSONStore put success')
}, (failure) => {
console.log('--> JSONStore put failed', failure)
})
}
getAll() {
console.log('--> JSONStore get all function called');
return new Promise( resolve => {
let collectionName = 'news';
let options = {};
WL.JSONStore.get(collectionName).findAll(options).then((success) => {
console.log('-->JSONStore get docs success', success)
resolve(success);
}, (failure) => {
console.log('-->JSONStore get docs failed', failure)
resolve('error');
})
})
}
}
News provider load function
load() {
console.log('---> called NewsProvider load');
let dataRequest = new WLResourceRequest("/adapters/JavaHTTP/", WLResourceRequest.GET);
dataRequest.send().then((response) => {
console.log('--> data loaded from adapter', response);
this.data = response.responseJSON.news;
console.log('--> puttin data to JSONStore');
this.storage.put(this.data);
}, (failure) => {
console.log('--> failed to load data', failure);
})
}
Link to completed lab github repository branch
Part 2
Lab highlights
- Authenticating external resources
- Using Direct Update and Remote Disable
- Mobile Operational Analytics
- Security scan of application binary
- Bug reporting and user sentiment analysis
- Moving Node.js app to IBM Cloud using Cloud Foundry Node runtime
- Creating Mobile Foundation instance on IBM Cloud
- Preparing mobile application for distribution
Time to complete: up to 4 hours
Lab steps
Time to complete: 45 minutes
Lab number for offline usage: 2.70
Lab helpers:
Schedule provider
import {Injectable} from '@angular/core';
import {Http} from '@angular/http';
import 'rxjs/add/operator/map';
@Injectable()
export class ScheduleProvider {
data: any = null;
distance: any = null;
constructor(public http: Http) {}
load() {
console.log('---> called ScheduleProvider load');
if (this.data) {
// already loaded data
return Promise.resolve(this.data);
}
return new Promise(resolve => {
let dataRequest = new WLResourceRequest("http://192.168.42.169:4567/schedule", WLResourceRequest.GET);
dataRequest.send().then((response) => {
console.log('--> data loaded from adapter', response);
this.data = response.responseJSON.delivery;
resolve(this.data)
}, (failure) => {
console.log('--> failed to load data', failure);
resolve('error')
})
});
}
calc(destinations) {
console.log('---> called ScheduleProvider calc');
if (this.distance) {
// already loaded data
return Promise.resolve(this.distance);
}
return new Promise(resolve => {
let dataRequest = new WLResourceRequest("http://192.168.42.169:4567/distance", WLResourceRequest.GET);
let curtime = Date.now();
let origin = '50.019275,14.347424';
let googleParams = 'origins=' + origin + '&destinations=' + destinations + '&departure_time=' + curtime + '&traffic_model=best_guess';
console.debug('google params', googleParams);
dataRequest.setQueryParameter("origins", origin);
dataRequest.setQueryParameter("destinations", destinations);
dataRequest.setQueryParameter("departure_time", curtime);
dataRequest.setQueryParameter("traffic_model", "best_guess");
dataRequest.send().then((response) => {
console.log('--> data loaded from adapter', response);
this.distance = response.responseJSON;
console.debug('Schedule calc data', this.distance.rows[0].elements);
resolve(this.distance.rows[0].elements);
}, (failure) => {
console.log('--> failed to load data', failure);
resolve('error')
})
});
}
}
Link to completed lab github repository branch
Time to complete: 15 minutes
Lab number for offline usage: 2.71
Link to completed lab github repository branch
Time to complete: 20 minutes
Lab number for offline usage: 2.72
Lab helpers:
Handle challenge
this.AuthHandler.handleChallenge = ((response) => {
console.log('--> inside handleChallenge');
if(response.errorMsg){
var msg = response.errorMsg + '<br>';
msg += 'Remaining attempts: ' + response.remainingAttempts;
WL.Logger.error("Auth error: " + response.errorMsg);
WL.Logger.send();
}
this.displayLogin(msg);
})
Link to completed lab github repository branch
Time to complete: 5 minutes
Lab number for offline usage: 7.8
Time to complete: 25 minutes
Lab number for offline usage: 7.9
Time to complete: 35 minutes
Lab number for offline usage: 7.10
Lab helpers:
Package dependencies
"dependencies": {
"express": "4.13.x",
"cfenv": "1.0.x",
"passport-mfp-token-validation": "8.0.x",
"request": "2.74.x",
"path": "0.12.x",
"compression": "1.6.x",
"body-parser": "1.15.x",
"cors": "2.8.x"
}
Time to complete: 25 minutes
Lab number for offline usage: 6.7
Time to complete: 20 minutes
Lab number for offline usage: 9.5
Lab helpers:
Terminal commands
keytool -genkey -v -keystore identity.keystore -alias ibm -keyalg RSA -keysize 2048 -validity 10000
ionic build android --release
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore /home/ibm/dev/workspaces/am/advancedMessenger/identity.keystore /home/ibm/dev/workspaces/am/advancedMessenger/platforms/android/build/outputs/apk/android-release-unsigned.apk ibm
/home/ibm/dev/sdk/build-tools/23.0.3/zipalign -v 4 /home/ibm/dev/workspaces/am/advancedMessenger/platforms/android/build/outputs/apk/android-release-unsigned.apk /home/ibm/dev/workspaces/am/advancedMessenger/platforms/android/build/outputs/apk/advancedMessenger_0.0.1.apk
Inclusive terminology note: The Mobile First Platform team is making changes to support the IBM® initiative to replace racially biased and other discriminatory language in our code and content with more inclusive language. While IBM values the use of inclusive language, terms that are outside of IBM's direct influence are sometimes required for the sake of maintaining user understanding. As other industry leaders join IBM in embracing the use of inclusive language, IBM will continue to update the documentation to reflect those changes.