Skip to content

Commit 994629e

Browse files
committed
Simplify settings package and a few more classes using DI
1 parent 0554f0d commit 994629e

20 files changed

+392
-346
lines changed

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,48 @@
1717
package com.google.firebase.sessions
1818

1919
import android.content.Context
20+
import android.util.Log
21+
import androidx.datastore.core.DataStore
22+
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
23+
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
24+
import androidx.datastore.preferences.core.Preferences
25+
import androidx.datastore.preferences.core.emptyPreferences
26+
import androidx.datastore.preferences.preferencesDataStoreFile
2027
import com.google.android.datatransport.TransportFactory
2128
import com.google.firebase.FirebaseApp
2229
import com.google.firebase.annotations.concurrent.Background
2330
import com.google.firebase.annotations.concurrent.Blocking
2431
import com.google.firebase.inject.Provider
2532
import com.google.firebase.installations.FirebaseInstallationsApi
33+
import com.google.firebase.sessions.ProcessDetailsProvider.getProcessName
34+
import com.google.firebase.sessions.settings.CrashlyticsSettingsFetcher
35+
import com.google.firebase.sessions.settings.LocalOverrideSettings
36+
import com.google.firebase.sessions.settings.RemoteSettings
37+
import com.google.firebase.sessions.settings.RemoteSettingsFetcher
2638
import com.google.firebase.sessions.settings.SessionsSettings
39+
import com.google.firebase.sessions.settings.SettingsProvider
2740
import dagger.Binds
2841
import dagger.BindsInstance
2942
import dagger.Component
3043
import dagger.Module
3144
import dagger.Provides
45+
import javax.inject.Qualifier
3246
import javax.inject.Singleton
3347
import kotlin.coroutines.CoroutineContext
3448

35-
/** Dagger component to provide [FirebaseSessions] and its dependencies. */
49+
@Qualifier internal annotation class SessionConfigsDataStore
50+
51+
@Qualifier internal annotation class SessionDetailsDataStore
52+
53+
@Qualifier internal annotation class LocalOverrideSettingsProvider
54+
55+
@Qualifier internal annotation class RemoteSettingsProvider
56+
57+
/**
58+
* Dagger component to provide [FirebaseSessions] and its dependencies.
59+
*
60+
* This gets configured and built in [FirebaseSessionsRegistrar.getComponents].
61+
*/
3662
@Singleton
3763
@Component(modules = [FirebaseSessionsComponent.MainModule::class])
3864
internal interface FirebaseSessionsComponent {
@@ -79,8 +105,59 @@ internal interface FirebaseSessionsComponent {
79105
impl: SessionLifecycleServiceBinderImpl
80106
): SessionLifecycleServiceBinder
81107

108+
@Binds
109+
@Singleton
110+
fun crashlyticsSettingsFetcher(impl: RemoteSettingsFetcher): CrashlyticsSettingsFetcher
111+
112+
@Binds
113+
@Singleton
114+
@LocalOverrideSettingsProvider
115+
fun localOverrideSettings(impl: LocalOverrideSettings): SettingsProvider
116+
117+
@Binds
118+
@Singleton
119+
@RemoteSettingsProvider
120+
fun remoteSettings(impl: RemoteSettings): SettingsProvider
121+
82122
companion object {
83-
@Provides @Singleton fun sessionGenerator() = SessionGenerator(timeProvider = WallClock)
123+
private const val TAG = "FirebaseSessions"
124+
125+
@Provides @Singleton fun timeProvider(): TimeProvider = TimeProviderImpl
126+
127+
@Provides @Singleton fun uuidGenerator(): UuidGenerator = UuidGeneratorImpl
128+
129+
@Provides
130+
@Singleton
131+
fun applicationInfo(firebaseApp: FirebaseApp): ApplicationInfo =
132+
SessionEvents.getApplicationInfo(firebaseApp)
133+
134+
@Provides
135+
@Singleton
136+
@SessionConfigsDataStore
137+
fun sessionConfigsDataStore(appContext: Context): DataStore<Preferences> =
138+
PreferenceDataStoreFactory.create(
139+
corruptionHandler =
140+
ReplaceFileCorruptionHandler { ex ->
141+
Log.w(TAG, "CorruptionException in settings DataStore in ${getProcessName()}.", ex)
142+
emptyPreferences()
143+
}
144+
) {
145+
appContext.preferencesDataStoreFile(SessionDataStoreConfigs.SETTINGS_CONFIG_NAME)
146+
}
147+
148+
@Provides
149+
@Singleton
150+
@SessionDetailsDataStore
151+
fun sessionDetailsDataStore(appContext: Context): DataStore<Preferences> =
152+
PreferenceDataStoreFactory.create(
153+
corruptionHandler =
154+
ReplaceFileCorruptionHandler { ex ->
155+
Log.w(TAG, "CorruptionException in sessions DataStore in ${getProcessName()}.", ex)
156+
emptyPreferences()
157+
}
158+
) {
159+
appContext.preferencesDataStoreFile(SessionDataStoreConfigs.SESSIONS_CONFIG_NAME)
160+
}
84161
}
85162
}
86163
}

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionDatastore.kt

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,15 @@
1616

