Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Serverless mobile applications with Firebase v2

Serverless mobile applications with Firebase v2

Serverless mobile applications with Firebase talk given at MADG (Madrid Android Developer Group). Some slides are different than the talk given at Droidcon Spain.

Talks about authentication and real time databases.

Alexandru Simonescu

July 27, 2016
Tweet

More Decks by Alexandru Simonescu

Other Decks in Programming

Transcript

  1. Firebase requirements - Device running Android 2.3 (Ginger Bread) -

    Google Play Services 9.2.0 - Google Play Services SDK
  2. // root build.gradle buildscript {
 repositories {
 jcenter()
 }
 dependencies

    {
 classpath 'com.android.tools.build:gradle:2.2.0-alpha4'
 classpath 'com.google.gms:google-services:3.0.0'
 }
 }
  3. // app build.gradle android {
 compileSdkVersion 23
 buildToolsVersion "23.0.3"
 defaultConfig

    {
 applicationId “com.alexsimo.sanum" [. . .] apply plugin: 'com.google.gms.google-services'
  4. // firebase libraries 
 def version = "9.2.0"
 
 compile

    "com.google.firebase:firebase-core:${version}" // Analytics
 compile "com.google.firebase:firebase-database:${version}" // Realtime Database
 compile "com.google.firebase:firebase-storage:${version}" // Storage
 compile "com.google.firebase:firebase-crash:${version}" // Crash Reporting
 compile "com.google.firebase:firebase-auth:${version}" // Authentication
 compile "com.google.firebase:firebase-messaging:${version}" // Cloud Messaging
 compile "com.google.firebase:firebase-config:${version}" // Remote Config
 compile "com.google.firebase:firebase-invites:${version}" // Invites / Dynamic Links
 compile "com.google.firebase:firebase-ads:${version}" // AdMob
 compile "com.google.android.gms:play-services-appindexing:${version}" // App Indexing
  5. // firebase libraries 
 def version = "9.2.0"
 
 compile

    "com.google.firebase:firebase-core:${version}" // Analytics
 compile "com.google.firebase:firebase-database:${version}" // Realtime Database
 compile "com.google.firebase:firebase-storage:${version}" // Storage
 compile "com.google.firebase:firebase-crash:${version}" // Crash Reporting
 compile "com.google.firebase:firebase-auth:${version}" // Authentication
 compile "com.google.firebase:firebase-messaging:${version}" // Cloud Messaging
 compile "com.google.firebase:firebase-config:${version}" // Remote Config
 compile "com.google.firebase:firebase-invites:${version}" // Invites / Dynamic Links
 compile "com.google.firebase:firebase-ads:${version}" // AdMob
 compile "com.google.android.gms:play-services-appindexing:${version}" // App Indexing
  6. // me sad.. D/FirebaseApp: Initialized class com.google.firebase.auth.FirebaseAuth.
 D/FirebaseApp: Initialized class

    com.google.firebase.iid.FirebaseInstanceId.
 D/FirebaseApp: com.google.firebase.crash.FirebaseCrash is not linked. Skipping initialization.
 I/FirebaseInitProvider: FirebaseApp initialization successful
 W/GooglePlayServicesUtil: Google Play services out of date. Requires 9080000 but found 8489434
 W/FA: Service connection failed: ConnectionResult{statusCode=SERVICE_VERSION_UPDATE_REQUIRED
  7. // check play services version 
 int result = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this);


    switch (result) {
 case ConnectionResult.SERVICE_MISSING:
 case ConnectionResult.SERVICE_DISABLED:
 case ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED:
 break;
 }

  8. // notify errors int result = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this);
 Dialog dialog =

    GoogleApiAvailability.getInstance().getErrorDialog(this, result, 123);
 dialog.show();

  9. // firebase user properties {
 "FirebaseUser": {
 "uniqueID": "wbNvyz6ENwPiTrjV2KRkEjH78ZMcU",
 "email":

    "[email protected]",
 "name": "Alexandru Simonescu",
 "photoURL": "http://alexsimo.com/avatar.png"
 }
 }

  10. firebaseAuth.createUserWithEmailAndPassword(email, password)
 .addOnCompleteListener(this, task -> {
 if (task.isSuccessful()) {
 //

    user created and logged in
 } else {
 // user not created
 // task.getException().getMessage()
 }
 }); // email and password sign up
  11. // email and password sign in firebaseAuth.signInWithEmailAndPassword(email, password) .addOnCompleteListener(task ->

    {
 if (task.isSuccessful()) {
 // user logged in
 String uuid = task.getResult().getUser().getUid();
 Timber.d("User UUID %s", uuid);
 } else {
 // fail sign in
 // task.getException().getMessage();
 }
 });

  12. // authentication listener private FirebaseAuth firebaseAuth;
 private FirebaseAuth.AuthStateListener authStateListener;
 


    @Override
 protected void onCreate(Bundle savedInstanceState) {
 // register listener onStart() and onStop() firebaseAuth = FirebaseAuth.getInstance();
 authStateListener = auth -> {
 FirebaseUser user = auth.getCurrentUser();
 if (user != null) {
 Timber.d("User is logged in");
 } else {
 Timber.d("User is not logged in");
 }
 };
 }
  13. // obtain user FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
 // user can

    be NULL if (user != null) {
 Timber.d("User is signed in");
 } else {
 Timber.d("No user is signed in");
 }

  14. // update user FirebaseUser user = provideFirebaseUser();
 
 UserProfileChangeRequest update

    = new UserProfileChangeRequest.Builder()
 .setDisplayName("Alex")
 .setPhotoUri(Uri.parse("http://alexsimo.com/avatar.png"))
 .build();
 
 user.updateProfile(update) .addOnSuccessListener(aVoid -> Timber.d("User updated!"));

  15. // update sensitive data FirebaseUser user = provideFirebaseUser(); 
 AuthCredential

    credential = EmailAuthProvider .getCredential("[email protected]", "verysecret");
 
 user.reauthenticate(credential).addOnCompleteListener(task -> {
 Timber.d("User re-authenticated.");
 updatePassword(user, "muchsecret");
 });
 

  16. Social Login 1. Send Google sign in intent for result

    2. Receive response on onActivityResult() 3. Extract sign in result and user credentials 4. Sign in using credentials with Firebase Auth
  17. Social Login 1. Send Google sign in intent for result

    2. Receive response on onActivityResult() 3. Extract sign in result and user credentials 4. Sign in using credentials with Firebase Auth
  18. Social Login 1. Send Google sign in intent for result

    2. Receive response on onActivityResult() 3. Extract sign in result and user credentials 4. Sign in using credentials with Firebase Auth
  19. Social Login 1. Send Google sign in intent for result

    2. Receive response on onActivityResult() 3. Extract sign in result and user credentials 4. Sign in using credentials with Firebase Auth
  20. // send google sign in intent @Override
 public void onClick(View

    v) {
 Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(client);
 startActivityForResult(signInIntent, 1337);
 }

  21. // handle onActivityResult @Override
 protected void onActivityResult(int requestCode, int resultCode,

    Intent data) {
 
 if (requestCode == 1337) {
 GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data); 
 firebaseAuthWithGoogle(result.getSignInAccount());
 }
 }
  22. // sign in with Firebase 
 AuthCredential credential = GoogleAuthProvider

    .getCredential(acct.getIdToken(), null);
 firebaseAuth.signInWithCredential(credential) .addOnCompleteListener(this, task -> {
 Timber.d("signInWithCredential: onComplete: %s", task.isSuccessful());
 
 if (!task.isSuccessful()) {
 Timber.e("signInWithCredential: %s", task.getException());
 }
 });
  23. // anonymous sign in 
 firebaseAuth.signInAnonymously() .addOnSuccessListener(authResult -> {
 String

    uid = authResult.getUser().getUid();
 Timber.d("UID: %s", uid); 
 });

  24. // link anonymous to permanent 
 AuthCredential credential = GoogleAuthProvider.getCredential(token,

    null);
 
 firebaseAuth.getCurrentUser()
 .linkWithCredential(credential)
 .addOnSuccessListener(authResult -> {
 // google account linked to anonymous
 });

  25. // link anonymous to permanent 
 AuthCredential credential = GoogleAuthProvider.getCredential(token,

    null);
 
 firebaseAuth.getCurrentUser()
 .linkWithCredential(credential)
 .addOnSuccessListener(authResult -> {
 // google account linked to anonymous
 });

  26. Hosting 1. Store simple static content: html, css, js 2.

    Served over secure connection 3. Fast content delivery network
  27. Hosting 1. Store simple static content: html, css, js 2.

    Served over secure connection 3. Fast content delivery network
  28. Hosting 1. Store simple static content: html, css, js 2.

    Served over secure connection 3. Fast content delivery network
  29. Storage 1. Store and server user generated content 2. Uploads

    and downloads on poor network quality 3. On shoulders of Google Cloud Storage
  30. Storage 1. Store and server user generated content 2. Uploads

    and downloads on poor network quality 3. On shoulders of Google Cloud Storage
  31. Storage 1. Store and server user generated content 2. Uploads

    and downloads on poor network quality 3. On shoulders of Google Cloud Storage
  32. // upload file 
 byte[] bytes = readFile();
 StorageReference docRef

    = storageReference.child(buildFileName());
 
 UploadTask uploadTask = docRef.putBytes(bytes);
 uploadTask.addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {
 @Override
 public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {
 Timber.d("Bytes transfered: %s", taskSnapshot.getBytesTransferred());
 }
 }).addOnFailureListener(new OnFailureListener() {
 @Override
 public void onFailure(@NonNull Exception e) {
 
 }
 }).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
 @Override
 public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
 Timber.d("Downloadable from %s", taskSnapshot.getDownloadUrl());
 }
 });

  33. // sexy upload file byte[] bytes = readFile();
 StorageReference docRef

    = storageReference.child(buildFileName());
 
 UploadTask uploadTask = docRef.putBytes(bytes);
 uploadTask
 .addOnProgressListener(snapshot -> Timber.d("Transfered: %s", snapshot.getBytesTransferred()))
 .addOnFailureListener(e -> Timber.e("Ups! %s", e.getMessage()))
 .addOnSuccessListener(snapshot -> Timber.d("Download: %s", snapshot.getDownloadUrl()));
  34. // sexy upload file StorageReference ref = storageReference.child("avatar.png");
 
 InputStream

    stream = new FileInputStream(new File("avatar.jpg"));
 
 UploadTask task = ref.putStream(stream);
 task.addOnFailureListener(exception -> {
 // handle failure
 }).addOnSuccessListener(snapshot -> {
 Uri url = snapshot.getDownloadUrl();
 });

  35. // sexy upload file StorageReference ref = storageReference.child("avatar.png");
 
 InputStream

    stream = new FileInputStream(new File("avatar.jpg"));
 
 UploadTask task = ref.putStream(stream);
 task.addOnFailureListener(exception -> {
 // handle failure
 }).addOnSuccessListener(snapshot -> {
 Uri url = snapshot.getDownloadUrl();
 });

  36. // sexy upload file StorageReference ref = storageReference.child("avatar.png");
 
 InputStream

    stream = new FileInputStream(new File("avatar.jpg"));
 
 UploadTask task = ref.putStream(stream);
 task.addOnFailureListener(exception -> {
 // handle failure
 }).addOnSuccessListener(snapshot -> {
 Uri url = snapshot.getDownloadUrl();
 });

  37. // and download? 
 File file = new File(getCacheDir().getAbsolutePath() +

    "/ temp.png");
 
 StorageReference ref = storageReference.child("avatar.png");
 
 ref.getFile(file).addOnSuccessListener(
 taskSnapshot -> Timber.d("Downloaded! %s", file.exists()));

  38. Activity lifecycle and storage 1. Firebase tasks are persistent 2.

    Pending tasks can be retrieved 3. Continue upload across process kill or restart
  39. Activity lifecycle and storage 1. Firebase tasks are persistent 2.

    Pending tasks can be retrieved 3. Continue upload across process kill or restart
  40. Activity lifecycle and storage 1. Firebase tasks are persistent 2.

    Pending tasks can be retrieved 3. Continue upload across process kill or restart
  41. byte[] bytes = readFile();
 docRef = storageReference.child(buildFileName());
 
 UploadTask uploadTask

    = docRef.putBytes(bytes);
 uploadTask.addOnProgressListener(
 snapshot -> { /* do stuff */ })
 .addOnFailureListener(e -> { /* do stuff */ })
 .addOnSuccessListener(s -> { /* do stuff */ });
 private StorageReference docRef;
  42. protected void onRestoreInstanceState(Bundle saved) {
 super.onRestoreInstanceState(saved);
 
 String ref =

    saved.getString(UPLOAD_KEY);
 
 StorageReference file = getFirebase().getReferenceFromUrl(ref);
 
 UploadTask task = file.getActiveUploadTasks().get(0);
 
 task.addOnCompleteListener(task1 -> { /* handle task */ });
 }

  43. Storage tips 1. Limit upload filesize 2. Download to local

    file, not in memory 3. Filters, crops and more, on client side
  44. Storage tips 1. Limit upload filesize 2. Download to local

    file, not in memory 3. Filters, crops and more, on client side
  45. Storage tips 1. Limit upload filesize 2. Download to local

    file, not in memory 3. Filters, crops and more, on client side
  46. // just auth users service firebase.storage {
 match /b/<project-url>.appspot.com/o {


    match /{allPaths=**} {
 allow read, write: if request.auth != null;
 }
 }
 }
  47. // file size validation service firebase.storage {
 match /b/<project-url>.appspot.com/o {


    
 match /{allPaths=**} {
 allow read, write: if request.auth != null;
 }
 
 match /images/{imageId} {
 // only images less than 5MB
 allow write: if request.resource.size < 5 * 1024 * 1024
 && request.resource.metadata.contentType.matches('image/.*');
 }
 }
 }
  48. // user based validation service firebase.storage {
 match /b/<project-url>.appspot.com/o {


    
 match /public/{imageId} {
 allow write: if request.auth != null;
 }
 
 match /users/{userId}/{imageId} {
 allow write: if request.auth.uid == userId;
 }
 }
  49. // user based validation service firebase.storage {
 match /b/<project-url>.appspot.com/o {


    
 match /public/{imageId} {
 allow write: if request.auth != null;
 }
 
 match /users/{userId}/{imageId} {
 allow write: if request.auth.uid == userId;
 }
 }
  50. // first approach 
 DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
 
 DatabaseReference

    recipes = rootRef.child("recipes");
 
 recipes.child(buildId()).setValue("Pancakes");

  51. // storing objects DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
 
 DatabaseReference recipes

    = rootRef.child("recipes");
 
 String id = buildId();
 
 Recipe recipe = new Recipe(id, "Eggs with potatoes", "Yummy eggs with potatoes");
 
 recipes.child(id).setValue(recipe);

  52. // first write 
 DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
 
 DatabaseReference

    recipes = rootRef.child("recipes");
 
 recipes.child(buildId()).setValue("Pancakes");

  53. // more writing recipes.child(buildId()).setValue("My awesome recipe");
 
 recipes.child("My awesome recipe").push();


    
 recipes.child("4fsh7sx").updateChildren(Map<String, Object>);
 
 recipes.child("4fsh7sx").runTransaction(new Transaction.Handler {...});
  54. DatabaseReference rootRef = getRootReference();
 DatabaseReference recipes = rootRef.child("recipes");
 
 recipes.addValueEventListener(new

    ValueEventListener() {
 
 public void onDataChange(DataSnapshot snapshot) {
 for (DataSnapshot child : snapshot.getChildren()) {
 Timber.d("Key %s", child.getKey());
 }
 }
 
 public void onCancelled(DatabaseError error) {
 // do stuff
 }
 }); // read and listen changes
  55. DatabaseReference recipeRef = recipes.child("004");
 
 recipeRef.addListenerForSingleValueEvent(new ValueEventListener() {
 
 public

    void onDataChange(DataSnapshot snapshot) { 
 Recipe recipe = snapshot.getValue(Recipe.class); 
 }
 }); // read + map objects
  56. Structuring data 1. Plan how data is going to be

    saved 2. Map screens to JSON trees 3. Avoid nested data 4. Keep structures flat
  57. Structuring data 1. Plan how data is going to be

    saved 2. Map screens to JSON trees 3. Avoid nested data 4. Keep structures flat
  58. Structuring data 1. Plan how data is going to be

    saved 2. Map screens to JSON trees 3. Avoid nested data 4. Keep structures flat
  59. Structuring data 1. Plan how data is going to be

    saved 2. Map screens to JSON trees 3. Avoid nested data 4. Keep structures flat
  60. // recipes {
 "users": {
 "wbNvyz6ENwPiT": {
 "email": "[email protected]",
 "name":

    "Alexandru Simonescu",
 "recipes": {
 "zxcasd": {
 "name" : "Eggs with bacon",
 "duration": "1 hour",
 "ingredientes" : {
 "1" : {
 "name" : "Eggs"
 },
 "2" : {
 "name" : "Bacon"
 }
 }
 }
 }
 }
 }
 }
  61. {
 "users": {
 "wbNvyz6ENwPiT": {
 "email": "[email protected]",
 "name": "Alexandru Simonescu",


    "recipes": {
 "zxcasd": {
 "name" : "Eggs with bacon",
 "duration": "1 hour",
 "ingredientes" : {
 "1" : {
 "name" : "Eggs"
 },
 "2" : {
 "name" : "Bacon"
 }
 }
 }
 }
 }
 }
 }
  62. // recipes v2 {
 "users" : {
 /* more */


    },
 "recipes" : {
 /* more */
 },
 "ingredients" : {
 /* more */
 }
 }
  63. // convert async callbacks to rx private Observable<User> createFirebaseUser( String

    email, String password) {
 
 return Observable.fromAsync(emitter -> auth.createUserWithEmailAndPassword(email, password)
 .addOnSuccessListener(authResult -> {
 FirebaseUser user = authResult.getUser();
 emitter.onNext(new User(user.getUid(), user.getEmail()));
 emitter.onCompleted();
 })
 .addOnFailureListener(emitter::onError), AsyncEmitter.BackpressureMode.BUFFER);
 }
  64. Tips and tricks 1. Cache data as much as posible

    2. Compare predefined plan with pay as you go 3. Take care with migrations 4. Versionate datasource entities
  65. Tips and tricks 1. Cache data as much as posible

    2. Compare predefined plan with pay as you go 3. Take care with migrations 4. Versionate datasource entities
  66. Tips and tricks 1. Cache data as much as posible

    2. Compare predefined plan with pay as you go 3. Take care with migrations 4. Versionate datasource entities
  67. Tips and tricks 1. Cache data as much as posible

    2. Compare predefined plan with pay as you go 3. Take care with migrations 4. Versionate datasource entities
  68. Links • Serverless architectures http://martinfowler.com/articles/serverless.html • Firebase official documentation https://firebase.google.com/docs

    • Firebase official documentation firebase-community.slack.com • The key to Firebase security https://www.youtube.com/watch?v=PUBnlbjZFAI