## [Setup](https://firebase.google.com/docs/android/setup) - Describe project - Create Firebase project - Describe console - Root `build.gradle` ```groovy= buildscript { dependencies { classpath 'com.google.gms:google-services:4.3.15' } } ``` - App module `build.gradle` ```groovy= plugins { ... ... id 'com.google.gms.google-services' } ``` - Try to run - Create app and download `google-services.json` - Mention that all apps that we want to run need to be there - Mention Firebase assistant ## [App distribution](https://firebase.google.com/docs/app-distribution) - Build APK - Drag & drop - Create testers - Download APK or FAD app - You can distribute app bundle with Google Play (but it's annoying) - Only 150 days ## [Crashlytics](https://firebase.google.com/docs/crashlytics/get-started?platform=android) - BoM ```groovy= implementation platform('com.google.firebase:firebase-bom:31.5.0') implementation 'com.google.firebase:firebase-crashlytics' implementation 'com.google.firebase:firebase-analytics' ``` ```groovy= classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.5' plugins { id 'com.google.firebase.crashlytics' } ``` - Customize init ```xml= <meta-data android:name="firebase_crashlytics_collection_enabled" android:value="false" /> ``` ```kotlin= if (!BuildConfig.DEBUG) { FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true) } ``` - Mention filtering non-crashes, crashes, devices atd. ## [Analytics](https://firebase.google.com/docs/analytics/get-started?platform=android) ```groovy= implementation 'com.google.firebase:firebase-analytics-ktx' ``` - Automatically gathering some info like user retention - Basically same as Google Analytics ### Debugging - It takes a while to receive analytics simply to not drain battery - Enable debug mode ```bash= adb shell setprop debug.firebase.analytics.app cz.cvut.fit.pesekmic.lecture11 ``` ### Events - Mention Custom Metrics - There are default events `FirebaseAnalytics.Event.` ```kotlin= analytics.logEvent("add_btn_clicked", bundleOf("random_num" to (1..10).random())) ``` ### User properties - Mention Custom Definitions ```kotlin= analytics.setUserProperty("something", "something else") ``` ### Track screens ```kotlin= firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW) { param(FirebaseAnalytics.Param.SCREEN_NAME, screenName) param(FirebaseAnalytics.Param.SCREEN_CLASS, "MainActivity") } ``` ### User id > ...Analytics in conjunction with BigQuery to associate analytics data for the same user across multiple apps... ```kotlin= analytics.setUserId("abcdefg") ``` ## [Firebase Auth](https://firebase.google.com/docs/auth/android/start) - Mention Browser, Firebase Auth UI, Manual - Explain more providers ```kotlin= data class User( val id: String, val name: String ) val userStream: MutableStateFlow<User?> = MutableStateFlow(null) init { auth.addAuthStateListener { val user = it.currentUser?.toUser() userStream.value = user analytics.setUserId(user?.id) } } private fun FirebaseUser.toUser(): User { val displayName = providerData.firstOrNull { !it.displayName.isNullOrBlank() }?.displayName return User( id = uid, name = displayName ?: "" ) } ``` ```kotlin= fun signIn(activity: Activity) { val scopes = listOf("email", "profile") val provider = OAuthProvider.newBuilder(GoogleAuthProvider.PROVIDER_ID, auth) .setScopes(scopes) .build() auth.startActivityForSignInWithProvider(activity, provider) } fun signOut() { auth.signOut() } ``` - Enable auth in Console - Add fingerprint # Firestore - Collections, documents - Ways to structure our problem. One big collection or subcollections - Create database - Go to rules in Firestore ### Add ```kotlin= suspend fun addNote(note: Note) { val user = userRepository.userStream.first() ?: return firestore.collection(USERS_COLLECTION) .document(user.id) .collection(NOTES_COLLECTION) .add(note) } ``` ``` if request.auth != null; ``` ### Get ```kotlin= fun getAllNotesStream(): Flow<List<Note>> { return userRepository.userStream.flatMapLatest { user -> if (user != null) { firestore.collection(USERS_COLLECTION) .document(user.id) .collection(NOTES_COLLECTION) .toFlow() } else { flowOf(emptyList()) } } } ``` - Real time updates ## [Remote Config](https://firebase.google.com/docs/remote-config/get-started?platform=android) - App ```kotlin= val configSettings = FirebaseRemoteConfigSettings.Builder() .setMinimumFetchIntervalInSeconds(0) .build() remoteConfig.setConfigSettingsAsync(configSettings) remoteConfig.fetchAndActivate() ``` - UserRepo ```kotlin= private val _userStream: MutableStateFlow<User?> = MutableStateFlow(null) val userStream = _userStream.map { user -> if (remoteConfig.getBoolean("hide_name")) { user?.copy(name = "HIDDEN") } else { user } } ``` - AB Testing - Conditions ### FCM - Background ```xml= <meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/ic_notes" /> <meta-data android:name="com.google.firebase.messaging.default_notification_color" android:resource="@color/red" /> <meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="@string/general_channel_id" /> ``` - Foreground ```xml= <service android:name=".FCMService" android:exported="false"> <intent-filter> <action android:name="com.google.firebase.MESSAGING_EVENT" /> </intent-filter> </service> ``` ```kotlin= class FCMService : FirebaseMessagingService() { override fun onMessageReceived(message: RemoteMessage) { super.onMessageReceived(message) val title = message.notification?.title val body = message.notification?.body val notification = NotificationCompat.Builder(this, getString(R.string.general_channel_id)) .setContentTitle(title) .setContentText(body) .setSmallIcon(R.drawable.ic_notes) .setColor(Color.BLUE) .setContentIntent(getContentIntent()) .build() val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.notify(Random.nextInt(), notification) } private fun getContentIntent(): PendingIntent { val notifyIntent = Intent(this, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } return PendingIntent.getActivity(this, 0, notifyIntent, PendingIntent.FLAG_IMMUTABLE) } } ```