lateinit property ViewModelFactory has not been initialized - android

I'm using dagger 2.25.2 and androidx.lifecycle for dependency injection in my android project. But I've got error ViewModelFactory has not been initialized
Here is my code's
class PropertyActivity : AppCompatActivity() {
#Inject
lateinit var propertyViewModelFactory: PropertyViewModelFactory
lateinit var propertyViewModel: PropertyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.property_activity)
propertyViewModel = ViewModelProviders.of(this, propertyViewModelFactory).get(
PropertyViewModel::class.java)
propertyViewModel.loadProperties()
}
ViewModelFactory:
class PropertyViewModelFactory #Inject constructor(
private val propertyViewModel: PropertyViewModel) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(PropertyViewModel::class.java!!)) {
return propertyViewModel as T
}
throw IllegalArgumentException("Unknown class name")
}
}
AppModule:
#Module
class AppModule(val app: Application){
#Provides
#Singleton
fun provideApplication(): Application = app
#Provides
#Singleton
fun providePropertyViewModelFactory(factory: PropertyViewModelFactory): ViewModelProvider.Factory = factory
}
BuildersModule:
#Module
abstract class BuildersModule {
#ContributesAndroidInjector
abstract fun contributePropertyActivity(): PropertyActivity
}
AppComponent:
#Singleton
#Component(modules = arrayOf(AndroidInjectionModule::class,BuildersModule::class, AppModule::class))interface PlotComponent {
fun inject(app: Application)
}
Application Class :
class PlotApplication : Application(), HasAndroidInjector {
#Inject
lateinit var activityInjector: DispatchingAndroidInjector<Any>
override fun onCreate() {
super.onCreate()
DaggerPlotComponent.builder()
.appModule(AppModule(this))
.build().inject(this)
}
override fun androidInjector(): AndroidInjector<Any> = activityInjector
}

I doesn't see where you inject dependency in your activity. The problem may be in this.
Add
AndroidInjection.inject(this)
in your onCreate

Related

Dagger2 viewModelFactory lateinit property has not been initialized

Some users have crashes, at what point they occur and for what reason it is not yet clear.
Logs (Firebase Crashlytics)
Fatal Exception: fu.p
lateinit property factory has not been initialized
com.its.yarus.base.BaseFragment.getFactory (BaseFragment.java:7)
com.its.yarus.ui.search.SearchTypeFragment$vm$3.invoke (SearchTypeFragment.java:1)
androidx.lifecycle.ViewModelLazy.getValue (ViewModelLazy.java:1)
com.its.yarus.ui.search.SearchTypeFragment.getVm (SearchTypeFragment.java:2)
com.its.yarus.ui.search.SearchTypeFragment.update$lambda-2 (SearchTypeFragment.java:5)
androidx.camera.camera2.internal.Camera2CameraControlImpl$$InternalSyntheticLambda$7$e9d410b43813df60e795612fb2f87f0233ac603a6d2ad492f8d3b620f6813f78$0.run$bridge (Camera2CameraControlImpl.java:5)
BaseFragment
abstract class BaseFragment: Fragment() {
#Inject
lateinit var uploadManager: UploadManager
#Inject
lateinit var factory: ViewModelFactory
#Inject
lateinit var ciceroneHolder: LocalCiceroneHolder
abstract var fragmentName: String
}
BaseMainFragment
....
override fun onCreate(savedInstanceState: Bundle?) {
YarusApp.component?.inject(this)
super.onCreate(savedInstanceState)
}
....
SearchTypeFragment
class SearchTypeFragment : BaseRecyclerFragment(), NotTopInset {
override val vm by viewModels<SearchViewModel>(
ownerProducer = { this },
factoryProducer = { factory }
)
private val vmSearch by viewModels<SearchMainViewModel>(
ownerProducer = { requireParentFragment() },
factoryProducer = { factory }
)
}
Inheritance chain:
SearchTypeFragment (located in the ViewPager) у SearchFragment ->
BaseRecyclerFragment ->
BaseMainFragment ->
BaseFragment
when passing the factory variable, the crash occurs
ViewModelFactory
#Singleton
class ViewModelFactory #Inject constructor(private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>) :
ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val viewModelProvider = viewModels[modelClass]
?: throw IllegalArgumentException("model class $modelClass not found")
return viewModelProvider.get() as T
}
}
ViewModelModule
#Module
abstract class ViewModelModule {
#Binds
#Singleton
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
MainComponent
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class,
NetworkModule::class,
RepositoryModule::class,
ApplicationModule::class,
ViewModelModule::class,
RoomModule::class,
NavigationModule::class,
SocketModule::class,
ServiceModule::class,
ExoPlayerModule::class
]
)
interface MainComponent {
fun inject(application: YarusApp)
fun inject(activity: BaseActivity)
fun inject(activity: OnBoardingActivity)
fun inject(activity: SignInActivity)
fun inject(fragment: BaseFragment)
fun inject(fragment: BaseSignInFragment)
fun inject(fragment: BaseOnboardingFragment)
fun inject(fragment: BaseDialogFragment)
fun inject(fragment: BaseMusicFragment)
fun inject(service: CloudMessagingService)
fun inject(service: PlayerService)
fun inject(seance: SeanceBottomSheet)
fun inject(seance: PlaceBottomSheet)
fun inject(promo: PromoBottomSheet)
fun inject(bottomSheet: BaseBottomSheetDialogFragment)
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): MainComponent
}
}
Application
class YarusApp : Application() {
override fun onCreate() {
super.onCreate()
component = DaggerMainComponent.builder().application(this).build()
component?.inject(this)
}
companion object {
var component: MainComponent? = null
}
}
Do you have any ideas or suggestions on how to fix this problem?

dagger2 does not provide injection in base activity

I am trying to implement the latest version of Dagger2 in a Single-Activity app, but it is not known why, when initializing my starting activity, Dagger2 does not inject dependencies, I has a
fatal error in my base activity : Unable to resume activity kotlin.UninitializedPropertyAccessException: lateinit property navigatorHolder has not been initialized
here is my code
AppComponent:
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
ActivityInjectionModule::class,
ActivityProviderModule::class,
AndroidInjectionModule::class,
NetworkModule::class,
RemoteModule::class,
NavigationModule::class,
ParserModule::class,
CacheModule::class])
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
#BindsInstance
fun context(context: Context) : Builder
fun build(): AppComponent
}
override fun inject(app: App)
}
App:
class App : DaggerApplication(){
private val applicationInjector =
DaggerAppComponent.builder().application(this).context(this).build()
override fun applicationInjector(): AndroidInjector<out DaggerApplication> =
applicationInjector
companion object {
lateinit var cicerone: Cicerone<Router>
private set
}
override fun onCreate() {
super.onCreate()
cicerone = Cicerone.create()
initAppComponent()
initStetho()
Timber.plant(Timber.DebugTree())
}
AppActivity:
class AppActivity : MvpAppCompatActivity() , HasAndroidInjector, RouterProvider {
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
#Inject
lateinit var mainActivityProvider: ActivityProvider
#Inject
lateinit var navigatorHolder: Lazy<NavigatorHolder>
#Inject
override lateinit var ciceroneRouter: Router
override fun androidInjector(): AndroidInjector<Any> = dispatchingAndroidInjector
private val navigator : Navigator by lazy {
CustomSupportAppNavigator(this, supportFragmentManager, R.layout.activity_main)
}
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
AndroidInjection.inject(this)
mainActivityProvider.acitvity = this
Timber.e("onCreate AppActivity")
super.onCreate(savedInstanceState, persistentState)
setContentView(R.layout.activity_main)
initBottomBar()
}
override fun onResumeFragments() {
super.onResumeFragments()
navigatorHolder.get().setNavigator(navigator)
}
ActivityInjectionModule
#Module(includes = [AndroidInjectionModule::class])
interface ActivityInjectionModule {
#ContributesAndroidInjector(
modules = [FragmentInjectionModule::class]
)
fun activityInjector() : AppActivity
}
NavigationModule
#Module
class NavigationModule {
#Provides
#Singleton
fun provideRouter() = App.cicerone.router
#Provides
#Singleton
fun provideNavigatorHolder() : NavigatorHolder {
return App.cicerone.navigatorHolder
}
#Provides
#Singleton
fun provideLocalNavigationHolder(): LocalCiceroneHolder {
return LocalCiceroneHolder()
}
}
Also i post android:name=".App" inandroid manifest.
I have tried many different options already, but I still cannot find the reason
Can you change your App to like this and try again:
class App : DaggerApplication(){
private val appComponent =
DaggerAppComponent.builder().application(this).context(this).build().inject(this)
companion object {
lateinit var cicerone: Cicerone<Router>
private set
}
override fun onCreate() {
super.onCreate()
cicerone = Cicerone.create()
initAppComponent()
initStetho()
Timber.plant(Timber.DebugTree())
}
Also remove AndroidInjector<App> from AppComponent

Dagger not initizalize var

I have problems when starting Dagger in Android project with Kotlin.
This estructure is the next one
Dagger is included in an Android module that is called by the client application
MagicBox.kt
interface MagicBox {
fun getDate(): Long?
}
MagicBoxImpl.kt
class MagicBoxImpl (): MagicBox{
var date: Long = Date().time
override fun getDate(): Long {
return date
}
}
MainModule.kt
#Module
class MainModule (private val app: Application) {
#Provides
#Singleton
fun provideMagicBox(): MagicBox {
return MagicBoxImpl()
}
}
MainComponent.kt
#Singleton
#Component(modules = [MainModule::class, PresenterModule::class])
interface MainComponent{
fun inject(target: Activity)
}
Application.kt
class Application: Application() {
lateinit var mainComponent: MainComponent
override fun onCreate() {
super.onCreate()
mainComponent = initDagger(this)
}
private fun initDagger(app: Application): MainComponent =
DaggerMainComponent.builder()
.mainModule(MainModule(app))
.build()
}
MainActivity.kt
#Inject
lateinit var magicBox: MagicBox
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_layout)
(application as ClientSdk).mainComponent.inject(this)
tvDaggerTest = findViewById(R.id.tvDaggerTest)
tvDaggerTest!!.text = magicBox.getDate().toString()
}
Get the following error
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property magicBox has not been initialized
fun inject(target: Activity) should be fun inject(target: MainActivity)
Also for better Dagger usage, the following should be:
#Module
abstract class MainModule {
#Binds
abstract fun magicBox(impl: MagicBoxImpl): MagicBox
}
and
#Singleton class MagicBoxImpl #Inject constructor(): MagicBox {

