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.
Related
Trying to understand the following example and do something similar.
In my application there is a module, in which I want to use Dagger.
To do this I need an Application class in which I initialize and store AppComponent
Judging from the documentation I need to create an interface with a component from my module:
interface PasscodeSetupComponentProvider {
fun providePasscodeSetupComponent(): PasscodeComponent
}
Then I will implement this interface for my Application class:
open class FenturyApplication : PasscodeSetupComponentProvider {
lateinit var appComponent: AppComponent
override fun onCreate() {
super.onCreate()
appComponent = DaggerAppComponent.builder()
.appModule(AppModule(applicationContext))
.build()
}
override fun providePasscodeSetupComponent(): PasscodeComponent {
return appComponent.passcodeComponent
}
}
But judging by the example from the documentation I don't understand what should be in my interface Appcomponent namely passcodeComponent.
In the example, it looks like this:
class MyApplication: Application(), LoginComponentProvider {
// Reference to the application graph that is used across the whole app
val appComponent = DaggerApplicationComponent.create()
override fun provideLoginComponent(): LoginComponent {
return appComponent.loginComponent().create()
}
}
I added the following code to my AppComponent:
#Component(modules = [AppModule::class])
#Singleton
interface AppComponent {
val applicationContext: Context
fun passcodeComponent(): PasscodeComponent
}
And if I understood correctly, then in the fragment that is in my module, I can write the following:
lateinit var passcodeComponent: PasscodeComponent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val context = activity?.applicationContext ?: return
passcodeComponent = (context as PasscodeSetupComponentProvider).providePasscodeSetupComponent()
passcodeComponent.inject(this)
After that I hope I can use dagger in my module.
You're missing a crucial part of how AppComponent is written. You can read the complete code in this link.
Please note how Google is declaring the LoginComponent (as Subcomponent) and how do they declare it inside AppComponent in order do return a new instance of LoginComponent.
AppComponent (copied from the link I posted)
#Singleton
#Component(modules = [NetworkModule::class, SubcomponentsModule::class])
interface ApplicationComponent {
// This function exposes the LoginComponent Factory out of the graph so consumers
// can use it to obtain new instances of LoginComponent
fun loginComponent(): LoginComponent.Factory
}
And the code for LoginComponent
#Subcomponent
interface LoginComponent {
// Factory that is used to create instances of this subcomponent
#Subcomponent.Factory
interface Factory {
fun create(): LoginComponent
}
fun inject(loginActivity: LoginActivity)
}
I am developing an Android application using Kotlin. I am writing integration tests for my application. Now I am having a problem mocking an injected dependency class that is injected using the Dagger 2 test if a method of the mocked object is called. Following is my code.
I have the TestAppComponent class with the following code
#Singleton
#Component(modules = [ TestAppModule::class ])
interface TestAppComponent
{
fun inject(app: ApplicationController)
fun inject(app: MockApplicationController)
}
As you can see in the class, I have two inject methods. One for the ApplicationController which basically is the application class used for the actual application. Another one for MockAppllication controller which is also the application class but it is used for the tests.
Following is the implementation of the ApplicationController class
open class ApplicationController: Application()
{
lateinit var appComponent: AppComponent
private fun initDagger(app: ApplicationController): AppComponent = DaggerAppComponent
.builder()
.appModule(AppModule(app)).build()
#Inject
lateinit var fileService: IFileService
override fun onCreate() {
super.onCreate()
appComponent = initDagger(this)
instance = this
this.appComponent.inject(this)
}
}
As you can see in the code, I am initializing the Dagger AppComponent class in the onCreate method of the application class. Then inject the dependency class.
This is the implementation of my MockApplicationController class which is used for the tests.
class MockApplicationController: ApplicationController()
{
private fun initDagger(app: MockApplicationController): AppComponent = DaggerTestAppComponent
.builder()
.testAppModule(TestAppModule(app))
.build()
override fun onCreate() {
super.onCreate()
//override the dagger app component appComponent
this.appComponent = initDagger(this)
instance = this
this.appComponent.inject(this)
}
}
This is my activity class.
class MainActivity: AppCompatActivity()
{
override fun onCreate(savedInstanceState: Bundle?) {
//rest of the code
button_save_file.setOnClickListener {
//rest of the code
ApplicationController.instance.fileService.saveFile(filePath)
}
}
}
As you can see, basically, what I am doing within the activity class is that I am just calling the saveFile of the IFileService interface.
Following is the definition of the IFileService interface
interface IFileService
{
fun saveFile(path: String)
}
There are two classes that are implementing the IFileService. One is FakeFileService class which will be used for the tests and the other one is FileService class which will be used for the actual application.
Following is the implementation of FakeFileService class
class FakeFileService: IFileService
{
fun saveFile(path: String)
{
//literally, this is doing nothing since we will only test that if the method is called
}
}
I also created two classes for the Dagger app modules. One for the tests and the other one for the actual application.
Following is the implementation of the TestAppModule class which will be used for tests.
#Module
open class TestAppModule (private val app: Application) {
private var fileService: IFileService? = null
#Singleton
#Provides
fun provideContext(): Context = app
//to override injecting the Mockito mock instance
fun injectFileService(fileService: IFileService) {
this.fileService = fileService
}
#Singleton
#Provides
open fun fileService(): IFileService {
if (this.fileService != null) {
return this.fileService as IFileService
}
return FakeFileService()
}
}
Note the injectFileService method. I created that method to inject the mock object/ instance mocked using Mockito within the tests.
This is my test class
#RunWith(AndroidJUnit4::class)
#LargeTest
class CameraTest: TestBuilder()
{
#get:Rule
var mainActivityRule: ActivityTestRule<MainActivity> = ActivityTestRule<MainActivity>(CameraActivity::class.java, true, false)
private lateinit var fileServiceMock: IFileIOService
#Before
fun setUp() {
this.fileServiceMock = mock(IFileService::class.java)
MockitoAnnotations.initMocks(this)
val instrumentation = InstrumentationRegistry.getInstrumentation()
val app = instrumentation.targetContext.applicationContext as MockApplicationController
var testModule = TestAppModule(app)
testModule.injectFileService(this.fileServiceMock)
app.appComponent = DaggerTestAppComponent
.builder()
.testAppModule(testModule)
.build()
app.appComponent.inject(app)
}
#Test
fun itSavesFileWhenButtonIsClicked() {
Intents.init()
var targetContext: Context = InstrumentationRegistry.getInstrumentation().targetContext
var intent: Intent = Intent(targetContext, MainActivity::class.java)
this.mainActivityRule.launchActivity(intent)
onView(withId(R.id.button_save_file)).perform(click())
verify(this.fileServiceMock).saveFile(any())
Intents.release()
}
}
As you can see, in my test class, I try to inject the mocked version of IFileService object before the test is run. Then in my test, I am asserting that the method is executed. When I run the test it is complaining that "zero interactions with this mock". How can I fix it? What is wrong with my code and how can I fix it?
This is my ApplicationController class
open class ApplicationController: Application()
{
lateinit var appComponent: AppComponent
private fun initDagger(app: ApplicationController): AppComponent = DaggerAppComponent
.builder()
.appModule(AppModule(app)).build()
#Inject
lateinit var fileService: IFileService
override fun onCreate() {
super.onCreate()
appComponent = initDagger(this)
instance = this
this.appComponent.inject(this)
}
}
When I put the loggin in each onCreate method of application controller classes and #Before method, they are called in the following order.
ApplicationController
MockApplicationController
The code to modify the MockApplicationController with the #Before method
I am using Dagger 2.14.1 in my Android application and migrated recently from 2.10. The new version makes it much easier to inject Activities and Fragments, but I can't find a way to inject a custom class where I cannot change the constructor as well. With 2.10 I could write custom inject functions and then use them to simply inject any class:
Simplified Dagger 2.10 Injection with Kotlin:
#Singleton
#Component(dependencies = [], modules = [ApplicationModule::class, ...])
interface ApplicationComponent {
fun application(): App
fun inject(authenticationActivity: AuthenticationActivity)
fun inject(converters: Converters)
// ...
}
class App : Application() {
companion object {
#JvmStatic
lateinit var component: ApplicationComponent
}
override fun onCreate() {
super.onCreate()
component = DaggerApplicationComponent.builder()
.applicationModule(ApplicationModule(this))
.build()
}
}
// This class is used by the Room database framwork and I cannot change the constructor and do class Converters #Inject constructor(private val gson: Gson) {
class Converters {
#Inject
protected lateinit var gson: Gson
init {
App.component.inject(this)
}
// ...
}
Simplified Dagger 2.14.1 Injection with Kotlin doesn't provide me with a ApplicationComponent Object to inject my custom classess:
#Singleton
#Component(modules = [ApplicationModule::class, ...])
interface ApplicationComponent : AndroidInjector<App> {
#Component.Builder
abstract class Builder : AndroidInjector.Builder<App>()
fun inject(converters: Converters) // doesn't work!
}
class App : Application(), HasActivityInjector {
#Inject
lateinit var activityInjector: DispatchingAndroidInjector<Activity>
override fun onCreate() {
super.onCreate()
// From where do I get my component to call .inject() from another class?
DaggerApplicationComponent.builder().create(this).inject(this)
}
override fun activityInjector(): AndroidInjector<Activity> = activityInjector
}
In short: the DaggerApplicationComponent.builder().create(this) returns an AndroidInjector<App!> Object. This Object only has one .inject() functions which only accepts my App Class. So I cannot inject anything else.
Of course there is a lot missing, but I wanted to only post the relevant code. My Dagger 2.14.1 implementation works and all my dependencies in Activities, Fragments and View Models get injected.
I am quite new to Dagger and the more I use it the more I love it, but I couldn't figure out on how to inject custom classes where I cannot change the constructor. Thanks for you help!
Try this:
class App : Application(), HasActivityInjector {
#Inject
lateinit var activityInjector: DispatchingAndroidInjector<Activity>
companion object {
#JvmStatic
lateinit var instance: App
}
#Inject
lateinit var component: ApplicationComponent
override fun onCreate() {
super.onCreate()
instance = this
DaggerApplicationComponent.builder().create(this).inject(this)
}
override fun activityInjector(): AndroidInjector<Activity> = activityInjector
}
Then
object Injector {
#JvmStatic fun get(): ApplicationComponent = App.instance.component
}
Now you can do
class Converters {
#Inject
protected lateinit var gson: Gson
init {
Injector.get().inject(this)
}
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)
}
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.