---
# System prepended metadata

title: Firestore

---

## [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)
    }
}
```