The Ultimate Guide For Building RTA Part 2

The Ultimate Guide For Building RTA Part 2

  • 2016-11-08
  • 1018

Previously on The Ultimate Guide For Building RTA I explained how to setup a Real Time API Server and how to create a software development kit that maps the API functionality to be accessible from our {N} Chat Application.

Today we are going continue with this series and focus ourselves in coding the mobile application to start consuming our LoopBack Framework API.

Important to Notice

During the previous weeks the community and I have been putting a lot of effort on the LoopBack SDK Builder which by now is much more mature, although it still is on beta; minor issues may appear, please add a github issue if you find something.

If you started to implement my previous post during the week I published it; I need to warn you that it has been updated to implement the latest version of the [email protected] please consider in updating your project since major changes has been released.

Project Description

For this part of the series we are going to focus in start creating sign in/up & chat rooms sections for our mobile application.

I don’t think we can finish the project example in this post so I’m estimating that I will be publishing at least 1 more post next week.

Sections to create in this post:

  • Sign In / Sign Up
  • Chat Room List

Pending section/feature

  • Chat Room
  • Add people to room

Update App Structure

In part 1 we did not worked with a real Application Structure, basically we just used the main app component to test we were able to connect with our API.

But now, we need to work in a real structure and for this I would like to follow the Angular 2 Style Guide.

├─ native-chat-app
| ├─ app
| | ├─ +rooms
| | ├─ +sign
| | ├─ App_Resources
| | ├─ shared
| | | ├─ sdk
| | | ├─ utils
| | | ├─ base.api.ts
| | | ├─ index.ts
| | ├─ app.css
| | ├─ app.component.html
| | ├─ app.component.ts
| | ├─ main.ts

As you can see we do not have the sdk inside the app folder anymore, but it was moved inside the shared folder. Therefore we will need to update our native-chat-api/package.json in order to create the SDK in the right folder.

{
  "scripts": {
    "build:sdk": "./node_modules/.bin/lb-sdk server/server.js ../native-chat-app/app/shared/sdk -d nativescript2 -i enabled""
  }
}

Basically I just added a shared directory between app and the sdk directories.

Re-Build SDK

$ cd native-chat/native-chat-api
$ npm run build:sdk

Update app.component.ts

First of all, we need to modify the app.component in order to start accepting routes.

import { Component } from "@angular/core";
import { AccountApi } from './shared';
import { Router, RouteConfig } from "@angular/router-deprecated";
import { NS_ROUTER_DIRECTIVES, NS_ROUTER_PROVIDERS} from "nativescript-angular/router";
import { SignComponent } from "./+sign";
import { RoomsComponent } from "./+rooms";

@Component({
    selector: "my-app",
    directives: [ NS_ROUTER_DIRECTIVES ],
    providers: [ NS_ROUTER_PROVIDERS ],
    template: "<page-router-outlet></page-router-outlet>"
})

@RouteConfig([
  { path: "/sign", component: SignComponent, name: "SignComponent", useAsDefault: true },
  { path: "/rooms", component: RoomsComponent, name: "RoomsComponent"  }
])

export class AppComponent {
    constructor(private _router: Router, private _account: AccountApi) {
        this._router.subscribe(() => {
            if (!this._account.isAuthenticated())
            this._router.navigate(['SignComponent'])
        })
    }
}

Create +Sign Section

Fortunately and unfortunately this section relates so much with the official guide from NativeScript’s Getting Started.

I say it’s unfortunate because this section would not be the ultimate guide for anything, but it’s part of a whole, which is great.

But… I say it’s also fortunate because I can refer you to the official getting started for some parts of the sign component that are officially published.

import { Component, ViewChild, ElementRef } from [email protected]/core';
import { Router } from "@angular/router-deprecated";
import { TextField } from "ui/text-field";
import {
    LoopBackConfig,
    AccountApi,
    Account,
    setHintColor,
    BASE_URL,
    API_VERSION
} from '../shared';
//http://docs.nativescript.org/angular/tutorial/ng-chapter-4#41-ui-elements
import { Page } from "ui/page"; 
import { Color } from "color";

@Component({
    selector: 'sign',
    templateUrl: '+sign/sign.html',
    styleUrls: ['+sign/sign.css'],
    providers: []
})

export class SignComponent {
    //http://docs.nativescript.org/angular/tutorial/ng-chapter-6#61-accessing-ios-apis
    @ViewChild("email") email: ElementRef;
    @ViewChild("password") password: ElementRef;

    private account: Account = new Account();
    private isLoggingIn: boolean = true;

    constructor(
        private _account: AccountApi,
        private _router: Router,
        private _page: Page
    ) {
        LoopBackConfig.setBaseURL(BASE_URL);
        LoopBackConfig.setApiVersion(API_VERSION);
        if (this._account.isAuthenticated())
            this._router.navigate(['RoomsComponent']);
    }

