Getting NullPointException when using Dagger2 and setRetainInstance(true); - android

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.

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

Related

When do views get injected when using the AndroidAnnotations library?

I am wondering when #ViewById-annotated views are injected in AndroidAnnotations. Basically, I want to know if it is safe to access one of these views during onResume? I assume they are injected during onCreate but would like confirmation.
Thank you.
The easiest way to figure out exactly when injection happens is to inspect the code that AndroidAnnotations generates. For your examples, I made a simple Activity and Fragment as below:
#EActivity(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
#ViewById(R.id.textView)
TextView textView;
#AfterViews
public void activityTestMethod() {
}
}
#EFragment(R.layout.fragment_main)
public class MainFragment extends Fragment {
#ViewById(R.id.imageView)
ImageView imageView;
#AfterViews
public void fragmentTestMethod() {
}
}
and then ran ./gradlew app:assembleDebug to force AndroidAnnotations to generate the corresponding classes MainActivity_ and MainFragment_. Let's look at MainActivity_ first (irrelevant code omitted):
public final class MainActivity_
extends MainActivity
implements HasViews, OnViewChangedListener
{
#Override
public void onCreate(Bundle savedInstanceState) {
OnViewChangedNotifier previousNotifier = OnViewChangedNotifier.replaceNotifier(onViewChangedNotifier_);
init_(savedInstanceState);
super.onCreate(savedInstanceState);
OnViewChangedNotifier.replaceNotifier(previousNotifier);
setContentView(R.layout.activity_main);
}
private void init_(Bundle savedInstanceState) {
OnViewChangedNotifier.registerOnViewChangedListener(this);
}
#Override
public void setContentView(int layoutResID) {
super.setContentView(layoutResID);
onViewChangedNotifier_.notifyViewChanged(this);
}
#Override
public void onViewChanged(HasViews hasViews) {
this.textView = hasViews.internalFindViewById(R.id.textView);
activityTestMethod();
}
}
The sequence of events that results in our views being bound and our #AfterViews methods being called is as follows:
In onCreate, the MainActivity_ instance is registered as an OnViewChangedNotifier.
onCreate calls setContentView.
setContentView calls notifyViewChanged, which triggers a (synchronous) call to onViewChanged.
onViewChanged binds all fields annotated with #ViewById, then calls all methods annotated with #AfterViews.
Therefore, #ViewById-annotated views are bound and available for use after onCreate has been called, and #AfterViews-annotated methods will be executed at the end of onCreate and before any other Activity lifecycle method.
The story is similar for MainFragment_:
public final class MainFragment_
extends com.stkent.aatest.MainFragment
implements HasViews, OnViewChangedListener
{
#Override
public void onCreate(Bundle savedInstanceState) {
OnViewChangedNotifier previousNotifier = OnViewChangedNotifier.replaceNotifier(onViewChangedNotifier_);
init_(savedInstanceState);
super.onCreate(savedInstanceState);
OnViewChangedNotifier.replaceNotifier(previousNotifier);
}
private void init_(Bundle savedInstanceState) {
OnViewChangedNotifier.registerOnViewChangedListener(this);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
onViewChangedNotifier_.notifyViewChanged(this);
}
#Override
public void onViewChanged(HasViews hasViews) {
this.imageView = hasViews.internalFindViewById(R.id.imageView);
fragmentTestMethod();
}
}
The sequence of events that results in our views being bound and our #AfterViews methods being called is as follows:
In onCreate, the MainFragment_ instance is registered as an OnViewChangedNotifier.
onViewCreated calls notifyViewChanged, which triggers a (synchronous) call to onViewChanged.
onViewChanged binds all fields annotated with #ViewById, then calls all methods annotated with #AfterViews.
Therefore, #ViewById-annotated views are bound and available for use after onViewCreated has been called, and #AfterViews-annotated methods will be executed at the end of onViewCreated and before any other Fragment lifecycle method.
In both our examples, all view binding is performed in a lifecycle method that occurs much earlier than onResume, so you are safe to access them there :)

Retrofit + RxJava - Not emitting when Fragment is restored, if I used Disposable.dispose()

