Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to use collectAsState() when getting data from Firestore?

A have a screen where I display 10 users. Each user is represented by a document in Firestore. On user click, I need to get its details. This is what I have tried:

fun getUserDetails(uid: String) {
    LaunchedEffect(uid) {
        viewModel.getUser(uid)
    }
    when(val userResult = viewModel.userResult) {
        is Result.Loading -> CircularProgressIndicator()
        is Result.Success -> Log.d("TAG", "You requested ${userResult.data.name}")
        is Result.Failure -> Log.d("TAG", userResult.e.message)
    }
}

Inside the ViewModel class, I have this code:

var userResult by mutableStateOf<Result<User>>(Result.Loading)
    private set

fun getUser(uid: String) = viewModelScope.launch {
    repo.getUser(uid).collect { result ->
        userResult = result
    }
}

As you see, I use Result.Loading as a default value, because the document is heavy, and it takes time to download it. So I decided to display a progress bar. Inside the repo class I do:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

override fun getUser(uid: String) = flow {
    try {
        emit(Result.Loading)
        val user = usersRef.document(uid).get().await().toObject(User::class.java)
        emit(Result.Success(user))
    } catch (e: Exception) {
        emit(Result.Failure(e))
    }
}

I have two questions, if I may.

  1. Is there something wrong with this code? As it works fine when I compile.
  2. I saw some questions here, that recommend using collectAsState() or .collectAsStateWithLifecycle(). I tried changing userResult.collectAsState() but I cannot find that function. Is there any benefit in using collectAsState() or .collectAsStateWithLifecycle() than in my actual code? I’m really confused.

>Solution :

If you wish to follow Uncle Bob’s clean architecture you can split your architecture into Data, Domain and Presentation layers.

For android image below shows how that onion shape can be simplified to

enter image description here

You emit your result from Repository and handle states or change data, if you Domain Driven Model, you store DTOs for data from REST api, if you have db you keep database classes instead of passing classes annotated with REST api annotation or db annotation to UI you pass a UI.

In repository you can pass data as

override fun getUser(uid: String) = flow {
       val user usersRef.document(uid).get().await().toObject(User::class.java)
  emit(user)
}

In UseCase you check if this returns error, or your User and then convert this to a State here. You can also change User data do Address for instance if your business logic requires you to return an address.

If you apply business logic inside UseCase you can unit test what you should return if you retrieve data sucessfully or in case error or any data manipulation happens without error without using anything related to Android. You can just take this java class and unit test anywhere not only in Android studio

In ViewModel after getting a Flow< Result<User>> you can pass this to Composable UI.

Since Compose requires a State to trigger recomposition you can convert your Flow with collectAsState to State and trigger recomposition with required data.

CollectAsState is nothing other than Composable function produceState

@Composable
fun <T : R, R> Flow<T>.collectAsState(
    initial: R,
    context: CoroutineContext = EmptyCoroutineContext
): State<R> = produceState(initial, this, context) {
    if (context == EmptyCoroutineContext) {
        collect { value = it }
    } else withContext(context) {
        collect { value = it }
    }
}

And produceState

@Composable
fun <T> produceState(
    initialValue: T,
    key1: Any?,
    key2: Any?,
    @BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
): State<T> {
    val result = remember { mutableStateOf(initialValue) }
    LaunchedEffect(key1, key2) {
        ProduceStateScopeImpl(result, coroutineContext).producer()
    }
    return result
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading