Injecting dependencies in a Android ViewModel? - android

Hi i have a ViewModel that has two var's that get injected using Dagger 2.11.
However it never gets injected in my viewModel whilst everywhere else in my app, these same dependecy Vars get initialised and injected perfectly
Below is my viewModel
class MyViewModel : AndroidViewModel, MyViewModelContract {
private lateinit var pointsBalance: MutableLiveData<PointsBalance>
#Inject
lateinit var accountDelegator: AccountDelegatorContract
#Inject
constructor(application: Application) : super(application)
init {
DaggerApplicationComponent.builder().application(getApplication() as MyApplication).build().inject(this)
}
override fun getPointsBalance(): LiveData<PointsBalance> {
if (!this::pointsBalance.isInitialized) {
//get balance from network api etc
}
return pointsBalance
}
accountDelegator complains that it is not initialised
Below is how i bind this viewModel inside MyModule.class
#Provides
#JvmStatic
#Singleton
fun providesViewModel(viewModel: MyViewModel): MyViewModelContract = viewModel
my custom application
class MyApplication : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
val applicationComponent = DaggerApplicationComponent.builder()
.application(this)
.build()
applicationComponent.inject(this)
return applicationComponent
}
}
my applicationComponent
#Singleton
#Component(modules = arrayOf(MyModule::class,
))
interface ApplicationComponent : AndroidInjector<DaggerApplication> {
fun inject(mApplication: MyApplication)
override fun inject(instance: DaggerApplication)
#Component.Builder
interface Builder {
#BindsInstance
fun application(applicaton: MyApplication): Builder
fun build(): ApplicationComponent
}
}
How i use this viewmodel in a activity/fragment
viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)

I believe issue is that you're not calling inject. You'd need for a start to add following to ApplicationComponent
void inject(MyViewModel viewModel);
What I'm doing here then fwiw is inheriting from AndroidViewModel (which gives access to application instance) then calling following in ViewModel class (you'd need to substitute DaggerProvider.getComponent with however you're accessing component in your code)
init {
DaggerProvider.getComponent(application).inject(this)
}

Related

Cannot inject object provided in AppModule to Activity in a dynamic feature module with Dagger2

