Skip to content

How to integrate Firebase across all platforms (web nativescript desktop)

Nathan Walker edited this page Apr 4, 2017 · 1 revision

This page shows how to integrate Firebase into the seed. Huge thank you to Scott Lowe.

There's a plugin for adding firebase to nativescript, but it doesn't have the same API as the web SDK. To accomodate this, we create one service for each platform (web and native), then swap the implementation during setup, depending on the platform.

Setup nativescript-plugin-firebase for native

Open your Firebase project at the Google console and follow the instructions for adding Firebase to your Android/iOS app to get the google-services.json and GoogleService-Info.plist files. (Make sure the package name for android matches your nativescript.id in ./nativescript/package.json)

Android

  1. Copy google-services.json into ./nativescript/platforms/android/
  2. Update ./nativescript/platforms/android/build.gradle:
  • Add google-services classpath dependency:

    ...
      dependencies {
        classpath "com.android.tools.build:gradle:1.5.0"
        classpath "com.google.gms:google-services:3.0.0"
      }
      ...
    
  • Add applicationId and multiDexEnabled to the defaultConfig (replace com.yourdomain.appname with your nativescript.id from ./nativescript/package.json):

    ...
    defaultConfig {
      minSdkVersion 17
      targetSdkVersion computeTargetSdkVersion()
      applicationId "com.yourdomain.appname"
      multiDexEnabled true
    }
    ...
    
  • Apply plugin at the end of the file:

    ...
      dependsOn deleteExplodedAarFolder
    }
    apply plugin: "com.google.gms.google-services"
    
  1. Verify you have the correct versions of Google Play Services (rev 30) and Google Repository (rev 26) installed

iOS

  1. Copy GoogleService-Info.plist into ./nativescript/app/App_Resources/iOS/

Setup firebase for web/desktop

  1. From the root of the seed (not the nativescript folder) run npm install firebase --save
  2. Update project config (./tools/config/project.config.ts) to add dependency for web support:
  let additionalPackages: ExtendPackages[] = [{
    name: 'firebase',
    // Path to the package's bundle
    path: 'node_modules/firebase/firebase.js'
  }];
  
  this.addPackagesBundles(additionalPackages);

We will be using your-folder folder as an example here.

Add InjectionToken to abstract underlying firebase dependency

  1. src/client/app/shared/your-folder/index.ts to contain the token
import {InjectionToken} from '@angular/core';

export const FIREBASE: InjectionToken = new InjectionToken('firebase');
  1. Create NSDatabaseService in ./nativescript/src/mobile/core/services/database.service.ts:
import {Injectable, Inject, NgZone} from '@angular/core';
import {FIREBASE} from '../../../app/shared/your-folder/index'

@Injectable()
export class NSDatabaseService {
  private database:any;
  private onSync:Function;
  private userID:string;
  constructor(@Inject(FIREBASE) firebase:any, private ngZone: NgZone) {
    console.log('Constructing NSDatabaseService');
    this.database = firebase;
    this.database.init({
      persist: true // Allow disk persistence. Default false.
    }).then((instance:any) => {
      console.log('firebase.init successful');
    }, (error:any) => {
      console.log('firebase.init error: ' + error);
    });
  }

  sync(path: string, onValueReceived: Function):void {
    this.database.addValueEventListener((result:any) => {
      this.ngZone.run(() => {
        onValueReceived(result.value);
      });
    }, path);
  }

  addChild(path:string, data:any, callback?:Function):void {
    this.database.push(path, data).then((result:any) => {
      console.log('created key: ' + result.key);
      if (callback) {
        this.ngZone.run(() => {
          callback(result.key);
        });
      }
    });
  }
}
  1. Add a line to ./nativescript/src/mobile/core/index.ts to export your new service:
export * from './services/database.service';
  1. Create DatabaseService in ./src/client/app/shared/your-folder/services/database.service.ts:
import {Injectable, Inject, NgZone} from '@angular/core';
import {FIREBASE} from '../../your-folder/index';

@Injectable()
export class DatabaseService {
  private database:any;
  private onSync:Function;
  private userID:string;
  constructor(@Inject(FIREBASE) firebase:any, private ngZone: NgZone) {
    console.log('Constructing DatabaseService');
    // Initialize Firebase
    var config = {
      // your web config from Firebase console
    };
    firebase.initializeApp(config);
    this.database = firebase.database();
  }

  sync(path: string, onValueReceived: Function):void {
    this.database.ref(path).on('value', (snapshot:any) => {
      this.ngZone.run(() => {
        onValueReceived(snapshot.val());
      });
    });
  }

  addChild(path: string, data:any, callback?:Function):void {
    this.database.ref(path).push(data, (err:any) => {
      if (callback && !err) {
        this.ngZone.run(() => {
          callback();
        });
      }
    });
  }
}
  1. Modify ./src/client/app/shared/your-folder/index.ts to export your new service:
export * from './services/database.service';
  1. Configure ./nativescript/src/native.module.ts to use NSDatabaseService:
...
import {FIREBASE} from './app/shared/your-folder/index';
var firebase = require('nativescript-plugin-firebase');

export function firebaseFactory() {
  return firebase;
}

@NgModule({
  // other imports, etc.
  providers: [
    NS_ANALYTICS_PROVIDERS,
    { provide: RouterExtensions, useClass: TNSRouterExtensions }
    { provide: FIREBASE, useFactory: (firebaseFactory) }
  ],
  bootstrap: [NSAppComponent]  
]);
  1. Configure ./src/client/web.module.ts to use DatabaseService:
...
import {FIREBASE} from './app/shared/your-folder/index';

var firebasePlugin = require('firebase');

export function firebaseFactory() {
  return firebasePlugin.firebase;
}

@NgModule({
  // other imports, etc.
  providers: [
    {
      provide: APP_BASE_HREF,
      useValue: '<%= APP_BASE %>'
    },
    { 
      provide: FIREBASE, useFactory: (firebaseFactory)
    }
],
bootstrap: [AppComponent]
})

Using the DatabaseService

Simply inject the DatabaseService as you would any other provider to interact with firebase, regardless of which platform you're on.

Example with the home component, ./src/client/app/components/home/home.component.ts:

// libs
import {Store} from '@ngrx/store';

// app
import {NameListService} from '../../shared/app/index';
import {DatabaseService} from '../../shared/core/services/database.service';

@Component({
  moduleId: module.id,
  selector: 'sd-home',
  templateUrl: 'home.component.html',
  styleUrls: ['home.component.css']
})
export class HomeComponent {
  public newName: string = '';
  constructor(private store: Store<any>, public nameListService: NameListService, private databaseService: DatabaseService) { 
    let count = 0;
    databaseService.sync('counters', (data:any) => {
      console.log('Synced path updated', data);
    });
    setInterval(() => {
      databaseService.addChild('counters', count++);
    }, 3000);
  }
  
  /*
   * @param newname  any text as input.
   * @returns return false to prevent default form submit behavior to refresh the page.
   */
  addName(): boolean {
    this.nameListService.add(this.newName);
    this.newName = '';
    return false;
  }
}

Adding functionality

Our service currently only supports addChild and sync. To add more functionality (such as auth), simply add new methods to DatabaseService/NSDatabaseService that consume their respective web/native firebase dependencies.