Slide 1

Slide 1 text

Quickly build your app alone with Firebase @renaud_mathieu

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

Have you ever thought about creating your own app?

Slide 4

Slide 4 text

I have an idea…

Slide 5

Slide 5 text

but…

Slide 6

Slide 6 text

I don’t have the time.

Slide 7

Slide 7 text

GET STARTED

Slide 8

Slide 8 text

‣ Design ‣ Develop ‣ Distribute GET STARTED

Slide 9

Slide 9 text

‣ Design ‣ Develop ‣ Distribute GET STARTED

Slide 10

Slide 10 text

Minimum Viable Product (MVP)

Slide 11

Slide 11 text

You might need ‣ Feedback system ‣ Authentication ‣ Social Integration ‣ Analytics ‣ Remote control ‣ Notifications ‣ Device compatibility ‣ Permissions ‣ Tests ‣ Architecture ‣ Kotlin

Slide 12

Slide 12 text

Why Firebase?

Slide 13

Slide 13 text

Firebase is not the answer.

Slide 14

Slide 14 text

Firebase could help.

Slide 15

Slide 15 text

You might need ‣ Feedback system ‣ Authentication ‣ Social Integration ‣ Analytics ‣ Remote control ‣ Notifications ‣ Device compatibility ‣ Permissions ‣ Tests ‣ Architecture ‣ Kotlin

Slide 16

Slide 16 text

‣ Free1 ‣ Scalable ‣ Solve common challenges 1. But limited

Slide 17

Slide 17 text

Time Money Resources

Slide 18

Slide 18 text

Firebase Products

Slide 19

Slide 19 text

Firebase Products Cloud Firestore Cloud Functions Authentication Cloud Storage Infra

Slide 20

Slide 20 text

Firebase Products Cloud Firestore Cloud Functions Authentication Cloud Storage Crashlytics Analytics Remote Config Feedback Infra Messaging

Slide 21

Slide 21 text

implementation 'com.crashlytics.sdk.android:crashlytics:2.9.1'
 implementation 'com.google.firebase:firebase-core:15.0.0' implementation 'com.google.firebase:firebase-config:15.0.0' implementation 'com.google.firebase:firebase-messaging:15.0.0' implementation 'com.google.firebase:firebase-firestore:15.0.0' implementation 'com.google.firebase:firebase-storage:15.0.0' implementation 'com.google.firebase:firebase-auth:15.0.0' build.gradle Firebase Products

Slide 22

Slide 22 text

implementation 'com.crashlytics.sdk.android:crashlytics:2.9.1'
 implementation 'com.google.firebase:firebase-core:15.0.0' implementation 'com.google.firebase:firebase-config:15.0.0' implementation 'com.google.firebase:firebase-messaging:15.0.0' implementation 'com.google.firebase:firebase-firestore:15.0.0' implementation 'com.google.firebase:firebase-storage:15.0.0' implementation ‘com.google.firebase:firebase-auth:15.0.0' build.gradle Firebase Products

Slide 23

Slide 23 text

Core

Slide 24

Slide 24 text

Core implementation 'com.google.firebase:firebase-core:15.0.0' 1/4 build.gradle

Slide 25

Slide 25 text

Core abstract class BaseFragment : Fragment() { lateinit var firebaseAnalytics: FirebaseAnalytics override fun onAttach(context: Context?) { super.onAttach(context) firebaseAnalytics = FirebaseAnalytics.getInstance(this) } } 2/4

Slide 26

Slide 26 text

Core // Analytics firebaseAnalytics.logEvent(FirebaseAnalytics.Event.ECOMMERCE_PURCHASE, Bundle().apply { putString(FirebaseAnalytics.Param.CURRENCY, receiptModel.currency) putDouble(FirebaseAnalytics.Param.PRICE, receiptModel.amount / 100.0) putString(FirebaseAnalytics.Param.TRANSACTION_ID, receiptModel.id) putInt(FirebaseAnalytics.Param.QUANTITY, receiptModel.items.sumBy { it.quantity }) }) 3/4

Slide 27

Slide 27 text