With the set up below i'm not be able to inject a Singleton object to a Activity inside a dynamic feature module. I can inject to a subComponent, MainActivity, but not to an Activity that's in dynamic feature module.
#Component(modules = [AppModule::class])
interface AppComponent {
fun inject(application: Application)
#Component.Factory
interface Factory {
fun create(#BindsInstance application: Application): AppComponent
}
// Types that can be retrieved from the graph
fun mainActivityComponentFactory(): MainActivitySubComponent.Factory
}
My AppModule
#Module(includes = [AppProviderModule::class])
abstract class AppModule {
#Binds
abstract fun bindContext(application: Application): Context
}
#Module
object AppProviderModule {
#Provides
#Singleton
fun provideSharedPreferences(application: Application): SharedPreferences {
return application.getSharedPreferences("PrefName", Context.MODE_PRIVATE)
}
}
Dynamic Feature Module GalleryComponent
#GalleryScope
#Component(
dependencies = [AppComponent::class],
modules = [GalleryModule::class])
interface GalleryComponent {
fun inject(galleryActivity: GalleryActivity)
}
And MyApplication
open class MyApplication : Application() {
// Instance of the AppComponent that will be used by all the Activities in the project
val appComponent: AppComponent by lazy {
initializeComponent()
}
open fun initializeComponent(): AppComponent {
// Creates an instance of AppComponent using its Factory constructor
// We pass the applicationContext that will be used as Application
return DaggerAppComponent.factory().create(this).apply {
inject(this#MyApplication)
}
}
}
Activity in dynamic feature module, when only inject GalleryViewer and DummyDependency is injected from GalleryModule it works fine
class GalleryActivity : AppCompatActivity() {
#Inject
lateinit var sharedPreferences: SharedPreferences
#Inject
lateinit var galleryViewer: GalleryViewer
#Inject
lateinit var dummyDependency: DummyDependency
override fun onCreate(savedInstanceState: Bundle?) {
DaggerGalleryComponent.builder()
.appComponent((application as MyApplication).appComponent)
.build()
.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_gallery)
}
When i try to inject SharedPreferences or any dependency that does not depend on any arguments like context or application from AppModule i get error
error: [Dagger/MissingBinding] android.content.SharedPreferences cannot be provided without an #Provides-annotated method.
The error here is not including provision method for dependencies in AppComponent and not building dynamic feature component properly.
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
/**
* 🔥🔥🔥 This method is required to get this object from a class that uses this component
* as dependent component
*/
fun provideSharedPreferences(): SharedPreferences
fun inject(application: Application)
#Component.Factory
interface Factory {
fun create(#BindsInstance application: Application): AppComponent
}
// Types that can be retrieved from the graph
fun mainActivityComponentFactory(): MainActivitySubComponent.Factory
}
In dynamic feature
#GalleryScope
#Component(
dependencies = [AppComponent::class],
modules = [GalleryModule::class])
interface GalleryComponent {
fun inject(galleryActivity: GalleryActivity)
// Alternative1 With Builder
#Component.Builder
interface Builder {
fun build(): GalleryComponent
#BindsInstance
fun application(application: Application): Builder
fun galleryModule(module: GalleryModule): Builder
fun appComponent(appComponent: AppComponent): Builder
}
// Alternative2 With Factory
#Component.Factory
interface Factory {
fun create(appComponent: AppComponent,
galleryModule: GalleryModule,
#BindsInstance application: Application): GalleryComponent
}
}
You must either use Builder or Factory, with hilt none of this might be necessary in the future, however it does not support dynamic feature yet, i rather factory pattern since they deprecated Builder pattern.
inside Activity onCreate initialize injection
private fun initInjections() {
// Alternative1 With Builder
DaggerGalleryComponent.builder()
.appComponent((application as MyApplication).appComponent)
.application(application)
.galleryModule(GalleryModule())
.build()
.inject(this)
// Alternative2 With Factory
DaggerGalleryComponent
.factory()
.create((application as MyApplication).appComponent, GalleryModule(), application)
.inject(this)
}
You should choose the same pattern used inside feature component.

Android UI testing for MVVM architecture with Dagger2, Espresso and Mockito

I'm trying to add UI tests for the first time to an app, and I was looking at using espresso.
The app uses Dagger2 for DI with #Inject annotations for classes that should be injectable, and AndroidInjection/AndroidSupportInjection in screens (Activity / Fragment).
class Application : android.app.Application(), HasActivityInjector, HasServiceInjector {
...
override fun onCreate() {
super.onCreate()
initDagger()
Timber.d("Application initialized successfully!")
}
protected open fun initDagger() {
Components.initialize(this)
}
}
object Components : ComponentFactory {
private lateinit var sComponent: AppComponent
fun initialize(app: Application) {
sAppComponent = DaggerAppComponent.builder()
.applicationModule(ApplicationModule(this))
.build()
}
// overrides
}
interface ComponentFactory {
fun app(): AppComponent
fun authenticated(): AuthenticatedComponent
}
Next the Components and Modules. The AppActivitiesModule and AuthenticatedActivitiesModule are classes with #ContributesAndroidInjector for screens.
#Singleton
#Component(modules = [AppModule::class, AppActivitiesModule::class, AndroidInjectionModule::class, AndroidSupportInjectionModule::class])
interface AppComponent {
fun authenticatedComponentBuilder(): AuthenticatedComponent.Builder
fun inject(app: Application)
#Component.Builder
interface Builder {
fun build(): AppComponent
fun applicationModule(applicationModule: ApplicationModule): Builder
}
}
#Module
open class AppModule(private val application: Application) {
// some #Provides
}
#AuthenticatedScope
#Subcomponent(modules = [AuthenticatedModule::class, AuthenticatedActivitiesModule::class])
interface AuthenticatedComponent {
fun inject(application: Application)
#Subcomponent.Builder
interface Builder {
fun userModule(module: UserModule): Builder
fun build(): AuthenticatedComponent
}
}
#Module
class AuthenticatedModule(private val userId: Long,
private val userRole: User.Role) {
// Some #Provides #AuthenticatedScope
}
And a typical use case would be:
#Singleton
class AppLevelService
#Inject constructor(...) { ... }
#AuthenticatedScope
class AuthenticatedLevelServices
#Inject constructor(...) { ... }
class ViewModel
#Inject constructor(private val appService: AppLevelService,
private val authService: AuthenticatedLevelServices) { ... }
class MyActivity : BaseActivity {
#Inject
lateinit var vmProvider: Provide<ViewModel>
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
}
}
How can I make the test setup for this "type" of dagger usage?
I find a lot of examples of testing with dagger2 but for #Provides annotations, and I'm thinking there should be a way to mock injectable classes with #Inject.
I've tried DaggerMock but I get:
You must define overridden objects using a #Provides annotated method instead of using #Inject annotation
Not that it should affect anything, but I'm also using a custom Runner with DexOpener.
Any ideas or good documentation / examples for testing this setup?