I have a Fragment A that uses Retrofit to calls an API in onCreateView.
The result will be used to display a list of objects.
Fragment A also has a button which navigates to Fragment B, by commiting a FragmentTransaction and replace itself. (Fragment A onDestroyView triggered)
Everything works well, but when I back from Fragment B to Fragment A(Fragment A onCreateView triggered), API is not called.
This only happens if I call Disposable.dispose() in onDestroyView, which is an attempt to prevent memory leaks.
What I expect
Since new Observables will be created every time onCreateView is triggered, they are supposed to be indenpendent of the previously disposed Disposables. And therefore API will be called again and callbacks will be triggered. The list of object should be refreshed.
What I observed
onCreateView is triggered, the line which tells Retrofit to call API is also triggered. But none of the callbacks (including .map()) are triggered. Since I have also logged network traffics, I am pretty sure that no network communication has actually been made. It looks like either RxJava or Retrofit decided to stop the stream.
The Question
Of course this can be avoided if I do not use Disposable.dispose(). But I do want to prevent memory leaks.
What is the correct way to do this?
My Code (Simplified)
Fragment A:
public class BeansFragment extends BaseFragment {
...
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
...
retrofit.getSomeBeans()
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new APIResponseObserver<List<Beans>>(this) {
#Override
public void onNext(List<Beans> beans) {
if (getContext() == null) return;
setupBeansList(beans);
}
});
...
}
...
}
BaseFragment:
public abstract class BaseFragment extends Fragment {
private CompositeDisposable compositeDisposable = new CompositeDisposable();
...
#Override
public void onDestroyView() {
compositeDisposable.dispose();
super.onDestroyView();
}
}
Most Importantly, APIResponseObserver which does the job of adding the disposable:
public abstract class APIResponseObserver<T> implements io.reactivex.Observer<T> {
private BaseFragment fragment;
public APIResponseObserver(BaseFragment fragment) {
this.fragment = fragment;
}
#Override
public void onSubscribe(Disposable d) {
if (fragment != null) fragment.addToCompositeDisposable(d);
}
#Override
public void onError(Throwable e) {
//...
}
#Override
public void onComplete() {
//...
}
}
Note
I have also tried to use RxLifecycle, but produced the same result.
Just found out that I should have used Disposable.clear() instead of dispose(). dispose() made me no longer be able to add Disposable to the same CompositeDisposable.
An interesting fact is, RxLifecycle produces the same problem. Does it mean they use dispose() instead of clear()?
EDIT#2018-04-20
For RxLifecycle, you may refer to this Github issue.

Is having a managed static reference to a Fragment or Activity ok?

I was wondering if having a managed static reference to a Fragment or Activity is ok? By managed I mean releasing the static reference on the relevant lifecycle callback. Consider the following code please:
public class StaticReferencedFragment extends Fragment {
public static StaticReferencedFragment instance;
public StaticReferencedFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_static_referenced, container, false);
}
#Override
public void onStart() {
super.onStart();
instance = this;
}
#Override
public void onStop() {
super.onStop();
instance = null;
}
}
Do I run the risk of leaking the Fragment/Activity object?
Do I run the risk of leaking the Fragment/Activity object?
Yes. For example, an unhandled exception while your fragment is visible will bypass your lifecycle methods and cause you to fail to null out the static field.
Beyond that, it's unclear what this buys you:
An activity hosting this fragment can simply hold onto the fragment in a regular field
Other fragments in the activity should neither know nor care that this fragment exists (fragments should worry about themselves and their activity, not peer fragments)
Other components, like services, and other threads should neither know nor care that this entire activity exists, let alone this fragment (use an event bus or other loosely-coupled modes of communication)

Is it a good practice to perform certain initialization code in Fragment constructor