    // http://docs.nativescript.org/angular/tutorial/ng-chapter-4#41-ui-elements
    ngOnInit() {
        this._page.actionBarHidden = true;
    }

    ngAfterViewInit() {
        this.setTextFieldColors();
    }

    private toggleDisplay(): void {
        this.isLoggingIn = !this.isLoggingIn;
    }

    private submit(): void {
        if (this.isLoggingIn) {
            this.signin();
        } else {
            this.signup();
        }
    }

    private signup(): void {
        this._account.create(this.account).subscribe(res => this.signin(), err => alert(err));
    }

    private signin(): void {
        this._account.login(this.account)
            .subscribe(res => this._router.navigate(['RoomsComponent']), err => alert(err));
    }

    //http://docs.nativescript.org/angular/tutorial/ng-chapter-6#61-accessing-ios-apis
    private setTextFieldColors() {
        let emailTextField = <TextField>this.email.nativeElement;
        let passwordTextField = <TextField>this.password.nativeElement;

        let mainTextColor = new Color('white');
        emailTextField.color = mainTextColor;
        passwordTextField.color = mainTextColor;

        let hintColor = new Color('white');
        setHintColor({ view: emailTextField, color: hintColor });
        setHintColor({ view: passwordTextField, color: hintColor });
    }
}

So… If you check the links I use as reference you will be able to know how to add a background image, change text field colors and know in general how to handle UI Elements.

But what I really want to explain is how we are connecting our application with our real time api, lets start with the constructor.

constructor(
    private _account: AccountApi
) {
    LoopBackConfig.setBaseURL(BASE_URL);
    LoopBackConfig.setApiVersion(API_VERSION);
    if (this._account.isAuthenticated())
        this._router.navigate(['RoomsComponent']);
}

As you can see every model api is a regular @Injectable service that can be used as any valid service for angular 2.

Also, the LoopBackConfig helpers are using a constant that we later will create in order to have 1 main place to configure the base url and api version.

Hey wait, but… What? Yes!!! I know it may be uncomfortable for some of you, but I have decided to provide the ability for every component to connect with different APIs.

Let me put an example; many projects connect the sign in/out component to a SSO API and once it is authenticated it start calling an API in another domain.

So, yes… You will need to configure these in every component, but will allow you to connect different components with different loopback providers, isn’t it a good deal?

private signup(): void {
    this._account.create(this.account).subscribe(res => this.signin(), err => alert(err));
}

private signin(): void {
    this._account.login(this.account)
        .subscribe(res => this._router.navigate(['RoomsComponent']), err => alert(err));
}

As we saw in my previous post… Thanks to the fact that the API SDK is fully typed we can take advantage of the IntelliSense feature from Visual Studio Code and TypeScript, so you can explore further and find a good amount of methods provided by the sdk.

But for now, we are using just 2 methods from the Account Model in order to create an account when registering but also to sign in.

The logic is really simple, when you create an account and after a successful registration we sign in the user and when the user is successfully logged in, then we just redirect him/her to the RoomsComponent.

Add Sign View

Create the file +sign/sign.html

<GridLayout #background
    scaleX="1.4"
    scaleY="1.4"
    class="background"></GridLayout>
<StackLayout>
    <Image src="res://logo" stretch="none" horizontalAlignment="center"></Image>
    <TextField
        #email
        hint                   = "Email Address"
        keyboardType           = "email"
        [(ngModel)]            = "account.email"
        autocorrect            = "false"
        autocapitalizationType = "none">
    </TextField>
    <TextField
        #password
        hint        = "Password"
        [(ngModel)] = "account.password"
        secure      = "true">
    </TextField>
    <Button [text]="isLoggingIn ? 'Sign in' : 'Sign up'" class="primary-btn" (tap)="submit()"></Button>
    <Button [text]="isLoggingIn ? 'Sign up' : 'Back to login'" (tap)="toggleDisplay()"></Button>
</StackLayout>

Add Style

Create the file +sign/sign.css

StackLayout {
  padding-top: 15;
  padding-bottom: 15;
}

TextField {
  text-align: center;
  color: white;
}

Button, TextField {
  margin-left: 16;
  margin-right: 16;
  margin-bottom: 10;
}

.background {
  background-image: url("res://bg");
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center;
}

Remember I’m not going in details in terms of the UI Elements because I don’t want to publish something that is already out there and even worst… Officially. So if you haven’t done it yet please follow the official NativeScript’s Getting Started for more details in terms of UI.

Add module index

Finally just add an index.ts to export the component:

export * from './sign.component';

Create +Rooms Section