How to have view models implement test services, while dagger 2 setup extends DaggerApplication, and uses a Component.Builder

During testing, my view models are still using production services, and I want them to use test services. I'm using robolectric for testing, and still haven't found a solution to my problem.
What I thought would be most promising would be using a viewModelFactory generator in my test fragment, like ViewModelUtil.createFor(mockedViewModel) is used in the GitHubBrowserSample. But that's not working for me, my test services created in the mocked viewModel will then get replaced by production services when running startFragment as shown below.
My main application extends DaggerApplication() and my production component uses a #Component.Builder so I'm not able to specify a module in my building process.
Additionally, I've tried having TestAppComponent extend from my AppComponent but that doesn't seem to do the trick either.
I've also tried setting a builder as a static object in MyApplication.kt like so:
companion object {
var builder: AndroidInjector.Factory<MyApplication> =
DaggerAppComponent.builder()
}
...
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return builder.create(this)
}
And in my FragmentTest.kt init doing something like:
private fun init() {
bagFragment = BagFragment.newInstance()
MyApplication.builder = DaggerTestAppComponent.builder()
SupportFragmentTestUtil.startFragment(bagFragment, AppCompatActivity::class.java)
}
But that doesn't work because of a type mismatches. If there was some solution where extending from DaggerApplication() was still a possibility I would be very interested in that.
Here's my setup:
AppComponent.kt
#Singleton
#Component(modules = [
AndroidInjectionModule::class,
AndroidSupportInjectionModule::class,
AppModule::class,
ActivityModule::class,
FragmentModule::class,
ProductionModule::class])
interface AppComponent : AndroidInjector<MyApplication>{
#Component.Builder
abstract class Builder : AndroidInjector.Builder<MyApplication>()
}
ProductionModule.kt
#Module
open class ProductionModule {
#Provides
fun browseCategoriesViewModel(authenticationService: AuthenticationService): BrowseCategoriesFragment.BrowseCategoriesViewModel{
return BrowseCategoriesFragment.BrowseCategoriesViewModel(authenticationService)
}
// Provides multiple things
...
ViewModelModule.kt
#Module
abstract class ViewModelModule {
#Binds
abstract fun bindViewModelFactory(factor: ViewModelFactory): ViewModelProvider.Factory
/*** Fragments ***/
#Binds
#IntoMap
#ViewModelKey(BagFragment.BagViewModel::class)
abstract fun bindBagViewModel(viewModel: BagFragment.BagViewModel): ViewModel
MyApplication.kt
class MyApplication: DaggerApplication(), HasActivityInjector, HasSupportFragmentInjector {
#Inject
lateinit var sharedPreferencesService: SharedPreferencesService
#Inject
lateinit var activityInjector: DispatchingAndroidInjector<Activity>
#Inject
lateinit var supportFragmentInjector: DispatchingAndroidInjector<Fragment>
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
MultiDex.install(this)
}
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().create(this)
}
override fun activityInjector() = activityInjector
override fun supportFragmentInjector() = supportFragmentInjector
}
The AppModule, FragmentModule, and ActivityModule are set up according to documentation, and haven't given me any issues.
Here is my test setup:
TestAppComponent.kt
#Singleton
#Component(modules =[
AndroidInjectionModule::class,
AndroidSupportInjectionModule::class,
TestModule::class])
interface TestAppComponent: AndroidInjector<MyApplication>{
#Component.Builder
abstract class Builder: AndroidInjector.Builder<MyApplication>()
}
TestModule.kt
#Module
internal class TestModule {
#Provides
fun bagViewModel(dataService: FakeDataService,
authenticationService: FakeAuthenticationService,
favoritesService: FakeFavoritesService,
cartService: FakeCartService): BagFragment.BagViewModel {
return BagFragment.BagViewModel(dataService, authenticationService, favoritesService, cartService)
}
#Provides
#Singleton
fun dataService(): FakeDataService = FakeDataService()
#Provides
#Singleton
fun authenticationService(): FakeAuthenticationService = FakeAuthenticationService()
// provides many other things
My current test fragment init method looks like this:
private fun init() {
bagFragment = BagFragment.newInstance()
SupportFragmentTestUtil.startFragment(bagFragment, AppCompatActivity::class.java)
}
Last but not least, an example production fragment:
BagFragment.kt
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
lateinit var viewModel: BagViewModel
override fun onCreate(savedInstanceState: Bundle?) {
AndroidSupportInjection.inject(this)
super.onCreate(savedInstanceState)
viewModel = ViewModelProviders.of(this, viewModelFactory).get(BagViewModel::class.java)
}
When I call SupportFragmentTestUtil.startFragment() i'm assuming that AndroidInjection.inject(this) will replace my fake test module with the production module. I've also tried putting AndroidInjection.inject(this) in an init method as opposed to onCreate but that gives me problems else where and is contrary to documentation. Please help!

Android Kotlin with Dagger2, lateinit var has not been initialized

I'm a Dagger newb and have a trouble with using it.
What I want to develop is that using RxAndroidBle and to initialize it by Dagger for providing Context.
So I researched how it can be implemented, and I wrote some codes and It seems to be working for me but not working at all.
The followings are my codes.
AppComponent.kt
#Singleton
#Component(modules = [
AppModule::class,
BluetoothModule::class,
AndroidInjectionModule::class])
interface AppComponent : AndroidInjector<BluetoothController> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(app: Application): Builder
fun build(): AppComponent
}
}
AppModule.kt
#Module
class AppModule {
#Provides
#Named("appContext")
#Singleton
fun provideContext(application: Application): Context =
application.applicationContext
}
BluetoothModule.kt
#Module
class BluetoothModule {
#Provides
#Named("rxBleClient")
#Singleton
fun provideRxBleClient(#Named("appContext") context: Context):RxBleClient =
RxBleClient.create(context)
}
BluetoothController.kt for injecting by DaggerApplication.
class BluetoothController : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().application(this).build()
}
}
I've inserted
android:name".BluetoothController"
to AndroidManifest.xml
And this is how I would use it.
#field:[Inject Named("rxBleClient")]
lateinit var rxBleClient: RxBleClient
But it always occurs an error says: lateinit property context has not been initialized
What things I've missed? Can anyone help me?
Thanks in advance.
Add the below code to make this happen.
Create ActivityBuilderModule for injecting within the activity. Consider our activity as MainActivity
#Module
abstract class ActivityBuilderModule {
#ContributesAndroidInjector(modules=[MainActivityModule::class])
abstract fun contributeSplashActivity(): MainActivity
}
Create your MainActivityModule
#Module
class MainActivityModule{
#Provides()
fun contributeSplashActivity(mainActivity: MainActivity):
MainActivity=mainActivity
}
Modify your component.
#Singleton
#Component(modules = [
AppModule::class,
BluetoothModule::class,
ActivityBuilderModule::class,
AndroidInjectionModule::class])
interface AppComponent : AndroidInjector<BluetoothController> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(app: Application): Builder
fun build(): AppComponent
}
}
Within MainActivity just inject.
class MainActivity{
...
#Inject
lateinit var rxBleClient: RxBleClient
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
}
}
Let us know in case of any issue.
the context object is not initialized before its called
Though i dont know, how chained your initialization are.
Use #Inject to add deppendencies, do something like this
#Module
class BluetoothModule(val context : Context) {
//#Inject private lateinit var context : Context
#Provides
#Named("rxBleClient")
#Singleton
fun provideRxBleClient():RxBleClient =
RxBleClient.create(context)
}
let your call be like this
val component = AppComponent()
component.bluetoothModule(appContext)
.//other calls here
.build()

