InsertKoinIO / koin

Koin - a pragmatic lightweight dependency injection framework for Kotlin & Kotlin Multiplatform
https://insert-koin.io
Apache License 2.0
8.98k stars 710 forks source link

Android screen rotation creates new instance of fragment which causes the instance of viewmodel to be lost #693

Closed vivekwolf123 closed 3 years ago

vivekwolf123 commented 4 years ago

Describe the bug I am using - implementation "org.koin:koin-androidx-viewmodel:2.1.0-alpha-3"

In my modules, I declare it as - private val viewModels = module { viewModel { (handle: SavedStateHandle) -> HomeViewModel(handle, get()) } }

I create my HomeFragment inside onCreate() of MainActivity when savedInstanceState == null.

In my HomeFragment I inject my viewModel like this -

private val homeViewModel: HomeViewModel by viewModel { parametersOf( Bundle(), "homeViewModel" ) }

And in HomeFragment, inside onViewCreated() I observe the changes in livedata -

homeViewModel.showResults().observe(this, Observer { .... })

When the screen is rotated to landscape, the onCreate() of MainActivity is called again and the fragment is newly created and the viewModel instance in the fragment is gone.

Is it not possible for the viewmodel instance to be retained even after screen rotation change?

nikhil-thakkar commented 4 years ago

ViewModel instances are meant to survive configuration changes, screen rotation for e.g. This is the default behaviour. Could try printing the ViewModel object to logcat and see the hash are the same before and after rotation? Log.d(TAG, homeViewModel); in onCreateView.

vivekwolf123 commented 4 years ago

Yes I printed out as you suggested and the hashes are same before and after rotation. I inject the viewModel in my fragment HomeFragment as -

private val homeViewModel: HomeViewModel by viewModel { parametersOf( Bundle(), "homeViewModel" ) }

Basically HomeFragment is a login screen where I enter email and password and I hit login button to make a login API request. In the middle of the API request, I rotate the screen and the progress dialog is gone and the login view stays although API request succeeds after a while. When I rotate back to portrait mode, the login view is still displayed.

I expect that the instance of viewModel should still observe the changes since it has survived the configuration changes but it doesn't seem to.

dllabs commented 4 years ago

Could it be that you're starting Koin in MainActivity rather than MainApplication? MainActivity gets destroyed on screen rotation, which would destroy Koin, no?

vivekwolf123 commented 4 years ago

No I start in MainApplication onCreate() only -

startKoin {
            androidContext(this@MyApp)
            modules(AppModules.appModules)
} 
nikhil-thakkar commented 4 years ago

For observing the results correctly use the viewLifecycleOwner:

homeViewModel.showResults().observe(viewLifecycleOwner, Observer { .... })

This may not solve your problem but whenever you need to use the events to update UI use viewLifecycleOwner

wax911 commented 4 years ago

I used to have this issue with a similar approach, I had a couple of fragments and a navigation drawer and each time I had a configuration change when the views are reconstructed my fragments would somehow get a new instance of their viewmodel/s, which didn't seem right, considering this lifecycle diagram, but I guess this is true for activities but not fragments?

image

What I had to do was to use tags/ids in my supportFragmentManager when I commit and upon getting navigation item change I would call supportFragmentManager.findFragmentByTag and do a null check on the result, if it is null I'll create a new instance of my fragment.

Additionally I also use retainInstance on my fragments, the difference in lifecycle handling are as follows: Complete lifecycle

image

Hope this helps you :wink:

julianvelandia35 commented 4 years ago

You must validate the status in the onActivityCreated if (savedInstanceState == null) YourviewModel.event()

nitinshambhu commented 4 years ago

I tried 2 things... neither of them works.

  1. Load Koin modules in onCreate() and unload it in onDestroy()

The init { } block of ViewModel gets called when I change the orientation of the screen from portrait to landscape

However, the same doesn't happen if I use,

val viewModel = ViewModelProvider(this).get(MainViewModel::class.java)

When I use ViewModelProvider, init { } block gets called only once. So, it survives orientation change

  1. I thought, ViewModel is probably not surviving orientation change because I am loading and unloading the module onCreate() and onDestroy() respectively.

So, I added this module in Application class. ViewModel still doesn't survive orientation change.

Either I am missing something or Koin still doesn't support this.

Ideally, I should be able to load and unload in onCreate() and onDestroy() of an Activity respectively & ViewModel should still survive orientation change

nitinshambhu commented 3 years ago

@arnaudgiuliani Is this issue resolved ?

songzhw commented 2 years ago

I tried and found out two interesting things:

if you are using val vm: MyViewModel by inject(), the viewModel can't survive config change.

But if you use:

import org.koin.androidx.viewmodel.ext.android.viewModel

class MyActivity : AppCompatActivity() {
    val vm: MyViewModel by viewModel()

Now the viewModel could survive config change