https://kotlinlang.org logo
a

Advitiay Anand

07/11/2023, 4:42 PM
Hey folks, I've been trying to incorporate Clean Architecture principles in Compose the past couple months. I have the basics understood well, but there's several issues I'm not so sure about. I referred to Philipp Lackner's guide while starting to learn, and he uses a separate ScreenState data class for each ScreenViewModel and ScreenComposable. Inside this State class, there's 3 fields: isLoading, data, and error. All 3 are observed as MutableState inside the ScreenComposable. For the purposes of his tutorial, this worked perfectly. My concern is that I have several different data fields for a single screen, and while fetching all of them the isLoading field is set to true (to display the Loader). As u can imagine, this can cause a bad UX as every minor data retrieval causes the whole screen to load. I would like to provide a much smoother UX by potentially having different loaders for each data field. But I'm not sure about any existing patterns I can use to achieve that. Is this a common problem with this state pattern, and are there any known solutions ? Any help is very much appreciated. Thanks in advance. 😄
d

dorche

07/12/2023, 7:29 PM
You could have something like
Copy code
sealed interface FieldValue {
    object Loading: FieldValue
    data class Error(val throwable: Throwable): FieldValue
    data class Success<out T>(val data: T): FieldValue
}
Although someone might have to check my generics there
a

Advitiay Anand

07/12/2023, 10:26 PM
I understand. This is cool if the screen has only one data field needed (e.g. one List<String>). My question is, if the screen has 3 UI elements, each requiring its own List<String> (let's call them data1, data2, data3), then having only one Loading state causes problems. The user sees the same animation no matter which data field is being updated, giving a bad UX.
d

dorche

07/13/2023, 10:35 AM
Just have all 3 fields use FieldValue and update them appropriately when the state changes for each
a

Advitiay Anand

07/13/2023, 11:16 AM
Fair enough. So keep each data field nullable, and display data-specific loader when they are null? Then the isLoading state gets updated only when the entire screen starts or stops loading. Is that what you're suggesting?
In case you have a repo I can refer to for seeing how it's implemented, that'd be even more helpful. Thanks a lot for your help btw!
t

Tom De Decker

07/13/2023, 12:05 PM
I think they mean something like this where each property you want to keep track of is wrapped in a
FieldValue
which can have their own Loading/Error state independent of the other properties:
Copy code
sealed interface FieldValue<T> {
    class Loading<T>: FieldValue<T>
    data class Error<T>(val throwable: Throwable): FieldValue<T>
    data class Success<T>(val data: T): FieldValue<T>
}

class MyViewModel {
    val data1 = mutableStateOf<FieldValue<List<String>>>(FieldValue.Loading())
    val data2 = mutableStateOf<FieldValue<List<Int>>>(FieldValue.Loading())
    val data3 = mutableStateOf<FieldValue<Boolean>>(FieldValue.Loading())

    fun updateData1(newData: List<String>) {
        data1.value = FieldValue.Success(newData)
    }
}
a

Advitiay Anand

07/13/2023, 12:14 PM
Oh, thanks for clarifying. I see, this makes a lot of sense. Then the only major change in approach I'll have to do is instead of using a State class to hold all the data and state fields, I would have to keep those fields in the ViewModel directly. And ensure that each field is of type MutableState<FieldValue<T>> Appreciate the explanation, especially with a code snippet. Is there by any chance a name for this pattern? I'd like to read more about it.
d

dorche

07/13/2023, 12:37 PM
Yep Tom has exactly what I meant there. You can still introduce a wrapper State class to hold all these fields if you want. The "problem" with this approach is that the number of combinations (e.g. component 1 is loading, component 2 has loaded already, component 3 has errored) explodes quite quickly so it's a bit of a pain to test if you want to cover all "screen states"
a

Advitiay Anand

07/13/2023, 4:03 PM
Got it. This has been very insightful! Since you mentioned the problem with "this" approach (I assume regardless of whether or not we use a wrapper State class), are there any alternative ways to handle persisted state in Jetpack Compose that are more efficient? For instance, I have seen some projects use mutableStateOf() inside data classes. Apparently, Composables work with those too. But I don't see the benefit of using such an approach tbh.
d

dorche

07/13/2023, 4:32 PM
Yeah mutableStateOf will have the same "problem". By saying this I meant the fact you want individual loading states - it will always be a lot more code to write than having 1 screen state just and everything loading together. This is not really a problem hence the quotes but it does increase complexity.