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

The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxMalak Abu Hammad
 
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphSIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphNeo4j
 
Azure Monitor & Application Insight to monitor Infrastructure & Application
Azure Monitor & Application Insight to monitor Infrastructure & ApplicationAzure Monitor & Application Insight to monitor Infrastructure & Application
Azure Monitor & Application Insight to monitor Infrastructure & ApplicationAndikSusilo4
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsMaria Levchenko
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking MenDelhi Call girls
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationMichael W. Hawkins
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonetsnaman860154
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptxHampshireHUG
 
Benefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksBenefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksSoftradix Technologies
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...shyamraj55
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)Gabriella Davis
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 
Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Paola De la Torre
 
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024BookNet Canada
 
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...HostedbyConfluent
 
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersThousandEyes
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 3652toLead Limited
 

Recently uploaded (20)

The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptx
 
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphSIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
 
Azure Monitor & Application Insight to monitor Infrastructure & Application
Azure Monitor & Application Insight to monitor Infrastructure & ApplicationAzure Monitor & Application Insight to monitor Infrastructure & Application
Azure Monitor & Application Insight to monitor Infrastructure & Application
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 
Benefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksBenefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other Frameworks
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101
 
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
 
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
 
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
 

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