Issue
So I was following Clean Architecture to design my application. I have an Activity
with a view pager which has two Fragment
in it. Im injecting the PagerAdapter
for this through Dagger.
I understand that calling setRetainInstance(true)
on a fragment prevents it from getting destroyed, and that getActivity()
on such fragment may return a problem if the Activity is destroyed. I'm getting a NullPointException when trying to resume my activity after it has been on background and the activity has been (presumably) destroyed.
So my question is
- Is there a better way to accomplish what I'm trying to do?
- Any resource someone can point me to?
- Also uncertain why the Fragment and the Adapter is still active if the fragment has been destroyed. I get no memory leaks with LeakCanary.
My activity has an Dagger Component MainActivityComponent which is injected as follows. And also extends HasComponent. For more info on this refer to HasComponent
MainActivity.java
DaggerMainActivityComponent.builder()
.applicationComponent(getApplicationComponent())
.activityModule(getActivityModule())
// Module for each fragment
.conversationListModule(new ConversationListModule(this))
.friendsListModule(new FriendsListModule(this))
.build()
.inject(this);
Getting the Activity's component
// Cause of the NullPointException getActivity()
protected <C> C getComponent(Class<C> componentType) {
return componentType.cast(((HasComponent<C>) getActivity()).getComponent());
}
Let me know if you guys have any confusion. I know my explanation is a mess. Thanks
Update
Seems even if I remove setRetainInstance(true)
this error isn't prevented.
Solution
Problem
The problem occured when the application stays in the background for a while and a recently displayed activity is recycled by Android. Bringing back the application causes NullPointerException (NPE) as the activity’s component is called by the fragment before it is initialized (both fragment and activity are recreated at the same time).
Solution
Introducing two lifecycle methods in BaseFragment class.
- onInjectView() — called to do an optional injection on onCreate(Bundle) and if an exception is thrown or false returned, it is called on onActivityCreated(Bundle) again. Within this method you can get the injection component and inject the view. Retrun true if the injection was succesfull, then it will not be called again.
- Based on returned value, the second method is called. The method is named onViewInjected(Bundle), as it is called only when the fragment has been injected and injected fields can be initialized.
Base Fragment
public abstract class BaseFragment extends Fragment {
private boolean mIsInjected = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
try {
mIsInjected = onInjectView();
} catch (IllegalStateException e) {
Log.e(e.getClass().getSimpleName(), e.getMessage());
mIsInjected = false;
}
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (mIsInjected) onViewInjected(savedInstanceState);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (!mIsInjected) {
mIsInjected = onInjectView();
if (mIsInjected) onViewInjected(savedInstanceState);
}
}
@SuppressWarnings("unchecked")
protected <C> C getComponent(Class<C> componentType) throws IllegalStateException {
C component = componentType.cast(((HasComponent<C>) getActivity()).getComponent());
if (component == null) {
throw new IllegalStateException(componentType.getSimpleName() + " has not been initialized yet.");
}
return component;
}
protected boolean onInjectView() throws IllegalStateException {
// Return false by default.
return false;
}
@CallSuper
protected void onViewInjected(Bundle savedInstanceState) {
// Intentionally left empty.
}
}
Usage
public class SampleFragment extends BaseFragment implements SampleView {
@Inject
SamplePresenter mSamplePresenter;
@Override
protected boolean onInjectView() throws IllegalStateException {
getComponent(SampleComponent.class).inject(this);
return true;
}
@Override
protected void onViewInjected(Bundle savedInstanceState) {
super.onViewInjected(savedInstanceState);
this.mSamplePresenter.setView(this);
}
}
More info can be found on Efficient and bug-free fragment injection in Android MVP applications
Answered By - Ritesh Shakya
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.