-
Notifications
You must be signed in to change notification settings - Fork 495
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
IllegalStateException in restoreViewModels on startup. #212
Comments
@jQrgen did you build your own MvRx binary? We did not release this yet. This looks like a some state where our view model did not get save but you try to restore them. |
I did not build my own binary. My mistake was assuming that MvRx was androidx ready. Now i have an app in production using androidx and MvRx so I guess i could pull master since it has the merged androidx update and try building my own binary? |
@jQrgen It's fine to use mvrx with androidx. Do you have your own BaseFragment or do you use BaseMvRxFragment that ships with MvRx directly? |
Basically i'm using a home-grown version of the setup in the official example. Not too sure if it is best practice and how robust it the setup is. I have inherited from BaseMvRxFragment into my own BaseFragment class BaseFragment.kt abstract class BaseFragment: BaseMvRxFragment() {
val currentUserSessionViewModel: CurrentUserSessionViewModel by activityViewModel ()
val chatListViewModel: ChatListViewModel by activityViewModel ()
val allUsersViewModel: AllUsersViewModel by activityViewModel ()
val newsPostsViewModel: NewsPostsViewModel by activityViewModel ()
val chatViewModel: ChatViewModel by activityViewModel ()
val markersViewModel: MarkersViewModel by activityViewModel ()
protected fun navigate(@IdRes id: Int, args: Parcelable? = null) {
findNavController().navigate(id, Bundle().apply { putParcelable(MvRx.KEY_ARG, args) })
}
protected fun hideSoftKeyboard() {
val context = activity?.applicationContext ?: return
val currentView = view ?: return
val inputMethodManager = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(currentView.windowToken, 0)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
// Forward results to EasyPermissions
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
}
} Then i have inherited from the BaseFragment into a BaseEpoxyFragment class BaseEpoxyFragment.kt abstract class BaseEpoxyFragment : BaseFragment() {
private var recyclerView: EpoxyRecyclerView? = null
protected val epoxyController by lazy { epoxyController() }
abstract fun epoxyController(): MvRxEpoxyController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
epoxyController.onRestoreInstanceState(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_base_mvrx, container, false).apply {
recyclerView = findViewById(R.id.recycler_view)
recyclerView?.setController(epoxyController)
}
}
override fun invalidate() {
recyclerView?.requestModelBuild()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
epoxyController.onSaveInstanceState(outState)
}
override fun onDestroyView() {
epoxyController.cancelPendingModelBuild()
super.onDestroyView()
}
} I have used MvRxEpoxyController from the example which i have limited understanding of and have never touched. MvRxEpoxyController.kt /**
* For use with [BaseEpoxyFragment.epoxyController].
*
* This builds Epoxy models in a background thread.
*/
open class MvRxEpoxyController(
val buildModelsCallback: EpoxyController.() -> Unit = {}
) : AsyncEpoxyController() {
override fun buildModels() {
buildModelsCallback()
}
}
/**
* Create a [MvRxEpoxyController] that builds models with the given callback.
*/
fun BaseEpoxyFragment.simpleController(
buildModels: EpoxyController.() -> Unit
) = MvRxEpoxyController {
// Models are built asynchronously, so it is possible that this is called after the fragment
// is detached under certain race conditions.
if (view == null || isRemoving) return@MvRxEpoxyController
buildModels()
}
/**
* Create a [MvRxEpoxyController] that builds models with the given callback.
* When models are built the current state of the viewmodel will be provided.
*/
fun <S : MvRxState, A : MvRxViewModel<S>> BaseEpoxyFragment.simpleController(
viewModel: A,
buildModels: EpoxyController.(state: S) -> Unit
) = MvRxEpoxyController {
if (view == null || isRemoving) return@MvRxEpoxyController
withState(viewModel) { state ->
buildModels(state)
}
}
/**
* Create a [MvRxEpoxyController] that builds models with the given callback.
* When models are built the current state of the viewmodels will be provided.
*/
fun <A : BaseMvRxViewModel<B>, B : MvRxState, C : BaseMvRxViewModel<D>, D : MvRxState> BaseEpoxyFragment.simpleController(
viewModel1: A,
viewModel2: C,
buildModels: EpoxyController.(state1: B, state2: D) -> Unit
) = MvRxEpoxyController {
if (view == null || isRemoving) return@MvRxEpoxyController
withState(viewModel1, viewModel2) { state1, state2 ->
buildModels(state1, state2)
}
}
/**
* Create a [MvRxEpoxyController] that builds models with the given callback.
* When models are built the current state of the viewmodels will be provided.
*/
fun <A : BaseMvRxViewModel<B>, B : MvRxState, C : BaseMvRxViewModel<D>, D : MvRxState, E : BaseMvRxViewModel<F>, F : MvRxState> BaseEpoxyFragment.simpleController(
viewModel1: A,
viewModel2: C,
viewModel3: E,
buildModels: EpoxyController.(state1: B, state2: D, state3: F) -> Unit
) = MvRxEpoxyController {
if (view == null || isRemoving) return@MvRxEpoxyController
withState(viewModel1, viewModel2, viewModel3) { state1, state2, state3 ->
buildModels(state1, state2, state3)
}
}
|
Maybe an update to 1.0.0 will mend this issue? The problem is that i cannot reproduce so i have to upload a new apk to my customer for quality assurance ¯_(ツ)_/¯ |
Solved by implementing the following: abstract class BaseMvRxActivity : AppCompatActivity(), MvRxViewModelStoreOwner {
/**
* MvRx has its own wrapped ViewModelStore that enables improved state restoration if Android
* kills your app and restores it in a new process.
*/
override val mvrxViewModelStore by lazy { MvRxViewModelStore(viewModelStore) }
override fun onCreate(savedInstanceState: Bundle?) {
/**
* This MUST be called before super!
* In a new process, super.onCreate will trigger Fragment.onCreate, which could access a ViewModel.
*/
mvrxViewModelStore.restoreViewModels(this, savedInstanceState)
super.onCreate(savedInstanceState)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mvrxViewModelStore.saveViewModels(outState)
}
} class MainActivity: BaseMvRxActivity() { override fun onCreate(savedInstanceState: Bundle?) {
if(savedInstanceState != null)
mvrxViewModelStore.saveViewModels(savedInstanceState)
super.onCreate(savedInstanceState)
// ....
} |
Hello
When my users restart/re-open (Not sure which one) the app i get the following error report in firebase crashlytics:
I'm having a hard time reproducing the error in development.
The project has several AndroidX dependencies. Maybe this could be the problem?
Maybe the following issue regarding MvRx and AndroidX is related?
#211
Thanks for any help!
The text was updated successfully, but these errors were encountered: