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

Quickly build your app alone with Firebase- And...

Quickly build your app alone with Firebase- Android Makers 2018

Creating a personal Android app requires us to solve a lot of common challenges. Sometimes some of these challenges require so much time that we start to forget about the product we are building. Firebase can act as a toolbox to give functionality like analytics, databases, messaging and crash reporting so we can move quickly and focus on our users.

Renaud MATHIEU

April 24, 2018
Tweet

More Decks by Renaud MATHIEU

Other Decks in Programming

Transcript

  1. You might need ‣ Feedback system ‣ Authentication ‣ Social

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

    Integration ‣ Analytics ‣ Remote control ‣ Notifications ‣ Device compatibility ‣ Permissions ‣ Tests ‣ Architecture ‣ Kotlin
  3. Core abstract class BaseFragment : Fragment() { lateinit var firebaseAnalytics:

    FirebaseAnalytics override fun onAttach(context: Context?) { super.onAttach(context) firebaseAnalytics = FirebaseAnalytics.getInstance(this) } } 2/4
  4. 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
  5. 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)) } } }
  6. Crashlytics object LogManager { fun init(tag: String) { Timber.plant(CrashlyticsTree(tag)) }

    fun setUserId(id: String) { Crashlytics.setUserIdentifier(id) } }
  7. 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)) }
  8. Remote Config @Singleton @Component( modules = arrayOf( AndroidSupportInjectionModule::class, ApplicationModule::class, ActivityModule::class,

    FirebaseModule::class ) ) interface ApplicationComponent : AndroidInjector<NeosApplication> { @Component.Builder interface Builder { fun build(): ApplicationComponent @BindsInstance fun application(application: Application): Builder } }
  9. 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.") } } } }
  10. 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.") } } } }
  11. 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.") } } } }
  12. Remote Config abstract class BaseFragment : Fragment(), HasSupportFragmentInjector { @Inject

    lateinit var childFragmentInjector: DispatchingAndroidInjector<Fragment> @Inject lateinit var firebaseRemoteConfig: FirebaseRemoteConfig lateinit var firebaseAnalytics: FirebaseAnalytics override fun onAttach(context: Context?) { AndroidSupportInjection.inject(this) super.onAttach(context) firebaseAnalytics = FirebaseAnalytics.getInstance(context) } }
  13. 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))) } }
  14. 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))) } }
  15. 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))) } }
  16. 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.
  17. Authentication ‣ Easily add sign-in to your Android app with

    FirebaseUI ‣ Get started with the Firebase SDK
  18. Firestore BÊTA @Module class FirebaseModule { @Singleton @Provides fun provideFirebaseRemoteConfig():

    FirebaseRemoteConfig = FirebaseRemoteConfig.getInstance() @Singleton @Provides fun provideFirebaseAuth(): FirebaseAuth = FirebaseAuth.getInstance() }
  19. 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) } } }
  20. 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) } } }
  21. 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) } } }
  22. 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
  23. 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() }
  24. Firestore BETA @Singleton @Component( modules = arrayOf( AndroidSupportInjectionModule::class, ApplicationModule::class, ActivityModule::class,

    RepositoryModule::class, FirebaseModule::class ) ) interface ApplicationComponent : AndroidInjector<NeosApplication> { @Component.Builder interface Builder { fun build(): ApplicationComponent @BindsInstance fun application(application: Application): Builder } }
  25. Firestore BETA @Module class RepositoryModule { @Singleton @Provides fun provideDataRepository(firestore:

    FirebaseFirestore): DataRepository { return DataRepository(firestore) } }
  26. Firestore BETA class DataRepository(val firestore: FirebaseFirestore) { /** * STORES

    */ fun getStores(): Single<List<StoreDomain>> { return Single.create { firestore.collection("stores") .get() .addOnSuccessListener { query -> it.onSuccess(query.toObjects(StoreDomain::class.java)) } .addOnFailureListener { exception -> it.onError(exception) } } } }
  27. 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 <condition>; allow write: if <condition>; } } }
  28. 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; } } }
  29. 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 <condition>; // Applies to queries and collection read requests allow list: if <condition>; } // A write rule can be divided into create, update, and delete rules match /cities/{city} { // Applies to writes to nonexistent documents allow create: if <condition>; // Applies to writes to existing documents allow update: if <condition>; // Applies to delete operations allow delete: if <condition>; } } }
  30. @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
  31. Cloud Storage @Module class RepositoryModule { @Singleton @Provides fun provideDataRepository(

    firestore: FirebaseFirestore, firebaseStorage: FirebaseStorage ): DataRepository { return DataRepository(firestore, firebaseStorage) } }
  32. 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) } } }
  33. 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() ) } }
  34. 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; } } }
  35. 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.