dagger2, how can i call #BindsInstance denoted method of SubComponent..?

please help me! I have a trouble in use of dagger 2.
I want to bind some dependency in runtime not in compile time inside MainActivity by using #Subcomponent.Builder and #BindsInstance
I have an ApplicationComponent and it has a Builder and its #BindsInstance looks working fine. I can use like below
DaggerApplicationComponent
.builder()
.application(this)
.build()
.inject(this)
but some trouble came from MainActivity...
below are snippets of codes
[ApplicationComponent]
#Singleton
#Component(modules = [ApplicationModule::class])
internal interface ApplicationComponent : AndroidInjector<MyApplication> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): ApplicationComponent
}
}
[ApplicationModule]
#Module(
includes = [
AndroidInjectionModule::class,
AndroidSupportInjectionModule::class,
ActivityInjectionModule::class
],
subcomponents = [
MainComponent::class
]
)
internal abstract class ApplicationModule {
#PerActivity
#ContributesAndroidInjector(modules = [SplashModule::class])
abstract fun splashActivity(): SplashActivity
#Binds
#IntoMap
#ActivityKey(MainActivity::class)
abstract fun mainActivity(builder: MainComponent.Builder): AndroidInjector.Factory<out Activity>
}
[MainComponent]
#PerActivity
#Subcomponent(modules = [MainModule::class])
internal interface MainComponent : AndroidInjector<MainActivity> {
#Subcomponent.Builder
abstract class Builder : AndroidInjector.Builder<MainActivity>() {
#BindsInstance
abstract fun testClass(mainTestClass: MainTestClass): Builder
}
}
[MainActivity]
internal class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// This works find without runtime injection
// AndroidInjection.inject(this)
/**
*I want to bind some dependency(in this case, MainTestClass) in runtime like below.
* so that I can use MainTestClass inside MainModule to inject this to other classes.
* but, for some reason,
* DaggerMainComponent IS NOT GENERATED AUTOMATICALLY...
*/
DaggerMainComponent.builder()
.testClass(MainTestClass())
.build()
.inject(this);
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startActivity(Intent(this, SplashActivity::class.java))
}
}
The problem is that I cannot access DaggerMainComponent because the Dagger doesn't generate it automatically.
I am searching for lots of websites to solve this but failed.
Is there any way to make it?
I have found a way to achieve what you want, I think. Apologies for not using your particular example, but it was easier to paste in the code I have and know that works from my own IDE. I've added comments on the critical lines. Here's the code:
Singleton component
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
RuntimeBindingModule::class // my addition!
])
interface MainApplicationComponent {
fun inject(app: MainApplication)
// my addition!
fun runtimeBuilder(): RuntimeBindingActivitySubcomponent.Builder
#Component.Builder
interface Builder {
fun build(): MainApplicationComponent
#BindsInstance fun app(app: Context): Builder
}
}
This binding code is essentially identical to yours.
#Subcomponent
interface RuntimeBindingSubcomponent : AndroidInjector<RuntimeBindingActivity> {
#Subcomponent.Builder
abstract class Builder : AndroidInjector.Builder<RuntimeBindingActivity>() {
#BindsInstance abstract fun bindInt(intVal: Int): Builder
}
}
#Module(subcomponents = [RuntimeBindingSubcomponent::class])
abstract class RuntimeBindingActivityModule {
#Binds #IntoMap #ActivityKey(RuntimeBindingActivity::class)
abstract fun bindInjectorFactory(
builder: RuntimeBindingActivitySubcomponent.Builder
): AndroidInjector.Factory<out Activity>
}
MainApplication
open class MainApplication : Application(), HasActivityInjector {
// This needs to be accessible to your Activities
lateinit var component: MainApplication.MainApplicationComponent
override fun onCreate() {
super.onCreate()
initDagger()
}
private fun initDagger() {
component = DaggerMainApplicationComponent.builder()
.app(this)
.build()
component.inject(this)
}
}
RuntimeBindingActivity
class RuntimeBindingActivity : AppCompatActivity() {
// I had to use #set:Inject because this is a primitive and we can't use lateinit
// on primitives. But for your case,
// `#Inject lateinit var mainTestClass: MainTestClass` would be fine
#set:Inject var intVal: Int = -1
override fun onCreate(savedInstanceState: Bundle?) {
// And this is how you can get runtime binding
val subComponent = (application as MainApplication).component.runtimeBuilder()
with(subComponent) {
seedInstance(this#RuntimeBindingActivity)
bindInt(10) // runtime binding
build()
}.inject(this)
Log.d("RuntimeBindingActivity", "intVal = $intVal")
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_runtime_binding)
}
}
It is critically important to note that the subcomponent which you generate in this way doesn't get magically stored somewhere by dagger. If you want your late-bound instance to be available for injecting into other classes controlled by your #PerActivity scope, you need to manually manage the lifecycle of this subcomponent. Store it somewhere (maybe in your custom Application class), and then you also must set its reference to null when your activity is destroyed, or you'll be leaking that activity.

Categories

Resources