Dagger dependency cycle between Presenter and Callback - android

I have a PresenterModule where I instantiate the presenter, passing as parameter a conteiner of callbacks. All these callbacks are instantiated, in CallbacksModule, passingas parameter the presenter, because need to communicate back to the presenters the results of the call.
The problem is that it is creating a dagger injection dependency cycle. How may I solve it? Thanks.
#Module
class CallbacksModule {
#Provides
fun provideProcessCallbacks(call1: RegistrationCallback,
call2: SignatureStartCallback,
call3: SignatureRegistrationCallback): GenerationProcessCallbacks {
return GenerationProcessCallbacks(call1, call2, call3)
}
#Provides
fun provideRegistrationCallback(presenter: StepPresenterImpl): RegistrationCallback {
return RegistrationCallback(presenter)
}
#Provides
fun provideSignatureStartCallback(presenter: StepPresenterImpl): SignatureStartCallback {
return SignatureStartCallback(presenter)
}
#Provides
fun provideSignatureRegistrationCallback(presenter: StepPresenterImpl): SignatureRegistrationCallback {
return SignatureRegistrationCallback(presenter)
}
}
presenter module:
#Module
class PresenterModule {
#Provides
fun provideStepProcessPresenter(callbacks: GenerationProcessCallbacks): StepPresenterImpl {
return StepPresenterImpl(callbacks)
}
callbacks code:
override fun onSuccess(registrationResponse: RegistrationRequestResponseModel?, useCaseIdentifier: String?) {
presenter?.onRegistered(registrationResponse)
}
override fun onUnexpectedError(t: Throwable?, useCaseIdentifier: String?) {
if (t != null && t is InvalidSessionException) {
return presenter?.showErrorOnView(t.code)
}
}
presenter code:
override fun onRegistered(registrationResponse: RegistrationRequestResponseModel?) {
signatureStartUseCase
.identifier(SignatureStartUseCase::class.simpleName)
.responseOnUI(false)
.execute(registrationResponse, callbacks.signatureStartCallback)
}
For the first two callbacks, they call another usecase. And for the last callback, it shows data on view.

As in the comments, you might not need the callbacks to be injected. Injection is typically pretty inexpensive, in terms of code and runtime cost, but it's not applicable for all cases. You may only want to inject in cases where it is useful to you to inject other dependencies or reconfigure the class through Dagger, though tests are a perfectly valid reason to want that reconfiguration.
The root of the dependency cycle problem, though, is that you are asking for a GenerationProcessCallbacks instance and Presenter instance at the same time, so Dagger can't create either one before the other. Instead, you can inject a Provider (Provider<GenerationProcessCallbacks>) or Lazy (Lazy<GenerationProcessCallbacks>), for which you only call get() outside the Presenter's constructor. This lets you create the Presenter first, because Dagger knows that you will have a Presenter instance with which to construct the GenerationProcessCallbacks. For that you'll also want to scope the component and Presenter with #Singleton (or some other scope annotation) so the same Presenter instance that creates the callbacks is also responsible for their generation; otherwise, Dagger will create an outer presenter that depends on the callbacks, and the callbacks will depend on one or more distinct Presenter instances compared to the outer one.

Related

Is it required to add #Singleton on top of a function using dagger hilt if the module scope is already singleton?

In the below code, I have a module where I call #InstallIn(SingletonComponent::class). So the scope of this module is the application scope or singleton scope))
If I'll provide something inside the module, do I need to annotate that function with singleton as well?
Yes you have to annotate that function with singleton as well.
#InstallIn(SingletonComponent::class)
means that you may use singleton in your #Provides functions, without it you can't make your #Provides functions singleton. In hilt #Provides functions have no-scope by default so if you don't annotate your #Provides function with singleton, every time it will return new object.
you can easily test my answer with below code
var count = 0
class TestingClass() {
init {
count++
}
fun printCount() = println("count: $count")
}
#Module
#InstallIn(SingletonComponent::class)
class TestModule {
#Provides
#Singleton // without this count will increase every time you inject TestingClass
fun provideTestClass(): TestingClass {
return TestingClass()
}
}

Dagger multibinding with a custom qualifier

I have the following interface called SettingHandler that is responsible for handling events related to a particular setting inside the Android app.
interface SettingHandler {
fun onHandleEvent(event: SettingEvent)
fun candleHandleEvent(event: SettingEvent): Boolean
fun getSettingId(): SettingId
}
Then let's say I have the following Dagger module:
#Module
object SettingsModule {
#Provides
fun provideSettingPresenter(
view: SettingsContract.View,
settingHandlers: Set<#JvmSuppressWildcards SettingHandler>
): SettingsPresenter {
//
}
#Provides
#IntoSet
fun provideSettingHandlerA(
dependencyA: A,
dependencyB: B
): SettingHandler {
//
}
#Provides
#IntoSet
fun provideSettingHandlerB(
settingHandlerC: Provider<SettingHandler>
): SettingHandler {
//
}
#Provides
#IntoSet
fun provideSettingHandlerC(
settingHandlerB: Provider<SettingHandler>
): SettingHandler {
//
}
}
As you can see, nothing too special here, expect for the SettingHandlerB and SettingHandlerC provider methods. They both depend on each other, so I've decided to resolve this circular dependency using the Dagger supplied Provider class. Since I require a concrete implementation of a setting handler (SettingHandlerC for the SettingHandlerB and SettingHandlerB for the SettingHandlerC), I now need to differentiate between them. That's where a #Qualifier annotation comes into play. I created the following qualifier annotation to differentiate among different implementations.
#Qualifier
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FIELD, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
annotation class SettingHandlerType(val id: SettingId)
SettingId is basically an enumeration that contains all possible setting constants. So now my SettingHandlers module looks as follows:
#Module
object SettingsModule {
#Provides
fun provideSettingPresenter(
view: SettingsContract.View,
settingHandlers: Set<#JvmSuppressWildcards SettingHandler>
): SettingsPresenter {
//
}
#Provides
#SettingHandlerType(SettingId.A)
fun provideSettingHandlerA(
dependencyA: A,
dependencyB: B
): SettingHandler {
//
}
#Provides
#SettingHandlerType(SettingId.B)
fun provideSettingHandlerB(
#SettingHandlerType(SettingId.C)
settingHandlerC: Provider<SettingHandler>
): SettingHandler {
//
}
#Provides
#SettingHandlerType(SettingId.C)
fun provideSettingHandlerC(
#SettingHandlerType(SettingId.B)
settingHandlerB: Provider<SettingHandler>
): SettingHandler {
//
}
}
And here comes the problem. Mutlibinding now does not work, because all my SettingHandlers are annotated with a #SettingHandlerType annotation and a Set that I'm injecting into the SettingsPresenter also needs to be annotated. However, annotating it with, for example, #SettingHandlerType(SettingId.A) won't work because in that case the Set will contain only setting handlers with that particular qualifier (SettingHandlerA, in this case). How can I construct a Set data structure using multibinding because I really don't want to provide another provider method where I'll be constructing it myself?
Any help would be much appreciated. Thanks in advance.
You want two different types of data: Individual handlers of type #SettingHandlerType(/*...*/) SettingHandler and a set of type Set<SettingHandler>. I think the best way would be to have each definition come with a #Binds #IntoSet complement.
#Binds #IntoSet abstract fun bindSettingHandlerA(
#SettingHandlerType(SettingId.A) handler: SettingHandler): SettingHandler
Unfortunately, since you've defined this as an object in Kotlin, it's slightly more complicated to put the necessarily-abstract #Binds methods on the same object. You may need to pursue one of these other solutions to make that topology work with your case, including the use of a companion object to capture the otherwise-static #Provides methods:
Dagger 2 static provider methods in kotlin
#Provides and #Binds methods in same class Kotlin
Dagger2 : How to use #Provides and #Binds in same module (java)

Shared ViewModel across two/or more fragments and an activity using Dagger

Well, as I tried to summarise in the title, here is the details.
We have a relatively large application, that uses Dagger, in really not ideal ways, so we decided to start writing tests, and for that, I needed to expose dependencies for Mockito, hence, I faced an issue to start provide view models using a singleton factory, still applicable and there is tons of tutorials around that explains this.
We have across our app, a lot of features, that is implemented using a single activity, and a navigation component, that single activity sometimes have a created view model that we use to share data between the container activity and the fragments populated using the navigation editor.
What I couldn't figure out is the following, how can I use dagger to inject a shared view model, to return the same instance each time I invoke #Inject for a specific view model, I know it could be done through scopes maybe, but I couldn't figure it out, and I have an explanation that I need to be verified. (I will provide my code below)
I started by implementing my Singleton ViewModelFactory as follows:
#Singleton
class ViewModelFactory #Inject constructor(private val creators: Map<Class<out ViewModel>,
#JvmSuppressWildcards Provider<ViewModel>>) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
val creator = creators[modelClass] ?: creators.entries.firstOrNull {
modelClass.isAssignableFrom(it.key)
}?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
try {
#Suppress("UNCHECKED_CAST")
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
Then I created my ViewModelModule that provides the ViewModelFactory and the ViewModel as follows:
#Module
abstract class ViewModelFactoryModule {
#Binds
abstract fun bindsViewModelFactory(viewModelFactory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#EbMainScope
#ViewModelKey(EBMainContainerViewModel::class)
abstract fun bindEbMainViewModel(ebMainContainerViewModel: EBMainContainerViewModel): ViewModel
}
And before you ask, here is the scope implementation:
#Scope
#Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
#Retention(AnnotationRetention.RUNTIME)
annotation class EbMainScope
Last step, here is my activity/fragment injectors module:
#Module
abstract class ScreensBuildersModule {
#ContributesAndroidInjector
#EbMainScope
abstract fun contributeEbMainActivity(): EBMainActivity
#ContributesAndroidInjector
#EbMainScope
abstract fun contributeEbDashboardMainFragment(): EBDashboardMainFragment
}
Of course I wired everything in the AppComponent, and the app ran smoothly, with the catch, that there was two instances of EbMainContainerViewModel, despite me defining the scope.
My explanation was, I actually had two different providers rather than one, but I still cannot understand why, since I marked it as #Singleton.
Does someone has an explanation to this ? If more input is needed let me know guys.
I had the same problem, but fix it this way:
I use for example this code:
https://github.com/android/architecture-samples/tree/dagger-android
In my fragments (in which I wanted to use Shared ViewModel) I use
this (it helped me):
private val viewModel by viewModels<SearchViewModel>({ activity as MainActivity }) { viewModelFactory }
instead of this(as in sample):
private val viewModel by viewModels<SearchViewModel> { viewModelFactory }
Because the first argument is ownerProducer, so we create a ViewModel in the activity scope.
Okay then, here is practical guide I have managed to do, a solution I guess viable, and since #pratz9999 asked for a solution, here it is:
In order to instantiate a ViewModel, you would need a ViewModelProvider, which under the hood creates a ViewModelFactory, if you would depend on the above implementation, for each entry in the module (i.e #IntoMap call) a new provider will be instantiated (which is fines) but here goes the catch, it will create a new ViewModelFactory each time, take a look at the following:
/**
* Creates a {#link ViewModelProvider}, which retains ViewModels while a scope of given
* {#code fragment} is alive. More detailed explanation is in {#link ViewModel}.
* <p>
* It uses the given {#link Factory} to instantiate new ViewModels.
*
* #param fragment a fragment, in whose scope ViewModels should be retained
* #param factory a {#code Factory} to instantiate new ViewModels
* #return a ViewModelProvider instance
*/
#NonNull
#MainThread
public static ViewModelProvider of(#NonNull Fragment fragment, #Nullable Factory factory) {
Application application = checkApplication(checkActivity(fragment));
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return new ViewModelProvider(fragment.getViewModelStore(), factory);
}
My fault as I guessed after some research, that I did not inject the proper ViewModelFactory, so I ended up doing the following:
In my base fragment class, I injected a ViewModelFactory as follows:
/**
* Factory for injecting view models
*/
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
Then in a utility class, I had a method that returns a shared ViewModel as follows (Note the activity?.run this makes the view model instance binded to the holding activity, and thus having the shared scope concept):
fun <T: ViewModel> BaseNavFragmentWithDagger.getSharedViewModelWithParams(clazz: Class<T>): T =
activity?.run { ViewModelProviders.of(this, viewModelFactory).get(clazz) }
?: throw RuntimeException("You called the view model too early")
And finally for private ViewModels I went with this:
fun <T: ViewModel> BaseNavFragmentWithDagger.getPrivateViewModelWithParams(clazz: Class<T>): T =
ViewModelProviders.of(this, viewModelFactory).get(clazz)

How to ensure ViewModel#onCleared is called in an Android unit test?

This is my MWE test class, which depends on AndroidX, JUnit 4 and MockK 1.9:
class ViewModelOnClearedTest {
#Test
fun `MyViewModel#onCleared calls Object#function`() = mockkObject(Object) {
MyViewModel::class.members
.single { it.name == "onCleared" }
.apply { isAccessible = true }
.call(MyViewModel())
verify { Object.function() }
}
}
class MyViewModel : ViewModel() {
override fun onCleared() = Object.function()
}
object Object {
fun function() {}
}
Note: the method is protected in superclass ViewModel.
I want to verify that MyViewModel#onCleared calls Object#function. The above code accomplished this through reflection. My question is: can I somehow run or mock the Android system so that the onCleared method is called, so that I don't need reflection?
From the onCleared JavaDoc:
This method will be called when this ViewModel is no longer used and will be destroyed.
So, in other words, how do I create this situation so that I know onCleared is called and I can verify its behaviour?
In kotlin you can override the protected visibility using public and then call it from a test.
class MyViewModel: ViewModel() {
public override fun onCleared() {
///...
}
}
I've just created this extension to ViewModel:
/**
* Will create new [ViewModelStore], add view model into it using [ViewModelProvider]
* and then call [ViewModelStore.clear], that will cause [ViewModel.onCleared] to be called
*/
fun ViewModel.callOnCleared() {
val viewModelStore = ViewModelStore()
val viewModelProvider = ViewModelProvider(viewModelStore, object : ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = this#callOnCleared as T
})
viewModelProvider.get(this#callOnCleared::class.java)
//Run 2
viewModelStore.clear()//To call clear() in ViewModel
}
TL;DR
In this answer, Robolectric is used to have the Android framework invoke onCleared on your ViewModel. This way of testing is slower than using reflection (like in the question) and depends on both Robolectric and the Android framework. That trade-off is up to you.
Looking at Android's source...
...you can see that ViewModel#onCleared is only called in ViewModelStore (for your own ViewModels). This is a storage class for view models and is owned by ViewModelStoreOwner classes, e.g. FragmentActivity. So, when does ViewModelStore invoke onCleared on your ViewModel?
It has to store your ViewModel, then the store has to be cleared (which you cannot do yourself).
Your view model is stored by the ViewModelProvider when you get your ViewModel using ViewModelProviders.of(FragmentActivity activity).get(Class<T> modelClass), where T is your view model class. It stores it in the ViewModelStore of the FragmentActivity.
The store is clear for example when your fragment activity is destroyed. It's a bunch of chained calls that go all over the place, but basically it is:
Have a FragmentActivity.
Get its ViewModelProvider using ViewModelProviders#of.
Get your ViewModel using ViewModelProvider#get.
Destroy your activity.
Now, onCleared should be invoked on your view model. Let's test it using Robolectric 4, JUnit 4, MockK 1.9:
Add #RunWith(RobolectricTestRunner::class) to your test class.
Create an activity controller using Robolectric.buildActivity(FragmentActivity::class.java)
Initialise the activity using setup on the controller, this allows it to be destroyed.
Get the activity with the controller's get method.
Get your view model with the steps described above.
Destroy the activity using destroy on the controller.
Verify the behaviour of onCleared.
Full example class...
...based on the question's example:
#RunWith(RobolectricTestRunner::class)
class ViewModelOnClearedTest {
#Test
fun `MyViewModel#onCleared calls Object#function`() = mockkObject(Object) {
val controller = Robolectric.buildActivity(FragmentActivity::class.java).setup()
ViewModelProviders.of(controller.get()).get(MyViewModel::class.java)
controller.destroy()
verify { Object.function() }
}
}
class MyViewModel : ViewModel() {
override fun onCleared() = Object.function()
}
object Object {
fun function() {}
}
For Java, if you create your test class in the same package (within the test directory) as of the ViewModel class (here, MyViewModel), then you can call onCleared method from the test class; since protected methods are also package private.

Dagger 2 how to access same component everywhere

How do I create a singleton object that I can access anywhere from any place in my code?
Objects(1) from which I want to inject my signleton component can't be created withing dagger.
Those objects don't have common dependencies which I can access (like getApplication()
Quick to an example:
InjectedClass:
public class InjectedClass {
public InjectedClass() {
System.out.println("injected class created");
}
}
HolderClassA:
public class HolderClassA { // this is one of object I marked with (1)
#Inject InjectedClass b;
public HolderClassA() {
Injector build = DaggerInjector.builder().build();
build.inject(this);
System.out.println(b);
}
}
HolderClassB:
public class HolderClassB { // this is another object I marked with (1)
#Inject InjectedClass b;
public HolderClassB() {
Injector build = DaggerInjector.builder().build();
build.inject(this);
System.out.println(b);
}
}
Provider:
#Module
public class Provider {
#Provides
#Singleton
InjectedClass provideInjectedClass() {
return new InjectedClass();
}
}
Injector:
#Component(modules = {Provider.class})
#Singleton
public interface Injector {
void inject(HolderClassA a);
void inject(HolderClassB a);
}
Somewhere in code:
new HolderClassA();
Somewhere else in code that is NO WAY related to previous code, nor has the same parent or can access same objects (like in dagger guide with getApplication()):
new HolderClassB();
Actual result: two instances of InjectedClass are crated
Expected result: a single instance of InjectedClass is created.
The issue is DaggerInjector.builder().build(); creates different scopes which don't know about each other. For example, I can solve this issue by creating static variable DaggerInjector.builder().build() and call inject from it. But thus it wouldn't be dependency injection anymore, but rather not trivial singleton pattern.
From what I understand from comments, you want separate components for your holder classes, that would inject same instance? Pardon me if I am wrong.
You can try this.
#Component(modules = arrayOf(AppModule::class))
#Singleton
interface AppComponent {
//sub components
fun plusHolderClass1(holderModule: HolderModule1):HolderComponent1
fun plusHolderClass2(holderModule: HolderModule2):HolderComponent2
//provision methods
fun getInjectedClass():InjectedClass
}
This is your application component, or the top level component, that you initialise in your application, of course using your Module class that will provide the Injected class as a singleton.
#Subcomponent(modules = arrayOf(HolderModule1::class))
#ActivityScope
interface HolderComponent1{
fun inject(holder:Holder1)
}
And a similar one for Holder2 class. You can define your local scope dependencies in the modules.
But of course even in this case you have to store the instance of appComponent in Application class.
While injecting
appComponent.plusHolderComponent1(HolderModule1()).inject(yourObject)
The InjectedClass object will be injected to yourObject by fetching it from provision methods

Categories

Resources