Reputation: 15808
I have objects of classes F1
and F2
that I want to inject in a retained Fragment. I also have an object of class A
that depends on Activity, and I want it to be injected in that Activity and in a retained Fragment attached to that Activity's Fragment Manager. I write the following code. First, the module for the Activity dependency:
@Module
public class MainActivityModule {
private Activity mActivity;
public MainActivityModule(Activity activity) {
mActivity = activity;
}
@Provides
@ActivityScope
public A provideA() {
return new A(mActivity);
}
}
Then, the corresponding component, that must make the A
object available to its dependent components:
@ActivityScope
@Component(modules = {MainActivityModule.class})
public interface MainActivityComponent {
void inject(MainActivity activity);
// make the A object available to dependent components
A getA();
}
I also write the Fragment-related module:
@Module
public class FragmentModule {
@Provides
@FragmentScope
public F1 provideF1() {
return new F1();
}
@Provides
@FragmentScope
public F2 provideF2() {
return new F2();
}
}
and its corresponding component:
@FragmentScope
@Component(modules = {FragmentModule.class}, dependencies = {MainActivityComponent.class})
public interface FragmentComponent {
void inject(MyFragment presenter);
}
Finally, I inject the dependency on A
in the Activity, where I also need to call specific life cycle methods on it. The Activity also provides a method to get the component so that the Fragment is able to use it when building its own component:
// in MainActivity.onCreate
mActivityComponent = DaggerMainActivityComponent.builder()
.mainActivityModule(new MainActivityModule(this))
.build();
mActivityComponent.inject(this);
mA.onCreate();
and I try to inject the dependencies on A
, F1
, F2
in the Fragment, too:
// in MyFragment.onCreate
FragmentComponent component = DaggerFragmentComponent.builder()
.fragmentModule(new FragmentModule())
.mainActivityComponent(((MainActivity) getActivity()).getComponent())
.build();
component.inject(this);
However, since the Fragment is retained, when the Activity is destroyed and recreated by the system reacting to a configuration change (e.g. a device rotation), the Fragment maintains a reference to the old A
instance, while the new Activity has correctly recreated a new A
instance to go with it. To work around this problem, I have to create the FragmentComponent
and inject dependencies in MyFragment.onActivityCreated
rather than MyFragment.onCreate
. On the other hand, this implies that F1
and F2
dependencies are recreated every time the activity is destroyed and recreated; but they are Fragment-scoped dependencies, so they should follow the Fragment life cycle instead of the Activity's.
Therefore, my question is as follows: is it possible to have differently-scoped dependencies injected in a retained Fragment? Ideally, F1
and F2
dependencies should be injected in MyFragment.onCreate
, while A
dependency should be injected in MyFragment.onActivityCreated
. I tried using two different components, but it seems not to be possible to perform partial injection. Currently, I ended up adding an explicit reassignment of the Fragment A
dependency in MyFragment.onActivityCreated
, but that's not really injection, you know. Could this be done in a better way?
Upvotes: 16
Views: 15339
Reputation: 81539
Considering your retained fragment lives longer than your activity, I'd wager that the proper way to do this would be to make the FragmentScope
contain the ActivityScope
, and not vice versa.
Meaning your FragmentComponent would have
@FragmentScope
@Component(modules = {FragmentModule.class})
public interface FragmentComponent {
void inject(MyFragment presenter);
}
And your Activity component would have
@ActivityScope
@Component(dependencies = {FragmentComponent.class}, modules = {MainActivityModule.class})
public interface MainActivityComponent extends FragmentComponent { //provision methods
void inject(MainActivity activity);
// make the A object available to dependent components
A getA();
}
Which is possible if your Fragment
injected classes don't rely on the Activity module as dependencies.
This can be done with something akin to
public class MainActivity extends AppCompatActivity {
private MainActivityComponent mainActivityComponent;
private MyFragment myFragment;
@Override
public void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_main);
if(saveInstanceState == null) { // first run
myFragment = new MyFragment(); //headless retained fragment
getSupportFragmentManager()
.beginTransaction()
.add(myFragment, MyFragment.class.getName()) //TAG
.commit();
} else {
myFragment = (MyFragment)(getSupportFragmentManager()
.findFragmentByTag(MyFragment.class.getName()));
}
}
@Override
public void onPostCreate() {
mainActivityComponent = DaggerMainActivityComponent.builder()
.fragmentComponent(myFragment.getComponent())
.build();
}
}
And
public class MyFragment extends Fragment {
public MyFragment() {
this.setRetainInstance(true);
}
private FragmentComponent fragmentComponent;
@Override
public void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
this.fragmentComponent = DaggerFragmentComponent.create();
}
public FragmentComponent getFragmentComponent() {
return fragmentComponent;
}
}
EDIT:
public class MyFragment extends Fragment {
public MyFragment() {
this.setRetainInstance(true);
this.fragmentComponent = DaggerFragmentComponent.create();
}
private FragmentComponent fragmentComponent;
public FragmentComponent getFragmentComponent() {
return fragmentComponent;
}
}
public class MainActivity extends AppCompatActivity {
private MainActivityComponent mainActivityComponent;
private MyFragment myFragment;
@Inject
A mA;
@Override
public void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_main);
if(saveInstanceState == null) { // first run
myFragment = new MyFragment(); //headless retained fragment
getSupportFragmentManager()
.beginTransaction()
.add(myFragment, MyFragment.class.getName()) //TAG
.commit();
} else {
myFragment = (MyFragment)(getSupportFragmentManager().findFragmentByTag(MyFragment.class.getName()));
}
mainActivityComponent = DaggerMainActivityComponent.builder()
.fragmentComponent(myFragment.getComponent())
.build();
mainActivityComponent.inject(this);
mA.onCreate();
}
}
Upvotes: 8