1

I develop app with MVVM pattern. I want save UI when user rotate screen.

MyViewModel.kt

class MyViewModel(val repository: SomeRepository,
                       state : SavedStateHandle) : ViewModel() {

    private val savedStateHandle = state
    companion object {
        const val KEY = "KEY"
    }

    fun saveCityId(cityId: String) {
        savedStateHandle.set(CITY_KEY, cityId)
    }

    fun getCityId(): String? {
        return savedStateHandle.get(CITY_KEY)
    }

}

ViewModelFactory.kt

@Suppress("UNCHECKED_CAST")
class ViewModelFactory(
    private val repository: SomeRepository,
    private val state: SavedStateHandle
) : ViewModelProvider.NewInstanceFactory() {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return MyViewModel(repository,state) as T
    }

}

I call it in MainActivity MainActivity.kt

class MainActivity: AppCompatActivity(), KodeinAware {
    private val factory: ViewModelFactoryby instance()
    override val kodein by kodein()
    private lateinit var viewModel: MyViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    cityId = intent.getStringExtra("cityId") ?: viewModel.getCityId()
        if (cityId != null) {
            viewModel.saveCityId(cityId!!)
            viewModel.getCurrentWeather(cityId!!)
        }
}

Here i inject dependencies

Application.kt

class ForecastApplication: Application(), KodeinAware {
    override val kodein = Kodein.lazy {
        import(androidXModule(this@ForecastApplication))

        bind<SomeApi>() with singleton {
            Retrofit.create()
        }

        bind<WeatherRepository>() with singleton {
            WeatherRepository(instance())
        }
        bind() from provider {
            WeatherViewModelFactory(
                instance(), instance()
            )
        }
}
}

And i have this error

 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.simpleforecast/com.example.simpleapp.UI.Cities.Activity}:org.kodein.di.Kodein$NotFoundException: No binding found for bind<SavedStateHandle>() 
with ?<Activity>().? { ? }

How shoud i build ViewModelFactory and inject Saved State module for ViewModel?

  • In your bindings you do not have any `SavedStateHandle` so the `instance()` function cannot guess it for you. Where does your state comes from? – romainbsl Mar 20 '20 at 13:15

1 Answers1

4

SavedStateHandle is parameter which cannot be bound to the DI graph, because it's retrieved from Fragment (or Activity), therefore you need to do several steps in order to make it work:

1) DI viewmodel definition - since you have custom parameter, you need to use from factory:

bind() from factory { handle: SavedStateHandle ->
    WeatherViewModel(
        state = handle,
        repository = instance()
    )
}

2) ViewModel Factory - you need to inherit from AbstractSavedStateViewModelFactory

val vmFactory = object : AbstractSavedStateViewModelFactory(this, arguments) {
    override fun <T : ViewModel> create(key: String, modelClass: Class<T>, handle: SavedStateHandle): T {
        val vmFactory: ((SavedStateHandle) -> WeatherViewModel) = kodein.direct.factory()
        return vmFactory(handle) as T
    }
}

Inside of the create method you'd retrieve the factory from your DI graph (from step 1).

3) You retrieve ViewModel with the specified factory:

lateinit var vm : WeatherViewModel

fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    vm = ViewModelProvider(this, vmFactory)[WeatherViewModel::class.java]
}

or android KTX way:

val vm : WeatherViewModel by viewModels { vmFactory }
mlykotom
  • 4,850
  • 1
  • 22
  • 27