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

View file

@ -90,9 +90,6 @@ fun LexiconScreen(
HandleFetchError( HandleFetchError(
errors = viewModel.error, 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.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
import com.pixelized.rplexicon.R import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.model.Lexicon 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.CharacterSheetRepository
import com.pixelized.rplexicon.repository.data.LexiconRepository 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.ui.composable.error.FetchErrorUio
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@ -102,11 +98,6 @@ class LexiconViewModel @Inject constructor(
lexiconRepository.fetchLexicon() 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 // the data sheet structure is not as expected
catch (exception: IncompatibleSheetStructure) { catch (exception: IncompatibleSheetStructure) {
Log.e(TAG, exception.message, exception) Log.e(TAG, exception.message, exception)
@ -125,11 +116,6 @@ class LexiconViewModel @Inject constructor(
characterSheetRepository.fetchCharacterSheet() 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 // the data sheet structure is not as expected
catch (exception: IncompatibleSheetStructure) { catch (exception: IncompatibleSheetStructure) {
Log.e(TAG, exception.message, exception) Log.e(TAG, exception.message, exception)

View file

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

View file

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

View file

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

View file

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

View file

@ -41,6 +41,7 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.rplexicon.R import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.composable.BackgroundImage 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.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.theme.LexiconTheme import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.scrollOffset import com.pixelized.rplexicon.utilitary.extentions.scrollOffset
@ -72,6 +73,10 @@ fun SpellDetailScreen(
spell = viewModel.spell, spell = viewModel.spell,
onBack = { screen.popBackStack() }, 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.compose.runtime.mutableStateOf
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.repository.data.SpellRepository 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.ui.navigation.screens.spellDetailArgument
import com.pixelized.rplexicon.utilitary.extentions.local.icon import com.pixelized.rplexicon.utilitary.extentions.local.icon
import com.pixelized.rplexicon.utilitary.extentions.local.label import com.pixelized.rplexicon.utilitary.extentions.local.label
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@ -17,12 +22,11 @@ class SpellDetailViewModel @Inject constructor(
repository: SpellRepository repository: SpellRepository
) : ViewModel() { ) : ViewModel() {
val spell: State<SpellDetailUio?> val spell: State<SpellDetailUio?>
val error: SharedFlow<FetchErrorUio>
init { init {
val argument = savedStateHandle.spellDetailArgument val argument = savedStateHandle.spellDetailArgument
val assignedSpell = repository.findAssignedSpell(
spell = mutableStateOf(
repository.findAssignedSpell(
character = argument.character, character = argument.character,
spell = argument.spell, spell = argument.spell,
)?.let { )?.let {
@ -39,6 +43,14 @@ class SpellDetailViewModel @Inject constructor(
description = it.spell.description, description = it.spell.description,
) )
} }
)
spell = mutableStateOf(assignedSpell)
error = MutableSharedFlow<FetchErrorUio>().also { flow ->
if (assignedSpell == null) {
viewModelScope.launch {
flow.tryEmit(FetchErrorUio.Default)
}
}
}
} }
} }