Pablo
07/02/2024, 9:50 PMsealed interface BooksGridUiState {
data class Success(
val booksList: List<BookData>,
val selectedBookId: String? = null
) : BooksGridUiState
object Error : BooksGridUiState
object Loading : BooksGridUiState
}
var uiState: BooksGridUiState by mutableStateOf(BooksGridUiState.Loading)
private set
fun selectBook(id: String?) {
(uiState as? BooksGridUiState.Success)?.let {
uiState = it.copy(selectedBookId = id)
}
}
How can I migrate that to Flow? I'm totally stuck.
I know that this UiState with success, error and loading is the common pattern nowadays for a screen that loads content, but can't find a sample of it working with flows, and I need also to modify a value, and previously it was done with it.copy and now I don't know how to do it with flowsCaleb Cook
07/02/2024, 11:41 PMdata class BookData(
val id: String,
)
sealed interface BookState {
data class Success(
val bookList: List<BookState>,
val selectedBookId: String? = null,
) : BookState
data object Loading : BookState
data object Error : BookState
}
private val _uiBookState = MutableStateFlow<BookState>(BookState.Loading)
val uiBookState = _uiBookState.asStateFlow()
fun selectBook(id: String?) {
_uiBookState.value = BookState.Success(
bookList = listOf(),
selectedBookId = id
)
}
Pablo
07/03/2024, 8:48 AMPablo
07/03/2024, 8:49 AMPablo
07/03/2024, 8:50 AMJakub Legindi
07/03/2024, 9:24 AMcopy
here to change only the properties you want to change, so there would be no need to set the list again. But in the meaning of creating new instance, this is what copy
is actually doing. See here: https://kotlinlang.org/docs/data-classes.html#copying
Regarding your second question, I think this is mainly about preferences and project you work on. I am using the same approach as Caleb which is more clear and without any dependencies on compose
in the ViewModel what could be necessary in Multiplatform projects.
You are just transforming the private one, while the public one is changing based on it. You are also avoiding human error in case you don't set private set
which would allow you to update uiState directly from Composable.
There is approach with copy
, instead of creating new BookState.Success
data class.
data class BookData(
val id: String,
)
sealed interface BookState {
data class Success(
val bookList: List<BookState>,
val selectedBookId: String? = null,
) : BookState
data object Loading : BookState
data object Error : BookState
}
private val _uiBookState = MutableStateFlow<BookState>(BookState.Loading)
val uiBookState = _uiBookState.asStateFlow()
fun selectBook(id: String?) {
_uiBookState.update { bookState ->
if (bookState !is BookState.Success) return@update bookState
bookState.copy(
selectedBookId = id,
)
}
}
Jakub Legindi
07/03/2024, 9:27 AMfun selectBook(id: String?) {
_uiBookState.update { bookState ->
val successBookState = bookState as? BookState.Success ?: return@update bookState
successBookState.copy(
selectedBookId = id,
)
}
}
or using let like you did, but I am not fan of let
as it can be abused by developers not using it properly 😄Pablo
07/03/2024, 9:31 AMsealed interface UiState {
object Success : UiState
object Error : UiState
data class Loading(
val progress: Int = 0
) : UiState
}
var uiState = MutableStateFlow<UiState>(UiState.Loading())
private set
What do you think about this approach for updating the progress value in the Loading UiState?
fun setProgress(value: Int) {
(uiState.value as? UiState.Loading)?.let {
uiState.value = it.copy(progress = value)
}
Log.d("XXXX", "decode progress: $value")
}
Jakub Legindi
07/03/2024, 9:41 AMcompose
code inside of ViewModel
.
sealed interface UiState {
data object Success : UiState
data object Error : UiState
data class Loading(
val progress: Int = 0
) : UiState
}
private val _uiState: MutableStateFlow<UiState> = MutableStateFlow(UiState.Loading())
val uiState = _uiState.asStateFlow()
fun setLoadingProgress(value: Int) {
_uiState.update { uiState ->
if (uiState !is UiState.Loading) return@update uiState
uiState.copy(
progress = value,
)
}
Log.d("XXXX", "decode progress: $value")
}
}
Pablo
07/03/2024, 9:46 AMJakub Legindi
07/03/2024, 9:49 AMmutableStateOf
here, but not, is it var
- nevermind then. Sorry for that.Jakub Legindi
07/03/2024, 9:50 AMvar
here at all btwPablo
07/03/2024, 9:52 AMsealed interface UiState {
object Success : UiState
object Error : UiState
data class Loading(
val progress: Int = 0
) : UiState
}
val uiState = MutableStateFlow<UiState>(UiState.Loading())
fun setProgress(value: Int) {
(uiState.value as? UiState.Loading)?.let {
uiState.value = it.copy(progress = value)
}
Log.d("XXXX", "decode progress: $value")
}
Pablo
07/03/2024, 9:53 AMJakub Legindi
07/03/2024, 9:57 AMStateFlow
in your Composable, but you should not expose Mutable properties out of the ViewModel
. By that you are exposing MutableStateFlow
which could lead to some unwanted side effects. Of course you can do that, but you don't want to do that.Jakub Legindi
07/03/2024, 9:58 AMMutable
one in my solution is private, you cannot set private set
here, because the MutableStateFlow
is still the same, the value in it isn'tPablo
07/03/2024, 10:03 AMPablo
07/03/2024, 10:03 AM_uiState.update { uiState ->
if (uiState !is UiState.Loading) return@update uiState
uiState.copy(
progress = value,
)
}
Instead of
(uiState.value as? UiState.Loading)?.let {
uiState.value = it.copy(progress = value)
}
Jakub Legindi
07/03/2024, 10:07 AM