Closed littleGnAl closed 5 years ago
@littleGnAl What is the purpose of testing the intermediate state prior to the completion of the ViewModel init
? That will never get dispatched to your UI or subscribers anyway. I would recommend just making sure that the state is correct after init
.
Hello, @gpeal thanks for your response. Because I want to make sure every state is correct include the the state indicate the loading, but not just the final state.
@littleGnAl If it's really critical, you could create an initializeIfNeeded
function that you call from any fragments that use the viewmodel in onCreate and that function could check if it is already initialized. Then in your test you could add your subscriber before calling it.
@gpeal Thanks for your suggestion, do you mean the code like this:
class CustomViewModel(...) : MvRxViewModel<...>(initialState) {
private var isInitialized = false
fun initializeIfNeeded() {
if (isInitialized) return
...
isInitialized = true
}
}
class CustomFragment : BaseFragment() {
private val viewModel by fragmentViewModel(...)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.initializeIfNeeded()
}
}
@littleGnAl No, you want to use some property on your state to determine that because MvRx can restore your state in a new process with @PersistState
You might want to do something like:
data class MyState(
val someProp: Async<String>,
...
) : MvRxState
class MyViewModel(state: MyState) : MvRxViewModel<MyState>(state) {
fun initializeIfNeeded() = withState { state ->
if (state.someProp !is Uninitialized) return@withState
// initialize
}
}
where someProp
is initialized to something in your initialize function.
@littleGnAl In general, never store state as properties in your ViewModel. They should be totally stateless. If this resolves your issue, can you close it?
@gpeal Sorry for so late to respond, thanks for your good suggestion, but there's a case that I don't really like using Async<*>
, such the code below:
data class SummaryMvRxViewState(
val isLoading: Boolean = false,
val summaryChartData: SummaryChartData = SummaryChartData(),
val summaryItemList: List<SummaryListItem> = emptyList(),
val error: Throwable? = null
) : MvRxState
fun loadSummary() {
Observable.zip(
getSummaryChartData(),
getSummaryItemList(),
BiFunction { summaryChartData: SummaryChartData, list: List<SummaryListItem> ->
summaryChartData to list
})
.execute {
when (it) {
is Fail -> {
copy(isLoading = false, error = it.error)
}
is Success -> {
val pair = it()!!
copy(
isLoading = false,
summaryChartData = pair.first,
summaryItemList = pair.second)
}
is Loading -> {
copy(isLoading = true)
}
else -> {
copy()
}
}
}
}
In this case if I use Async
I need to define the state like:
data class SummaryMvRxViewState(
val summaryPair: Async<Pair<SummaryChartData, List<SummaryListItem>>> = Uninitialized
) : MvRxState
Which make the state so unclear, although I can create a new class to host these data, but it seems a little redundant.
Btw, maybe it's inappropriate to discuss this in this issue.
@littleGnAl What I would do (without knowing anything else about your app) is{
data class SummaryMvRxViewState(
val summaryChartData: Async<SummaryChartData> = Uninitialized,
val summaryItemList: Async<List<SummaryListItem>> = Uninitialized
) : MvRxState {
val isLoading: Boolean = summaryChartData is Loading || summaryItemList is Loading
var error: Throwable? = (summaryChartData as? Fail)?.error ?: (summaryItemList as? Fail)?.error
}
Then you can fetch them independently without your complicated zip which also has the effect of failing both stream if either fails.
@gpeal Thank you so much, I will think more about this.
Hello, I encounter a problem when I write the unit test, I judge the state to determine if the test case passed:
But when I invoke the function in
MvRxViewModel
init
I can't get all the states from theMvRxViewModel
. Although I can use theselectSubscribe
function to get the state, this function will only get the last state after I create theMvRxViewModel
. The reason why I invoke the function ininit
is that there are some functions which we only need to invoke once for one page, such as the initial function, so I don't invoke the initial function in theonCreate
of theActivity/Fragment
, when the screen is rotated, we don't need to invoke the initialization function again but just render the UI by using the lastMvRxState
whichMvRx
will handle for us, and there is similar usage in the Wiki.My current solution is to expose the
MvRxStateStore
for eachMvRxViewModel
(more detail here MainViewModel.kt):So I can wrap the
RealMvRxStateStore
to get all the states for testing:And my test case will be like this (more detail here MainViewModelTest):
But In this way, I need to pass the
RealMvRxStateStore
to eachMvRxViewModel
.Any suggestions for this? How you guys test this case? Thanks.