# adb shell setprop debug.firebase.analytics.app DebugView 4/4

Slide 28

Slide 28 text

Crashlytics

Slide 29

Slide 29 text

Crashlytics implementation 'com.crashlytics.sdk.android:crashlytics:2.9.1' 1/4 build.gradle

Slide 30

Slide 30 text

Crashlytics Build Type Release Build Type Debug Enabled Disabled

Slide 31

Slide 31 text

Crashlytics debug/AndroidManifest.xml

Slide 32

Slide 32 text

Crashlytics https://developer.android.com/studio/build/manifest-merge.html

Slide 33

Slide 33 text

Crashlytics class CrashlyticsTree(val applicationTag: String) : Timber.Tree() { override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { val crashlyticsTag = tag ?: applicationTag // Log the message to Crashlytics, so we can see it in crash reports Crashlytics.log(priority, crashlyticsTag, message); // Log the exception in Crashlytics if we have one. if (t != null) { Crashlytics.logException(t) } // If this is an error or a warning, log it as a exception so we see it in Crashlytics. if (priority > Log.WARN) { Crashlytics.logException(Throwable(message)) } } }

Slide 34

Slide 34 text

Crashlytics object LogManager { fun init(tag: String) { Timber.plant(Timber.DebugTree()) } fun setUserId(id: String) { // No-op in Debug } }

Slide 35

Slide 35 text

Crashlytics object LogManager { fun init(tag: String) { Timber.plant(CrashlyticsTree(tag)) } fun setUserId(id: String) { Crashlytics.setUserIdentifier(id) } }

Slide 36

Slide 36 text

Crashlytics object LogManager { fun init(tag: String) { Timber.plant(Timber.DebugTree()) } fun setUserId(id: String) { // No-op in Debug } } object LogManager { fun init(tag: String) { Timber.plant(CrashlyticsTree(tag)) } fun setUserId(id: String) { Crashlytics.setUserIdentifier(id) } } private fun initLogManager() { if (!BuildConfig.DEBUG) { Fabric.with(this, Crashlytics()) } LogManager.init(getString(R.string.app_name)) }

Slide 37

Slide 37 text

Crashlytics

Slide 38

Slide 38 text

Crashlytics

Slide 39

Slide 39 text

Remote Config

Slide 40

Slide 40 text

Remote Config @Singleton @Component( modules = arrayOf( AndroidSupportInjectionModule::class, ApplicationModule::class, ActivityModule::class, FirebaseModule::class ) ) interface ApplicationComponent : AndroidInjector { @Component.Builder interface Builder { fun build(): ApplicationComponent @BindsInstance fun application(application: Application): Builder } }

Slide 41

Slide 41 text

Remote Config @Module class FirebaseModule { @Singleton @Provides fun provideFirebaseRemoteConfig(): FirebaseRemoteConfig = FirebaseRemoteConfig.getInstance() }

Slide 42

Slide 42 text

Remote Config abstract class BaseActivity : AppCompatActivity(), HasSupportFragmentInjector { companion object { const val FIREBASE_REMOTE_CONFIG_CACHE_EXPIRATION: Long = 3600 } @Inject lateinit var firebaseRemoteConfig: FirebaseRemoteConfig override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) initAndFetchFirebaseRemoteConfig() } private fun initAndFetchFirebaseRemoteConfig() { firebaseRemoteConfig.setDefaults(R.xml.remote_config_defaults) firebaseRemoteConfig.fetch(FIREBASE_REMOTE_CONFIG_CACHE_EXPIRATION).addOnCompleteListener { if (it.isSuccessful) { firebaseRemoteConfig.activateFetched() } else { Timber.w("Firebase Remote Config Fetch Failed.") } } } }

Slide 43

Slide 43 text

Remote Config abstract class BaseActivity : AppCompatActivity(), HasSupportFragmentInjector { companion object { const val FIREBASE_REMOTE_CONFIG_CACHE_EXPIRATION: Long = 3600 } @Inject lateinit var firebaseRemoteConfig: FirebaseRemoteConfig override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) initAndFetchFirebaseRemoteConfig() } private fun initAndFetchFirebaseRemoteConfig() { firebaseRemoteConfig.setDefaults(R.xml.remote_config_defaults) firebaseRemoteConfig.fetch(FIREBASE_REMOTE_CONFIG_CACHE_EXPIRATION).addOnCompleteListener { if (it.isSuccessful) { firebaseRemoteConfig.activateFetched() } else { Timber.w("Firebase Remote Config Fetch Failed.") } } } }

Slide 44

Slide 44 text

Remote Config abstract class BaseActivity : AppCompatActivity(), HasSupportFragmentInjector { companion object { const val FIREBASE_REMOTE_CONFIG_CACHE_EXPIRATION: Long = 3600 } @Inject lateinit var firebaseRemoteConfig: FirebaseRemoteConfig override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) initAndFetchFirebaseRemoteConfig() } private fun initAndFetchFirebaseRemoteConfig() { firebaseRemoteConfig.setDefaults(R.xml.remote_config_defaults) firebaseRemoteConfig.fetch(FIREBASE_REMOTE_CONFIG_CACHE_EXPIRATION).addOnCompleteListener { if (it.isSuccessful) { firebaseRemoteConfig.activateFetched() } else { Timber.w("Firebase Remote Config Fetch Failed.") } } } }

