Home > OS >  How to propagate the response of an async operation to the view using Jetpack Compose?
How to propagate the response of an async operation to the view using Jetpack Compose?

Time:10-01

I have this sealed class:

sealed class Resource<out T> {
    object Loading: Resource<Nothing>()
    data class Success<out T>(val data: T): Resource<T>()
    data class Failure(val message: String): Resource<Nothing>()
}

In the repository class I have this function that deletes an item from an API:

override suspend fun deleteItem(id: String) = flow {
    try {
        emit(Resource.Loading)
        emit(Resource.Success(itemsRef.document(id).delete().await()))
    } catch (e: Exception) {
        emit(Resource.Failure(e.message))
    }
}

The result of the delete operation is Void?. Now, in the ViewModel class I declare:

val state = mutableStateOf<Resource<Void?>>(Success(null))

And update it when the delete completes:

fun deleteItem(id: String) {
    viewModelScope.launch {
        repo.deleteItem(id).collect { response ->
            state.value = response
        }
    }
}

I have created a Card and inside onClick I have added:

IconButton(
    onClick = viewModel.deleteItem(id),
)

Which actually deletes that item form database correctly. But I cannot track the result of the operation. I tried using:

when(val res = viewModel.state.value) {
    is Resource.Loading -> Log.d(TAG, "Loading")
    is Resource.Success -> Log.d(TAG, "Success")
    is Resource.Failure -> Log.d(TAG, "Failure")
}

But only the case Loading is triggered. No success/failure at all. What can be wrong here? As it really acts like a synchronous operation.

CodePudding user response:

I've tested your approach without a repository, and compose part looks totally fine:

var i = 0

@Composable
fun TestScreen(viewModel: TestViewModel = viewModel()) {
    val state by viewModel.state
    Text(
        when (val stateSmartCast = state) {
            is Resource.Failure -> "Failure ${stateSmartCast.message}"
            Resource.Loading -> "Loading"
            is Resource.Success -> "Success ${stateSmartCast.data}"
        }
    )
    Button(onClick = {
        viewModel.deleteItem(  i)
    }) {

    }
}

class TestViewModel : ViewModel() {
    val state = mutableStateOf<Resource<Int>>(Resource.Success(i))

    fun deleteItem(id: Int) {
        viewModelScope.launch {
            deleteItemInternal(id).collect { response ->
                state.value = response
            }
        }
    }

    suspend fun deleteItemInternal(id: Int) = flow {
        try {
            emit(Resource.Loading)
            delay(1000)
            if (id % 3 == 0) {
                throw IllegalStateException("error on third")
            }
            emit(Resource.Success(id))
        } catch (e: Exception) {
            emit(Resource.Failure(e.message ?: e.toString()))
        }
    }
}

enter image description here

So the the problem looks like in this line itemsRef.document(id).delete().await()), or in your connection to the repository.

CodePudding user response:

Try collecting in the composable function:

val state = viewModel.state.collectAsState()

Then you can do: when (val res = viewModel.state.value){...}.

However I am sceptical about the deleteItem in the repository returning a flow. Do you really need such thing? You can always map stuff in the viewModel.

  • Related