SlideShare a Scribd company logo
1 of 62
Download to read offline
Launching Beeline with Firebase
25-04-2017
Who is this guy?
CTO at beeline.co
@chetbox
What is Beeline?
We make journeys
more enjoyable
What is Beeline?
What is Beeline?
The app
The app
When I joined
BOM almost finalised
iOS prototype (built by contractor)
Firmware prototype (built by Charlie)
⚡ Bluetooth proof-of-concept (Android)
First tech hire
The big
challenge
● 3 months
● Android and iOS
● Bluetooth Low Energy
● User accounts
● Data storage
● Analytics
User accounts
User accounts
Just use Firebase Authentication
Data storage Realm + S3 → ?
Data storage
Why Firebase Database?
● Hosted
● Automatic sync to server
● iOS and Android SDK
● Open-source UI libraries
Data storage
What’s different?
● NoSQL
● Security enforced by rules.json
● Realtime
● Reactive UI
rules.json
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
rules.json
{
"rules": {
"destinations": {
"$user_id": {
".validate": "newData.hasChildren(['latitude', 'longitude'])”,
".read": "auth != null && auth.uid == $user_id",
".write": "auth != null && auth.uid == $user_id",
".indexOn": [
"isFavourite"
],
"$id": {
"latitude": {
".validate": "newData.isNumber() && newData.val() >= -90 &&
newData.val() <= 90"
},
"longitude": {
".validate": "newData.isNumber() && newData.val() >= -180 &&
newData.val() <= 180"
},
"name": {
".validate": "newData.isString()"
}
...
Data storage
FIREBASE WARNING:
set at /destinations/abc/def failed:
permission_denied
rules.bolt
path /destinations/{user_id}/{id} {
latitude: Number;
longitude: Number;
name: String | Null;
}
path /destinations/{user_id} {
read() {auth && auth.uid == user_id}
write() {auth && auth.uid == user_id}
}
rules.bolt
path /destinations/{user_id}/{id}
is Destination;
type LatLon {
latitude: Number;
longitude: Number;
}
type Destination extends LatLon {
name: String | Null;
}
rules.bolt
type LatLon {
latitude: Number;
longitude: Number;
validate() {
latitude >= -90 &&
latitude <= 90 &&
longitude >= -180 &&
longitude <= 180
}
}
rules.bolt
path /destinations/{user_id} {
read() { isSignedInUser(user_id) }
write() { isSignedInUser(user_id) }
}
isSignedInUser(user_id) {
isSignedIn() && auth.uid === user_id
}
isSignedIn() {
auth != null
}
Test your rules!
Test your rules!
Targaryen is a former Great House of Westeros and
was the ruling royal House of the Seven Kingdoms
for three centuries, before it was deposed during
Robert's Rebellion and House Baratheon replaced it
as the new royal House. The few surviving
Targaryens fled into exile. Currently based in Essos,
House Targaryen seeks to retake the Seven
Kingdoms from House Lannister, who formally
replaced House Baratheon as the royal House
following the destruction of the Great Sept of Baelor.
http://gameofthrones.wikia.com/wiki/House_Targaryen
Test your rules!
targaryen = require 'targaryen'
rules = require 'rules.json'
describe 'Destinations', ->
beforeEach ->
targaryen.setFirebaseRules rules
targaryen.setFirebaseData
destinations:
queen:
buckingham_palace:
latitude: 51.5013021
longitude: -0.148308
it 'cannot be read publicly', ->
expect targaryen.users.unauthenticated
.cannotRead '/destinations'
Test your rules!
it 'can be read by owner', ->
expect uid: 'queen'
.canRead '/destinations/queen'
it 'cannot be read by others', ->
expect {}
.cannotRead '/destinations/queen'
expect uid: 'mayor'
.cannotRead '/destinations/queen'
Test your rules!
it 'can be created by owner', ->
expect uid: 'queen'
.canWrite '/destinations/queen/trafalgar_square',
latitude: 51.5069249
longitude: -0.1297893
it 'cannot be created by others', ->
[{}, uid: 'peasant']
.forEach (user) ->
expect user
.cannotWrite '/destinations/queen/trafalgar_square',
latitude: 51.5069249
longitude: -0.1297893
Test your rules!
it 'can be re-named by owner', ->
expect uid: 'queen'
.canWrite '/destinations/queen/buckingham_palace/name',
'Home'
Test your rules!
$ firebase-bolt rules.bolt
$ node node_modules/jasmine/bin/jasmine.js
// package.json
{
"scripts": {
"test": "..."
}
}
$ npm test
Test your rules!
language: node_js
node_js: "6"
before_script:
- npm install
script:
- npm test
- ./deploy
Automatic
deployment
#!/bin/bash
set -e
if [ "$TRAVIS_PULL_REQUEST" == "false" ] ; then
if [ "$TRAVIS_BRANCH" == "master" ] ; then
FIREBASE_PROJECT="my-staging-project"
fi
if [ "$TRAVIS_BRANCH" == "release" ] ; then
FIREBASE_PROJECT="my-production-project"
fi
fi
if [ -n "$FIREBASE_PROJECT" ] ; then
node "node_modules/firebase-tools/bin/firebase" 
--project "$FIREBASE_PROJECT" 
--token "$FIREBASE_TOKEN" 
deploy 
--message "$TRAVIS_COMMIT")
fi
Automatic
deployment
$ npm test
bolt: Generating rules.json...
Started
...............................................................
...............................................................
...............................................................
..........................
215 specs, 0 failures
Finished in 0.5 seconds
The command "npm test" exited with 0.
$ ./deploy
Deploying to 'master' beeline-test-e7288
=== Deploying to 'beeline-test-e7288'...
i deploying database, functions
✔ database: rules ready to deploy.
i starting release process (may take several minutes)...
✔ Deploy complete!
Migration
Upgrade your
anonymous
accounts
final Task<AuthResult> task;
if (user != null && user.isAnonymous()) {
task = user.linkWithCredential(credential);
} else {
task = FirebaseAuth.getInstance()
.signInWithCredential(credential);
}
Android
● Use Firebase Authentication
● Serialise to/from POJOs
● Reactive binding to UI
Android
- app
- src
- debug
- google-services.json
- release
- google-services.json
Android
package co.beeline.model;
// imports...
@AutoValue
@FirebaseValue
@IgnoreExtraProperties
public abstract class Destination {
@Nullable public abstract String name();
public abstract Double latitude();
public abstract Double longitude();
...
}
Android
public static Destination create(
String name,
Location location) {
return create(
name,
location.getLatitude(),
location.getLongitude());
}
public static Destination create(DataSnapshot snapshot) {
return snapshot.getValue(
AutoValue_Destination.FirebaseValue.class
).toAutoValue();
}
Sprinkle on
some RxJava
public static <T> Observable<T> fromTask(Task<T> task) {
return Observable.create(subscriber -> {
task.addOnCompleteListener(completeTask -> {
if (completeTask.isSuccessful()) {
subscriber.onNext(completeTask.getResult());
subscriber.onCompleted();
} else {
subscriber.onError(
completeTask.getException()
);
}
});
});
}
Sprinkle on
some RxJava
public static Observable<Void> save(DatabaseReference ref,
Object value) {
return fromTask(ref.setValue(value));
}
public static Observable<Void> update(DatabaseReference ref,
Map<String, Object> values) {
return fromTask(ref.updateChildren(values));
}
public static Observable<Void> delete(DatabaseReference ref) {
return fromTask(ref.setValue(null));
}
Sprinkle on
some RxJava
public static Observable<DataSnapshot> value(DatabaseReference ref) {
return Observable.create(subscriber -> {
final ValueEventListener listener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
subscriber.onNext(dataSnapshot);
}
@Override
public void onCancelled(DatabaseError databaseError) {
subscriber.onError(databaseError.toException());
}
};
ref.addValueEventListener(listener);
subscriber.add(Subscriptions.create(
() -> ref.removeEventListener(listener)
));
});
}
Sprinkle on
some RxJava
public static Observable<Void> save(
DatabaseReference ref,
Destination destination) {
return save(ref,
new AutoValue_Destination.FirebaseValue(destination));
}
public static Observable<Destination> get(
DatabaseReference ref) {
return value(ref).map(Destination::create);
}
Sprinkle on
some RxJava
FirebaseDatabase db = FirebaseDatabase.getInstance();
DatabaseReference dests = db.getReference("destinations");
save(dests.push(), Destination.create(location, “My Location”))
.subscribeOn(Schedulers.io())
.subscribe();
UI binding
public static abstract class RecyclerAdapter<VH extends
RecyclerView.ViewHolder> extends
FirebaseRecyclerAdapter<AutoValue_Destination.FirebaseValue, VH> {
public RecyclerAdapter(int modelLayout, Class<VH> viewHolderClass,
Query query) {
super(AutoValue_Destination.FirebaseValue.class, modelLayout,
viewHolderClass, query);
}
@Override
protected final void populateViewHolder(VH viewHolder,
AutoValue_Destination.FirebaseValue model, int position) {
populateViewHolder(viewHolder, model.toAutoValue(), position);
}
abstract protected void populateViewHolder(VH viewHolder,
Destination model, int position);
}
UI binding
public class DestinationsAdapter extends
Destination.RecyclerAdapter<DestinationViewHolder> {
public DestinationsAdapter(FirebaseDatabase db) {
super(
R.layout.destination,
DestinationViewHolder.class,
db.getReference(“destinations”));
}
@Override
protected void populateViewHolder(
DestinationViewHolder viewHolder,
Destination model,
int position) {
viewHolder.populate(model);
}
}
Caching and
offline support
FirebaseDatabase.getInstance()
.setPersistenceEnabled(true);
FirebaseDatabase.getInstance()
.getReference("destinations")
.keepSynced(true);
How did it go?
● Android + iOS
○ Worked in parallel
○ Integration/migration in a few weeks
○ Offline “just works” (but a bit slow)
● rules.json
○ A bit of a learning curve
○ No servers to manage
○ Querying the data is not straightforward
Querying
ref.orderBy("date")
.startAt("2017-04-16")
.endAt("2017-04-23")
ref.orderBy("date")
.equalTo("2017-04-24")
.limitToLast(1)
Aggregating
// index.js
const admin = require('firebase-admin');
const db = admin.database();
exports.all_destinations =
functions.database.ref('/destinations/{userId}/{id}')
.onWrite(event => {
return db.ref(`/all-destinations/${event.params.id}`)
.set(event.data.val());
});
exports.count_user_destinations =
functions.database.ref('/destinations/{userId}')
.onWrite(event => {
return db.ref(`/stats/user/${event.params.userId}/`)
.set({destinations: event.numChildren()});
});
Cloud function
security
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(Object.assign(
{databaseAuthVariableOverride: {
uid: "stats@cloudfunction.beeline.co"
},
functions.config().firebase
);
const db = admin.database();
Cloud function
security
path /stats/user/{user_id} {
read() { isSignedInUser(user_id) }
write() { isCloudFunction('stats') }
}
isCloudFunction(name) {
auth.uid === name + '@cloudfunction.beeline.co'
}
Cloud function
tests
● Use .val()
● Unit test your data logic
● Connect it up in index.js
Cloud function
tests
language: node_js
node_js: "6"
before_script:
- npm install
- (cd functions && npm install)
script:
- npm test
- (cd functions && npm test)
- ./deploy
Web dashboard
Web dashboard
Web dashboard
Web dashboard
Permissions for
special users
path /permissions/{user_id} {
change_permissions: PermissionGranted | Null;
internal_feature_1: PermissionGranted | Null;
beta_feature_1: PermissionGranted | Null;
write(){ userHasPermission('change_permissions') }
}
type PermissionGranted extends Boolean {
validate(){ this === true }
}
userHasPermission(permission) {
root.permissions[auth.uid][permission] === true
}
Automatically
save device
metadata
exports.ride_saveAppAndDeviceInfo =
functions.analytics.event('ride_started')
.onLog(event => {
const user = event.data.user;
const uid = user && user.userId;
const rideId = event.data.params && event.data.params.value;
if (uid && rideId) {
return db.ref(`/rides/${uid}/${rideId}`).update({
app_version: user.appInfo.appVersion,
phone_os: user.appInfo.appPlatform,
phone_os_version: user.deviceInfo.platformVersion,
phone_model: user.deviceInfo.mobileModelName,
phone_manufacturer: user.deviceInfo.mobileBrandName
});
}
});
What we like
so far
● Easy to set up
● Use as much as you like
● Realtime DB updates
● Minimal backend code
● Detailed docs
What we like
less so far
● A bit of a learning curve
● Lacking examples
● Limited querying
● Testing functions is slow
● Slow to load lots of data
● In flux
What’s next
● More in-depth stats
● Large data → Long term (cheaper)
storage
● Functions error logging
● Statically typed functions
● Emails/push for in-app events
Questions?
https://beeline.co
@chetbox

More Related Content

What's hot

Build Lightweight Web Module
Build Lightweight Web ModuleBuild Lightweight Web Module
Build Lightweight Web ModuleMorgan Cheng
 
Async servers and clients in Rest.li
Async servers and clients in Rest.liAsync servers and clients in Rest.li
Async servers and clients in Rest.liKaran Parikh
 
Programming IoT Gateways in JavaScript with macchina.io
Programming IoT Gateways in JavaScript with macchina.ioProgramming IoT Gateways in JavaScript with macchina.io
Programming IoT Gateways in JavaScript with macchina.ioGünter Obiltschnig
 
Java Configuration Deep Dive with Spring
Java Configuration Deep Dive with SpringJava Configuration Deep Dive with Spring
Java Configuration Deep Dive with SpringJoshua Long
 
Advanced #2 networking
Advanced #2   networkingAdvanced #2   networking
Advanced #2 networkingVitali Pekelis
 
Beacons, Raspberry Pi & Node.js
Beacons, Raspberry Pi & Node.jsBeacons, Raspberry Pi & Node.js
Beacons, Raspberry Pi & Node.jsJeff Prestes
 
Orion Context Broker NGSI-v2 Overview for Developers That Already Know NGSI-v...
Orion Context Broker NGSI-v2 Overview for Developers That Already Know NGSI-v...Orion Context Broker NGSI-v2 Overview for Developers That Already Know NGSI-v...
Orion Context Broker NGSI-v2 Overview for Developers That Already Know NGSI-v...Fermin Galan
 
Java web programming
Java web programmingJava web programming
Java web programmingChing Yi Chan
 
201410 2 fiware-orion-contextbroker
201410 2 fiware-orion-contextbroker201410 2 fiware-orion-contextbroker
201410 2 fiware-orion-contextbrokerFIWARE
 
Introduction to rest.li
Introduction to rest.liIntroduction to rest.li
Introduction to rest.liJoe Betz
 
Jsp/Servlet
Jsp/ServletJsp/Servlet
Jsp/ServletSunil OS
 
Programming IoT Gateways with macchina.io
Programming IoT Gateways with macchina.ioProgramming IoT Gateways with macchina.io
Programming IoT Gateways with macchina.ioGünter Obiltschnig
 
Future of Web Apps: Google Gears
Future of Web Apps: Google GearsFuture of Web Apps: Google Gears
Future of Web Apps: Google Gearsdion
 
Greach 2019 - Creating Micronaut Configurations
Greach 2019 - Creating Micronaut ConfigurationsGreach 2019 - Creating Micronaut Configurations
Greach 2019 - Creating Micronaut ConfigurationsIván López Martín
 
Performance #1: Memory
Performance #1: MemoryPerformance #1: Memory
Performance #1: MemoryYonatan Levin
 
ActiveRecord Query Interface (2), Season 2
ActiveRecord Query Interface (2), Season 2ActiveRecord Query Interface (2), Season 2
ActiveRecord Query Interface (2), Season 2RORLAB
 

What's hot (20)

Build Lightweight Web Module
Build Lightweight Web ModuleBuild Lightweight Web Module
Build Lightweight Web Module
 
Async servers and clients in Rest.li
Async servers and clients in Rest.liAsync servers and clients in Rest.li
Async servers and clients in Rest.li
 
Programming IoT Gateways in JavaScript with macchina.io
Programming IoT Gateways in JavaScript with macchina.ioProgramming IoT Gateways in JavaScript with macchina.io
Programming IoT Gateways in JavaScript with macchina.io
 
Java Configuration Deep Dive with Spring
Java Configuration Deep Dive with SpringJava Configuration Deep Dive with Spring
Java Configuration Deep Dive with Spring
 
Advanced #2 networking
Advanced #2   networkingAdvanced #2   networking
Advanced #2 networking
 
Beacons, Raspberry Pi & Node.js
Beacons, Raspberry Pi & Node.jsBeacons, Raspberry Pi & Node.js
Beacons, Raspberry Pi & Node.js
 
Orion Context Broker NGSI-v2 Overview for Developers That Already Know NGSI-v...
Orion Context Broker NGSI-v2 Overview for Developers That Already Know NGSI-v...Orion Context Broker NGSI-v2 Overview for Developers That Already Know NGSI-v...
Orion Context Broker NGSI-v2 Overview for Developers That Already Know NGSI-v...
 
Java web programming
Java web programmingJava web programming
Java web programming
 
Play!ng with scala
Play!ng with scalaPlay!ng with scala
Play!ng with scala
 
201410 2 fiware-orion-contextbroker
201410 2 fiware-orion-contextbroker201410 2 fiware-orion-contextbroker
201410 2 fiware-orion-contextbroker
 
Introduction to rest.li
Introduction to rest.liIntroduction to rest.li
Introduction to rest.li
 
Jsp/Servlet
Jsp/ServletJsp/Servlet
Jsp/Servlet
 
Programming IoT Gateways with macchina.io
Programming IoT Gateways with macchina.ioProgramming IoT Gateways with macchina.io
Programming IoT Gateways with macchina.io
 
Nancy + rest mow2012
Nancy + rest   mow2012Nancy + rest   mow2012
Nancy + rest mow2012
 
Future of Web Apps: Google Gears
Future of Web Apps: Google GearsFuture of Web Apps: Google Gears
Future of Web Apps: Google Gears
 
IO::Iron
IO::IronIO::Iron
IO::Iron
 
Ironmq slides
Ironmq slidesIronmq slides
Ironmq slides
 
Greach 2019 - Creating Micronaut Configurations
Greach 2019 - Creating Micronaut ConfigurationsGreach 2019 - Creating Micronaut Configurations
Greach 2019 - Creating Micronaut Configurations
 
Performance #1: Memory
Performance #1: MemoryPerformance #1: Memory
Performance #1: Memory
 
ActiveRecord Query Interface (2), Season 2
ActiveRecord Query Interface (2), Season 2ActiveRecord Query Interface (2), Season 2
ActiveRecord Query Interface (2), Season 2
 

Similar to Launching Beeline with Firebase

Launching Beeline with Firebase
Launching Beeline with FirebaseLaunching Beeline with Firebase
Launching Beeline with FirebaseChetan Padia
 
Beeline Firebase talk - Firebase event Jun 2017
Beeline Firebase talk - Firebase event Jun 2017Beeline Firebase talk - Firebase event Jun 2017
Beeline Firebase talk - Firebase event Jun 2017Chetan Padia
 
Painless Persistence in a Disconnected World
Painless Persistence in a Disconnected WorldPainless Persistence in a Disconnected World
Painless Persistence in a Disconnected WorldChristian Melchior
 
Cross Domain Web
Mashups with JQuery and Google App Engine
Cross Domain Web
Mashups with JQuery and Google App EngineCross Domain Web
Mashups with JQuery and Google App Engine
Cross Domain Web
Mashups with JQuery and Google App EngineAndy McKay
 
RESTful apps and services with ASP.NET MVC
RESTful apps and services with ASP.NET MVCRESTful apps and services with ASP.NET MVC
RESTful apps and services with ASP.NET MVCbnoyle
 
Esri Dev Summit 2009 Rest and Mvc Final
Esri Dev Summit 2009 Rest and Mvc FinalEsri Dev Summit 2009 Rest and Mvc Final
Esri Dev Summit 2009 Rest and Mvc Finalguestcd4688
 
My way to clean android - Android day salamanca edition
My way to clean android - Android day salamanca editionMy way to clean android - Android day salamanca edition
My way to clean android - Android day salamanca editionChristian Panadero
 
Serverless Ballerina
Serverless BallerinaServerless Ballerina
Serverless BallerinaBallerina
 
Concept of BlockChain & Decentralized Application
Concept of BlockChain & Decentralized ApplicationConcept of BlockChain & Decentralized Application
Concept of BlockChain & Decentralized ApplicationSeiji Takahashi
 
Implement Service Broker with Spring Boot #cf_tokyo
Implement Service Broker with Spring Boot #cf_tokyoImplement Service Broker with Spring Boot #cf_tokyo
Implement Service Broker with Spring Boot #cf_tokyoToshiaki Maki
 
robrighter's Node.js presentation for DevChatt
robrighter's Node.js presentation for DevChattrobrighter's Node.js presentation for DevChatt
robrighter's Node.js presentation for DevChattrobrighter
 
Mobile webapplication development
Mobile webapplication developmentMobile webapplication development
Mobile webapplication developmentGanesh Gembali
 
Flask and Angular: An approach to build robust platforms
Flask and Angular:  An approach to build robust platformsFlask and Angular:  An approach to build robust platforms
Flask and Angular: An approach to build robust platformsAyush Sharma
 
Realm Java 2.2.0: Build better apps, faster apps
Realm Java 2.2.0: Build better apps, faster appsRealm Java 2.2.0: Build better apps, faster apps
Realm Java 2.2.0: Build better apps, faster appsSavvycom Savvycom
 
Crafting Evolvable Api Responses
Crafting Evolvable Api ResponsesCrafting Evolvable Api Responses
Crafting Evolvable Api Responsesdarrelmiller71
 
Realm or: How I learned to stop worrying and love my app database
Realm or: How I learned to stop worrying and love my app databaseRealm or: How I learned to stop worrying and love my app database
Realm or: How I learned to stop worrying and love my app databaseSergi Martínez
 

Similar to Launching Beeline with Firebase (20)

Launching Beeline with Firebase
Launching Beeline with FirebaseLaunching Beeline with Firebase
Launching Beeline with Firebase
 
Beeline Firebase talk - Firebase event Jun 2017
Beeline Firebase talk - Firebase event Jun 2017Beeline Firebase talk - Firebase event Jun 2017
Beeline Firebase talk - Firebase event Jun 2017
 
Painless Persistence in a Disconnected World
Painless Persistence in a Disconnected WorldPainless Persistence in a Disconnected World
Painless Persistence in a Disconnected World
 
Cross Domain Web
Mashups with JQuery and Google App Engine
Cross Domain Web
Mashups with JQuery and Google App EngineCross Domain Web
Mashups with JQuery and Google App Engine
Cross Domain Web
Mashups with JQuery and Google App Engine
 
RESTful apps and services with ASP.NET MVC
RESTful apps and services with ASP.NET MVCRESTful apps and services with ASP.NET MVC
RESTful apps and services with ASP.NET MVC
 
Esri Dev Summit 2009 Rest and Mvc Final
Esri Dev Summit 2009 Rest and Mvc FinalEsri Dev Summit 2009 Rest and Mvc Final
Esri Dev Summit 2009 Rest and Mvc Final
 
My way to clean android - Android day salamanca edition
My way to clean android - Android day salamanca editionMy way to clean android - Android day salamanca edition
My way to clean android - Android day salamanca edition
 
huhu
huhuhuhu
huhu
 
Serverless Ballerina
Serverless BallerinaServerless Ballerina
Serverless Ballerina
 
Concept of BlockChain & Decentralized Application
Concept of BlockChain & Decentralized ApplicationConcept of BlockChain & Decentralized Application
Concept of BlockChain & Decentralized Application
 
Implement Service Broker with Spring Boot #cf_tokyo
Implement Service Broker with Spring Boot #cf_tokyoImplement Service Broker with Spring Boot #cf_tokyo
Implement Service Broker with Spring Boot #cf_tokyo
 
Intro to Sail.js
Intro to Sail.jsIntro to Sail.js
Intro to Sail.js
 
robrighter's Node.js presentation for DevChatt
robrighter's Node.js presentation for DevChattrobrighter's Node.js presentation for DevChatt
robrighter's Node.js presentation for DevChatt
 
Mobile webapplication development
Mobile webapplication developmentMobile webapplication development
Mobile webapplication development
 
Flask and Angular: An approach to build robust platforms
Flask and Angular:  An approach to build robust platformsFlask and Angular:  An approach to build robust platforms
Flask and Angular: An approach to build robust platforms
 
Realm Java 2.2.0: Build better apps, faster apps
Realm Java 2.2.0: Build better apps, faster appsRealm Java 2.2.0: Build better apps, faster apps
Realm Java 2.2.0: Build better apps, faster apps
 
Realm Java 2.2.0: Build better apps, faster apps
Realm Java 2.2.0: Build better apps, faster appsRealm Java 2.2.0: Build better apps, faster apps
Realm Java 2.2.0: Build better apps, faster apps
 
Crafting Evolvable Api Responses
Crafting Evolvable Api ResponsesCrafting Evolvable Api Responses
Crafting Evolvable Api Responses
 
Realm or: How I learned to stop worrying and love my app database
Realm or: How I learned to stop worrying and love my app databaseRealm or: How I learned to stop worrying and love my app database
Realm or: How I learned to stop worrying and love my app database
 
Codable routing
Codable routingCodable routing
Codable routing
 

Recently uploaded

EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWERMadyBayot
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodJuan lago vázquez
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century educationjfdjdjcjdnsjd
 
Vector Search -An Introduction in Oracle Database 23ai.pptx
Vector Search -An Introduction in Oracle Database 23ai.pptxVector Search -An Introduction in Oracle Database 23ai.pptx
Vector Search -An Introduction in Oracle Database 23ai.pptxRemote DBA Services
 
Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)Zilliz
 
Platformless Horizons for Digital Adaptability
Platformless Horizons for Digital AdaptabilityPlatformless Horizons for Digital Adaptability
Platformless Horizons for Digital AdaptabilityWSO2
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FMESafe Software
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesrafiqahmad00786416
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAndrey Devyatkin
 
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 AmsterdamDEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 AmsterdamUiPathCommunity
 
[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdfSandro Moreira
 
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot ModelMcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot ModelDeepika Singh
 
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...Angeliki Cooney
 
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...apidays
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingEdi Saputra
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Victor Rentea
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...apidays
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...apidays
 

Recently uploaded (20)

EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
 
Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
Vector Search -An Introduction in Oracle Database 23ai.pptx
Vector Search -An Introduction in Oracle Database 23ai.pptxVector Search -An Introduction in Oracle Database 23ai.pptx
Vector Search -An Introduction in Oracle Database 23ai.pptx
 
Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)
 
Platformless Horizons for Digital Adaptability
Platformless Horizons for Digital AdaptabilityPlatformless Horizons for Digital Adaptability
Platformless Horizons for Digital Adaptability
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challenges
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 AmsterdamDEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
 
[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf
 
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot ModelMcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
 
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
 
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 

Launching Beeline with Firebase

  • 1. Launching Beeline with Firebase 25-04-2017
  • 2. Who is this guy? CTO at beeline.co @chetbox
  • 3. What is Beeline? We make journeys more enjoyable
  • 8. When I joined BOM almost finalised iOS prototype (built by contractor) Firmware prototype (built by Charlie) ⚡ Bluetooth proof-of-concept (Android) First tech hire
  • 9. The big challenge ● 3 months ● Android and iOS ● Bluetooth Low Energy ● User accounts ● Data storage ● Analytics
  • 11. User accounts Just use Firebase Authentication
  • 12. Data storage Realm + S3 → ?
  • 13. Data storage Why Firebase Database? ● Hosted ● Automatic sync to server ● iOS and Android SDK ● Open-source UI libraries
  • 14. Data storage What’s different? ● NoSQL ● Security enforced by rules.json ● Realtime ● Reactive UI
  • 15. rules.json { "rules": { ".read": "auth != null", ".write": "auth != null" } }
  • 16. rules.json { "rules": { "destinations": { "$user_id": { ".validate": "newData.hasChildren(['latitude', 'longitude'])”, ".read": "auth != null && auth.uid == $user_id", ".write": "auth != null && auth.uid == $user_id", ".indexOn": [ "isFavourite" ], "$id": { "latitude": { ".validate": "newData.isNumber() && newData.val() >= -90 && newData.val() <= 90" }, "longitude": { ".validate": "newData.isNumber() && newData.val() >= -180 && newData.val() <= 180" }, "name": { ".validate": "newData.isString()" } ...
  • 17. Data storage FIREBASE WARNING: set at /destinations/abc/def failed: permission_denied
  • 18. rules.bolt path /destinations/{user_id}/{id} { latitude: Number; longitude: Number; name: String | Null; } path /destinations/{user_id} { read() {auth && auth.uid == user_id} write() {auth && auth.uid == user_id} }
  • 19. rules.bolt path /destinations/{user_id}/{id} is Destination; type LatLon { latitude: Number; longitude: Number; } type Destination extends LatLon { name: String | Null; }
  • 20. rules.bolt type LatLon { latitude: Number; longitude: Number; validate() { latitude >= -90 && latitude <= 90 && longitude >= -180 && longitude <= 180 } }
  • 21. rules.bolt path /destinations/{user_id} { read() { isSignedInUser(user_id) } write() { isSignedInUser(user_id) } } isSignedInUser(user_id) { isSignedIn() && auth.uid === user_id } isSignedIn() { auth != null }
  • 23. Test your rules! Targaryen is a former Great House of Westeros and was the ruling royal House of the Seven Kingdoms for three centuries, before it was deposed during Robert's Rebellion and House Baratheon replaced it as the new royal House. The few surviving Targaryens fled into exile. Currently based in Essos, House Targaryen seeks to retake the Seven Kingdoms from House Lannister, who formally replaced House Baratheon as the royal House following the destruction of the Great Sept of Baelor. http://gameofthrones.wikia.com/wiki/House_Targaryen
  • 24. Test your rules! targaryen = require 'targaryen' rules = require 'rules.json' describe 'Destinations', -> beforeEach -> targaryen.setFirebaseRules rules targaryen.setFirebaseData destinations: queen: buckingham_palace: latitude: 51.5013021 longitude: -0.148308 it 'cannot be read publicly', -> expect targaryen.users.unauthenticated .cannotRead '/destinations'
  • 25. Test your rules! it 'can be read by owner', -> expect uid: 'queen' .canRead '/destinations/queen' it 'cannot be read by others', -> expect {} .cannotRead '/destinations/queen' expect uid: 'mayor' .cannotRead '/destinations/queen'
  • 26. Test your rules! it 'can be created by owner', -> expect uid: 'queen' .canWrite '/destinations/queen/trafalgar_square', latitude: 51.5069249 longitude: -0.1297893 it 'cannot be created by others', -> [{}, uid: 'peasant'] .forEach (user) -> expect user .cannotWrite '/destinations/queen/trafalgar_square', latitude: 51.5069249 longitude: -0.1297893
  • 27. Test your rules! it 'can be re-named by owner', -> expect uid: 'queen' .canWrite '/destinations/queen/buckingham_palace/name', 'Home'
  • 28. Test your rules! $ firebase-bolt rules.bolt $ node node_modules/jasmine/bin/jasmine.js // package.json { "scripts": { "test": "..." } } $ npm test
  • 29. Test your rules! language: node_js node_js: "6" before_script: - npm install script: - npm test - ./deploy
  • 30. Automatic deployment #!/bin/bash set -e if [ "$TRAVIS_PULL_REQUEST" == "false" ] ; then if [ "$TRAVIS_BRANCH" == "master" ] ; then FIREBASE_PROJECT="my-staging-project" fi if [ "$TRAVIS_BRANCH" == "release" ] ; then FIREBASE_PROJECT="my-production-project" fi fi if [ -n "$FIREBASE_PROJECT" ] ; then node "node_modules/firebase-tools/bin/firebase" --project "$FIREBASE_PROJECT" --token "$FIREBASE_TOKEN" deploy --message "$TRAVIS_COMMIT") fi
  • 31. Automatic deployment $ npm test bolt: Generating rules.json... Started ............................................................... ............................................................... ............................................................... .......................... 215 specs, 0 failures Finished in 0.5 seconds The command "npm test" exited with 0. $ ./deploy Deploying to 'master' beeline-test-e7288 === Deploying to 'beeline-test-e7288'... i deploying database, functions ✔ database: rules ready to deploy. i starting release process (may take several minutes)... ✔ Deploy complete!
  • 33. Upgrade your anonymous accounts final Task<AuthResult> task; if (user != null && user.isAnonymous()) { task = user.linkWithCredential(credential); } else { task = FirebaseAuth.getInstance() .signInWithCredential(credential); }
  • 34. Android ● Use Firebase Authentication ● Serialise to/from POJOs ● Reactive binding to UI
  • 35. Android - app - src - debug - google-services.json - release - google-services.json
  • 36. Android package co.beeline.model; // imports... @AutoValue @FirebaseValue @IgnoreExtraProperties public abstract class Destination { @Nullable public abstract String name(); public abstract Double latitude(); public abstract Double longitude(); ... }
  • 37. Android public static Destination create( String name, Location location) { return create( name, location.getLatitude(), location.getLongitude()); } public static Destination create(DataSnapshot snapshot) { return snapshot.getValue( AutoValue_Destination.FirebaseValue.class ).toAutoValue(); }
  • 38. Sprinkle on some RxJava public static <T> Observable<T> fromTask(Task<T> task) { return Observable.create(subscriber -> { task.addOnCompleteListener(completeTask -> { if (completeTask.isSuccessful()) { subscriber.onNext(completeTask.getResult()); subscriber.onCompleted(); } else { subscriber.onError( completeTask.getException() ); } }); }); }
  • 39. Sprinkle on some RxJava public static Observable<Void> save(DatabaseReference ref, Object value) { return fromTask(ref.setValue(value)); } public static Observable<Void> update(DatabaseReference ref, Map<String, Object> values) { return fromTask(ref.updateChildren(values)); } public static Observable<Void> delete(DatabaseReference ref) { return fromTask(ref.setValue(null)); }
  • 40. Sprinkle on some RxJava public static Observable<DataSnapshot> value(DatabaseReference ref) { return Observable.create(subscriber -> { final ValueEventListener listener = new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { subscriber.onNext(dataSnapshot); } @Override public void onCancelled(DatabaseError databaseError) { subscriber.onError(databaseError.toException()); } }; ref.addValueEventListener(listener); subscriber.add(Subscriptions.create( () -> ref.removeEventListener(listener) )); }); }
  • 41. Sprinkle on some RxJava public static Observable<Void> save( DatabaseReference ref, Destination destination) { return save(ref, new AutoValue_Destination.FirebaseValue(destination)); } public static Observable<Destination> get( DatabaseReference ref) { return value(ref).map(Destination::create); }
  • 42. Sprinkle on some RxJava FirebaseDatabase db = FirebaseDatabase.getInstance(); DatabaseReference dests = db.getReference("destinations"); save(dests.push(), Destination.create(location, “My Location”)) .subscribeOn(Schedulers.io()) .subscribe();
  • 43. UI binding public static abstract class RecyclerAdapter<VH extends RecyclerView.ViewHolder> extends FirebaseRecyclerAdapter<AutoValue_Destination.FirebaseValue, VH> { public RecyclerAdapter(int modelLayout, Class<VH> viewHolderClass, Query query) { super(AutoValue_Destination.FirebaseValue.class, modelLayout, viewHolderClass, query); } @Override protected final void populateViewHolder(VH viewHolder, AutoValue_Destination.FirebaseValue model, int position) { populateViewHolder(viewHolder, model.toAutoValue(), position); } abstract protected void populateViewHolder(VH viewHolder, Destination model, int position); }
  • 44. UI binding public class DestinationsAdapter extends Destination.RecyclerAdapter<DestinationViewHolder> { public DestinationsAdapter(FirebaseDatabase db) { super( R.layout.destination, DestinationViewHolder.class, db.getReference(“destinations”)); } @Override protected void populateViewHolder( DestinationViewHolder viewHolder, Destination model, int position) { viewHolder.populate(model); } }
  • 46. How did it go? ● Android + iOS ○ Worked in parallel ○ Integration/migration in a few weeks ○ Offline “just works” (but a bit slow) ● rules.json ○ A bit of a learning curve ○ No servers to manage ○ Querying the data is not straightforward
  • 48. Aggregating // index.js const admin = require('firebase-admin'); const db = admin.database(); exports.all_destinations = functions.database.ref('/destinations/{userId}/{id}') .onWrite(event => { return db.ref(`/all-destinations/${event.params.id}`) .set(event.data.val()); }); exports.count_user_destinations = functions.database.ref('/destinations/{userId}') .onWrite(event => { return db.ref(`/stats/user/${event.params.userId}/`) .set({destinations: event.numChildren()}); });
  • 49. Cloud function security const functions = require('firebase-functions'); const admin = require('firebase-admin'); admin.initializeApp(Object.assign( {databaseAuthVariableOverride: { uid: "stats@cloudfunction.beeline.co" }, functions.config().firebase ); const db = admin.database();
  • 50. Cloud function security path /stats/user/{user_id} { read() { isSignedInUser(user_id) } write() { isCloudFunction('stats') } } isCloudFunction(name) { auth.uid === name + '@cloudfunction.beeline.co' }
  • 51. Cloud function tests ● Use .val() ● Unit test your data logic ● Connect it up in index.js
  • 52. Cloud function tests language: node_js node_js: "6" before_script: - npm install - (cd functions && npm install) script: - npm test - (cd functions && npm test) - ./deploy
  • 57. Permissions for special users path /permissions/{user_id} { change_permissions: PermissionGranted | Null; internal_feature_1: PermissionGranted | Null; beta_feature_1: PermissionGranted | Null; write(){ userHasPermission('change_permissions') } } type PermissionGranted extends Boolean { validate(){ this === true } } userHasPermission(permission) { root.permissions[auth.uid][permission] === true }
  • 58. Automatically save device metadata exports.ride_saveAppAndDeviceInfo = functions.analytics.event('ride_started') .onLog(event => { const user = event.data.user; const uid = user && user.userId; const rideId = event.data.params && event.data.params.value; if (uid && rideId) { return db.ref(`/rides/${uid}/${rideId}`).update({ app_version: user.appInfo.appVersion, phone_os: user.appInfo.appPlatform, phone_os_version: user.deviceInfo.platformVersion, phone_model: user.deviceInfo.mobileModelName, phone_manufacturer: user.deviceInfo.mobileBrandName }); } });
  • 59. What we like so far ● Easy to set up ● Use as much as you like ● Realtime DB updates ● Minimal backend code ● Detailed docs
  • 60. What we like less so far ● A bit of a learning curve ● Lacking examples ● Limited querying ● Testing functions is slow ● Slow to load lots of data ● In flux
  • 61. What’s next ● More in-depth stats ● Large data → Long term (cheaper) storage ● Functions error logging ● Statically typed functions ● Emails/push for in-app events