Dagger2 + Kotlin: lateinit property has not been initialized

I'm trying to inject the ViewModelFactory into my Activity, but it keeps throwing this same error: lateinit property viewModelFactory has not been initialized. I can't find what I may be doing wrong. See the code above from my classes
AppComponent.kt
#Component(modules = [(AppModule::class), (NetworkModule::class), (MainModule::class)])
interface AppComponent {
fun inject(application: TweetSentimentsApplication)
fun inject(mainActivity: MainActivity)
fun context(): Context
fun retrofit(): Retrofit
}
MainModule.kt
#Module
class MainModule {
#Provides
fun mainViewModelFactorty(repository: TweetRepository): MainViewModelFactory = MainViewModelFactory(repository)
#Provides
fun local(database: AppDatabase): TweetLocal = TweetLocal(database)
#Provides
fun remote(tweetService: TweetService): TweetRemote = TweetRemote(tweetService)
#Provides
fun tweetService(retrofit: Retrofit): TweetService = retrofit.create(TweetService::class.java)
#Provides
fun repository(local: TweetLocal, remote: TweetRemote): TweetRepository = TweetRepository(local, remote)
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
#Inject lateinit var viewModelFactory: MainViewModelFactory
private val viewModel: MainViewModel? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
ViewModelProviders.of(this, viewModelFactory).get(MainViewModel::class.java)
viewModel?.init("guuilp")
viewModel?.getTweetList()?.observe(this, Observer {
Toast.makeText(this, it?.size.toString(), Toast.LENGTH_LONG).show()
})
}
}
TweetSentimentsApplication.kt
open class TweetSentimentsApplication: Application(){
companion object {
lateinit var appComponent: AppComponent
}
override fun onCreate() {
super.onCreate()
initDI()
}
private fun initDI() {
appComponent = DaggerAppComponent.builder()
.appModule(AppModule(this))
.build()
}
}
You have to call the inject(mainActivity: MainActivity) method you've defined in AppComponent when you're initializing your MainActivity, that's how Dagger actually injects the dependencies you need.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
// This is where the dependencies are injected
TweetSentimentsApplication.appComponent.inject(this)
ViewModelProviders.of(this, viewModelFactory).get(MainViewModel::class.java)
...
}
Also, make sure that your application name is added in the AndroidManifest.xml file.
<application
android:name=".YourAppName"
..../>
You can also do this:
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
val mainViewModel: MainViewModel by lazy {
ViewModelProviders.of(this, viewModelFactory)[MainViewModel::class.java]
}
and use abstract modules with #ContributesAndroidInjector for the activity, and
abstract module for view model. Using abstract is more efficient:
#Module
abstract class AndroidBindingModule {
#ContributesAndroidInjector
internal abstract fun contributesAnActivity(): AnActivity
}
#Module
abstract class ViewModelModule {
//the default factory only works with default constructor
#Binds
#IntoMap
#ViewModelKey(AViewModel::class)
abstract fun bindArtViewModel(aViewModel: AViewModel): ViewModel
#Binds
abstract fun bindViewModelFactory(factory: AViewModelFactory): ViewModelProvider.Factory
}
#Documented
#Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
#Retention(RetentionPolicy.RUNTIME)
#MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
You could also extend DaggerAppCompatActivity in place of AppCompatActivity. E.g.
class MainActivity : DaggerAppCompatActivity() {
#Inject lateinit var viewModelFactory: MainViewModelFactory
private val viewModel: MainViewModel? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
ViewModelProviders.of(this, viewModelFactory).get(MainViewModel::class.java)
viewModel?.init("guuilp")
viewModel?.getTweetList()?.observe(this, Observer {
Toast.makeText(this, it?.size.toString(), Toast.LENGTH_LONG).show()
})
}
}
My mistake was creating a new object and taking a component from it such as App().component.
So in this situation you need to put component field in companion object and replace code with App.component
Maybe you missed implementing "Injectable" Interface in fragment/activity. Which marks fragment/activity as injectable.

