Hello, what is a good idiomatic way to fetch data ...
# compose-desktop
j
Hello, what is a good idiomatic way to fetch data async and show a loading state?
o
From UI
Copy code
@Composable
fun MyScreen(viewModel: MyViewModel) {
  val myData by viewModel.myState.collectAsStateWithLifecycle(null)
  LaunchedEffect(myData) {
    if (myData == null) {
      viewModel.fetchData()
    }
  }

  when (myData) {
    null,
    MyUiState.Loading -> CircularProgressIndicator()
    MyUiState.Error -> SomeErrorContent()
    is MyUiState.Data -> SomeContent(myData) // cast as MyDataState
  }
}
Given
Copy code
sealed interface MyUiState {
  object Loading : MyUiState
  object Error : MyUiState
  data class Data(val some: Data, ...) : MyUiState
}
Copy code
class MyViewModel(...) : ViewModel() {
  private val _myState = MutableStateFlow<MyUiState?>(null)
  val myState = _myState.asStateFlow()

  fun fetchData() {
    viewModelScope.launch {
      _myState.value = MyUiState.Loading
      // usually extracted in a domain layer through a use case or stuff like that, or directly with a data layer repository depending on the complexity & chosen architecture
      val result = withContext(Dispatchers.IO) {
        try { // or use runCatching + Result depending on the goal
          ... compute result somehow
        } catch (e: Exception) {
          null
        }
      }
      _myState.value = when (result) {
        null -> MyUiState.Error
        else -> MyUiState.Data(result.something)
      }
    }
  }
}
j
Thanks!
o
(this is is a straight to the point idea, there are refinements and usually, things are split a bit more)
this has nothing to do with Compose desktop that being said. If you use multiplatform APIs, you can use
collectAsStateWithLifecycle
, if it's purely desktop and you only use Jetbrains APIs in the
jvmMain
source set, you might only have
.collectAsState
but this would be the same
With
runCatching
Copy code
_myState.value = withContext(Dispatchers.IO) {
        runCatching { // or use runCatching + Result depending on the goal
          ... compute result somehow
        }
      }.fold(
        onFailure = { e -> MyUiState.Error },
        onSuccess = { result -> MyUiState.Data(result.something) },
      )
a
I keep any state in the composable itself as separate mutableStates. no view models, sealed classes or anything like that. havent used view models since android
j
I use decompose rn and quite like it
a
I’d say produceState is the idiomatic way. With Loading, Success(val data) and Failure(val error) states.
2
a
Since you are using Decompose, you can expose a StateFlow from your component, initialize it with a Loading state (e.g. a flag in your state class, or define a sealed class), then start fetching data in the
init {}
block or in
doOnCreate {}
, lastly update your state with the fetched data.
This will reduce the amount of side effects in your Composable, and you can also easily test the logic with simple unit tests.
j
Thanks!
l
Depends. If the data is rather small,
produceState
with different states and the value onces loaded might seem fine, but you might want to handle retries, and it immediately becomes not so simple.
It ultimately depends on how well you want to handle errors, and the QoS (Quality of Service) you want to provide. If it's a prototype, it probably doesn't matter. Otherwise, it might be very important.