Samuel
10/13/2024, 3:54 AMtry
works catch
doesn't display errors; I've confirmed through debugger that it sets errors in my state flow):
[click reply to see code sample]
Samuel
10/13/2024, 3:54 AMdata class ProductDetailState(
val barcode: String = "",
val product: PatchApiV3ProductBarcode200ResponseAllOfProduct? = null,
)
private val productDetailStateMutableFlow = MutableStateFlow(
ProductDetailState()
)
val productDetailStateFlow: StateFlow<ProductDetailState> = productDetailStateMutableFlow
@Composable
fun ShowError(error: String) {
Text(error, color = Color.Red)
}
@Composable
fun ProductScreen(
globalState: GlobalState,
upvote: IUpvote?,
component: ProductComponent,
modifier: Modifier = Modifier
) {
val globalGlobalGlobalStateLocal = globalGlobalState.collectAsState()
val productStateLocalLocal = productDetailStateFlow.collectAsState()
Scaffold(
topBar = {
ExpandableSearchView(
searchDisplay = globalGlobalGlobalStateLocal.value.barcode ?: "",
onSearchDisplayChanged = {},
onSearchDisplayClosed = {},
)
},
modifier = modifier
) { innerPadding ->
Column(Modifier.padding(innerPadding)) {
if (!globalGlobalGlobalStateLocal.value.lastErrorStr.isNullOrBlank()) {
ShowError(globalGlobalGlobalStateLocal.value.lastErrorStr!!)
} else if (!productStateLocalLocal.value.lastErrorStr.isNullOrBlank()) {
ShowError(productStateLocalLocal.value.lastErrorStr!!)
} else if ((globalGlobalGlobalStateLocal.value.barcode?.length ?: 0) > 3) {
val openFoodFactsApi = OpenFoodFactsApi(HttpClient {
/*expectSuccess = true
HttpResponseValidator {
handleResponseExceptionWithRequest { exception, _ /* request */ ->
val clientException = exception as? ClientRequestException
?: return@handleResponseExceptionWithRequest
val exceptionResponse = clientException.response
if (exceptionResponse.status == HttpStatusCode.NotFound) {
val exceptionResponseText = exceptionResponse.bodyAsText()
productDetailStateMutableFlow.update { it.copy(lastErrorStr = "[404] $exceptionResponseText") }
} else {
productDetailStateMutableFlow.update {
it.copy(
lastErrorStr = "[${exceptionResponse.status.value}] ${exceptionResponse.bodyAsText()}"
)
}
}
}
}*/
install(ContentNegotiation) {
json(Json {
encodeDefaults = true
isLenient = true
allowSpecialFloatingPointValues = true
allowStructuredMapKeys = true
prettyPrint = false
useArrayPolymorphism = false
ignoreUnknownKeys = true
})
}
})
val scope = rememberCoroutineScope()
LaunchedEffect(globalGlobalGlobalStateLocal.value.barcode) {
try {
val product = scope.async {
openFoodFactsApi.productById(globalGlobalGlobalStateLocal.value.barcode!!)
}.await().product
if (product?.productName != null) {
productDetailStateMutableFlow.update { it.copy(productName = product.productName) }
}
if (product?.imageFrontUrl != null) {
productDetailStateMutableFlow.update { it.copy(imageFrontUrl = product.imageFrontUrl) }
}
} catch (e: CancellationException) {
currentCoroutineContext().ensureActive()
} catch (e: HttpResponseException) {
productDetailStateMutableFlow.update {
it.copy(
lastErrorStr = "[${e.response.status}] ${e.response.bodyAsText()}"
)
}
} catch (e: SocketTimeoutException) {
productDetailStateMutableFlow.update {
it.copy(
lastErrorStr = "[SocketTimeoutException] ${e.message}"
)
}
}
}
if (!productStateLocalLocal.value.productName.isNullOrEmpty()) {
Column {
Text(productStateLocalLocal.value.productName)
SubcomposeAsyncImage(
model = productStateLocalLocal.value.imageFrontUrl,
loading = {
CircularProgressIndicator()
},
contentDescription = "${productStateLocalLocal.value.productName} image",
onError = { e -> println("img e $e") }
)
}
} else if (!globalGlobalGlobalStateLocal.value.lastErrorStr.isNullOrBlank()) {
ShowError(globalGlobalGlobalStateLocal.value.lastErrorStr!!)
} else if (!productStateLocalLocal.value.lastErrorStr.isNullOrBlank()) {
ShowError(productStateLocalLocal.value.lastErrorStr!!)
} else {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator(
modifier = Modifier.width(64.dp),
)
}
}
Text("globalGlobalGlobalStateLocal.value.lastErrorStr.isNullOrBlank(): ${globalGlobalGlobalStateLocal.value.lastErrorStr.isNullOrBlank()}")
Text("globalGlobalGlobalStateLocal.value.lastErrorStr = ${globalGlobalGlobalStateLocal.value.lastErrorStr}")
Text("produceStateLocalLocal.value.lastErrorStr.isNullOrBlank(): ${productStateLocalLocal.value.lastErrorStr.isNullOrBlank()}")
Text("produceStateLocalLocal.value.lastErrorStr = ${productStateLocalLocal.value.lastErrorStr}")
/*if (globalGlobalGlobalStateLocal.barcode.isNullOrEmpty()
|| produceStateLocalLocal.productName.isNullOrEmpty()
) {*/
}
}
}
}
sigmadelta
10/13/2024, 5:47 AMglobalGlobalGlobalStateLocal
into account. But there is a strong chance that your error is not displaying as the API doesn't get a decent chance to return a value as it will be rebuilt with every recomposition where the barcode is > 3, or because you are launching a coroutine within the coroutine that contains your try-catch block.
I would strongly suggest taking another look at how things like `ViewModel`s, Repositories
and items like remember
work to get a better grip on how to deal with architectures & UI-state in Android.
Things I would do in short:
• Move creation of HttpClient
& OpenFoodFactsApi
out of the UI (ideally use something Koin for dependency injection but if you're not familiar with that concept yet, just do it somewhere not in a Composable).
• Create a OpenFoodFactsViewModel
which has a trigger to get productById
and afterward translate the output from the OpenFoodFactsApi
into a state-object. (This is where your try-catch could be done so you can figure out whether the state you're returning to the UI is either data or an error)
• Collect that state object from OpenFoodFactsViewModel
in your ProductScreen
There are plenty of similar samples on developer.android that can help you out: https://developer.android.com/develop/ui/compose/architecture#additional-resourcesSamuel
10/13/2024, 4:00 PMsigmadelta
10/14/2024, 6:07 AMSamuel
10/14/2024, 9:51 PMViewModel
working on Android and desktop. Time to try and get it working on iOS and web next.