Jetpack Compose Load Data (Flow.collectAsState) Common Mistakes (Compose Infinite Loop)

December 14, 2021

This is the data class to be used for data loading from firestore db.

If you are using flow to load initial data, you must make sure it only the fetch only run once.

class CardViewModel : ViewModel() {

    val itemsFlow = flow {
      val items = MutableList((1..100).random()) { index ->
          index
      }
      emit(items)
    }
}

Put a timestamp to check if the data is only load once.

fun CardScreen(viewModel: CardViewModel = viewModel()) {
    val items by viewModel.itemsFlow.collectAsState(initial = emptyList())
    Text("Items: ${items.size}, Time: ${LocalTime.now()}")
}

If you use a function, it will cause compose infinite loop (the timestamp will update forever).

class CardViewModel : ViewModel() {
    private val db = Firebase.firestore

    fun getItems() = flow {
      val items = MutableList((1..100).random()) { index ->
          index
      }
      emit(items)
    }
}
fun CardScreen(viewModel: CardViewModel = viewModel()) {
    val items by viewModel.getItems().collectAsState(initial = emptyList())
    Text("Items: ${items.size}, Time: ${LocalTime.now()}")
}

If you must use a function, you need to call the function in LaunchedEffect.

fun CardScreen(viewModel: CardViewModel = viewModel()) {
    var items by remember { mutableStateOf<List<Int>>( emptyList()) }

    LaunchedEffect(true) { // only launch once
        viewModel.getItems().collect { 
            items = it
        }
    }

    Text("Items: ${items.size}, Time: ${LocalTime.now()}")
}

Assuming you need to load initial data, and perform a fetch on every click of a button.

fun CardScreen(viewModel: CardViewModel = viewModel()) {
    var refreshCount by remember { mutableStateOf(0) }
    var items by remember { mutableStateOf<List<Int>>( emptyList()) }

    LaunchedEffect(refreshCount) { // run if refreshCount is updated
        // you can choose to generate items based on refreshCount
        viewModel.getItems().collect {
            items = it
        }
    }

    // button click will 
    Button(onClick = { refreshCount += 1 }) {
        Text("Refresh")
    }

    Text("Items: ${items.size}, Time: ${LocalTime.now()}")
}

Refer to Jetpack Compose Load Data (Initial on Show) using Flow or Coroutines.

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.