Sometimes, I have some headless fragments, which I need to run some initialization even before onCreate
For instance,
public class NetworkMonitorFragment extends Fragment {
public static NetworkMonitorFragment newInstance() {
return new NetworkMonitorFragment();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
public void init() {
// This function shall be call even before onCreate.
}
}
NetworkMonitorFragment networkMonitorFragment = NetworkMonitorFragment.newInstance();
networkMonitorFragment.init();
I was wondering, is it a good practice, to have certain initialization inside Fragment constructor? Is there any drawback for doing so? The reason I'm asking, because I don't see many code example for doing so.
public class NetworkMonitorFragment extends Fragment {
public static NetworkMonitorFragment newInstance() {
return new NetworkMonitorFragment();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
public NetworkMonitorFragment() {
init();
}
private void init() {
// This function shall be call even before onCreate.
}
}
NetworkMonitorFragment networkMonitorFragment = NetworkMonitorFragment.newInstance();
You certainly can, even other methods like Fragment.instantiate(Context context, String fname, Bundle args) calls newInstance() which calls default constructor. Although you must be aware of some things:
You should not do any stuff that is not independent of fragment's
state, lifecycle or Android's context
You should not do any stuff that takes up most of the 16ms UI
drawing window
You should not spawn new threads there
So while variable instantiation or some quick calculations based on external context, let's say, Date, for example, is perfectly fine, but decoding even a small bitmap either synchronously or asynchronously is a quick way to break things.
If execution and results of this function are not tied to fragment lifecycle and not dependent on parent activity then, I guess, it's just a matter of preference.
You can get more specific answers by describing what this function does.
If you want to do something before fragment created, do it in onAttach
(onAttach always calls before onCreate, look fragment lifecycle)
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
init();
...
}
If you want to sure view is created, do it in onViewCreated with same way.

Fragment's reference to mActivity becomes null after orientation change. Ineffective fragment state maintenance

My application consists of several fragments. Up until now I've had references to them stored in a custom Application object, but I am beginning to think that I'm doing something wrong.
My problems started when I realized that all my fragment's references to mActivity becomes null after an orientation change. So when I call getActivity() after an orientation change, a NullPointerException is thrown.
I have checked that my fragment's onAttach() is called before I make the call to getActivity(), but it still returns null.
The following is a stripped version of my MainActivity, which is the only activity in my application.
public class MainActivity extends BaseActivity implements OnItemClickListener,
OnBackStackChangedListener, OnSlidingMenuActionListener {
private ListView mSlidingMenuListView;
private SlidingMenu mSlidingMenu;
private boolean mMenuFragmentVisible;
private boolean mContentFragmentVisible;
private boolean mQuickAccessFragmentVisible;
private FragmentManager mManager;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/*
* Boolean variables indicating which of the 3 fragment slots are visible at a given time
*/
mMenuFragmentVisible = findViewById(R.id.menuFragment) != null;
mContentFragmentVisible = findViewById(R.id.contentFragment) != null;
mQuickAccessFragmentVisible = findViewById(R.id.quickAccessFragment) != null;
if(!savedInstanceState != null) {
if(!mMenuFragmentVisible && mContentFragmentVisible) {
setupSlidingMenu(true);
} else if(mMenuFragmentVisible && mContentFragmentVisible) {
setupSlidingMenu(false);
}
return;
}
mManager = getSupportFragmentManager();
mManager.addOnBackStackChangedListener(this);
final FragmentTransaction ft = mManager.beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if (!mMenuFragmentVisible && mContentFragmentVisible) {
/*
* Only the content fragment is visible, will enable sliding menu
*/
setupSlidingMenu(true);
onToggle();
ft.replace(R.id.contentFragment, getCustomApplication().getSportsFragment(), SportsFragment.TAG);
} else if (mMenuFragmentVisible && mContentFragmentVisible) {
setupSlidingMenu(false);
/*
* Both menu and content fragments are visible
*/
ft.replace(R.id.menuFragment, getCustomApplication().getMenuFragment(), MenuFragment.TAG);
ft.replace(R.id.contentFragment, getCustomApplication().getSportsFragment(), SportsFragment.TAG);
}
if (mQuickAccessFragmentVisible) {
/*
* The quick access fragment is visible
*/
ft.replace(R.id.quickAccessFragment, getCustomApplication().getQuickAccessFragment());
}
ft.commit();
}
private void setupSlidingMenu(boolean enable) {
/*
* if enable is true, enable sliding menu, if false
* disable it
*/
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// launch the fragment that was clicked from the menu
}
#Override
public void onBackPressed() {
// Will let the user press the back button when
// the sliding menu is open to display the content.
if (mSlidingMenu != null && mSlidingMenu.isMenuShowing()) {
onShowContent();
} else {
super.onBackPressed();
}
}
#Override
public void onBackStackChanged() {
/*
* Change selected position when the back stack changes
*/
if(mSlidingMenuListView != null) {
mSlidingMenuListView.setItemChecked(getCustomApplication().getSelectedPosition(), true);
}
}
#Override
public void onToggle() {
if (mSlidingMenu != null) {
mSlidingMenu.toggle();
}
}
#Override
public void onShowContent() {
if (mSlidingMenu != null) {
mSlidingMenu.showContent();
}
}
}
The following is a stripped version of the CustomApplication. My thoughts behind this implementation was to guarantee only one instance of each fragment throughout my application's life cycle.
public class CustomApplication extends Application {
private Fragment mSsportsFragment;
private Fragment mCarsFragment;
private Fragment mMusicFragment;
private Fragment mMoviesFragment;
public Fragment getSportsFragment() {
if(mSsportsFragment == null) {
mSsportsFragment = new SportsFragment();
}
return mSsportsFragment;
}
public Fragment getCarsFragment() {
if(mCarsFragment == null) {
mCarsFragment = new CarsFragment();
}
return mCarsFragment;
}
public Fragment getMusicFragment() {
if(mMusicFragment == null) {
mMusicFragment = new MusicFragment();
}
return mMusicFragment;
}
public Fragment getMoviesFragment() {
if(mMoviesFragment == null) {
mMoviesFragment = new MoviesFragment();
}
return mMoviesFragment;
}
}
I am very interested in tips on how to best implement multiple fragments and how to maintain their states. For your information, my applicaion consists of 15+ fragments so far.
I have done some research and it seems that FragmentManager.findFragmentByTag() is a good bet, but I haven't been able to successfully implement it.
My implementation seems to work good except for the fact that mActivity references become null after orientation changes, which lets me to believe that I may have some memory leak issues as well.
If you need to see more code, please let me know. I purposely avoided including fragment code as I strongly believe issues are related to my Activity and Application implementations, but I may be wrong.
Thanks for your time.
My thoughts behind this implementation was to guarantee only one instance of each fragment throughout my application's life cycle
This is probably part, if not all, of the source of your difficulty.
On a configuration change, Android will re-create your fragments by using the public zero-argument constructor to create a new instance. Hence, your global-scope fragments will not "guarantee only one instance of each fragment".
Please delete this custom Application class. Please allow the fragments to be re-created naturally, or if they need to live for the life of a single activity, use setRetainInstance(true). Do not attempt to reuse fragments across activities.
I don't see where are you using the reference to mActivity. But don't hold a reference to it. Always use getActivity since the Activity can be recreated after orientation change. Also, don't ever set the fragment's fields by setters or by assigning always use a Bundle and Arguments
Best practice for instantiating a new Android Fragment
Also you can use setRetainInstance(true) to keep all the fragment's members during orientation change.
Understanding Fragment's setRetainInstance(boolean)
To resolve this problem you have to use the activity object provided by onAttach method of fragment so when you change the orientation fragment is recreated so onAttach give you the current reference
you can use onAttach(Context context) to create a private context variable in fragment like this
#Override
public void onAttach(Context context) {
this.context = context;
super.onAttach(context);
}
on changing orientation, onAttach gives you new reference to the context, if you want reference to activity, you can typecast context to activity.
Context can also be reassigned inside onCreate in fragments as OnCreate is called when device is rotated
private Context mContext;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//get new activity reference here
mContext = getActivity();
}
pass this mContext throughout the fragment
If you don't setRetainInstance(true) in onCreate ... the collection e.g List<Object>, Vector<Object> in Application class will get null. Make sure you setRetainInstance(true) to make them alive.

Categories

Resources