Awesome!!! We are now able to sign up/in but now we need to create a component in which the user will land after a successful register or login.

import { Component } from [email protected]/core';
import { Router } from [email protected]/router-deprecated';
import {
  LoopBackConfig,
  Room,
  AccountApi,
  BASE_URL,
  API_VERSION
} from '../shared';

@Component({
  selector: 'rooms',
  templateUrl: '+rooms/rooms.component.html',
  providers: []
})

export class RoomsComponent {

  private room: Room = new Room();
  private rooms: Array<Room> = new Array();

  constructor(private _account: AccountApi) {
      LoopBackConfig.setBaseURL(BASE_URL);
      LoopBackConfig.setApiVersion(API_VERSION);
      this.getRooms();
  }

  private addRoom(): void {
    this._account.createRooms(this._account.getCurrentId(), this.room)
                 .subscribe(()=> this.room = new Room());
  }

  private getRooms(): void {
    this._account.getRooms(this._account.getCurrentId()).subscribe((rooms: Array<Room>) => {
        this.rooms = rooms;
    });
      
    this._account.onCreateRooms(this._account.getCurrentId()).subscribe((room: Room) => {
        this.rooms.push(room);
    });
  }
}

I really like this component much more than the +sign one because I’m not really using any special behavior for UI Elements, which really allows me to show you how powerful the sdk builder is. Literally you will start forgetting about:

  • The need to build your own API calls @Injectable services.
  • The need to write declaration files since interfaces and models are built in.
  • Misused wasted hours of development.
  • Tons of bugs and pain.

So guys… This is really important, I do need to make you something really clear:

You MUST directly use the service layer that the sdk provides; therefore you MUST NOT write another layer of services to make API requests.

Just remember we want to maintain our system as decoupled as we can, so really… There is no necessity to create more layers of complexity and footprint.

Built In Models

I already explained that the sdk comes with every interface and model built in, but how to differentiate between services, interfaces and models.

  • Services.- Import services by using the model name plus the ‘Api’ postfix.
  • Interfaces.- Import interfaces by using the model name plus the ‘Interface’ postfix.
  • Models.- Import models just by using the model name.

In general you will be mainly importing services and models, but there is going to be sometimes that you will need to import interfaces, since models only implement the required properties and not all of the possible properties, therefore if you find your self in a situation when importing a model declaration does not provide a property you need to use, then you will need to import the interface.

Import Service

import { AccountApi } from '../shared';

Import Interface

import { RoomInterface } from '../shared';

Import Model

import { Room } from '../shared';

RESTful API Calls

The following calls are standard RESTful API Calls that creates and list rooms

private addRoom(): void {
  this._account.createRooms(this._account.getCurrentId(), this.room)
               .subscribe(()=> this.room = new Room());
}

private getRooms(): void {
  this._account.getRooms(this._account.getCurrentId()).subscribe((rooms: Array<Room>) => {
      this.rooms = rooms;
  });
}

PubSub Subscriptions

You can listen for server publications by registering into specific events; these event methods will always start with the on prefix.

this._account.onCreateRooms(this._account.getCurrentId()).subscribe((room: Room) => {
    this.rooms.push(room);
});

This subscription event will fire every time there is a new room created for the given account id, of course we could just simply push the new room within the create method -ajax- result -as usual- but by subscribing for these events you can now create rooms and while using different devices you are now able to see how new rooms appear in every device you are connected, cool… huh?.

So if you are guessing already, if you would like to subscribe to newly created messages within a specific room you would do something like:

this._room.onCreateMessages(this.room.id).subscribe((message: Message) => {
    this.messages.push(message);
});

But this is just a sneak peak, we will be covering the messages part in my next tutorial.

Add Rooms View

Create the file +rooms/rooms.component.html

<ActionBar title="My Chat Room"></ActionBar>
<GridLayout rows="auto, *"  class="small-spacing">
  <GridLayout row="0" columns="*, auto">
    <TextField [(ngModel)]="room.name" hint="New Room Name" col="0"></TextField>
    <Button text="Add" col="1" (tap)="addRoom()"></Button>
  </GridLayout>
  <ListView [items]="rooms" row="1">
    <template let-item="item">
      <StackLayout>
        <Label [text]="item.name" class="medium-spacing"></Label>
      </StackLayout>
    </template>
  </ListView>
</GridLayout>

Add module index

Finally just add an index.ts to export the component:

export * from './rooms.component';

Configure Shared

Ok, I’m pretty sure you have been asking your self all over the tutorial what the heck is inside the shared folder? Lol, I didn’t forget is just a couple of configurations, remember we already automatically built our sdk but we still need to create the utils folder and its helpers.