1717
package com.google.firebase.sessions
1818

19-
import android.content.Context
2019
import android.util.Log
2120
import androidx.datastore.core.DataStore
22-
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
2321
import androidx.datastore.preferences.core.Preferences
2422
import androidx.datastore.preferences.core.edit
2523
import androidx.datastore.preferences.core.emptyPreferences
2624
import androidx.datastore.preferences.core.stringPreferencesKey
27-
import androidx.datastore.preferences.preferencesDataStore
2825
import com.google.firebase.Firebase
2926
import com.google.firebase.annotations.concurrent.Background
3027
import com.google.firebase.app
31-
import com.google.firebase.sessions.ProcessDetailsProvider.getProcessName
3228
import java.io.IOException
3329
import java.util.concurrent.atomic.AtomicReference
3430
import javax.inject.Inject
@@ -64,8 +60,8 @@ internal interface SessionDatastore {
6460
internal class SessionDatastoreImpl
6561
@Inject
6662
constructor(
67-
private val appContext: Context,
6863
@Background private val backgroundDispatcher: CoroutineContext,
64+
@SessionDetailsDataStore private val dataStore: DataStore<Preferences>,
6965
) : SessionDatastore {
7066

7167
/** Most recent session from datastore is updated asynchronously whenever it changes */
@@ -76,7 +72,7 @@ constructor(
7672
}
7773

7874
private val firebaseSessionDataFlow: Flow<FirebaseSessionsData> =
79-
appContext.dataStore.data
75+
dataStore.data
8076
.catch { exception ->
8177
Log.e(TAG, "Error reading stored session data.", exception)
8278
emit(emptyPreferences())
@@ -92,7 +88,7 @@ constructor(
9288
override fun updateSessionId(sessionId: String) {
9389
CoroutineScope(backgroundDispatcher).launch {
9490
try {
95-
appContext.dataStore.edit { preferences ->
91+
dataStore.edit { preferences ->
9692
preferences[FirebaseSessionDataKeys.SESSION_ID] = sessionId
9793
}
9894
} catch (e: IOException) {
@@ -108,15 +104,5 @@ constructor(
108104

109105
private companion object {
110106
private const val TAG = "FirebaseSessionsRepo"
111-
112-
private val Context.dataStore: DataStore<Preferences> by
113-
preferencesDataStore(
114-
name = SessionDataStoreConfigs.SESSIONS_CONFIG_NAME,
115-
corruptionHandler =
116-
ReplaceFileCorruptionHandler { ex ->
117-
Log.w(TAG, "CorruptionException in sessions DataStore in ${getProcessName()}.", ex)
118-
emptyPreferences()
119-
},
120-
)
121107
}
122108
}

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionGenerator.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package com.google.firebase.sessions
1919
import com.google.errorprone.annotations.CanIgnoreReturnValue
2020
import com.google.firebase.Firebase
2121
import com.google.firebase.app
22-
import java.util.UUID
22+
import javax.inject.Inject
2323
import javax.inject.Singleton
2424

2525
/**
@@ -37,10 +37,9 @@ internal data class SessionDetails(
3737
* [SessionDetails] up to date with the latest values.
3838
*/
3939
@Singleton
40-
internal class SessionGenerator(
41-
private val timeProvider: TimeProvider,
42-
private val uuidGenerator: () -> UUID = UUID::randomUUID,
43-
) {
40+
internal class SessionGenerator
41+
@Inject
42+
constructor(private val timeProvider: TimeProvider, private val uuidGenerator: UuidGenerator) {
4443
private val firstSessionId = generateSessionId()
4544
private var sessionIndex = -1
4645

@@ -66,7 +65,7 @@ internal class SessionGenerator(
6665
return currentSession
6766
}
6867

69-
private fun generateSessionId() = uuidGenerator().toString().replace("-", "").lowercase()
68+
private fun generateSessionId() = uuidGenerator.next().toString().replace("-", "").lowercase()
7069

7170
internal companion object {
7271
val instance: SessionGenerator

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/TimeProvider.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ import kotlin.time.Duration.Companion.milliseconds
2323
/** Time provider interface, for testing purposes. */
2424
internal interface TimeProvider {
2525
fun elapsedRealtime(): Duration
26+
2627
fun currentTimeUs(): Long
2728
}
2829

29-
/** "Wall clock" time provider. */
30-
internal object WallClock : TimeProvider {
30+
/** "Wall clock" time provider implementation. */
31+
internal object TimeProviderImpl : TimeProvider {
3132
/**
3233
* Gets the [Duration] elapsed in "wall clock" time since device boot.
3334
*
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.sessions
18+
19+
import java.util.UUID
20+
21+
/** UUID generator interface. */
22+
internal fun interface UuidGenerator {
23+
fun next(): UUID
24+
}
25+
26+
/** Generate random UUIDs using [UUID.randomUUID]. */
27+
internal object UuidGeneratorImpl : UuidGenerator {
28+
override fun next(): UUID = UUID.randomUUID()
29+
}

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/LocalOverrideSettings.kt

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,19 @@ package com.google.firebase.sessions.settings
1919
import android.content.Context
2020
import android.content.pm.PackageManager
2121
import android.os.Bundle
22+
import javax.inject.Inject
23+
import javax.inject.Singleton
2224
import kotlin.time.Duration
2325
import kotlin.time.DurationUnit
2426
import kotlin.time.toDuration
2527

26-
internal class LocalOverrideSettings(context: Context) : SettingsProvider {
27-
@Suppress("DEPRECATION") // TODO(mrober): Use ApplicationInfoFlags when target sdk set to 33
28+
@Singleton
29+
internal class LocalOverrideSettings @Inject constructor(appContext: Context) : SettingsProvider {
2830
private val metadata =
29-
context.packageManager
30-
.getApplicationInfo(
31-
context.packageName,
32-
PackageManager.GET_META_DATA,
33-
)
31+
appContext.packageManager
32+
.getApplicationInfo(appContext.packageName, PackageManager.GET_META_DATA)
3433
.metaData
35-
?: Bundle.EMPTY // Default to an empty bundle, meaning no cached values.
34+
?: Bundle.EMPTY // Default to an empty bundle
3635

3736
override val sessionEnabled: Boolean?
3837
get() =

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettings.kt

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ package com.google.firebase.sessions.settings
1919
import android.os.Build
2020
import android.util.Log
2121
import androidx.annotation.VisibleForTesting
22-
import androidx.datastore.core.DataStore
23-
import androidx.datastore.preferences.core.Preferences
22+
import com.google.firebase.annotations.concurrent.Background
2423
import com.google.firebase.installations.FirebaseInstallationsApi
2524
import com.google.firebase.sessions.ApplicationInfo
2625
import com.google.firebase.sessions.InstallationId
26+
import dagger.Lazy
27+
import javax.inject.Inject
28+
import javax.inject.Singleton
2729
import kotlin.coroutines.CoroutineContext
2830
import kotlin.time.Duration
2931
import kotlin.time.Duration.Companion.seconds
@@ -34,14 +36,19 @@ import kotlinx.coroutines.sync.withLock
3436
import org.json.JSONException
3537
import org.json.JSONObject
3638

37-
internal class RemoteSettings(
38-
private val backgroundDispatcher: CoroutineContext,
39+
@Singleton
40+
internal class RemoteSettings
41+
@Inject
42+
constructor(
43+
@Background private val backgroundDispatcher: CoroutineContext,
3944
private val firebaseInstallationsApi: FirebaseInstallationsApi,
4045
private val appInfo: ApplicationInfo,
4146
private val configsFetcher: CrashlyticsSettingsFetcher,
42-
dataStore: DataStore<Preferences>,
47+
private val lazySettingsCache: Lazy<SettingsCache>,
4348
) : SettingsProvider {
44-
private val settingsCache by lazy { SettingsCache(dataStore) }
49+
private val settingsCache: SettingsCache
50+
get() = lazySettingsCache.get()
51+
4552
private val fetchInProgress = Mutex()
4653

4754
override val sessionEnabled: Boolean?

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettingsFetcher.kt

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@
1717
package com.google.firebase.sessions.settings
1818

1919
import android.net.Uri
20+
import com.google.firebase.annotations.concurrent.Background
2021
import com.google.firebase.sessions.ApplicationInfo
2122
import java.io.BufferedReader
2223
import java.io.InputStreamReader
2324
import java.net.URL
25+
import javax.inject.Inject
26+
import javax.inject.Singleton
2427
import javax.net.ssl.HttpsURLConnection
2528
import kotlin.coroutines.CoroutineContext
2629
import kotlinx.coroutines.withContext
@@ -30,20 +33,22 @@ internal fun interface CrashlyticsSettingsFetcher {
3033
suspend fun doConfigFetch(
3134
headerOptions: Map<String, String>,
3235
onSuccess: suspend (JSONObject) -> Unit,
33-
onFailure: suspend (msg: String) -> Unit
36+
onFailure: suspend (msg: String) -> Unit,
3437
)
3538
}
3639

37-
internal class RemoteSettingsFetcher(
40+
@Singleton
41+
internal class RemoteSettingsFetcher
42+
@Inject
43+
constructor(
3844
private val appInfo: ApplicationInfo,
39-
private val blockingDispatcher: CoroutineContext,
40-
private val baseUrl: String = FIREBASE_SESSIONS_BASE_URL_STRING,
45+
@Background private val blockingDispatcher: CoroutineContext,
4146
) : CrashlyticsSettingsFetcher {
4247
@Suppress("BlockingMethodInNonBlockingContext") // blockingDispatcher is safe for blocking calls.
4348
override suspend fun doConfigFetch(
4449
headerOptions: Map<String, String>,
4550
onSuccess: suspend (JSONObject) -> Unit,
46-
onFailure: suspend (String) -> Unit
51+
onFailure: suspend (String) -> Unit,
4752
) =
4853
withContext(blockingDispatcher) {
4954
try {
@@ -78,7 +83,7 @@ internal class RemoteSettingsFetcher(
7883
val uri =
7984
Uri.Builder()
8085
.scheme("https")
81-
.authority(baseUrl)
86+
.authority(FIREBASE_SESSIONS_BASE_URL_STRING)
8287
.appendPath("spi")
8388
.appendPath("v2")
8489
.appendPath("platforms")

0 commit comments

Comments
 (0)