Get directly an instance from Hilt - android

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()
}
}

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.

Android - Dagger inject Shared ViewModel to Fragment

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.

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.

one object if injected into 2 subcomponents under same custom scope, every time new instance is created of that object

one object if injected into 2 subcomponents under same custom scope, every time new instance is created of that object. I want same instance to be passed to all subcomponents
this is the module
#CustomScope
#Module
public class EventBusModule {
PublishSubject<Boolean> bus = PublishSubject.create();
#CustomScope
#Provides
public PublishSubject<Boolean> provideRxBus() {
return bus;
}
}
these are my subcomponents
#Module
public abstract class ActivityBindingModule {
#CustomScope
#ContributesAndroidInjector(modules = {HomeActivityModule.class,
EwayBillFragmentProvider.class, EventBusModule.class})
abstract HomeActivity mainActivity();
#CustomScope
#ContributesAndroidInjector(modules =
{EwayBillDetailActivityModule.class, EventBusModule.class})
abstract EwayBillDetailActivity ewayBillDetailActivity();
}
these subcomponents are written inside ActivityBindingModule which is added to my application component. Now I want same instance of my PublishSubject object in both the subcomponents, I am fairly new to dagger and I want to know what am I doing wrong?
You'll need to move your bus into Application scope, which typically means annotating it with #Singleton (if that's how you've annotated your top-level component that ActivityBindingModule is installed into). You'll also need to move your method into a Module installed on that component, which might as well be ActivityBindingModule.
#Module
public abstract class ActivityBindingModule {
#Singleton
#Provides
public PublishSubject<Boolean> provideRxBus() {
// Dagger stores the instance in your Application component, so you don't have to.
return PublishSubject.create();
}
/* ... your #ContributesAndroidInjector Activity bindings remain here ... */
}
First, an explanation of what you see: #ContributesAndroidInjector creates a subcomponent for each object it annotates, marked with the scope annotations and modules you put on the #ContributesAndroidInjector method and annotation, so that your call to AndroidInjection.inject(this) in onCreate creates a new instance of that subcomponent and uses it to inject the Activity instance.
Your #CustomScope (which may be better-named as #ActivityScope here) on the #Provides PublishSubject<Boolean> method means that your instance will share the same lifecycle as the component that is also annotated with that scope annotation. Here, that's each automatically-generated subcomponent. Furthermore, because your Module is a non-abstract class with public no-arg constructor, Dagger will automatically create a new instance every time it creates a Component that requires your module, which means a different bus for each Activity instance. (It can't and won't do so for Modules that are abstract classes or interfaces.)
You want your bus object to be the same instance between Activities, which means that #CustomScope/#ActivityScope is much too short: You want the object to outlast any single Activity's lifecycle. This means that you'll either need to store the instance elsewhere and pass it into each Activity, or you'll need to store the instance in your Application component itself. I'd recommend the latter, because this is one of the problems Dagger was created to solve, and because this will automatically make the bus available across your application: Dagger subcomponents inherit access to all of the bindings in their parent components. That gives the code you see above. (Note that by doing this, you'll keep the instance of PublishSubject around even when there is no Activity showing, when your application is running in the background; if you want the same instance between Activities, this is a necessary consequence, but choose this carefully to avoid too much background memory use.)
One alternative is that you keep track of the bus instance yourself, and insert it into each Activity. You could do this by having your Module take a parameter, but that is rather tricky to do with dagger.android (which powers #ContributesAndroidInjector). You could also write a #Provides method that delegates to a WeakReference, or use the #Singleton technique above to write a holder that temporarily stores your bus between Activities. However, because Android keeps a lot of control over your transitions between Activities and the Activity lifecycle, it may be the best you can do to keep the bus in #Singleton scope as I did in the code above.

Dagger2/Android: My #FragmentScoped subcomponents are being recreated with each rotation

Originally posted as an issue on the Dagger2 repo.
Summary: I have an activity with one fragment that has setRetainInstance(true). Despite the fragment being retained, every time I call AndroidSupportInjection.inject(this) on it, it injects new instances of its dependencies. It looks like the fragment subcomponent is being recreated (I think?) each time the activity subcomponent is recreated (on rotation).
Is this expected, or is my graph misconfigured?
I have an app component like so:
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
ActivitiesModule::class,
AndroidViewInjectionModule::class,
NetModule::class
])
interface MainApplicationComponent {
fun inject(app: MainApplication)
#Component.Builder
interface Builder {
fun build(): MainApplicationComponent
#BindsInstance fun app(app: Context): Builder
// ... other things ...
}
}
ActivitiesModule looks like:
#Module
abstract class ActivitiesModule {
// ... other things ...
#ActivityScoped
#ContributesAndroidInjector(modules = [
UpgradeActivityModule::class,
UpgradeFragmentModule::class
]) abstract fun upgradeActivity(): UpgradeActivity
}
UpgradeFragmentModule:
#Module
abstract class UpgradeFragmentModule {
#FragmentScoped
#ContributesAndroidInjector(modules = [
UpgradeActivity.UpgradeFragmentModule::class,
ViewInjectorModule::class
]) abstract fun upgradeFragment(): UpgradeFragment
}
And UpgradeActivity.UpgradeFragmentModule (this is all very much WIP, sorry for weird names):
#Module
abstract class UpgradeFragmentModule {
#Binds #FragmentScoped abstract fun bindUpgradeModel(model: UpgradeModel): UpgradeMvp.Model
#Binds #FragmentScoped abstract fun bindUpgradePresenter(presenter: UpgradePresenter): UpgradeMvp.Presenter
// ... other things ...
#Module
companion object {
#Provides #JvmStatic fun provideResources(activityProvider: Provider<UpgradeActivity>): Resources {
return activityProvider.get().resources
}
// ... other things ...
}
}
I experimented further and tried to make my #FragmentScoped elements direct descendants of my #Singleton app component, but it has the same issue. In fact, if I just inject my fragment twice in a row, I get new instances each time. Clearly I'm doing something wrong....
I strongly encourage you to simply have a look at the Dagger Android source code, since it's only a few classes that do all the work.
[...] every time I call AndroidSupportInjection.inject(this) on it, it injects new instances of its dependencies. It looks like the fragment subcomponent is being recreated [...]
That's exactly what's going on.
To give an inaccurate and simplified summary, you register your Subcomponent.Builders in a Map, and when you call AndroidInjection.inject() it will look up and create the right Builder and Component, with which it will then inject the object.
You should never inject objects multiple times as this will accomplish nothing in the best case, or lead to errors/bugs otherwise. Scopes are per component, so if you recreate the component, you recreate every object within its scope along with it. And calling AndroidInjection.inject() will always create a new component.
You don't go into detail about what you inject when and where, but if you keep the same fragment object around, you should not inject it again.
[...] and tried to make my #FragmentScoped elements direct descendants of my #Singleton app component, but it has the same issue.
That's what you should do. If you use setRetainInstance(true), then the fragment should most likely not be a Subcomponent of your UpgradeActivity, or it will leak the reference when the Activity gets recreated.
if I just inject my fragment twice in a row, I get new instances each time.
If you call AndroidInjection.inject() then it will create a new component with every call, so I assume that's what you did and observed. If you inject an object twice with the same component, then any scoped objects will be the same. Unscoped objects will always be created for every use. But in any case, you should never inject an object more than once.

Categories

Resources