1

Okay so I've been using StateFlow with Room database for a while. Now I have one common case. At the start of my app I have a logic that if ROOM database is empty I should show an EmptyContent(), otherwise I will show the ListContent() from ROOM database.

Now every time I launch the app, I'm always getting that EmptyContent() shown for a HALF a second maybe, and then the ListContent() is displayed. After that when I'm using the app everything works normal. But at that app launch time, while ROOM database is working I guess, that EmptyContent() is shown for just a small amount of period (Because my StateFlow default value is an empty list), and after that the actual LIST from Database is displayed.

Now I have one solution for that, to just use delay() function inside a Coroutine, to wait for example 200MS and then trigger the function for reading the DATABASE, because those 200MS are enough for ROOM database to actually get the value and update my STATE FLOW variable with the actual data instead of using that StateFlow default value for a half second at the beginning.

Is that a good solution, I must ask? Because I'm using coroutine, the thread is not blocked, and I'm just waiting until ROOM database updates my STATE FLOW variable the second time.

@Composable
fun displayContent(
    tasks: List<ToDoTask>,
    ListContent: @Composable () -> Unit
) {
    val scope = rememberCoroutineScope()
    var counter by remember { mutableStateOf(0)}
    LaunchedEffect(Unit){
        scope.launch {
            delay(200)
            counter = 1
        }
    }
    if(counter == 1){
        if (tasks.isNotEmpty()) {
            ListContent()
        } else {
            EmptyContent()
        }
    }
}
Stefan
  • 2,829
  • 5
  • 20
  • 44

1 Answers1

1

My suggestion would be map your expected states. For instance:

sealed class RequestState<out T> {

    object Idle : RequestState<Nothing>()

    object Loading : RequestState<Nothing>()

    data class Success<T>(val data: T) : RequestState<T>()
    
    data class Error(
        val t: Throwable,
        var consumed: Boolean = false
    ) : RequestState<Nothing>()
}

And your function would be something like:

@Composable
fun YourScreen() {
   val requestState = viewModel.screenData.collectAsState()
   when (requestState) {
       is Idle -> 
           // This is the default state, do nothing
       is Loading -> 
           // Display some progress indicator
       is Success ->
           YourListScreen(requestState.data) // Show the list
       is Error ->
           // Display an error.
   }
   LaunchedEffect(Unit) {
      viewModel.loadData()
   }
}

Of course, in your view model you must emit these values properly...

class YourView: ViewModel() {
    private val _screenData =
        MutableStateFlow<RequestState<List<ToDoTask>>>(RequestState.Idle)
    val screenDatae: StateFlow<RequestState<List<ToDoTask>>> = _screenData

    fun loadData() {
       _screenData.value = Loading
       try {
           // load the data from database
           _screenData.value = Success(yourLoadedData)
       } catch (e: Exception) {
           _screenData.value = Error(e)
       }
    }
}
nglauber
  • 18,674
  • 6
  • 70
  • 75
  • But what if we have to call viewModel.loadData() inside click listener of a button? It gets observed again two times in this scenario, and LaunchedEffect doesn't work under the clickListener scope. – Talha Akbar Oct 10 '22 at 11:20
  • Yes. You're right. In this case you should keep the reference of the job in your viewmodel. Check this example here: https://github.com/nglauber/books_compose/blob/master/features/books/src/main/java/com/nglauber/architecture_sample/books/viewmodel/BookListViewModel.kt – nglauber Oct 10 '22 at 12:12
  • I have found MutableSharedFlow useful for sending events from ViewModel to Composable – Talha Akbar Oct 13 '22 at 15:04
  • `MutableStateFlow` interface "extends" from the `MutableSharedFlow` interface :) – nglauber Oct 14 '22 at 14:23
  • Right. But anyways I have further found that SharedFlow is good when we have to share the state between two or more composables. For a single composable, it is good to use Flow only. And also the difference between SharedFlow (or even Flow) and StateFlow is that SharedFlow is cold and StateFlow is hot. Thank you :) – Talha Akbar Oct 14 '22 at 16:50