Need help migrating mutablestateof uistate to flow...
# compose
p
Need help migrating mutablestateof uistate to flows:
Copy code
sealed 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 flows
c
Copy code
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.value = BookState.Success(
        bookList = listOf(),
        selectedBookId = id
    )
}
p
is that the best way? there is not a copy option?
I mean, do I need to create a new instance every time a change on selected book is produced? also you are setting bookList to empty list, but I need to to keep the current bookList
also, another point, why you used a _ mutable variable and a non mutbale variable? is not possible to simply add a "private set" below the mutable variable like it was done in the sample code I provided? to avoid boilerplate variables
j
Well you can use
copy
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.
Copy code
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,
        )
    }
}
or this
Copy code
fun 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 😄
p
having this scenario, which is very similar:
Copy code
sealed 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?
Copy code
fun setProgress(value: Int) {
    (uiState.value as? UiState.Loading)?.let {
        uiState.value = it.copy(progress = value)
    }
    Log.d("XXXX", "decode progress: $value")
}
j
I would go this way, but it is my personal preference again. I am avoiding having
compose
code inside of
ViewModel
.
Copy code
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")
    }
}
p
Please can you clarify me which is the compose code? I'm not aware of it
j
Well i thought you were using
mutableStateOf
here, but not, is it
var
- nevermind then. Sorry for that.
no need of
var
here at all btw
p
You mean this can work exactly the same?
Copy code
sealed 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")
}
seems to be more simple and with less boilerplate code, maybe is something in your code that is better and I didn't notice about it?
j
Well you can collect it the same way as
StateFlow
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.
That is also why the
Mutable
one in my solution is private, you cannot set
private set
here, because the
MutableStateFlow
is still the same, the value in it isn't
p
Now I see why is better your proposal, thank you
and why you prefeer to do
Copy code
_uiState.update { uiState ->
            if (uiState !is UiState.Loading) return@update uiState

            uiState.copy(
                progress = value,
            )
        }
Instead of
Copy code
(uiState.value as? UiState.Loading)?.let {
        uiState.value = it.copy(progress = value)
    }
j
It is just more readable