Android - Dagger inject Shared ViewModel to Fragment - android

I need to bind two fragments with shared ViewModel.
Component:
void injectMovieFragment(MovieFragment movieFragment);
void injectMovieCollectionFragment(MovieCollectionFragment movieCollectionFragment);
SharedViewModelModule:
#Provides
MovieSharedViewModel provideMovieSharedViewModel(Fragment fragment) {
return new ViewModelProvider(fragment.requireActivity()).get(MovieSharedViewModel.class);
}
This code cause MissingBinding error on Dagger (cannot get Fragment from MovieFragment/ MovieCollectionFragment).
SharedViewModelModule v2:
#Provides
MovieSharedViewModel provideMovieSharedViewModelToMovieFragment(MovieFragment fragment) {
return new ViewModelProvider(fragment.requireActivity()).get(MovieSharedViewModel.class);
}
#Provides
MovieSharedViewModel provideMovieSharedViewModelToMovieCollectionFragment(MovieCollectionFragment fragment) {
return new ViewModelProvider(fragment.requireActivity()).get(MovieSharedViewModel.class);
}
This code cause DuplicateBindings error on Dagger.
To get sharedViewModel without Dagger2 injection, I simply using:
sharedViewModel = new ViewModelProvider(requireActivity()).get(MovieSharedViewModel.class);
inside onViewCreated of MovieFragment & MovieCollectionFragment classes, and this works well.
How to properly inject this shared ViewModel using Dagger 2?

It would be nice if you could add how you are using injection in your fragment. From the error it seems like you are not injecting the created fragment into your dagger graph.
This is required as fragments are normally created by android and you need to provide the created instance to dagger.
The way I used to do it is like expose my DaggerComponent from my CustomApplication class(You can also expose the component through your activity).
Then in the fragment's onAttach function inject the created fragment like this.
override fun onAttach(context: Context) {
super.onAttach(context)
(context.applicationContext as CustomApplication).applicationComponent.injectMovieFragment(this)
}
This should fix the error. Please provide how you are using injection in fragments for more complete answers.

Related

Android Dagger - How to Inject from other module

I've multiple modules in my app, each one of them have his own Module for UI Injection.
Now, i want to have "feed" fragment that have a some pieces from other modules.
So i trying doing it using FragmentContainerView.
I want to Inject the fragment that defined on other module. I'm trying to "include" my other fragment, so i can navigate into this fragment, but if i'm try to inject it, i'm getting this following error:
error: [Dagger/MissingBinding] DotsFragment cannot be provided without an #Inject constructor or an #Provides-annotated method. This type supports members injection but cannot be implicitly provided.
Any idea how can i do it?
HomeModule:
#Module(
includes = [
DotsModule::class
]
)
abstract class HomeModule {
}
DotsModule:
#Module
abstract class DotsModule {
#FragmentScoped
#ContributesAndroidInjector
abstract fun contributeDotsFragment(): DotsFragment
}
HomeFragment:
#Inject
lateinit var dotsFragment: DotsFragment
Just push the Fragment into the container as described in the docs, and don't worry about the injection. You don't even need the explicit inclusion of DotsModule from HomeModule, though you may choose to keep it, especially if HomeModule is in a different Activity. DotsFragment will inject itself in onAttach.
Unlike most classes that use Dagger injection, Fragments can't be injected in their constructors: Android will only call the zero-arg or one-arg Fragment() constructor, so there isn't room for other constructor parameters. When you see your abstract fun contributeDotsFragment() function with #ContributesAndroidInjector, it is not the case that DotsFragment is available on your graph to be constructed or provided. Instead, Dagger does some magic1 that allows the Fragment to have its #Inject-labeled fields populated2 once you call AndroidSupportInjection.inject(this) in onAttach, which might happen in DaggerFragment if you inherit from that. Your Fragment has to assume that anything marked #Inject is null until the Fragment is attached.
Consequently, if you need an instance of DotsFragment, you can just call DotsFragment() or delegate to your FragmentManager's FragmentFactory (which will likely just call DotsFragment()). You can also use overloads to FragmentTransaction.replace(...) that take a Class<Fragment> rather than a Fragment instance.
The Fragment will inject itself later in onAttach, as it would if Android itself called the constructor. If you interact with the Fragment before that, be careful, as its #Inject-annotated fields will not be populated yet. If you need to interact with the Fragment in ways that use Dagger-provided instances, it may be best to create a Bundle that the Fragment can use to initialize itself in onCreate.
1: Behind the scenes, dagger.android creates a code-generated subcomponent instance, probably called DotsModule_BindDotsFragment.DotsFragmentSubcomponent, and registers that subcomponent as an AndroidInjector.Factory<DotsFragment> in a Map. The generated subcomponent receives all the modules you set on the #ContributesAndroidInjector annotation as well as all the scope annotations you set on the abstract fun. In onAttach (yours or DaggerFragment's), the call to AndroidSupportInjection.inject(this) reads from that Map, finds the Injector/Subcomponent, creates an instance, and uses that to inject the Fragment with its own set of #FragmentScope instances.
2 The #Inject-annotated methods will be called too. That's method injection, compared to field injection; the general term is members injection for all of the post-constructor initialization Dagger can do.

