There are two bugs related to `Result` affecting 1...
# compose
n
There are two bugs related to
Result
affecting 1.4.31, which is needed by compose beta: • https://youtrack.jetbrains.com/issue/KT-44867https://youtrack.jetbrains.com/issue/KT-45259 These problems result in a
ClassCastException
and the subsequent app crash. Is there any workaround? How are people using compose dealing with this?
t
Do you have a piece of code which is not working? Maybe we can help you when you post a snippet.
n
Sure, here it is:
Copy code
class GreetingService {
    fun getGreeting(callback: (Result<String>) -> Unit) {
        callback(Result.success("Hi there"))
    }
}

@Composable
fun Greeting(greetingService: GreetingService) {

    var greeting: String by remember { mutableStateOf("") }

    DisposableEffect(Unit) {

        greetingService.getGreeting { result ->
            result.fold(
                onSuccess = { greeting = it },
                onFailure = { greeting = "NOOO" }
            )
        }
        onDispose {  }
    }

    Text(text = greeting)
}
This results in a
java.lang.ClassCastException: java.lang.String cannot be cast to kotlin.Result
and the subsequent app crash.
t
I use sealed classes currently to have some kind of similar functionality.
Copy code
sealed class MyResult<out T> {
    data class Success<T>(val data: T): MyResult<T>()
    class Error(val error: Throwable): MyResult<Nothing>()
}

class GreetingService {
    fun getGreeting(callback: (MyResult<String>) -> Unit) {
        callback(MyResult.Success("Hi there"))
    }
}
@Composable
fun Greeting(greetingService: GreetingService) {
    var greeting: String by remember { mutableStateOf("") }
    DisposableEffect(Unit) {
        greetingService.getGreeting { result ->
            when (result) {
                is MyResult.Success -> { greeting = result.data }
                is MyResult.Error -> { greeting = "NOOO" }
            }
        }
        onDispose {  }
    }
    Text(text = greeting)
}
n
Thanks for the suggestion. Sure that for the people in control of the APIs using
Result
, one option is to implement their own
Result
to avoid the bug. However, given that
Result
is a standard Kotlin thing, I was wondering if no one else had bumped into this bug and if there wasn’t a “known fix”, given than the version of the Kotlin gradle plugin required by compose breaks a standard Kotlin mechanism.
🤔 1
t
Interestingly i never bumped into this Result. Where is it used?
n
Result
is from the Kotlin standard library: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/
t
So maybe as a workaround you could put the service code into an other module. In the past this worked for stuff that is not compatible with the compose compiler plugin. But i am not sure it is still a possible solution.
n
I think that if I had to modify my code anyway, I’d rather have my own temporary implementation of
Result
until the bugs are fixed.
t
In practice also often i need one more state. InProgress so i can show a loading spinner.
Copy code
sealed class LoadingState<out T: Any> {
    object Loading : LoadingState<Nothing>()
    class Error(val error: Throwable): LoadingState<Nothing>()
    class Success<T: Any>(val data: T): LoadingState<T>()
}
👍 1
I do have following pattern to do background stuff with coroutines:
Copy code
sealed class LoadingState<out T: Any> {
    object Loading: LoadingState<Nothing>()
    class Error(val error: Throwable): LoadingState<Nothing>()
    class Success<T: Any>(val data: T): LoadingState<T>()
}
@Composable
fun <T: Any> loadingStateFor(vararg inputs: Any?,
                             loadingBlock: suspend CoroutineScope.() -> T): LoadingState<T> {
    var state by remember(*inputs) { mutableStateOf<LoadingState<T>>(LoadingState.Loading) }
    LaunchedEffect(*inputs) {
        state = try {
            LoadingState.Success(loadingBlock())
        } catch (err: Exception) {
            LoadingState.Error(err)
        }
    }
    return state
}
data class CustomData(val test: String)
suspend fun dummyLoader() = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
    delay(1000)
    CustomData("Loaded data")
}
@Composable
fun exampleUsage() {
    var retryCounter by remember { mutableStateOf(0) }
    val imageState = loadingStateFor(retryCounter) {
        dummyLoader()
    }
    when (val state = imageState) {
        is LoadingState.Loading -> LoadingBox()
        is LoadingState.Success -> YourDataScreen(state.data)
        is LoadingState.Error -> ErrorBox(message = state.error.message, onRetry = { retryCounter++ })
    }
}
@Composable
fun YourDataScreen(data: CustomData) {
    //....
}
👍 1
n
Thanks for sharing this pattern, it is very useful
👍 1
t
Other approach is to use kotlin flow for this kind of things. And there is already a function observeAsState. But it depends on how you build you backend services.
t
I had the same problem in my app using Result and Compose. I moved the Compose code into a sep Android module, so that one uses IR, but the main app does not. That fixed it for me.
👍 2