I am trying to replicate this this Singleton using Dagger 2.
I would like to have a BehaviorSubject in which I call onNext() to in a service and subscribe() to in a ViewModel.
Here is my Singleton object:
object MyMessageBus {
val pusher = BehaviorSubject.create<String>()
}
In my Service I can do this:
private val pusherObservable = MyMessageBus.pusher
override fun onCreate() {
super.onCreate()
println("service myObservable = ${pusherObservable.hashCode()}")
}
and then in my ViewModel I can do this:
private val pusherObservable = MyMessageBus.pusher
init {
println("viewModel myObservable = ${pusherObservable.hashCode()}")
}
When I run with this I get the same observable in both the Service and the ViewModel.
viewModel myObservable = 218654319
service myObservable = 218654319
and so I can call onNext in the Service and observe the change in the ViewModel.
As I said above I am trying to replicate this using Dagger, but I cant seem to get the same instance in both the Service and the ViewModel. I think the problem is that I just have two different graphs, but I cant figure out how to join them - even after reading the docs and knowing about Subcomponents.
Besides that, this seems like a huge amount of code to do something so simple.
So my question is how do I do the equivalent using Dagger with the minimum amount of code?
Here is what I have tried so far:
Here is the class I would like to inject into the Service and ViewModel.
Note: I originally thought just annotating the class and constructor would work, but have also tried with a module (hence the comments)
//#Singleton
class MessageBus {
val pusher: BehaviorSubject<String>
// #Inject
constructor() {
pusher = BehaviorSubject.create<String>()
}
}
Here is the module for providing the Service injections
#Singleton
#Module()
abstract class AndroidModule {
#ContributesAndroidInjector
abstract fun contributesPusherService(): PusherService
}
Here is the module that provides the MessageBus (Note I also tried just using commented out annoations above.
#Module
class PervasiveModule {
#Provides #Singleton fun provideMessageBus() = MessageBus()
}
Next is the App component
#Singleton
#Component(modules = arrayOf(AndroidInjectionModule::class, AndroidModule::class, PervasiveModule::class))
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(myApp: MyApp): Builder
fun build(): AppComponent
}
fun inject(myApp: MyApp): MyApp
}
finally the component for the ViewModel.
#Component(modules = arrayOf(PervasiveModule::class))
#Singleton
interface MainViewModelComponent {
#Component.Builder
interface Builder {
fun build(): MainViewModelComponent
}
fun inject(mainViewModel: MainViewModel)
}
In the service I have:
#Inject lateinit var messageBus: MessageBus
override fun onCreate() {
AndroidInjection.inject(this)
super.onCreate()
setupPusher()
println("service created")
println("service messageBus = ${messageBus.hashCode()}")
}
In the ViewModel I have:
#Inject lateinit var messageBus: MessageBus
init {
DaggerMainViewModelComponent.create().inject(this)
println("view model messageBus = ${messageBus.hashCode()}")
}
When I run this, everything is injected, but I end up with two instances of MessageBus rather than one.
view model messageBus = 252114254
service messageBus = 91479273
As I said above I think the issue is that MainViewModelComponent and the AppComponent are actually two different graphs. Is this the case? If so how do I join them. If not can someone explain what's going on and how to get this to work.
Related
I'm planning to create Espresso tests on my app multi-module, and I'm about to create the first Espresso test, but what I'm seeing is that on my app I do not have an AppComponent where I can fake it. Since I want to add the test on my feature-module, I'll create the TestApp, TestRunner there from now.
What I have on my feature-module is a FeatureComponent that is injected via ComponentFactory from the App, so what I thought is to create a class like this :
#Component (
dependencies = [ MoreComponents::class],
modules = [ DataSourceModule::class ]
)
interface FeatureOneComponent {
fun activityOneSubComponent(): FeatureOneActivity.Component.Factory
fun activityTwoSubComponent(): FeatureTwoActivity.Component.Factory
#Component.Factory
interface Factory {
fun create(
dependencies
):FeatureOneComponent
}
}
interface FeatureOneProvider {
fun getFeatureOneComponent(): FeatureOneComponent
}
///ACTIVITY
class FeatureOneActivity : AppCompatActivity() {
//this comes from Subcomponent is what I want to MOCK
#Inject lateinit var presenter
//these comes from the factory and I have it mocked
#Inject lateinit var manager
override fun onCreate(){
(applicationContext as FeatureOneProvider).getFeatureOneComponent().activityOneSubComponent().create(this).inject(this)
}
}
#Subcomponent(modules = [ActivityOneModule::class]) <--- THIS I WANT TO MOCK
interface Component {
fun inject(activity: FeatureOneActivity)
#SubComponent.Factory
interface Factory {
fun create(#BindsInstance activity: FeatureOneActivity): Component
}
}
#Module
interface ActivityOneModule {
#Binds
fun bindPresenter(impl: PresenterImpl): Contract.Presenter
}
TEST
class MyTestApp : Application(), FeatureOneProvider {
override fun getFeatureOneComponent(): FeatureOneComponent {
return DaggerMockFeatureOneComponent.create()
}
}
#Component(
modules = [MockFeatureOneModules::class]
)
interface MockFeatureOneComponent : FeatureOneComponent {
//I NEED TO MOCK THE SUBCOMPONENT WITH `MockSubcomponent`
}
#Component
object MockFeatureOneModules {
#Provides
fun providesManager() : MyManager = mock(MyManager)
}
//I want to use this module to replace the subcomponent of my activity
#Module
object MockSubcomponent() {
#Provides
fun providesFakePresenter() : FeatureOneContract.Presenter = mock { FeatureOneContract.Presenter::class.java }
}
To better understand the problem
When I run my test and I put a debugger point I see everything is mocked but the Presenter, and that's because the presenter is in
#Subcomponent(modules = [ActivityOneModule::class]
interface Component {
fun inject(activity: FeatureOneActivity)
#SubComponent.Factory
interface Factory {
fun create(#BindsInstance activity: FeatureOneActivity): Component
}
}
#Module
interface ActivityOneModule {
#Binds
fun bindPresenter(impl: PresenterImpl): Contract.Presenter
}
And in my test component I don't have access to "override" this subcomponent so everything is mocked but this subcomponent and I need this mocked.
I don't know if that's the best idea but if I did not misunderstand you you want this Presenter to return a mock {}. The changes you could do are :
In your TestComponent change interface to abstract class
Duplicate your subcomponent and extends from the real one
#Component(
modules = [MockFeatureOneModules::class]
)
abstract class MockFeatureOneComponent : FeatureOneComponent {
abstract fun subcomponent() : MockComponent.FactoryMock
override fun activityOneSubComponent(): FeatureOneActivity.Component.Factory {
return subcomponent()
}
#Subcomponent
interface MockComponent : FeatureOneActivity.Component {
#Subcomponent.Factory
interface FactoryMock : FeatureOneActivity.Component.Factory {
override fun create....
}
}
}
And that's it, it should work.
Your example code is pretty complex to understand and the actual problem.
But what I understand you want to setup expresso test for your feature module and you need to setup dagger component for it.
So, I can give you some guidelines and example code so that you can follow and setup your dagger architecture for your espresso test very simply.
First of all, you need setup/create your App for espresso test like this:
class MyTestApplication : MyApplication() {
//Call this from MyApplication onCreate()
override fun initDaggerGraph() {
component = DaggerTestAppComponent.builder()
.application(this)
.appLifecycle(appLifecycle)
.build()
component.inject(this)
}
}
Then create your Test app component like this:
//Add all of your dependent modules in this TestAppModule
#Component(modules = [TestAppModule::class])
interface TestAppComponent : AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
#BindsInstance
fun appLifecycle(appLifecycle: AppLifecycle): Builder
fun build(): TestAppComponent
}
fun inject(activityTest: SomeActivityTest) //Your activity where to inject
}
Also, make sure to initialize your component in your Test activity class when launching the activity like this:
val component = MyApplication.instance.component as TestAppComponent
component.inject(this)
Now you have done all the setup and your dependency should resolve as well as your espresso test should work.
So as far as i get it, you have multiple modules, components and subcomponents, but since most of their names don't quite match up in the code you posted, nor you posted the error log, i have to guess whats going wrong where.
Instead, how about something like this.
public interface SomeComponent {
WhateverClass1 provideWhatever1();
WhateverClass2 provideWhatever2();
WhateverRepository1 provideWhateverRepository1();
SomeOtherComponent getSomeOtherComponent();
// and so on and on
}
and then have your production component look something like this:
#SomeComponentSingleton
#Component(modules = { ... },
dependencies = { ... })
public interface SomeProductionScopedComponent extends SomeComponent {
#Component.Builder
interface Builder {
// you know best what needs to go here
SomeProductionScopedComponent build();
}
}
and this
#SomeComponentSingleton
#Component(dependencies = SomeComponent.class, modules =
{ ... })
public interface TestSomeComponent {
#Component.Builder
interface Builder {
Builder bindCoreComponent(SomeComponent coreComponent);
#BindsInstance
Builder bindContext(Context context);
TestSomeComponent build();
}
}
then, given that you're somewhat manually instantiating the components with these Builders, as long as they depend on the interface (SomeComponent), you should be able to bind a ProductionSomeComponent or a TestSomeComponent into your module.
Makes sense or gives some hint?
We're using Dagger2 in our application. I am trying to do a room database and I am writing the repository code, but I would like to inject application context and the DAO for the class.
I have a feeling that you can only do Dagger injection in Fragments, Activities, Services, Applications, etc.
Here's what I have:
class DownloadsDataRepositoryImpl : IDownloadsDataRepository, HasAndroidInjector {
#Inject
lateinit var androidInjector : DispatchingAndroidInjector<Any>
#Inject
lateinit var downloadsDao: DownloadsDao
override fun androidInjector(): AndroidInjector<Any> = androidInjector
init {
androidInjector()
}
}
But I'm sure it's not going to work. Is there a way to do it?
As stated, dagger-android is just a tool to help injecting specific framework classes that you can't have control on it's creation.
The proper approach is to use simple construction injection.
To be more direct on how you should expose it on your #Component, I would need more code, specifically on what you have on your activity/fragment, but here is a crude example (that I did not tested, if there are minor errors, you can fix them following the compiler error messages):
First, you will have some object that exposes your DAO. Probably it's room?
#Entity(tableName = "download_table")
data class DownloadEntity(
#PrimaryKey
val key: String
)
#Dao
interface DownloadsDao {
#Query("SELECT * FROM download_table")
fun load(): List<DownloadEntity>
}
#Database(
entities = [DownloadEntity::class], version = 1
)
abstract class DownloadRoomDatabase : RoomDatabase() {
abstract val downloadsDao: DownloadsDao
}
Now we will create a crude repository that is build with #Inject annotation. Dagger will take care of building this object for us. Notice that I am not using dagger-android for it:
interface IDownloadsDataRepository
class DownloadsDataRepositoryImpl #Inject constructor(
val downloadsDao: DownloadsDao
) : IDownloadsDataRepository
How to expose it to your activity/fragment/service requires more details on your implementation. For example, if it's inside a ViewModel or a Presenter that is annotated with #Inject or you are accessing directly on your activity will result in different implementations. Without more details, I will suppose that you are accessing the repository directly on your activity:
class DownloadActivity : FragmentActivity() {
#Inject
lateinit val repo: IDownloadsDataRepository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DaggerDownloadComponent.factory().create(this).inject(this)
}
}
Now we need to instruct Dagger on how to:
Bind your concrete DownloadsDataRepositoryImpl to the IDownloadsDataRepository interface that the activity requires
How to provide the dependencies to build DownloadsDataRepositoryImpl
For this we will need a module:
#Module
abstract class RepositoryModule {
//We will bind our actual implementation to the IDownloadsDataRepository
#Binds
abstract fun bindRepo(repo: DownloadsDataRepositoryImpl): IDownloadsDataRepository
#Module
companion object {
//We need the database to get access to the DAO
#Provides
#JvmStatic
fun provideDataBase(context: Context): DownloadRoomDatabase =
Room.databaseBuilder(
context,
DownloadRoomDatabase::class.java,
"download_database.db"
).build()
//With the database, we can provide the DAO:
#Provides
#JvmStatic
fun provideDao(db: DownloadRoomDatabase): DownloadsDao = db.downloadsDao
}
}
With this, we can finish the last part of our puzzle, creating the #Component:
#Component(
modules = [
RepositoryModule::class
]
)
interface DownloadComponent {
fun inject(activity: DownloadActivity)
#Component.Factory
interface Factory {
fun create(context: Context): DownloadComponent
}
}
Notice that I did not use any dagger-android code, I don't think it's useful and causes more confusion than necessary. Stick with basic dagger2 constructs and you are fine. You can implement 99.9% of your app only understanding how those constructs works:
#Module, #Component and #Subcomponent
Edit: As stated in the comments, probably you will need to properly manage the scope of your repository, specially the DB creation if you are actually using Room.
Not sure how you implemented dagger, but here is an example how you can provide context to non activity class.
Suppose you have AppModule class, so there you can add provideContext() method:
#Module
class AppModule(app: App) {
private var application: Application = app
#Provides
fun provideContext(): Context {
return application
}
}
and here is non activity class written in Kotlin:
class Utils #inject constructor(private val context: Context) {
..
}
And that's it, just rebuild j
I have a feeling that you can only do Dagger injection in Fragments, Activities, Services, Applications, etc.
You were correct to assume that before Dagger-Android 2.20, but not after 2.20+.
Now you can create a #ContributesAndroidInjector for any class, which will generate an AndroidInjector<T> for that T for which you added #ContributesAndroidInjector.
This means that there is a multi-binding that allows you to get an AndroidInjector<T> for a T, and this is what HasAndroidInjector does for you.
So the following worked for me in a different scenario (for member-injecting Workers in work-manager, instead of creating a multi-binding and a factory):
#Keep
class SyncWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
init {
val injector = context.applicationContext as HasAndroidInjector
injector.androidInjector().inject(this)
}
#Inject
lateinit var apiService: ApiService
and
#ContributesAndroidInjector
abstract fun syncWorker(): SyncWorker
HOWEVER in your particular case, none of this is required.
Dagger-Android is for member-injecting classes using an auto-generated subcomponent, that you typically need only if your injected type is inside a different module, and therefore you can't directly add fun inject(T t) into your AppComponent, OR you don't see your AppComponent.
In your case, simple constructor injection is enough, as you own your own class.
#Singleton
class DownloadsDataRepositoryImpl #Inject constructor(
private val downloadsDao: DownloadsDao
): IDownloadsDataRepository {}
Which you can bind via a module
#Module
abstract class DownloadsModule {
#Binds
abstract fun dataRepository(impl: DownloadsDataRepositoryImpl): IDownloadsDataRepository
}
And otherwise you just create your component instance inside Application.onCreate()
#Component(modules = [DownloadsModule::class])
#Singleton
interface AppComponent {
fun dataRepository(): DownloadsDataRepository
#Component.Factory
interface Factory {
fun create(#BindsInstance appContext: Context): AppComponent
}
}
And
class CustomApplication: Application() {
lateinit var component: AppComponent
private set
override fun onCreate() {
super.onCreate()
component = DaggerAppComponent.factory().create(this)
}
}
Then you can get it as
val component = (context.applicationContext as CustomApplication).component
Though technically you may as well create an extension function
val Context.appComponent: AppComponent
get() = (applicationContext as CustomApplication).component
val component = context.appComponent
I'm really new with Dagger 2, I know how it works and what it does, but I'm getting some problems trying to implement it into my project.
My objective for now, is just inject the presenter into my view, the goal is to decouple my view of doing
presenter = Presenter(myInteractor())
This is what I have tried
MyAppApplication
class MyAppApplication: Application() {
lateinit var presentationComponent: PresentationComponent
override fun onCreate() {
super.onCreate()
createPresentationComponent()
}
private fun createPresentationComponent() {
presentationComponent = DaggerPresentationComponent.builder()
.presentationModule(PresentationModule(this))
.build()
}
}
PresentationComponent
#Component(modules = arrayOf(PresentationModule::class))
#Singleton
interface PresentationComponent {
fun inject(loginActivity: LoginView)
fun loginUserPresenter(): LoginPresenter
}
PresentationModule
#Module
class PresentationModule(application: Application) {
#Provides #Singleton fun provideLoginUserPresenter(signInInteractor: SignInInteractor): LoginPresenter {
return LoginPresenter(signInInteractor)
}
}
SignInInteractor
interface SignInInteractor {
interface SignInCallBack {
fun onSuccess()
fun onFailure(errormsg:String)
}
fun signInWithEmailAndPassword(email:String,password:String,listener:SignInCallBack)
fun firebaseAuthWithGoogle(account: GoogleSignInAccount, listener:SignInCallBack)
}
Now, I thinked that this is all I needed to inject the interactor into my presenter without any problems and then inject the presenter inside my view, but is giving me this error
error: [Dagger/MissingBinding] com.myapp.domain.interactor.logininteractor.SignInInteractor cannot be provided without an #Provides-annotated method.
I'm kinda confused because If I just provide the presentationModule that is responsible to bind my signInInteractor into my Presenter, it should be working, but is not.
Thanks in advance for any help
It's as the error message says, you're trying to pass a SignInInteractor in your PresentationModule to your LoginPresenter, yet you're not providing an implementation for it anywhere. A possible solution would be to add the following block of code to your PresentationModule:
#Provides #Singleton fun provideSignInInteractor(): SignInInteractor {
return TODO("Add an implementation of SignInInteractor here.")
}
Of course the TODO needs to be replaced by a SignInInteractor of your choosing (the myInteractor() function would work for example). Then that SignInInteractor will be used by your LoginPresenter. Hope that helps!
i try to use dependency injection also in the service that handles recive notification.
I followed the solution by Nauce https://github.com/googlesamples/android-architecture-components/issues/253
App.kt
class App : MultiDexApplication(), HasServiceInjector {
#Inject lateinit var dispatchingServiceInjector: DispatchingAndroidInjector<Service>
companion object {
lateinit var applicationComponent: ApplicationComponent
}
override fun serviceInjector() = dispatchingServiceInjector
override fun onCreate() {
super.onCreate()
applicationComponent = DaggerApplicationComponent
.builder()
.applicationModule(ApplicationModule(this))
.build()
}
}
MyOneSignalMessagingService.kt
#Singleton
class MyOneSignalMessagingService : NotificationExtenderService() {
#Inject lateinit var ApiService: ApiService
override fun onCreate() {
super.onCreate()
AndroidInjection.inject(this)
}
override fun onNotificationProcessing(notification: OSNotificationReceivedResult?): Boolean {
// He i want to use ApiService
}
But I cannot inject dispatchingServiceInjector in App.kt.
It will throw lateinit property dispatchingServiceInjector has not been initialized, whenMyOneSignalMessagingService receive the notification.
You aren't injecting your Application. Note in Nauce's example that ApplicationComponent has this method:
#Component(/* ... */) #Singleton public interface AppComponent {
/* ... */
void inject(App app);
}
By making a one-arg method that returns void, you've defined a members-injection method, which populates #Inject-annotated fields and calls #Inject-annotated methods on an existing object instance. Because Android itself creates your Application instance, it will not have its #Inject fields populated automatically, so it's up to you to inject it with your component. (You do the same in Services and Activities by calling AndroidInjection.inject(this), but because you're responsible for creating your own Component implementation, there's no equivalent call for Application.)
Without defining AppInjector, Nauce calls this in onCreate:
AppInjector.init(this);
But in your example, this would look more like this, in App.kt:
override fun onCreate() {
super.onCreate()
applicationComponent = DaggerApplicationComponent
.builder()
.applicationModule(ApplicationModule(this))
.build()
// Inject this class's #Inject-annotated members.
applicationComponent.inject(this)
}
For future reference, you also have the choice to extend from DaggerApplication (in dagger.android or dagger.android.support), which would automatically provide all of your injectors as long as you make your ApplicationComponent extend AndroidInjector<App> and return it from the applicationInjector() method.
I am trying to get dagger working in my application.
After creating Module Component and MyApp i can use dagger to inject database service into view but i am having trouble doing same thing with presenter.
Code:
class MyApp : Application() {
var daoComponent: DaoComponent? = null
private set
override fun onCreate() {
super.onCreate()
daoComponent = DaggerDaoComponent.builder()
.appModule(AppModule(this)) // This also corresponds to the name of your module: %component_name%Module
.daoModule(DaoModule())
.build()
}
}
Module
#Module
class DaoModule {
#Provides
fun providesEstateService(): EstateService = EstateServiceImpl()
}
Component
#Singleton
#Component(modules = arrayOf(AppModule::class, DaoModule::class))
interface DaoComponent {
fun inject(activity: MainActivity)
}
AppModule
#Module
class AppModule(internal var mApplication: Application) {
#Provides
#Singleton
internal fun providesApplication(): Application {
return mApplication
}
}
MainActivity
class MainActivity : MvpActivity<MainView, MainPresenter>(), MainView {
#Inject
lateinit var estateService : EstateService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
(application as MyApp).daoComponent!!.inject(this)estateService.numberOfInvoicedEstates.toString()
}
override fun createPresenter(): MainPresenter = MainPresenterImpl()
}
After injecting estateService this way I can use it, but I cant figure out how do I inject service directly into the presenter.
I tried doing it like this but it isn't working.
Should I just pass injected objects from the activity? or maybe I should pass MyApp as an argument or make static method allowing my to get it from any place in the application?
class MainPresenterImpl
#Inject
constructor(): MvpBasePresenter<MainView>(),MainPresenter {
#Inject
lateinit var estateService : EstateService
}
Your component should provide the presenter like that:
#Component(modules = arrayOf(AppModule::class, DaoModule::class))
#Singleton
interface MyComponent {
mainPresenter() : MainPresenter
}
And all dependencies the presenter needs are injected to the presenter via constructor parameters:
class MainPresenterImpl
#Inject constructor(private val estateService : EstateService ) :
MvpBasePresenter<MainView>(),MainPresenter {
...
}
Than in createPresenter() just grab the presenter from dagger component like this:
class MainActivity : MvpActivity<MainView, MainPresenter>(), MainView {
...
override fun createPresenter(): MainPresenter =
(application as MyApp).myComponent().mainPresenter()
}
Please note that the code shown above will not compile. This is just pseudocode to give you an idea how the dependency graph could look like in Dagger.
Please refer to this example on how to use Dagger 2 in combination with MVP.
You have to tell your component where you want to inject it.
So, try with this component:
#Singleton
#Component(modules = arrayOf(AppModule::class, DaoModule::class))
interface DaoComponent {
fun inject(presenter: MainPresenterImpl)
}