Get directly an instance from Hilt

I inject MyObject in FirstFragment by Hilt, This fragment replaces with another fragment, and as well as you know, when one fragment is replaced with another, onDestroyView() called and when you press back, the view will be created again.
I need to create a new instance of MyObject whenever the fragment's view is created. in Koin I could get directly an instance inside of my code with get<MyObject>() but in Hilt, I couldn't find anything and I have to inject it as a constructor or property.
I also tried to use ViewWithFragmentComponent to change my scope, but I get an error when I use this instead of ApplicationComponent
How can I have a new instance of MyObject whenever my view created in my fragment?
#AndroidEntryPoint
class FirstFragment : BaseFragment(){
#Inject lateinit var myObject: MyObject
}
#InstallIn(ViewWithFragmentComponent::class)
#Module
object MyModule{
#Provides
internal fun provideMyObject(): MyObject {
...
}
}
As far as I know, whenever you inject something into a fragment and this fragment gets destroyed, the injected object gets destroyed as well. So in your example, when onDestroyView() is called myObject will be garbage collected. Clicking the back button then will trigger onCreateView() and dagger-hilt will automatically provide a new instance of myObject.
If you always want the same object, you have to annotate it with #Singleton. Optional, you can also use #FragmentScoped, where the documentation says:
Scope annotation for bindings that should exist for the life of a fragment.
But to my mind, this is not needed as I personally found at that an object provided by dagger-hilt gets recreated when the fragments gets recreated. Using #FragmentScope made my life harder as it introduced some other bugs.
You can look here to learn more about scoping and the functionality of dagger-hilt
To get instance from hilt directly you should use EntryPoint annotation:
example code can be found here:
https://developer.android.com/codelabs/android-hilt#10
Steps are as follows:
Declare in your non-Hilt aware class an interface implementing a method returning interface of instance you want to retrieve from Hilt.
Annotate this interface with
#InstallIn(SingletonComponent::class)
#EntryPoint
assuming your instance is a singleton, so it would look as follows:
class MyAnyKindOfClass {
#InstallIn(SingletonComponent::class)
#EntryPoint
interface MyObjectEntryPoint{
fun getMyObject(): MyObject;
}
fun getMyObject(): MyObject {
val hiltEntryPoint = EntryPointAccessors.fromApplication(
appContext, // You need to provid ApplicationContext
MyObjectEntryPoint::class.java)
return hiltEntryPoint.getMyObject()
}
}

How to test a fragment that depends on a dagger subcomponent?

Note: I am not using Dagger-Android, just Dagger 2.
When I started writing my app I was injecting the fragment through the AppComponent so my integration test worked.
Then I created a subcomponent called MainComponent which lives in MainActivity. In my fragment I was getting the subcomponent using
((MainActivity) getActivity()).mainComponent.inject(this);
Then in my integration tests I would do
FragmentScenario.launchInContainer(RecipesListFragment.class, null, R.style.AppTheme, null)
which throws an error
EmptyFragmentActivity cannot be cast to ...MainActivity
as FragmentScenario launches the fragment in an EmptyFragmentActivity.
I thought that in order to fix this I could remove the dependency on MainActivity to get the component so I used a FragmentFactory and passed in MainComponent as a parameter. But now the test fails because when I create the FragmentScenario I do not have the MainComponent to pass it in the factory.
So is there a way to launch the scenario and still use the MainComponent subcomponent?
Posting what I did in the end to answer Henrique's question. I read a few posts about it but I can't find them now since it was some time ago.
There was no clear answer so I ended up using the dependency on MainActivity to inject since using the FragmentFactory proved too much hassle without many gains.
In the AndroidTest folder I created an empty MainTestActivity that extends MainActivity.
Then in the FragmentTest
ActivityScenario<MainTestActivity> scenario;
#Before
public void setUp() {
scenario = ActivityScenario.launch(MainTestActivity.class).onActivity(activity -> {
MainFragment fragment = new MainFragment();
activity.startActivityFromFragment(fragment, new Intent(activity, MainTestActivity.class), 0);
});
}

How to scope dagger dependencies with fragments when using Fragment Factory?

