hi i'm new with flow and StateFlow. I have this ar...
# android
a
hi i'm new with flow and StateFlow. I have this architecture with LiveData:
Copy code
open class BaseViewModel<State : Any, Event : Any> : ViewModel() {



     private val stateLiveData: MutableLiveData<State> = MutableLiveData()
and now with StateFlow:
Copy code
open class BaseFlowViewModel<State : Any, Event : Any> : ViewModel() {





      private val _uiState = MutableStateFlow<State>()

    val uiState: StateFlow<State> = _uiState
MutableStateFlow<State>() wants a parameter... what can i put as paramter?
c
k
MutableStateFlow
has always
value
, so it needs initial value, so you need to pass it when initializing. I would suggest to make your
BaseViewModel
accept
initialState
, that you will then pass to
MutableStateFlow
"constructor"
a
Copy code
private val _uiState = MutableStateFlow<State>( Any() as State)
can i use this syntax?
c
No, Kotlin does not support dynamic typing like that, casting
Any
to
State
will crash. You need to create an instance of
State
with default values that represent “the state of your screen before you’ve started loading or changing anything”. Typically, I make sure my
State
class has default values for all its constructor parameters, so that I can simply do
MutableStateFlow<State>(State())
in my ViewModel, and leave the “uninitialized-state values” as a concern of the
State
class itself
a
State is a Generic
how can create and isntance
e
you can start with
MutableStateFlow<State?>(null)
and
.filterNotNull()
for clients
a
mmm 😞 it isn't a good solution for me
i
If you don't want a default initial value, maybe you're looking for
MutableSharedFlow
, which is a
MutableStateFlow
without the
state
c
Give
BaseFlowViewModel
a constructor parameter of that initial state. Subclasses must provide the initial value themselves
Copy code
open class BaseFlowViewModel<State : Any, Events : Any> : ViewModel() {
    private val _uiState = MutableStateFlow<State>()
}

class Screen1ViewModel : BaseFlowViewModel<Screen1ViewModel.State, Screen1ViewModel.Events>(Screen1ViewModel.State()) {

    data class State(
        val loading: Boolean = false,
        val intValue: Int = 0,
    ) 
    
    sealed class Events { 
        object GoToScreen2 : Events()
    }
}
a
nice
i can change Generic with a sealed class
ok i use nullable 😞
Copy code
open class BaseFlowViewModel<State : Any, Event : Any> : ViewModel() {

    
    val _uiState = MutableStateFlow<State?>(null)

    val uiState: StateFlow<State?> = _uiState
it works with generic
so i can use my activity with generic
Copy code
abstract class BaseFlowActivityViewBinding<State : Any, Event : Any>() : AppCompatActivity() {
and onCreate:
Copy code
lifecycleScope.launchWhenStarted {
 
    provideBaseViewModel().uiState.collect { uiState ->
       handleUiState(uiState)

    }
}
in Activity has :
Copy code
override fun handleUiState(state: MainState?) {
   
        when(state){
            is MainState.Print ->{
                Log.i("asarch", " PRINT")
            }
        }
    

}
I can delete LiveData and the price is... nullable State or Events... 🤪
e
livedata also has potential nulls, flow just made it obvious
i
Yep, if you were calling
stateLiveData.value
before that initial value, you'd get
null
But in coroutines, there's not just StateFlow. If don't need a value, use SharedFlow. If you want single processing events, you should be using a Channel and
receiveAsFlow()
to process them as per https://elizarov.medium.com/shared-flows-broadcast-channels-899b675e805c#49e3
a
thank @Ian Lake
all works now.. with StateFlow.. but i'll try ShareFow asap
j
Well a livedata is basically always giving a possible nullable variable, so using a nullable value in your stateflow is basically the same as what you are doing with livedata. Also providing a default state wouldn't be a bad thing, perhaps you can think whether you really need it to be missing. How would you get stateFlow.value if it is not initialized? 🙂 a liveData.value just returns null if you did not set a value to it. Wouldn't it be nice to have an initial state? I suggest you use a default state. For example "what page am I showing?" I show a page called StartView while I retrieve from preferences whether I have credentials stored or not on app start. When I have a ViewState I initialize a view state with empty or sane default values for all fields in the view state.
MutableStatEFlow(ViewState(title = "", items = emptyList())
You can also wrap things in a sealed result object that can be Loading, Success, Error: Result<State> Then you initialize your state with
MutableStateFlow(Loading())
because that is often the state you are in at the beginning, loading your UI. Also, for event you don't want to use a stateflow. An event is a 1-off thing you fire into the wild, doesn't need to have state (even if it can be replayed).
a
@Joost Klitsie i agree but there is a Generic Type so i don't know what is empty or inittilize state
i
Then you should not use
StateFlow
j
Or perhaps think about the way you try to do this. I think I have 0 open classes in my project, perhaps you can try out an abstract way of providing the base view model, in which you can either make the flow abstract or let the child implement an initial value. I think abstract base classes are a very useful tool, open classes are prone to hidden errors.