Slide 45

Slide 45 text

Remote Config abstract class BaseFragment : Fragment(), HasSupportFragmentInjector { @Inject lateinit var childFragmentInjector: DispatchingAndroidInjector @Inject lateinit var firebaseRemoteConfig: FirebaseRemoteConfig lateinit var firebaseAnalytics: FirebaseAnalytics override fun onAttach(context: Context?) { AndroidSupportInjection.inject(this) super.onAttach(context) firebaseAnalytics = FirebaseAnalytics.getInstance(context) } }

Slide 46

Slide 46 text

Remote Config class ProfileFragment @Inject constructor() : BaseFragment() { val URL_SHARING: String by lazy { firebaseRemoteConfig.getString(REMOTE_CONFIG_SHARING_URL) } private fun shareNeosApp() { val intent = Intent().apply { action = Intent.ACTION_SEND putExtra(Intent.EXTRA_TEXT, URL_SHARING) type = "text/plain" } startActivity(Intent.createChooser( intent, resources.getText(R.string.profile_share))) } }

Slide 47

Slide 47 text

Remote Config class ProfileFragment @Inject constructor() : BaseFragment() { val URL_SHARING: String by lazy { firebaseRemoteConfig.getString(REMOTE_CONFIG_SHARING_URL) } private fun shareNeosApp() { val intent = Intent().apply { action = Intent.ACTION_SEND putExtra(Intent.EXTRA_TEXT, URL_SHARING) type = "text/plain" } startActivity(Intent.createChooser( intent, resources.getText(R.string.profile_share))) } }

Slide 48

Slide 48 text

Remote Config class ProfileFragment @Inject constructor() : BaseFragment() { val URL_SHARING: String by lazy { firebaseRemoteConfig.getString(REMOTE_CONFIG_SHARING_URL) } private fun shareNeosApp() { val intent = Intent().apply { action = Intent.ACTION_SEND putExtra(Intent.EXTRA_TEXT, URL_SHARING) type = "text/plain" } startActivity(Intent.createChooser( intent, resources.getText(R.string.profile_share))) } }

Slide 49

Slide 49 text

Messaging

Slide 50

Slide 50 text

Messaging As of April 10, 2018, Google has deprecated GCM. The GCM server and client APIs are deprecated and will be removed as soon as April 11, 2019.

Slide 51

Slide 51 text

Authentication

Slide 52

Slide 52 text

Authentication ‣ Easily add sign-in to your Android app with FirebaseUI ‣ Get started with the Firebase SDK

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

‣ Smart Lock support ‣ Forgot password

Slide 55

Slide 55 text

Authentication implementation 'com.firebaseui:firebase-ui-auth:3.3.1' build.gradle

Slide 56

Slide 56 text