Without using FragmentFactory, scoping dependencies within Fragment is straigth forward. Just create a Subcomponent for your fragment and just create the Fragment's Subcomponent in onAttach().
But when using FragmentFactory you no longer inject dependencies via the subcomponent but instead pass then in Fragment's constructor.
I was wondering if I could still declare a dependency which should only last within the fragment's lifecycle using Dagger. I currently could not think of a way to achieve this.
So instead of binding my dependencies to a certain scope, I just declare the dependency with any scope or just use #Reusable on them.
Also, since fragments are created through FragmentFactory, the created Fragments doesn't exist in the DI graph.
How can we properly scope dependencies to fragment and also be able to add the fragment in the DI graph when using FragmentFactory?
You can accomplish this by making a Subcomponent responsible for creating your Fragment. The Fragment should have the same scope as this Subcomponent.
#Subcomponent(modules = [/* ... */])
#FragmentScope
interface FooSubcomponent {
fun fooFragment(): FooFragment
#Subcomponent.Factory
interface Factory {
fun create(): FooSubcomponent
}
}
After taking care of any cyclic dependency issues, this Subcomponent acts the same as if you had explicitly created a subcomponent in onAttach() using #BindsInstance, except that you can (and must) now use constructor injection on FooFragment.
In order to provide FooFragment in your main component (or parent subcomponent), you will need to install this Subcomponent into a module.
#Module(subcomponents = [FooSubcomponent::class])
object MyModule {
#Provides
#IntoMap
#FragmentKey(FooFragment::class)
fun provideFooFragment(factory: FooSubcomponent.Factory): Fragment {
return factory.create().fooFragment()
}
}
Some caveats:
The scenario you describe (FooFragment depends on some class which depends on FooFragment) is the definition of a cyclic dependency. You will need to inject a Lazy or Provider at some point in this cycle, or Dagger will throw an error at compile time.
If you split this up into two steps, first providing FooFragment and then binding it into your map, the subcomponent will see the #Provides method from its parent component. This is likely to cause a StackOverflowError if you aren't careful.

Is Dagger component bound to the object that instantiate it?

I have been using Dagger 2 in my project. I understand that the lifetime of scoped object is the same as the lifetime of component (with the same scope). What about the lifetime of component then?
For example, I have a component:
#MyApp
#Component(modules = {
ApplicationModule.class})
public interface ApplicationComponent {
// Injection methods
void inject(MyApplication mainApplication);
}
I build component by:
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
buildApplicationComponent();
mApplicationComponent.inject(this);
}
private void buildApplicationComponent() {
mApplicationComponent = DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule(this))
.build();
}
}
Currently, the code to build component is in Application class. But
is it so that if I build component in Fragment the ApplicationComponent would have the same lifetime as the fragment & if I execute it in Application class the component would have the same lifetime as the whole application? Or how the lifetime of component is defined?
It is plain Java. You have object no matter where. The object provides you dependencies. If you use the same instance you will have the same dependencies or basically the same "Scope/lifetime". If you create somewhere new object it means new other object\dependencies it can provide so "another Scope/lifetime". If you share the object between different objects (You create it in the Application class, but reuse it in another fragment) you are in the "same Scope/lifetime".
But in general I see only "old way" of using Dagger 2 here. This is how your classes should end up everywhere. No need to find reference to any Components and etc or try to instantiate them on your own or clear them on your own in Fragments or Actvities. You have some code for Dagger 2 in one place and then some Annotations in you classes "which do the real work of your project". No need for complicated "connection" between the two parts and I see here a lot of cases like this which are part of tutorials that date back to 2016-2017...
class Repostory #Inject constructor(
private val dependency1: Dependency1
) {}
class Activity or Fragment {
#Inject lateinit var dependency2: Dependency2
}
This is a great example for right usage of Dagger2.
https://github.com/google/iosched
Here is an article about the app:
https://medium.com/#JoseAlcerreca
Jose Alcerreca is one of the Google Lead Devs responsible for creating the guidelines to write Android apps.
#Module
abstract class ActivityBindingModule {
#ActivityScoped
#ContributesAndroidInjector(modules = [MainActivityModule::class])
internal abstract fun mainActivity(): MainActivity
}
Just try to compile something like this. There is the exaxt same class in the app I gave you. Check the generated code and also the docs. If you see ContributesAndroidInjector there is explained:
Generates an {#link AndroidInjector} for the return type of this method. The injector is
implemented with a {#link dagger.Subcomponent} and will be a child of the {#link dagger.Module}'s component.
If you check the AndroidInjector docs you will see that it is the one that is injectin the Activity. And there it will be the implementation:
#Subcomponent(modules = {MainActivityModule.class})
#ActivityScoped
public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
#Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {}
}
So in general these annotations do the magic for you when a new activity starts. They create the Subcomponent, they use it and when the Activity Lifecycle ends the Subcomponent is also dead. Dagger clears the reference. Dagger knows how Activities work or Fragments and etc. There was the old way when you craete the component in the Activity, use it, call inject on your own. Or put some component in the AppClass and then clear it on your onw. But now there is a bigger amount of annotations.

Categories

Resources