Inject ViewModel using Dagger 2 + Kotlin + ViewModel

class SlideshowViewModel : ViewModel() {
#Inject lateinit var mediaItemRepository : MediaItemRepository
fun init() {
What goes here?
}
So I'm trying to learn Dagger2 so I can make my apps more testable. Problem is, I've already integrated Kotlin and am working on the Android Architectural components. I understand that constructor injection is preferable but this isn't possible with ViewModel. Instead, I can use lateinit in order to inject but I'm at a loss to figure out how to inject.
Do I need to create a Component for SlideshowViewModel, then inject it? Or do I use the Application component?
gradle:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
kapt {
generateStubs = true
}
dependencies {
compile "com.google.dagger:dagger:2.8"
annotationProcessor "com.google.dagger:dagger-compiler:2.8"
provided 'javax.annotation:jsr250-api:1.0'
compile 'javax.inject:javax.inject:1'
}
Application Component
#ApplicationScope
#Component (modules = PersistenceModule.class)
public interface ApplicationComponent {
void injectBaseApplication(BaseApplication baseApplication);
}
BaseApplication
private static ApplicationComponent component;
#Override
public void onCreate() {
super.onCreate();
component = DaggerApplicationComponent
.builder()
.contextModule(new ContextModule(this))
.build();
component.injectBaseApplication(this);
}
public static ApplicationComponent getComponent() {
return component;
}
You can enable constructor injection for your ViewModels. You can check out Google samples to see how to do it in Java. (Update: looks like they converted the project to Kotlin so this URL no longer works)
Here is how to do a similar thing in Kotlin:
Add ViewModelKey annotation:
import android.arch.lifecycle.ViewModel
import java.lang.annotation.Documented
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
import dagger.MapKey
import kotlin.reflect.KClass
#Suppress("DEPRECATED_JAVA_ANNOTATION")
#Documented
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
Add ViewModelFactory:
import android.arch.lifecycle.ViewModel
import android.arch.lifecycle.ViewModelProvider
import javax.inject.Inject
import javax.inject.Provider
import javax.inject.Singleton
#Singleton
class ViewModelFactory #Inject constructor(
private val creators: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
var creator: Provider<out ViewModel>? = creators[modelClass]
if (creator == null) {
for ((key, value) in creators) {
if (modelClass.isAssignableFrom(key)) {
creator = value
break
}
}
}
if (creator == null) {
throw IllegalArgumentException("unknown model class " + modelClass)
}
try {
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
Add ViewModelModule:
import dagger.Module
import android.arch.lifecycle.ViewModel
import dagger.multibindings.IntoMap
import dagger.Binds
import android.arch.lifecycle.ViewModelProvider
import com.bubelov.coins.ui.viewmodel.EditPlaceViewModel
#Module
abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(EditPlaceViewModel::class) // PROVIDE YOUR OWN MODELS HERE
internal abstract fun bindEditPlaceViewModel(editPlaceViewModel: EditPlaceViewModel): ViewModel
#Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
Register your ViewModelModule in your component
Inject ViewModelProvider.Factory in your activity:
#Inject lateinit var modelFactory: ViewModelProvider.Factory
private lateinit var model: EditPlaceViewModel
Pass your modelFactory to each ViewModelProviders.of method:
model = ViewModelProviders.of(this, modelFactory)[EditPlaceViewModel::class.java]
Here is the sample commit which contains all of the required changes: Support constructor injection for view models
Assuming you have a Repository class that can be injected by Dagger and a MyViewModel class that has a dependency on Repository defined as such:
class Repository #Inject constructor() {
...
}
class MyViewModel #Inject constructor(private val repository: Repository) : ViewModel() {
...
}
Now you can create your ViewModelProvider.Factory implementation:
class MyViewModelFactory #Inject constructor(private val myViewModelProvider: Provider<MyViewModel>) : ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return myViewModelProvider.get() as T
}
}
Dagger setup does not look too complicated:
#Component(modules = [MyModule::class])
interface MyComponent {
fun inject(activity: MainActivity)
}
#Module
abstract class MyModule {
#Binds
abstract fun bindsViewModelFactory(factory: MyViewModelFactory): ViewModelProvider.Factory
}
Here's the activity class (might be fragment as well), where the actual injection takes place:
class MainActivity : AppCompatActivity() {
#Inject
lateinit var factory: ViewModelProvider.Factory
lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// retrieve the component from application class
val component = MyApplication.getComponent()
component.inject(this)
viewModel = ViewModelProviders.of(this, factory).get(MyViewModel::class.java)
}
}
No. You create a component where you are declaring (using) your viewModel. It is normally an activity/fragment. The viewModel has dependencies (mediaitemrepository), so you need a factory. Something like this:
class MainViewModelFactory (
val repository: IExerciseRepository): ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(p0: Class<T>?): T {
return MainViewModel(repository) as T
}
}
Then the dagger part (activity module)
#Provides
#ActivityScope
fun providesViewModelFactory(
exerciseRepos: IExerciseRepository
) = MainViewModelFactory(exerciseRepos)
#Provides
#ActivityScope
fun provideViewModel(
viewModelFactory: MainViewModelFactory
): MainViewModel {
return ViewModelProviders
.of(act, viewModelFactory)
.get(MainViewModel::class.java)
}
Refer to a repo I created when I was learning dagger+kotlin
Essentially you need a ViewModelFactory instance to the UI layer, you use that to create a viewmodel.
#AppScope
class ViewModelFactory
#Inject
constructor(private val creators: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>)
: ViewModelProvider.Factory {
#SuppressWarnings("Unchecked")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
var creator = creators[modelClass]
if (creator == null) {
for (entry in creators) {
if (modelClass.isAssignableFrom(entry.key)) {
creator = entry.value
break
}
}
}
if (creator == null) throw IllegalArgumentException("Unknown model class" + modelClass)
try {
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
Your ViewModelModule should look like (this is where you store all viewmodels).
#Module
abstract class ViewModelModule {
#AppScope
#Binds
#IntoMap
#ViewModelKey(YourViewModel::class)
abstract fun bindsYourViewModel(yourViewModel: YourViewModel): ViewModel
// Factory
#AppScope
#Binds abstract fun bindViewModelFactory(vmFactory: ViewModelFactory): ViewModelProvider.Factory
}
Then create a dagger map key
#Documented
#Target(AnnotationTarget.FUNCTION)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
Then on your UI layer, inject the factory and instantiate your viewmodel using ViewModelProviders
class YourActivity : BaseActivity() {
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
lateinit var yourViewModel: YourViewModel
override fun onCreate(savedInstanceState: Bundle?) {
...
...
(application as App).component.inject(this)
}
override fun onStart() {
super.onStart()
yourViewModel = ViewModelProviders.of(this, viewModelFactory).get(YourViewModel::class.java)
// you can now use your viewmodels properties and methods
yourViewModel.methodName()
yourViewModel.list.observe(this, { ... })
}
you expose the ViewModel on your component:
#Singleton
#Component(modules={...})
public interface SingletonComponent {
BrandsViewModel brandsViewModel();
}
And now you can access this method on the component inside the ViewModelFactory:
// #Inject
BrandsViewModel brandsViewModel;
...
brandsViewModel = new ViewModelProvider(this, new ViewModelProvider.Factory() {
#Override
public <T extends ViewModel> create(Class<T> modelClazz) {
if(modelClazz == BrandsViewModel.class) {
return singletonComponent.brandsViewModel();
}
throw new IllegalArgumentException("Unexpected class: [" + modelClazz + "]");
}).get(BrandsViewModel.class);
All this can be simplified and hidden with Kotlin:
inline fun <reified T: ViewModel> AppCompatActivity.createViewModel(crossinline factory: () -> T): T = T::class.java.let { clazz ->
ViewModelProvider(this, object: ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if(modelClass == clazz) {
#Suppress("UNCHECKED_CAST")
return factory() as T
}
throw IllegalArgumentException("Unexpected argument: $modelClass")
}
}).get(clazz)
}
which now lets you do
brandsViewModel = createViewModel { singletonComponent.brandsViewModel() }
Where now BrandsViewModel can receive its parameters from Dagger:
class BrandsViewModel #Inject constructor(
private val appContext: Context,
/* other deps */
): ViewModel() {
...
}
Though the intent might be cleaner if a Provider<BrandsViewModel> is exposed from Dagger instead
interface SingletonComponent {
fun brandsViewModel(): Provider<BrandsViewModel>
}
brandsViewModel = createViewModel { singletonComponent.brandsViewModel().get() }
Try with below code :
#Provides
#Singleton
fun provideRepository(): Repository {
return Repository(DataSource())
}
I wrote a library that should make this more straightforward and way cleaner, no multibindings or factory boilerplate needed, while also giving the ability to further parametrise the ViewModel at runtime:
https://github.com/radutopor/ViewModelFactory
#ViewModelFactory
class UserViewModel(#Provided repository: Repository, userId: Int) : ViewModel() {
val greeting = MutableLiveData<String>()
init {
val user = repository.getUser(userId)
greeting.value = "Hello, $user.name"
}
}
In the view:
class UserActivity : AppCompatActivity() {
#Inject
lateinit var userViewModelFactory2: UserViewModelFactory2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
appComponent.inject(this)
val userId = intent.getIntExtra("USER_ID", -1)
val viewModel = ViewModelProviders.of(this, userViewModelFactory2.create(userId))
.get(UserViewModel::class.java)
viewModel.greeting.observe(this, Observer { greetingText ->
greetingTextView.text = greetingText
})
}
}
Here is my solution using reflection.
Let's say for simplicity you have AppComponent
#AppScope
#Component(modules = [AppModule::class])
interface AppComponent {
fun getAppContext(): Context
fun getRepository(): Repository
fun inject(someViewModel: SomeViewModel)
class App : Application() {
companion object {
lateinit var appComponent: AppComponent
private set
}
...
}
fun appComponent() = App.appComponent
And you need inject SomeViewModel class
class SomeViewModel: ViewModel() {
#Inject
lateinit var repository: Repository
}
Create custom lazy property delegate
inline fun <reified T: ViewModel> Fragment.viewModel(component: Any?) = lazy {
val vm = ViewModelProvider(this).get(T::class.java)
component?.let {
val m = component.javaClass.getMethod("inject", T::class.java)
m.invoke(component, vm)
}
vm
}
And use it
class SomeFragment: Fragment() {
private val vm: SomeViewModel by viewModel(appComponent())
...
}
With the solution below, I found I can use injection anywhere I want by including this line in the init or onCreate methods (No factories needed, so it works with ViewModel and WorkManager)
Injector.getComponent().inject(this)
BaseApplication
class BaseApplication : Application() {
lateinit var applicationComponent: ApplicationComponent
override fun onCreate() {
super.onCreate()
INSTANCE = this
applicationComponent = DaggerApplicationComponent
.builder()
//Add your modules like you did in your question above
.build()
}
companion object {
private var INSTANCE: BaseApplication? = null
#JvmStatic
fun get(): BaseApplication= INSTANCE!!
}
}
Injector
class Injector private constructor() {
companion object {
#JvmStatic
fun getComponent(): ApplicationComponent = BaseApplication.get().applicationComponent
}
}
Essentially, you access applicationComponent with a static method. With that, you should be able to inject any class you've made an inject method for in your component with this line:
Injector.getComponent().inject(this)
in your case
init{
Injector.getComponent().inject(this)
}

Categories

Resources