ErrorMonitor implementation more like NetworkMonitor

Removed all but the message.stateIn from AppState

Added interface and implementation, dependency injection
pull/1461/head
TM 1 year ago
parent 86c32eedd3
commit 3c69b57579

@ -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.AnalyticsHelper
import com.google.samples.apps.nowinandroid.core.analytics.LocalAnalyticsHelper 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.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.NetworkMonitor
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
@ -69,6 +70,9 @@ class MainActivity : ComponentActivity() {
@Inject @Inject
lateinit var networkMonitor: NetworkMonitor lateinit var networkMonitor: NetworkMonitor
@Inject
lateinit var errorMonitor: ErrorMonitor
@Inject @Inject
lateinit var timeZoneMonitor: TimeZoneMonitor lateinit var timeZoneMonitor: TimeZoneMonitor
@ -133,6 +137,7 @@ class MainActivity : ComponentActivity() {
val appState = rememberNiaAppState( val appState = rememberNiaAppState(
networkMonitor = networkMonitor, networkMonitor = networkMonitor,
errorMonitor = errorMonitor,
userNewsResourceRepository = userNewsResourceRepository, userNewsResourceRepository = userNewsResourceRepository,
timeZoneMonitor = timeZoneMonitor, timeZoneMonitor = timeZoneMonitor,
) )

@ -29,6 +29,7 @@ import androidx.navigation.compose.rememberNavController
import androidx.navigation.navOptions import androidx.navigation.navOptions
import androidx.tracing.trace import androidx.tracing.trace
import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourceRepository 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.NetworkMonitor
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import com.google.samples.apps.nowinandroid.core.ui.TrackDisposableJank 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.FOR_YOU
import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination.INTERESTS import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination.INTERESTS
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone
import java.util.UUID
@Composable @Composable
fun rememberNiaAppState( fun rememberNiaAppState(
networkMonitor: NetworkMonitor, networkMonitor: NetworkMonitor,
errorMonitor: ErrorMonitor,
userNewsResourceRepository: UserNewsResourceRepository, userNewsResourceRepository: UserNewsResourceRepository,
timeZoneMonitor: TimeZoneMonitor, timeZoneMonitor: TimeZoneMonitor,
coroutineScope: CoroutineScope = rememberCoroutineScope(), coroutineScope: CoroutineScope = rememberCoroutineScope(),
@ -67,6 +66,7 @@ fun rememberNiaAppState(
navController, navController,
coroutineScope, coroutineScope,
networkMonitor, networkMonitor,
errorMonitor,
userNewsResourceRepository, userNewsResourceRepository,
timeZoneMonitor, timeZoneMonitor,
) { ) {
@ -74,6 +74,7 @@ fun rememberNiaAppState(
navController = navController, navController = navController,
coroutineScope = coroutineScope, coroutineScope = coroutineScope,
networkMonitor = networkMonitor, networkMonitor = networkMonitor,
errorMonitor = errorMonitor,
userNewsResourceRepository = userNewsResourceRepository, userNewsResourceRepository = userNewsResourceRepository,
timeZoneMonitor = timeZoneMonitor, timeZoneMonitor = timeZoneMonitor,
) )
@ -85,6 +86,7 @@ class NiaAppState(
val navController: NavHostController, val navController: NavHostController,
coroutineScope: CoroutineScope, coroutineScope: CoroutineScope,
networkMonitor: NetworkMonitor, networkMonitor: NetworkMonitor,
errorMonitor: ErrorMonitor,
userNewsResourceRepository: UserNewsResourceRepository, userNewsResourceRepository: UserNewsResourceRepository,
timeZoneMonitor: TimeZoneMonitor, timeZoneMonitor: TimeZoneMonitor,
) { ) {
@ -108,44 +110,12 @@ class NiaAppState(
initialValue = false, initialValue = false,
) )
/** val errorMessage = errorMonitor.errorMessage.stateIn(
* List of [ErrorMessage] to be shown to the user, via Snackbar.
*/
private val errorMessages = MutableStateFlow<List<ErrorMessage>>(emptyList())
/**
* Current [ErrorMessage] or null if there isn't any.
*/
val errorMessage: StateFlow<ErrorMessage?> = errorMessages.map { it.firstOrNull() }.stateIn(
scope = coroutineScope, scope = coroutineScope,
started = SharingStarted.WhileSubscribed(5_000), started = SharingStarted.WhileSubscribed(5_000),
initialValue = null, 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 * Map of top level destinations to be used in the TopBar, BottomBar and NavRail. The key is the
* route. * 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(),
)

@ -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.TopicsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository 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.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.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.TimeZoneBroadcastMonitor
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import dagger.Binds import dagger.Binds
@ -71,4 +73,9 @@ abstract class DataModule {
@Binds @Binds
internal abstract fun binds(impl: TimeZoneBroadcastMonitor): TimeZoneMonitor internal abstract fun binds(impl: TimeZoneBroadcastMonitor): TimeZoneMonitor
@Binds
internal abstract fun bindsErrorMonitor(
errorMonitor: SnackbarErrorMonitor,
): ErrorMonitor
} }

@ -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<ErrorMessage?>
}

@ -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<List<ErrorMessage>>(emptyList())
/**
* Current [ErrorMessage] or null if there are none.
*/
override val errorMessage: Flow<ErrorMessage?> = 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(),
)
Loading…
Cancel
Save