I am at stage when I would like to expose my StateFlow<Model>()
into the common BaseViewModel()
class to implement common operations on it without repeating them in others implementations of ViewModels
. I ended up with one idea, but faced with several limitations when has built PoC. My idea and its limitations are below, your solutions for origin question are welcomed.
I split my model into interface
and one concrete implementation.
sealed interface Model {
var isLoading: Boolean
var errors: List<String>
/**
* @param obj Should has the same concrete type as concrete type of object which copy() it invokes
* */
fun copy(obj: Model): Model
}
data class DeputiesModel(
override var isLoading: Boolean = false,
override var errors: List<String> = emptyList<String>(),
var deputies: List<Deputy> = emptyList()
) : Model {
override fun copy(obj: Model): DeputiesModel {
if (obj !is DeputiesModel)
throw IllegalArgumentException("Passed object implements ${Model::javaClass.name}" +
" interface, but should be concrete ${DeputiesModel::javaClass::name} implementation.")
return this.copy(deputies = obj.deputies, isLoading = obj.isLoading, errors = obj.errors)
}
}
I need a copy()
method in interface to data class copy()
is closed for overloading and overriding.
My StateFlow
implementation has been moved in BaseViewModel()
abstract class BaseViewModel<T : Model> : ViewModel() {
protected lateinit var state: MutableStateFlow<T>
lateinit var uiState: StateFlow<T>
}
I added generics here to avoid casting Model
type to one of its concrete implementation, e.g. DeputiesModel
, in classes which inherits BaseViewModel
, otherwise this extra code would made a whole intent to expose common methods redundant.
And here is a first common method:
fun removeShownError() {
state.update { state ->
state.errors = state.errors.filter { str -> !str.equals(state.errors.first()) }
state.copy(state) as T
}
}
The limitation of this design&implementation is state.copy(state)
doesn't trigger uiState.collectLatest{}
call when origin parameterized state.copy(isLoading = false)
does it. I haven't find yet the root cause of it.
val viewModel: DeputiesViewModel by viewModels { viewModelFactory }
lifecycleScope.launch {
viewModel.uiState.collectLatest { it ->
if (it.errors.isNotEmpty()) {
showError(
view = requireActivity().findViewById(R.id.nav_view),
text = it.errors.first(),
onDismiss = { viewModel.removeShownError() }
)
}
(binding.list.adapter as DeputiesAdapter).update(it.deputies)
}
}
That's all. Your ideas are appreciated.