But hey!!! I used the util helpers from the original getting started from nativescript, so we will create these but again I won’t go in details, just walk your self through the official getting started for these utils.

Add Hint Util

Create the file shared/utils/hint-util.ts.

import {Color} from "color";
import {TextField} from "ui/text-field";

declare var NSAttributedString: any;
declare var NSDictionary: any;
declare var NSForegroundColorAttributeName: any;

export function setHintColor(args: { view: TextField, color: Color }) {
  if (args.view.android) {
    args.view.android.setHintTextColor(args.color.android);
  }
  if (args.view.ios) {
    let dictionary = new NSDictionary(
      [args.color.ios],
      [NSForegroundColorAttributeName]
    );
    args.view.ios.attributedPlaceholder = NSAttributedString.alloc().initWithStringAttributes(
      args.view.hint, dictionary);
  }
}

Add Status Bar Util

Create the file shared/utils/status-bar-util.ts.

import * as application from "application";
import * as platform from "platform";

declare var android: any;
declare var UIResponder: any;
declare var UIStatusBarStyle: any;
declare var UIApplication: any;
declare var UIApplicationDelegate: any;

export function setStatusBarColors() {
  // Make the iOS status bar transparent with white text.
  // See https://github.com/burkeholland/nativescript-statusbar/issues/2
  // for details on the technique used.
  if (application.ios) {
    var AppDelegate = UIResponder.extend({
      applicationDidFinishLaunchingWithOptions: function() {
        UIApplication.sharedApplication().statusBarStyle = UIStatusBarStyle.LightContent;
        return true;
      }
    }, {
        name: "AppDelegate",
        protocols: [UIApplicationDelegate]
      });
    application.ios.delegate = AppDelegate;
  }

  // Make the Android status bar transparent.
  // See http://bradmartin.net/2016/03/10/fullscreen-and-navigation-bar-color-in-a-nativescript-android-app/
  // for details on the technique used.
  if (application.android) {
    application.android.onActivityStarted = function() {
      if (application.android && platform.device.sdkVersion >= "21") {
        var View = android.view.View;
        var window = application.android.startActivity.getWindow();
        window.setStatusBarColor(0x000000);

        var decorView = window.getDecorView();
        decorView.setSystemUiVisibility(
          View.SYSTEM_UI_FLAG_LAYOUT_STABLE
          | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
          | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
          | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
      }
    }
  }
}

Create Base Api Config File

This is not a helper from nativescript but is just a configuration file for you to determine a place to configure the majority of your component api calls.

Create the file shared/base.api.ts.

export const BASE_URL = 'http://127.0.0.1:3000';
export const API_VERSION = 'api';

Create Shared Index

As you saw during the whole tutorial I have been importing resources from the shared folder, this is thanks to the fact that we are bundling everything together.

In order to achieve this we will need to export everything we have inside the shared folder by creating a index.ts file:

export * from './sdk/index';
export * from './base.api';
export * from './utils/hint-util';
export * from './utils/status-bar-util';

This is how we export/import everything from our shared folder, if you explore the sdk directory you will find another index.ts that basically does the same, it exports everything so you can easily import anything you need from shared.

Update main.ts

This has been a long journey, but we are almost there… We just need to update the main.ts file in order to call the status bar util that hides the device status bar, also we need to import the API_PROVIDERS, so you don’t need to import these in every component, also it provides the HTTP_PROVIDER from @angular/http so you won’t need to import it later.

// this import should be first in order to load some required settings (like globals and reflect-metadata)
import { nativeScriptBootstrap } from "nativescript-angular/application";
import { AppComponent } from "./app.component";
import { API_PROVIDERS, setStatusBarColors } from "./shared";
setStatusBarColors();
nativeScriptBootstrap(AppComponent, [ ...API_PROVIDERS ]);

Lets test our application

Open 2 terminal windows and start both… The API and App (use device or emulator, your choice)

Terminal 1:

$ cd native-chat-api
$ node .
RTC server listening at ws://0.0.0.0:3000/
Web server listening at: http://0.0.0.0:3000
Browse your REST API at http://0.0.0.0:3000/explorer

Terminal 2:

$ cd native-chat-app
$ tns livesync android --watch

Now, when the application loads you can register and create new rooms.

Registering and creating my first chat room

What is next?

In my next blog posts I will continue with this tutorial by building the rest of the application, adding the chat room section in which we will add users and send/receive messages.

Suggest

Angular 2 - The Complete Guide (Final Version!)

Learn Angular 2 from Beginner to Advanced

Angular 2 with TypeScript for Beginners: The Pragmatic Guide

The Complete Angular 2 With Typescript Course - Update RC 6

Angular 2 With Typescript : A Comprehensive Project

Build An Online Bank With Java, Angular 2, Spring and More