Reputation: 4249
I have a MainActivity which requires a MeasurementFragment which in turn requires a Converter.
The problem is: when MesurementFragment is injected at [1]
, its own dependency converter
[2]
is left un-injected.
class MainActivity extends AppCompatActivity {
@Inject MeasurementFragment measureFrag;
@Override
protected void OnCreate(Bundle b){
//[1]*
DaggerMainActivityComponent().create().inject(this);
}
}
Is there a way to have the injection "cascade" so that converter
is injected when measureFrag
is being injected?
I could create a second instance of a DaggerMainActivityComponent in the constructor ([3]
) and inject there, but that sounds like a bad idea.
class MeasurementFragment extends Fragment {
// [2]*
@Inject Converter converter;
// required *empty* constructor
MeasrementFragment(){
// [3]*
}
}
Other potentially relevant code (The component and module):
@Component
interface MainActivityComponent(){
void inject(MainActivity ma);
void inject(MeasurementFragment mf); // << not used :(
}
@Module
class MainActivityModule{
@Provides MeasurementFragment getMF(){ return new MeasurementFragment(); }
@Provides Converter getConverter(){ return new Converter(); }
}
Is there a better design to achieve this?
Upvotes: 0
Views: 565
Reputation: 21497
Sometimes when we are learning a dependency injection framework like Dagger 2, the misconception can arise that the new
keyword and static factories are to be avoided at all costs and that every single object should be injected.
In the case of Fragments, the best practice for instantiation is to use static factory methods. Even though a previous DI framework, Roboguice, encouraged injection of Fragments as properties of an Activity (@Inject MeasurementFragment mf
), with Dagger 2 it may be better to try a different approach. The reason for this is that we need to co-ordinate with the FragmentManager
for the Activity:
For a given Activity, Fragments are handled by a FragmentManager
. When the Activity onSaveInstanceState(Bundle outBundle)
is called (for instance, in conditions of low memory) the FragmentManager
will save the instance state of each Fragment for restoration inside onCreate(Bundle savedInstanceState);
.
This is why you often see in the Android examples:
if (savedInstanceState == null) {
// During initial setup, plug in the details fragment.
DetailsFragment details = new DetailsFragment();
details.setArguments(getIntent().getExtras());
getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
}
The check for saveInstanceState == null
is in case the Fragment is already present in the FragmentManager. In the case of restoring the Activity from a saved instance state you can use:
fragmentManager.findFragmentByTag(String tag)
to get a handle on it. This means that simple property injection of a Fragment inside an Activity using Dagger 2 is not enough to account for the complex lifecycles of Fragments and Activities.
A better approach maybe to abandon the property injection of your Fragment inside your Activity and use static factories as recommended in the Developer's Guide. You would then inject the dependencies for your Fragment inside the Fragment itself. Something like this:
class MeasurementFragment extends Fragment {
@Inject Converter converter;
static MeasurementFragment instantiate() {
//if MeasurementFragment comes to require params in the future
//you can pass them in here and use setArguments(Bundle params);
return new MeasurementFragment(null);
}
MeasurementFragment(){
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
DaggerMainActivityComponent.builder()
.build()
.inject(this);
}
}
Now when you use fragmentManager.beginTransaction().add()
on the MeasurementFragment
inside your Activity, injection will be performed on the commit when the Android system has invoked the lifecycle callbacks through to onActivityCreated(Bundle bundle)
.
Here is what your Activity might look like with this approach:
class MainActivity extends AppCompatActivity {
public static final String MEASUREMENT_FRAG = "MEASUREMENT_FRAG";
MeasurementFragment measureFrag;
@Override
protected void OnCreate(Bundle b){
super(b);
injectMembers();
if (b == null) {
measureFrag = MeasurementFragment.instantiate();
getFragmentManager().beginTransaction().add(R.id.frag_container, measureFrag, MEASUREMENT_FRAG).commit();
}
else {
//frag is already instantiated and added to container
measureFrag = getFragmentManager().findFragmentByTag(MEASUREMENT_FRAG);
}
}
@VisibleForTesting
void injectMembers() {
DaggerMainActivityComponent().create().inject(this);
}
}
What I have outlined here is the approach to injecting members inside Fragments that is demonstrated in this popular Dagger 2 example on Github. A step further is to make the Fragments as lightweight as possible and to totally eschew injecting dependencies inside them, as illustrated in the Google Android Architecture Blueprint for Dagger 2.
Upvotes: 1