Firestore BÊTA @Module class FirebaseModule { @Singleton @Provides fun provideFirebaseRemoteConfig(): FirebaseRemoteConfig = FirebaseRemoteConfig.getInstance() @Singleton @Provides fun provideFirebaseAuth(): FirebaseAuth = FirebaseAuth.getInstance() }

Slide 57

Slide 57 text

class HomeActivity : BaseActivity() { @Inject lateinit var auth: FirebaseAuth override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) setContentView(R.layout.activity_home) if (auth.currentUser != null) { // Set user properties or welcome back? } else { startActivityForResult( // Get an instance of AuthUI based on the default app AuthUI.getInstance().createSignInIntentBuilder().build(), RC_SIGN_IN ) } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == RC_SIGN_IN) { val response = IdpResponse.fromResultIntent(data) // Successfully signed in if (resultCode == Activity.RESULT_OK) { Timber.w("Successfully signed in") } else { // Sign in failed if (response == null) { // User pressed back button return } if (response.error!!.errorCode == ErrorCodes.NO_NETWORK) { // Handle this case return } Timber.e(response.error) } } }

Slide 58

Slide 58 text

class HomeActivity : BaseActivity() { @Inject lateinit var auth: FirebaseAuth override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) setContentView(R.layout.activity_home) if (auth.currentUser != null) { // Set user properties or welcome back? } else { startActivityForResult( // Get an instance of AuthUI based on the default app AuthUI.getInstance().createSignInIntentBuilder().build(), RC_SIGN_IN ) } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == RC_SIGN_IN) { val response = IdpResponse.fromResultIntent(data) // Successfully signed in if (resultCode == Activity.RESULT_OK) { Timber.w("Successfully signed in") } else { // Sign in failed if (response == null) { // User pressed back button return } if (response.error!!.errorCode == ErrorCodes.NO_NETWORK) { // Handle this case return } Timber.e(response.error) } } }

Slide 59

Slide 59 text

class HomeActivity : BaseActivity() { @Inject lateinit var auth: FirebaseAuth override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) setContentView(R.layout.activity_home) if (auth.currentUser != null) { // Set user properties or welcome back? } else { startActivityForResult( // Get an instance of AuthUI based on the default app AuthUI.getInstance().createSignInIntentBuilder().build(), RC_SIGN_IN ) } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == RC_SIGN_IN) { val response = IdpResponse.fromResultIntent(data) // Successfully signed in if (resultCode == Activity.RESULT_OK) { Timber.w("Successfully signed in") } else { // Sign in failed if (response == null) { // User pressed back button return } if (response.error!!.errorCode == ErrorCodes.NO_NETWORK) { // Handle this case return } Timber.e(response.error) } } }

Slide 60

Slide 60 text

signOutButton.setOnClickListener { v -> AuthUI.getInstance() .signOut(this) .addOnCompleteListener { Timber.d("User is now signed out") } } Authentication

Slide 61

Slide 61 text

Authentication ‣ Migrate from Firebase to your back end # npm install -g firebase-tools # firebase login # firebase init # firebase list # firebase auth:export myexport.csv --format=csv

Slide 62

Slide 62 text

Firestore

Slide 63

Slide 63 text

Firestore BETA

Slide 64

Slide 64 text

Firestore BETA

Slide 65

Slide 65 text

Firestore BETA Collection Documents

Slide 66

Slide 66 text

Firestore BETA @Module class FirebaseModule { @Singleton @Provides fun provideFirebaseRemoteConfig(): FirebaseRemoteConfig = FirebaseRemoteConfig.getInstance() @Singleton @Provides fun provideFirebaseAuth(): FirebaseAuth = FirebaseAuth.getInstance() @Singleton @Provides fun provideFirebaseFirestore(): FirebaseFirestore = FirebaseFirestore.getInstance() }

Slide 67

Slide 67 text

Firestore BETA @Singleton @Component( modules = arrayOf( AndroidSupportInjectionModule::class, ApplicationModule::class, ActivityModule::class, RepositoryModule::class, FirebaseModule::class ) ) interface ApplicationComponent : AndroidInjector { @Component.Builder interface Builder { fun build(): ApplicationComponent @BindsInstance fun application(application: Application): Builder } }

Slide 68

Slide 68 text

Firestore BETA @Module class RepositoryModule { @Singleton @Provides fun provideDataRepository(firestore: FirebaseFirestore): DataRepository { return DataRepository(firestore) } }

Slide 69

Slide 69 text

Firestore BETA class DataRepository(val firestore: FirebaseFirestore) { /** * STORES */ fun getStores(): Single> { return Single.create { firestore.collection("stores") .get() .addOnSuccessListener { query -> it.onSuccess(query.toObjects(StoreDomain::class.java)) } .addOnFailureListener { exception -> it.onError(exception) } } } }

Slide 70

Slide 70 text

Firestore BETA ‣ Structuring Security Rules service cloud.firestore { match /databases/{database}/documents { // Match any document in the 'stores' collection match /stores/{store} { allow read: if ; allow write: if ; } } }

Slide 71

Slide 71 text

Firestore BETA ‣ Structuring Security Rules // Allow read/write access on all documents to any user signed in to the application service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if request.auth.uid != null; } } }

Slide 72

Slide 72 text

Firestore BETA ‣ Structuring Security Rules service cloud.firestore { match /databases/{database}/documents { // A read rule can be divided into get and list rules match /cities/{city} { // Applies to single document read requests allow get: if ; // Applies to queries and collection read requests allow list: if ; } // A write rule can be divided into create, update, and delete rules match /cities/{city} { // Applies to writes to nonexistent documents allow create: if ; // Applies to writes to existing documents allow update: if ; // Applies to delete operations allow delete: if ; } } }

Slide 73

Slide 73 text

Cloud Storage

Slide 74

Slide 74 text

@Module class FirebaseModule { @Singleton @Provides fun provideFirebaseRemoteConfig(): FirebaseRemoteConfig = FirebaseRemoteConfig.getInstance() @Singleton @Provides fun provideFirebaseAuth(): FirebaseAuth = FirebaseAuth.getInstance() @Singleton @Provides fun provideFirebaseFirestore(): FirebaseFirestore = FirebaseFirestore.getInstance() @Singleton @Provides fun provideFirebaseStorage(): FirebaseStorage = FirebaseStorage.getInstance() } Cloud Storage

Slide 75

Slide 75 text

Cloud Storage @Module class RepositoryModule { @Singleton @Provides fun provideDataRepository( firestore: FirebaseFirestore, firebaseStorage: FirebaseStorage ): DataRepository { return DataRepository(firestore, firebaseStorage) } }

Slide 76

Slide 76 text

Cloud Storage class DataRepository( val firestore: FirebaseFirestore, val storage: FirebaseStorage ) { fun getStoreLogoURL() { storage.reference.child("stores/logos/gep_rive_gauche.png") .getDownloadUrl() .addOnSuccessListener { // Emit Single? Download to a file? } .addOnFailureListener { Timber.e(it) } } }

Slide 77

Slide 77 text

Cloud Storage ‣ Using FirebaseUI to download and display images using Glide @GlideModule class MyAppGlideModule : AppGlideModule() { fun registerComponents(context: Context, glide: Glide, registry: Registry) { // Register FirebaseImageLoader to handle StorageReference registry.append( StorageReference::class.java, InputStream::class.java, FirebaseImageLoader.Factory() ) } }

Slide 78

Slide 78 text

Cloud Storage GlideApp.with(this /* context */) .load(storageReference) .into(imageView)

Slide 79

Slide 79 text

Cloud Storage ‣ Structuring Security Rules // Allow read/write access on all documents to any user signed in to the application service firebase.storage { match /b/{bucket}/o { match /{allPaths=**} { allow read, write: if request.auth.uid != null; } } }

Slide 80

Slide 80 text

https://firebase.community/ @renaud_mathieu

Slide 81

Slide 81 text

Cloud Functions BÊTA Scenarios ‣ Notify users when something interesting happens. ‣ Perform Realtime Database sanitization and maintenance. ‣ Execute intensive tasks in the cloud instead of in your app. ‣ Integrate with third-party services and APIs.