Skip to content
This repository was archived by the owner on Jan 5, 2023. It is now read-only.

Commit 3ca052c

Browse files
Moving from SharedPreferences to DataStore
Change-Id: If399a466a7741c32277413827ad2397756e298f6
1 parent 2ddf3f2 commit 3ca052c

File tree

47 files changed

+815
-329
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+815
-329
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ from a few different sources - user data is stored in
6161
[Cloud Firestore](https://firebase.google.com/docs/firestore/)
6262
(either remotely or in
6363
a local cache for offline use), user preferences and settings are stored in
64-
SharedPreferences, conference data is stored remotely and is fetched and stored
64+
DataStore, conference data is stored remotely and is fetched and stored
6565
in memory for the app to use, etc. - and the repository modules
6666
are responsible for handling all data operations and abstracting the data sources
6767
from the rest of the app (we liked using Firestore, but if we wanted to swap it

buildSrc/src/main/java/Libs.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ object Libs {
3434
const val COMPOSE_TOOLING = "androidx.compose.ui:ui-tooling"
3535
const val COROUTINES = "org.jetbrains.kotlinx:kotlinx-coroutines-core"
3636
const val COROUTINES_TEST = "org.jetbrains.kotlinx:kotlinx-coroutines-test"
37+
const val DATA_STORE_PREFERENCES = "androidx.datastore:datastore-preferences"
3738
const val DRAWER_LAYOUT = "androidx.drawerlayout:drawerlayout"
3839
const val ESPRESSO_CONTRIB = "androidx.test.espresso:espresso-contrib"
3940
const val ESPRESSO_CORE = "androidx.test.espresso:espresso-core"

depconstraints/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ val core = "1.3.2"
3333
val coroutines = "1.4.2"
3434
val coroutinesTest = "1.3.4"
3535
val crashlytics = "17.2.2"
36+
val dataStore = "1.0.0-beta01"
3637
val drawerLayout = "1.1.0-rc01"
3738
val espresso = "3.1.1"
3839
val firebaseAnalytics = "17.4.0"
@@ -91,6 +92,7 @@ dependencies {
9192
api("${Libs.COROUTINES}:$coroutines")
9293
api("${Libs.COROUTINES_TEST}:$coroutines")
9394
api("${Libs.CRASHLYTICS}:$crashlytics")
95+
api("${Libs.DATA_STORE_PREFERENCES}:$dataStore")
9496
api("${Libs.DRAWER_LAYOUT}:$drawerLayout")
9597
api("${Libs.ESPRESSO_CORE}:$espresso")
9698
api("${Libs.ESPRESSO_CONTRIB}:$espresso")

mobile/build.gradle.kts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,12 @@ android {
167167
}
168168
}
169169

170+
kapt {
171+
arguments {
172+
arg("dagger.hilt.shareTestComponents", "true")
173+
}
174+
}
175+
170176
dependencies {
171177
api(platform(project(":depconstraints")))
172178
kapt(platform(project(":depconstraints")))
@@ -224,6 +230,10 @@ dependencies {
224230
kapt(Libs.HILT_COMPILER)
225231
kaptAndroidTest(Libs.HILT_COMPILER)
226232

233+
// DataStore
234+
implementation(Libs.DATA_STORE_PREFERENCES)
235+
androidTestImplementation(Libs.DATA_STORE_PREFERENCES)
236+
227237
// Glide
228238
implementation(Libs.GLIDE)
229239
kapt(Libs.GLIDE_COMPILER)

mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/SetPreferencesRule.kt

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,19 @@ package com.google.samples.apps.iosched.tests
1818

1919
import androidx.test.core.app.ApplicationProvider
2020
import com.google.samples.apps.iosched.shared.data.prefs.PreferenceStorage
21+
import com.google.samples.apps.iosched.shared.di.ApplicationScope
2122
import dagger.hilt.EntryPoint
2223
import dagger.hilt.InstallIn
2324
import dagger.hilt.android.EntryPointAccessors
2425
import dagger.hilt.components.SingletonComponent
26+
import kotlinx.coroutines.CoroutineScope
27+
import kotlinx.coroutines.cancel
28+
import kotlinx.coroutines.runBlocking
2529
import org.junit.rules.TestWatcher
2630
import org.junit.runner.Description
2731

2832
/**
29-
* Rule to be used in tests that sets the SharedPreferences needed for avoiding onboarding flows,
33+
* Rule to be used in tests that sets the preferences needed for avoiding onboarding flows,
3034
* resetting filters, etc.
3135
*/
3236
class SetPreferencesRule : TestWatcher() {
@@ -35,6 +39,8 @@ class SetPreferencesRule : TestWatcher() {
3539
@EntryPoint
3640
interface SetPreferencesRuleEntryPoint {
3741
fun preferenceStorage(): PreferenceStorage
42+
@ApplicationScope
43+
fun applicationScope(): CoroutineScope
3844
}
3945

4046
override fun starting(description: Description?) {
@@ -44,12 +50,24 @@ class SetPreferencesRule : TestWatcher() {
4450
ApplicationProvider.getApplicationContext(),
4551
SetPreferencesRuleEntryPoint::class.java
4652
).preferenceStorage().apply {
47-
onboardingCompleted = true
48-
scheduleUiHintsShown = true
49-
preferConferenceTimeZone = true
50-
selectedFilters = ""
51-
sendUsageStatistics = false
52-
notificationsPreferenceShown = true
53+
runBlocking {
54+
completeOnboarding(true)
55+
showScheduleUiHints(true)
56+
preferConferenceTimeZone(true)
57+
selectFilters("")
58+
sendUsageStatistics(false)
59+
showNotificationsPreference(true)
60+
}
5361
}
5462
}
63+
64+
override fun finished(description: Description) {
65+
// At the end of every test, cancel the application scope
66+
// So DataStore is closed
67+
EntryPointAccessors.fromApplication(
68+
ApplicationProvider.getApplicationContext(),
69+
SetPreferencesRuleEntryPoint::class.java
70+
).applicationScope().cancel()
71+
super.finished(description)
72+
}
5573
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2021 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+
* https://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.samples.apps.iosched.tests.di
18+
19+
import android.content.Context
20+
import androidx.datastore.core.DataStore
21+
import androidx.datastore.preferences.SharedPreferencesMigration
22+
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
23+
import androidx.datastore.preferences.core.Preferences
24+
import androidx.datastore.preferences.preferencesDataStoreFile
25+
import com.google.samples.apps.iosched.shared.data.prefs.DataStorePreferenceStorage.Companion.PREFS_NAME
26+
import com.google.samples.apps.iosched.di.PreferencesStorageModule
27+
import com.google.samples.apps.iosched.shared.data.prefs.DataStorePreferenceStorage
28+
import com.google.samples.apps.iosched.shared.data.prefs.PreferenceStorage
29+
import com.google.samples.apps.iosched.shared.di.ApplicationScope
30+
import dagger.Module
31+
import dagger.Provides
32+
import dagger.hilt.android.qualifiers.ApplicationContext
33+
import dagger.hilt.components.SingletonComponent
34+
import dagger.hilt.testing.TestInstallIn
35+
import kotlinx.coroutines.CoroutineScope
36+
import javax.inject.Singleton
37+
38+
@TestInstallIn(
39+
components = [SingletonComponent::class],
40+
replaces = [PreferencesStorageModule::class]
41+
)
42+
@Module
43+
object TestPreferencesStorageModule {
44+
45+
@Singleton
46+
@Provides
47+
fun providePreferenceStorage(dataStore: DataStore<Preferences>): PreferenceStorage =
48+
DataStorePreferenceStorage(dataStore)
49+
50+
@Singleton
51+
@Provides
52+
fun provideDataStore(
53+
@ApplicationContext context: Context,
54+
@ApplicationScope applicationScope: CoroutineScope
55+
): DataStore<Preferences> {
56+
// Using PreferenceDataStoreFactory so we can set our own application scope
57+
// that we can control and cancel in UI tests
58+
val datastore = PreferenceDataStoreFactory.create(
59+
migrations = listOf(SharedPreferencesMigration(context, PREFS_NAME)),
60+
scope = applicationScope
61+
) {
62+
context.preferencesDataStoreFile(PREFS_NAME)
63+
}
64+
return datastore
65+
}
66+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright 2021 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+
* https://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.samples.apps.iosched.tests.prefs
18+
19+
import android.content.Context
20+
import androidx.datastore.preferences.preferencesDataStore
21+
import androidx.test.core.app.ApplicationProvider
22+
import com.google.samples.apps.iosched.shared.data.prefs.DataStorePreferenceStorage
23+
import com.google.samples.apps.iosched.shared.data.prefs.PreferenceStorage
24+
import kotlinx.coroutines.flow.first
25+
import kotlinx.coroutines.runBlocking
26+
import kotlinx.coroutines.test.TestCoroutineScope
27+
import org.junit.Assert.assertEquals
28+
import org.junit.Assert.assertTrue
29+
import org.junit.Before
30+
import org.junit.Test
31+
32+
val Context.dataStore by preferencesDataStore(name = "test")
33+
34+
class DataStorePreferenceStorageTest {
35+
36+
private lateinit var dataStoreScope: TestCoroutineScope
37+
private lateinit var context: Context
38+
private lateinit var preferenceStorage: PreferenceStorage
39+
40+
@Before
41+
fun init() {
42+
dataStoreScope = TestCoroutineScope()
43+
context = ApplicationProvider.getApplicationContext()
44+
preferenceStorage = DataStorePreferenceStorage(context.dataStore)
45+
}
46+
47+
@Test
48+
fun completeOnboarding() = runBlocking {
49+
preferenceStorage.completeOnboarding(true)
50+
val result = preferenceStorage.onboardingCompleted.first()
51+
assertTrue(result)
52+
}
53+
54+
@Test
55+
fun showScheduleUiHints() = runBlocking {
56+
preferenceStorage.showScheduleUiHints(true)
57+
val result = preferenceStorage.areScheduleUiHintsShown()
58+
assertTrue(result)
59+
}
60+
61+
@Test
62+
fun showNotificationsPreference() = runBlocking {
63+
preferenceStorage.showNotificationsPreference(true)
64+
val result = preferenceStorage.notificationsPreferenceShown.first()
65+
assertTrue(result)
66+
}
67+
68+
@Test
69+
fun preferToReceiveNotifications() = runBlocking {
70+
preferenceStorage.preferToReceiveNotifications(true)
71+
val result = preferenceStorage.preferToReceiveNotifications.first()
72+
assertTrue(result)
73+
}
74+
75+
@Test
76+
fun optInMyLocation() = runBlocking {
77+
preferenceStorage.optInMyLocation(true)
78+
val result = preferenceStorage.myLocationOptedIn.first()
79+
assertTrue(result)
80+
}
81+
82+
@Test
83+
fun stopSnackbar() = runBlocking {
84+
preferenceStorage.stopSnackbar(true)
85+
val result = preferenceStorage.isSnackbarStopped()
86+
assertTrue(result)
87+
}
88+
89+
@Test
90+
fun sendUsageStatistics() = runBlocking {
91+
preferenceStorage.sendUsageStatistics(true)
92+
val result = preferenceStorage.sendUsageStatistics.first()
93+
assertTrue(result)
94+
}
95+
96+
@Test
97+
fun preferConferenceTimeZone() = runBlocking {
98+
preferenceStorage.preferConferenceTimeZone(true)
99+
val result = preferenceStorage.preferConferenceTimeZone.first()
100+
assertTrue(result)
101+
}
102+
103+
@Test
104+
fun selectFilters() = runBlocking {
105+
val filters = "filter1, filter2"
106+
preferenceStorage.selectFilters(filters)
107+
val result = preferenceStorage.selectedFilters.first()
108+
assertEquals(filters, result)
109+
}
110+
111+
@Test
112+
fun selectTheme() = runBlocking {
113+
val theme = "theme"
114+
preferenceStorage.selectTheme(theme)
115+
val result = preferenceStorage.selectedTheme.first()
116+
assertEquals(theme, result)
117+
}
118+
119+
@Test
120+
fun showCodelabsInfo() = runBlocking {
121+
preferenceStorage.showCodelabsInfo(true)
122+
val result = preferenceStorage.codelabsInfoShown.first()
123+
assertTrue(result)
124+
}
125+
}

mobile/src/main/java/com/google/samples/apps/iosched/di/AppModule.kt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import com.google.samples.apps.iosched.shared.data.agenda.DefaultAgendaRepositor
2828
import com.google.samples.apps.iosched.shared.data.config.AppConfigDataSource
2929
import com.google.samples.apps.iosched.shared.data.db.AppDatabase
3030
import com.google.samples.apps.iosched.shared.data.prefs.PreferenceStorage
31-
import com.google.samples.apps.iosched.shared.data.prefs.SharedPreferenceStorage
3231
import com.google.samples.apps.iosched.shared.di.ApplicationScope
3332
import com.google.samples.apps.iosched.shared.di.DefaultDispatcher
3433
import com.google.samples.apps.iosched.shared.di.MainThreadHandler
@@ -56,11 +55,6 @@ import javax.inject.Singleton
5655
@Module
5756
class AppModule {
5857

59-
@Singleton
60-
@Provides
61-
fun providePreferenceStorage(@ApplicationContext context: Context): PreferenceStorage =
62-
SharedPreferenceStorage(context)
63-
6458
@Provides
6559
fun provideWifiManager(@ApplicationContext context: Context): WifiManager =
6660
context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2021 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+
* https://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.samples.apps.iosched.di
18+
19+
import android.content.Context
20+
import androidx.datastore.preferences.SharedPreferencesMigration
21+
import androidx.datastore.preferences.preferencesDataStore
22+
import com.google.samples.apps.iosched.shared.data.prefs.DataStorePreferenceStorage
23+
import com.google.samples.apps.iosched.shared.data.prefs.PreferenceStorage
24+
import dagger.Module
25+
import dagger.Provides
26+
import dagger.hilt.InstallIn
27+
import dagger.hilt.android.qualifiers.ApplicationContext
28+
import dagger.hilt.components.SingletonComponent
29+
import javax.inject.Singleton
30+
31+
@InstallIn(SingletonComponent::class)
32+
@Module
33+
object PreferencesStorageModule {
34+
35+
val Context.dataStore by preferencesDataStore(
36+
name = DataStorePreferenceStorage.PREFS_NAME,
37+
produceMigrations = { context ->
38+
listOf(
39+
SharedPreferencesMigration(
40+
context,
41+
DataStorePreferenceStorage.PREFS_NAME
42+
)
43+
)
44+
}
45+
)
46+
47+
@Singleton
48+
@Provides
49+
fun providePreferenceStorage(@ApplicationContext context: Context): PreferenceStorage =
50+
DataStorePreferenceStorage(context.dataStore)
51+
}

0 commit comments

Comments
 (0)