I have a Fragment Pager Adapter
with 5 fragments with a RecyclerView
, all the same but the last one, which has a button to alter the contents, when I press this button it opens a Dialog Fragment
that will replace all the contents of the RecyclerView
.
My problem comes only when I send the app to background while the Dialog Fragment
is open, when I resume the app and submit the changes, the app crashes showing that the RecyclerView
is null.
How can I recover the full state of the Fragment after resuming.
By Logging the Life cycle I can see that after resume OnCreate, OnCreateView & OnActivityCreated
are run, but my method setUpCustomPatternsRecyclerView
reports that RecyclerView
is null.
Below is the code for my Fragment(Very simple)
IMPORTANT: The App crashes only when I try it in my phone, in 4 different emulators, the App DO NOT crashes.
Is it the the UI is not fully drawn in my phone?
If so how can I get notified that it is fully drawn?
Fragment:
public static FragmentCustomPatterns newInstance(ArrayList<PatternArrays> patternArrays, String fragmentTitle, int patternSelectedMode) {
FragmentCustomPatterns patterns = new FragmentCustomPatterns();
Bundle args = new Bundle();
args.putParcelableArrayList(CUSTOM_PATTERNS, patternArrays);
args.putInt(PATTERN_SELECTED_MODE, patternSelectedMode);
args.putString(TITLE, fragmentTitle);
patterns.setArguments(args);
return patterns;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
patternSelectedMode = getArguments().getInt(PATTERN_SELECTED_MODE);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
openCustomCoordinatesFragment = (OpenCustomCoordinatesFragment) getActivity();
View view = inflater.inflate(R.layout.fragment_custom_patterns, container, false);
FloatingActionButton floatingActionButton = view.findViewById(R.id.fab_custom_patterns);
floatingActionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openCustomCoordinatesFragment.openCoordinatesDialog();
}
});
recyclerFlag = false;
return view;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
activityPatternSelector = (ActivityPatternSelector) getActivity();
setUpCustomPatternsRecyclerView();
}
public void setUpCustomPatternsRecyclerView() {
recyclerPatternsView = getView().findViewById(R.id.custom_pattern_recycler_view);
adapter = new PatternCoordinatesAdapter(getActivity(),activityPatternSelector.getCustomPatternArrays(), patternSelectedMode);
recyclerPatternsView.setAdapter(adapter);
Utilities utilities = new Utilities(getActivity(), R.layout.item_coordinates_pattern_cardview);
GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(), utilities.calculateNoOfColumns());
recyclerPatternsView.setLayoutManager(gridLayoutManager);
recyclerPatternsView.setItemAnimator(new DefaultItemAnimator());
if (!recyclerFlag) {
recyclerPatternsView.addItemDecoration(new GridSpacing(utilities.calculateSpacing()));
recyclerFlag = true;
}
}
public interface OpenCustomCoordinatesFragment {
void openCoordinatesDialog();
}
Emulator(API22 & API26) logcat of LifeCycle recreating the error:
E/TESTING ACT: onPause:
D/TESTING FRAG: onPause:
E/TESTING ACT: onSaveInstanceState:
D/TESTING FRAG: onSaveInstanceState:
D/TESTING FRAG: onResume:
Phone Logcat(API25) logcat of LifeCycle recreating the error:
E/TESTING ACT: onPause:
D/TESTING FRAG: onPause:
E/TESTING ACT: onSaveInstanceState:
D/TESTING FRAG: onSaveInstanceState:
E/TESTING ACT: onCreate:
D/TESTING FRAG: onCreate:
E/TESTING ACT: onCreate: Array Size 25
D/TESTING FRAG: onCreateView:
D/TESTING FRAG: onActivityCreated:
D/TESTING FRAG: onActivityCreated: Array Size 25
D/TESTING FRAG: onResume:
Both are totally different I'll work my way on the phone logcat to create a Bundle in the OnSaveInstanceState
. Note that on the emulator OnResume
is called after OnSavedInstanceState
while on the phone after OnActivityCreated
and none of the other method are called.
Can anybody explain this lifecycle difference between physical devices and emulator?
UPDATE:
I can't find a fix for this. Running in the activity hosting the FragmentPagerAdapter
which also launches the DialogFragment
:
final FragmentCustomPatterns myTempFrag = (FragmentCustomPatterns) patternsPagerAdapter.getItem(4);
System.out.println("TESTING: Added " + myTempFrag.isAdded() + " Visible " + myTempFrag.isVisible() + " In Layout " + myTempFrag.isInLayout() + " Resumed " + myTempFrag.isResumed() + " Count " + patternsPagerAdapter.getCount());
In normal conditions returns:
TESTING: 'Added' true 'Visible' true 'In Layout' false 'Resumed' true Count 5
After sending the app to background with the Dialog Fragment open, and back to the App again, returns:
TESTING: 'Added' false 'Visible' false 'In Layout' false 'Resumed' false Count 5
I assume that this is the reason that I get:
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.support.v7.widget.RecyclerView.swapAdapter(android.support.v7.widget.RecyclerView$Adapter, boolean)' on a null object reference
Does this means that the fragment is not there?, How do I interpret this last message?, How do I fix the 'Added' to true?
PARTIAL FIX:
I have a "Restart Activity" Method, and I'm using it to "fix" my problem, but newbie as I am I don't think that's the proper answer.
Set the RecyclerView
on onCreateView
, the restoring should go automatically.
About the lifecycle: The difference exists because your emulator didn't destroy your activity / fragments and your device did, probably because the emulator has more memory.
If you would have opened many more apps on the emulator before re-opening your own app, you would have had the same. Or if you, on the emulator, put the developer option "don't keep activities" on, that mimics this behaviour: a device destroying your activities and fragments right away because it wants to use the memory elsewhere.
So any device/emulator can take any of the two routs you see. You should support both.