Standardization of error mamagement.

This commit is contained in:
Thomas Andres Gomez 2023-09-26 16:36:26 +02:00
parent a97a1b1ce0
commit e8504c3b52
9 changed files with 60 additions and 77 deletions

View file

@ -1,56 +1,51 @@
package com.pixelized.rplexicon.ui.composable.error
import android.app.Activity
import android.content.Intent
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import android.content.Context
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.platform.LocalContext
import com.pixelized.rplexicon.LocalSnack
import com.pixelized.rplexicon.R
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch
@Stable
sealed class FetchErrorUio {
@Stable
data class Permission(val intent: Intent) : FetchErrorUio()
data object Structure : FetchErrorUio()
@Stable
object Structure : FetchErrorUio()
@Stable
object Default : FetchErrorUio()
data object Default : FetchErrorUio()
}
@Composable
fun HandleFetchError(
errors: SharedFlow<FetchErrorUio>,
onPermissionGranted: suspend () -> Unit,
onStructureError: suspend (context: Context, snack: SnackbarHostState, error: FetchErrorUio.Structure) -> Unit = { context, snack, _ ->
snack.showSnackbar(message = context.getString(R.string.error_structure))
},
onDefaultError: suspend (context: Context, snack: SnackbarHostState, error: FetchErrorUio.Default) -> Unit = { context, snack, _ ->
snack.showSnackbar(message = context.getString(R.string.error_generic))
},
) {
val context = LocalContext.current
val snack = LocalSnack.current
val scope = rememberCoroutineScope()
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult(),
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
scope.launch {
onPermissionGranted()
}
}
}
val currentOnStructureError = rememberUpdatedState(newValue = onStructureError)
val currentOnDefaultError = rememberUpdatedState(newValue = onDefaultError)
LaunchedEffect(key1 = "LexiconErrorManagement") {
LaunchedEffect(key1 = "HandleFetchError") {
errors.collect { error ->
when (error) {
is FetchErrorUio.Permission -> launcher.launch(error.intent)
is FetchErrorUio.Structure -> snack.showSnackbar(message = context.getString(R.string.error_structure))
is FetchErrorUio.Default -> snack.showSnackbar(message = context.getString(R.string.error_generic))
is FetchErrorUio.Structure -> currentOnStructureError.value.invoke(
context, snack, error,
)
is FetchErrorUio.Default -> currentOnDefaultError.value.invoke(
context, snack, error,
)
}
}
}

View file

@ -90,9 +90,6 @@ fun LexiconScreen(
HandleFetchError(
errors = viewModel.error,
onPermissionGranted = {
viewModel.updateLexicon(force = true)
}
)
}
}

View file

@ -5,14 +5,10 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.model.Lexicon
import com.pixelized.rplexicon.repository.data.ActionRepository
import com.pixelized.rplexicon.repository.data.AlterationRepository
import com.pixelized.rplexicon.repository.data.CharacterSheetRepository
import com.pixelized.rplexicon.repository.data.LexiconRepository
import com.pixelized.rplexicon.repository.data.SpellRepository
import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import dagger.hilt.android.lifecycle.HiltViewModel
@ -102,11 +98,6 @@ class LexiconViewModel @Inject constructor(
lexiconRepository.fetchLexicon()
}
}
// user need to accept OAuth2 permission.
catch (exception: UserRecoverableAuthIOException) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Permission(intent = exception.intent))
}
// the data sheet structure is not as expected
catch (exception: IncompatibleSheetStructure) {
Log.e(TAG, exception.message, exception)
@ -125,11 +116,6 @@ class LexiconViewModel @Inject constructor(
characterSheetRepository.fetchCharacterSheet()
}
}
// user need to accept OAuth2 permission.
catch (exception: UserRecoverableAuthIOException) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Permission(intent = exception.intent))
}
// the data sheet structure is not as expected
catch (exception: IncompatibleSheetStructure) {
Log.e(TAG, exception.message, exception)

View file

@ -65,9 +65,6 @@ fun LocationScreen(
HandleFetchError(
errors = viewModel.error,
onPermissionGranted = {
viewModel.update(force = true)
}
)
}
}

View file

@ -54,11 +54,8 @@ class LocationViewModel @Inject constructor(
repository.fetchLocation()
}
}
// user need to accept OAuth2 permission.
catch (exception: UserRecoverableAuthIOException) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Permission(intent = exception.intent))
} catch (exception: IncompatibleSheetStructure) {
// the data sheet structure is not as expected
catch (exception: IncompatibleSheetStructure) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Structure)
}

View file

@ -65,9 +65,6 @@ fun QuestListScreen(
HandleFetchError(
errors = viewModel.error,
onPermissionGranted = {
viewModel.update(force = true)
}
)
}
}

View file

@ -55,11 +55,8 @@ class QuestListViewModel @Inject constructor(
repository.fetchQuests()
}
}
// user need to accept OAuth2 permission.
catch (exception: UserRecoverableAuthIOException) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Permission(intent = exception.intent))
} catch (exception: IncompatibleSheetStructure) {
// the data sheet structure is not as expected
catch (exception: IncompatibleSheetStructure) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Structure)
}

View file

@ -41,6 +41,7 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.composable.BackgroundImage
import com.pixelized.rplexicon.ui.composable.error.HandleFetchError
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.scrollOffset
@ -72,6 +73,10 @@ fun SpellDetailScreen(
spell = viewModel.spell,
onBack = { screen.popBackStack() },
)
HandleFetchError(
errors = viewModel.error,
)
}
}

View file

@ -4,11 +4,16 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.repository.data.SpellRepository
import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio
import com.pixelized.rplexicon.ui.navigation.screens.spellDetailArgument
import com.pixelized.rplexicon.utilitary.extentions.local.icon
import com.pixelized.rplexicon.utilitary.extentions.local.label
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
@ -17,28 +22,35 @@ class SpellDetailViewModel @Inject constructor(
repository: SpellRepository
) : ViewModel() {
val spell: State<SpellDetailUio?>
val error: SharedFlow<FetchErrorUio>
init {
val argument = savedStateHandle.spellDetailArgument
val assignedSpell = repository.findAssignedSpell(
character = argument.character,
spell = argument.spell,
)?.let {
SpellDetailUio(
icon = it.spell.school.icon,
name = it.spell.name,
translated = it.spell.originalName,
level = "${it.spell.level}",
school = it.spell.school.label,
castingTime = it.spell.castingTime,
range = it.spell.range,
requirement = it.spell.requirement,
duration = it.spell.duration,
description = it.spell.description,
)
}
spell = mutableStateOf(
repository.findAssignedSpell(
character = argument.character,
spell = argument.spell,
)?.let {
SpellDetailUio(
icon = it.spell.school.icon,
name = it.spell.name,
translated = it.spell.originalName,
level = "${it.spell.level}",
school = it.spell.school.label,
castingTime = it.spell.castingTime,
range = it.spell.range,
requirement = it.spell.requirement,
duration = it.spell.duration,
description = it.spell.description,
)
spell = mutableStateOf(assignedSpell)
error = MutableSharedFlow<FetchErrorUio>().also { flow ->
if (assignedSpell == null) {
viewModelScope.launch {
flow.tryEmit(FetchErrorUio.Default)
}
}
)
}
}
}