From b405e5a484b4cf7e169fc9c91278fdc953d54925 Mon Sep 17 00:00:00 2001 From: Shuvo Date: Sun, 24 Nov 2024 21:22:51 +0600 Subject: [PATCH 1/4] Exception handling for when reading datastore. --- .../core/datastore/NiaPreferencesDataSource.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt b/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt index 9a76a75a1..5cc237ff7 100644 --- a/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt +++ b/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt @@ -21,6 +21,7 @@ import androidx.datastore.core.DataStore import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand import com.google.samples.apps.nowinandroid.core.model.data.UserData +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import java.io.IOException @@ -30,6 +31,14 @@ class NiaPreferencesDataSource @Inject constructor( private val userPreferences: DataStore, ) { val userData = userPreferences.data + .catch { exception -> + if (exception is IOException) { + Log.e("NiaPreferences", "Error reading user preferences.", exception) + emit(UserPreferences.getDefaultInstance()) + } else { + throw exception + } + } .map { UserData( bookmarkedNewsResources = it.bookmarkedNewsResourceIdsMap.keys, From 9e36bdc5a6a749291d36bf29d38eb0bc338786b4 Mon Sep 17 00:00:00 2001 From: Shuvo Date: Mon, 25 Nov 2024 20:34:35 +0600 Subject: [PATCH 2/4] Catch exception and replace with default data --- .../core/datastore/NiaPreferencesDataSource.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt b/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt index 5cc237ff7..ba45606df 100644 --- a/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt +++ b/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt @@ -164,6 +164,14 @@ class NiaPreferencesDataSource @Inject constructor( } suspend fun getChangeListVersions() = userPreferences.data + .catch { exception -> + if (exception is IOException) { + Log.e("NiaPreferences", "Error reading user preferences.", exception) + emit(UserPreferences.getDefaultInstance()) + } else { + throw exception + } + } .map { ChangeListVersions( topicVersion = it.topicChangeListVersion, From 46d8cb9eb7b2a4f7edda5c46e2adb3917a0617a7 Mon Sep 17 00:00:00 2001 From: Shuvo Date: Tue, 10 Dec 2024 21:45:27 +0600 Subject: [PATCH 3/4] Add some unit tests. --- .../datastore/NiaPreferencesDataSourceTest.kt | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSourceTest.kt b/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSourceTest.kt index 433bbb5ea..2804b6857 100644 --- a/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSourceTest.kt +++ b/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSourceTest.kt @@ -16,13 +16,18 @@ package com.google.samples.apps.nowinandroid.core.datastore +import androidx.datastore.core.DataStore +import androidx.datastore.core.IOException import com.google.samples.apps.nowinandroid.core.datastore.test.InMemoryDataStore +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -32,11 +37,34 @@ class NiaPreferencesDataSourceTest { private lateinit var subject: NiaPreferencesDataSource + // A DataStore implementation that throws an IOException when accessed + private val ioExceptionThrowingDataStore = object : DataStore { + override val data: Flow + get() = flow { throw IOException("Failed to read proto") } + + override suspend fun updateData(transform: suspend (t: UserPreferences) -> UserPreferences): UserPreferences { + throw Exception("Not needed for this test") + } + } + @Before fun setup() { subject = NiaPreferencesDataSource(InMemoryDataStore(UserPreferences.getDefaultInstance())) } + @Test + fun userData_emitDefault_whenDataStoreThrowsIOException() = + testScope.runTest { + // Given: NiaPreferencesDataSource with ioException throwing datastore + val dataSource = NiaPreferencesDataSource(ioExceptionThrowingDataStore) + + // When: Retrieving user data from the data source + val actualUserData = dataSource.userData.first() + + // Then: The default user data is returned + assertEquals(subject.userData.first(), actualUserData) + } + @Test fun shouldHideOnboardingIsFalseByDefault() = testScope.runTest { assertFalse(subject.userData.first().shouldHideOnboarding) @@ -76,6 +104,19 @@ class NiaPreferencesDataSourceTest { assertFalse(subject.userData.first().shouldHideOnboarding) } + @Test + fun getChangeListVersions_returnsDefault_whenDataStoreThrowsIOException() = + testScope.runTest { + // Given: NiaPreferencesDataSource with ioException throwing datastore + val dataSource = NiaPreferencesDataSource(ioExceptionThrowingDataStore) + + // When: Retrieving change list versions from the data source + val actualResult = dataSource.getChangeListVersions() + + // Then: The default value is returned + assertEquals(subject.getChangeListVersions(), actualResult) + } + @Test fun shouldUseDynamicColorFalseByDefault() = testScope.runTest { assertFalse(subject.userData.first().useDynamicColor) From 931de6b302ff646a5b3b79552cb0b58a4ff23e6c Mon Sep 17 00:00:00 2001 From: Shuvo Date: Wed, 11 Dec 2024 07:47:03 +0600 Subject: [PATCH 4/4] kotlin-test fail to mark test case failed --- .../core/datastore/NiaPreferencesDataSourceTest.kt | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSourceTest.kt b/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSourceTest.kt index 2804b6857..190afea74 100644 --- a/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSourceTest.kt +++ b/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSourceTest.kt @@ -30,6 +30,7 @@ import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue +import kotlin.test.fail class NiaPreferencesDataSourceTest { @@ -43,7 +44,7 @@ class NiaPreferencesDataSourceTest { get() = flow { throw IOException("Failed to read proto") } override suspend fun updateData(transform: suspend (t: UserPreferences) -> UserPreferences): UserPreferences { - throw Exception("Not needed for this test") + fail("Not needed for this test") } } @@ -55,13 +56,9 @@ class NiaPreferencesDataSourceTest { @Test fun userData_emitDefault_whenDataStoreThrowsIOException() = testScope.runTest { - // Given: NiaPreferencesDataSource with ioException throwing datastore val dataSource = NiaPreferencesDataSource(ioExceptionThrowingDataStore) - - // When: Retrieving user data from the data source val actualUserData = dataSource.userData.first() - // Then: The default user data is returned assertEquals(subject.userData.first(), actualUserData) } @@ -107,13 +104,9 @@ class NiaPreferencesDataSourceTest { @Test fun getChangeListVersions_returnsDefault_whenDataStoreThrowsIOException() = testScope.runTest { - // Given: NiaPreferencesDataSource with ioException throwing datastore val dataSource = NiaPreferencesDataSource(ioExceptionThrowingDataStore) - - // When: Retrieving change list versions from the data source val actualResult = dataSource.getChangeListVersions() - // Then: The default value is returned assertEquals(subject.getChangeListVersions(), actualResult) }