From 3c69b57579de5794cb1ea5456ba7d4e01d3f491f Mon Sep 17 00:00:00 2001 From: TM Date: Thu, 23 May 2024 20:53:21 -0700 Subject: [PATCH] ErrorMonitor implementation more like NetworkMonitor Removed all but the message.stateIn from AppState Added interface and implementation, dependency injection --- .../samples/apps/nowinandroid/MainActivity.kt | 5 ++ .../apps/nowinandroid/ui/NiaAppState.kt | 50 ++----------- .../nowinandroid/core/data/di/DataModule.kt | 7 ++ .../core/data/util/ErrorMonitor.kt | 28 ++++++++ .../core/data/util/SnackbarErrorMonitor.kt | 71 +++++++++++++++++++ 5 files changed, 117 insertions(+), 44 deletions(-) create mode 100644 core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ErrorMonitor.kt create mode 100644 core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/SnackbarErrorMonitor.kt diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt index 2f8572102..8316931e2 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt @@ -41,6 +41,7 @@ import com.google.samples.apps.nowinandroid.MainActivityUiState.Success import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper import com.google.samples.apps.nowinandroid.core.analytics.LocalAnalyticsHelper import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourceRepository +import com.google.samples.apps.nowinandroid.core.data.util.ErrorMonitor import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme @@ -69,6 +70,9 @@ class MainActivity : ComponentActivity() { @Inject lateinit var networkMonitor: NetworkMonitor + @Inject + lateinit var errorMonitor: ErrorMonitor + @Inject lateinit var timeZoneMonitor: TimeZoneMonitor @@ -133,6 +137,7 @@ class MainActivity : ComponentActivity() { val appState = rememberNiaAppState( networkMonitor = networkMonitor, + errorMonitor = errorMonitor, userNewsResourceRepository = userNewsResourceRepository, timeZoneMonitor = timeZoneMonitor, ) diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt index 09902c4a5..4dd384f93 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt @@ -29,6 +29,7 @@ import androidx.navigation.compose.rememberNavController import androidx.navigation.navOptions import androidx.tracing.trace import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourceRepository +import com.google.samples.apps.nowinandroid.core.data.util.ErrorMonitor import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import com.google.samples.apps.nowinandroid.core.ui.TrackDisposableJank @@ -44,19 +45,17 @@ import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination.BOOKM import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination.FOR_YOU import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination.INTERESTS import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.flow.update import kotlinx.datetime.TimeZone -import java.util.UUID @Composable fun rememberNiaAppState( networkMonitor: NetworkMonitor, + errorMonitor: ErrorMonitor, userNewsResourceRepository: UserNewsResourceRepository, timeZoneMonitor: TimeZoneMonitor, coroutineScope: CoroutineScope = rememberCoroutineScope(), @@ -67,6 +66,7 @@ fun rememberNiaAppState( navController, coroutineScope, networkMonitor, + errorMonitor, userNewsResourceRepository, timeZoneMonitor, ) { @@ -74,6 +74,7 @@ fun rememberNiaAppState( navController = navController, coroutineScope = coroutineScope, networkMonitor = networkMonitor, + errorMonitor = errorMonitor, userNewsResourceRepository = userNewsResourceRepository, timeZoneMonitor = timeZoneMonitor, ) @@ -85,6 +86,7 @@ class NiaAppState( val navController: NavHostController, coroutineScope: CoroutineScope, networkMonitor: NetworkMonitor, + errorMonitor: ErrorMonitor, userNewsResourceRepository: UserNewsResourceRepository, timeZoneMonitor: TimeZoneMonitor, ) { @@ -108,44 +110,12 @@ class NiaAppState( initialValue = false, ) - /** - * List of [ErrorMessage] to be shown to the user, via Snackbar. - */ - private val errorMessages = MutableStateFlow>(emptyList()) - - /** - * Current [ErrorMessage] or null if there isn't any. - */ - val errorMessage: StateFlow = errorMessages.map { it.firstOrNull() }.stateIn( + val errorMessage = errorMonitor.errorMessage.stateIn( scope = coroutineScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = null, ) - /** - * Creates an [ErrorMessage] from String value and adds it to the list. - * - * @param error: String value of the error message. - * - * Returns the ID of the new [ErrorMessage] if success - * Returns null if [error] is Blank - */ - fun addErrorMessage(error: String): String? { - if (error.isNotBlank()) { - val newError = ErrorMessage(error) - errorMessages.update { it + newError } - return newError.id - } - return null - } - - /** - * Removes the [ErrorMessage] with the specified [id] from the list. - */ - fun clearErrorMessage(id: String) { - errorMessages.update { it.filter { item -> item.id != id } } - } - /** * Map of top level destinations to be used in the TopBar, BottomBar and NavRail. The key is the * route. @@ -227,11 +197,3 @@ private fun NavigationTrackingSideEffect(navController: NavHostController) { } } } - -/** - * Models the data needed for an error message to be displayed and tracked. - */ -data class ErrorMessage( - val message: String, - val id: String = UUID.randomUUID().toString(), -) diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/di/DataModule.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/di/DataModule.kt index fa4bde8b8..06f780f8b 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/di/DataModule.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/di/DataModule.kt @@ -27,7 +27,9 @@ import com.google.samples.apps.nowinandroid.core.data.repository.SearchContentsR import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository import com.google.samples.apps.nowinandroid.core.data.util.ConnectivityManagerNetworkMonitor +import com.google.samples.apps.nowinandroid.core.data.util.ErrorMonitor import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor +import com.google.samples.apps.nowinandroid.core.data.util.SnackbarErrorMonitor import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneBroadcastMonitor import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import dagger.Binds @@ -71,4 +73,9 @@ abstract class DataModule { @Binds internal abstract fun binds(impl: TimeZoneBroadcastMonitor): TimeZoneMonitor + + @Binds + internal abstract fun bindsErrorMonitor( + errorMonitor: SnackbarErrorMonitor, + ): ErrorMonitor } diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ErrorMonitor.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ErrorMonitor.kt new file mode 100644 index 000000000..6c54af621 --- /dev/null +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ErrorMonitor.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.core.data.util + +import kotlinx.coroutines.flow.Flow + +/** + * Interface for handling error messages. + */ +interface ErrorMonitor { + fun addErrorMessage(error: String): String? + fun clearErrorMessage(id: String) + val errorMessage: Flow +} \ No newline at end of file diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/SnackbarErrorMonitor.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/SnackbarErrorMonitor.kt new file mode 100644 index 000000000..4cdb91b0b --- /dev/null +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/SnackbarErrorMonitor.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.core.data.util + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update +import java.util.UUID + +/** + * Interface implementation for handling general errors. + */ + +class SnackbarErrorMonitor : ErrorMonitor { + /** + * List of [ErrorMessage] to be shown to the user, via Snackbar. + */ + private val errorMessages = MutableStateFlow>(emptyList()) + + /** + * Current [ErrorMessage] or null if there are none. + */ + override val errorMessage: Flow = errorMessages.map { it.firstOrNull() } + + /** + * Creates an [ErrorMessage] from String value and adds it to the list. + * + * @param error: String value of the error message. + * + * Returns the ID of the new [ErrorMessage] if success + * Returns null if [error] is Blank + */ + override fun addErrorMessage(error: String): String? { + if (error.isNotBlank()) { + val newError = ErrorMessage(error) + errorMessages.update { it + newError } + return newError.id + } + return null + } + + /** + * Removes the [ErrorMessage] with the specified [id] from the list. + */ + override fun clearErrorMessage(id: String) { + errorMessages.update { it.filter { item -> item.id != id } } + } +} + +/** + * Models the data needed for an error message to be displayed and tracked. + */ +data class ErrorMessage( + val message: String, + val id: String = UUID.randomUUID().toString(), +) \ No newline at end of file