Introduction
Huh, this is a tough one. At least, I think so... To clear things up:
I did not find any answers to my question after searching the internet (using Google).
Everything I found was about people setting up the onClickListener
's for their View
's wrong. I guess it is the same problem in my case, but none of the other problems matched mine.
This seems to be the same problem... It has no answers.
Setup
I've got three Fragment
's set up together with a ViewPager
in my AppCompatActivity
.
viewPager = (ViewPager) findViewById(R.id.main_view_pager);
viewPager.setAdapter(new SectionsPagerAdapter(getSupportFragmentManager()));
Handled by the SectionsPagerAdapter
.
public class SectionsPagerAdapter extends FragmentPagerAdapter {
SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return MainTextHolder.newInstance(tabIndicatorOnClick);
case 1:
return PostWriter.newInstance(tabIndicatorOnClick);
case 2:
return TopPage.newInstance(tabIndicatorOnClick);
default:
return Error.newInstance();
}
}
@Override
public int getCount() {
return 3;
}
// ... more methods
}
In each of the Fragment
's I have some content plus a custom TabIndicator
.
(the following xml
file is my View
's for the indicator)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<View
android:id="@+id/fragment_divider_one"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:tag="one" />
<View
android:id="@+id/fragment_divider_two"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:tag="two" />
<View
android:id="@+id/fragment_divider_three"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:tag="three" />
</LinearLayout>
And then I set an OnClickListener
for each of those View
's (dividers) in the Fragment
's onCreateView(LayoutInflater inflater, ViewGroup Bundle savedInstanceState)
method. The OnClickListener
is already prepared in my Activity
where I also instantiate my ViewPager
so that I can change the current Item
(Fragment
/tab).
private final View.OnClickListener tabIndicatorOnClick = new View.OnClickListener() {
@Override
public void onClick(View view) {
if (view.getTag().toString().equals("one"))
viewPager.setCurrentItem(0, true); // second argument for smooth transition
else if (view.getTag().toString().equals("two"))
viewPager.setCurrentItem(1, true);
else if (view.getTag().toString().equals("three"))
viewPager.setCurrentItem(2, true);
}
};
I pass that OnClickListener
to the Fragment
's by putting it into my newInstance(View.OnClickListener tabIndicatorOnClick)
method.
This is for one of the fragments. It is identical for the others!
public static MainTextHolder newInstance(View.OnClickListener tabIndicatorOnClick) {
MainTextHolder fragment = new MainTextHolder();
Bundle args = new Bundle();
fragment.putIndicatorTabIndicatorOnClick(tabIndicatorOnClick);
fragment.setArguments(args);
fragment.setRetainInstance(true);
return fragment;
}
My putIndicatorTabIndicatorOnClick(View.OnClickListener tabIndicatorOnClick)
method is a Void
in an Interface
. It just applies the OnClickListener
to the Class
(Fragment
).
@Override
public void putIndicatorTabIndicatorOnClick(View.OnClickListener tabIndicatorOnClick) {
this.tabIndicatorOnClick = tabIndicatorOnClick;
}
Does it work
Yes it does. It works perfectly fine... until a ConfigurationChange
happens. In my case I tested it with changing the orientation.
The Problem
What happens after that ConfigurationChange
is that everything goes normally. The OnClickListener
gets applied to all View
's in all Fragment
's, but the tabIndicatorOnClick
OnClickListener
is a null object reference in the second and third Fragment
.
So in PostWriter and TopPage there isn't even really an OnClickListener
on the View
's for what ever reason. But it gets better: In MainTextHolder
Fragment
the tabIndicatorOnClick
is not a null object reference
, but it does not change the ViewPager
's Item
anymore. It runs the code, but it does not scroll the Tab
.
When turning on "Don't keep activities" ind Developer options of the device and leaving the app, all OnClickListener
's are null object references
. Even those in MainTextHolder
.
Summary
After the ConfigurationChange
the OnClickListener
gets passed into my first Fragment
, but it is no working properly and in the remaining Fragment
's it is not even passed / a null object reference
.
The issue can be replicated through destroying the Activity
and then reloading it.
In the end...
... I have no clue what is going wrong. I hope that I structured my question properly.
May be of interest
minSdkVersion: 14
targetSdkVersion: 25
I already have a SpringIndicator on my ViewPager
. This does not feature an OnClickListener
so I added my own "overlayed" indicators which have the needed OnClick
feature. I also like the look of it, but if you can give me a better solution for my TabIndicator
, I would also love to have that as an answer.
I came across the solution to put the method into the xml
:onClick
attribute multiple times, but for that I would need to create the same OnClickListener
for every Fragment
which is not really the nice way and it is also not solving my problem, but a way around it. - I would need to pass the ViewPager
into my Fragment
and then call that method from xml
which is, as I said, just another way of doing it and not the way I prefer to do it. Also I don't know if it works. I'll most likely test it.
Other OnClickListener
's in the Fragment
still work properly. So as I said the problem lays in the OnClickListener
itself not working right/being a null object reference
.
Firstly, well done for writing a detailed description of the problem. Many questioners on StackOverflow can learn from the ability of this question to articulate the problem.
This error would seem to come from not accounting for Fragment lifecycle changes. While inside a ViewPager, Fragments will pass through various states including hidden and shown when they are temporarily offscreen, and paused and resumed if they are offscreen and the system frees memory, and finally created and destroyed if the Android system decides to save the instance state of your Activity (such as from a configuration change). In the latter case, Android will try and restore the state of your Fragments from saved instance state in your ViewPager. Since your Fragments have no way to save the
View.OnClickedListener
they were passed in the arguments, they end up with anull
pointer when they are restored which is causing your error.To fix the error, I suggest that you do not pass the
View.OnClickedListener
as a parameter to the Fragments. Rather, expose theOnClickListener
to your Fragments via a public method in your Activity and have the Fragments get it themselves in theironResume()
. This way you can guarantee that the Fragments will have a reference to theView.OnClickedListener
whenever they are in a resumed state. So youronResume()
might look something like this:}
Since you have set it in
onResume()
, you should remove it inonPause()
:Using
onResume()
andonPause()
like this to free up resources from listeners is the approach recommended in the developer guide.Of course, you will have to make sure your Activity also handles its own lifecycle by checking for saved state in
onCreate(Bundle savedInstanceState)
and restoring it appropriately.Please note that saving instance state and restoring can be triggered in a number of ways:
You will be able to use one of these methods to determine if you have correctly handled the lifecycles of your Activities and Fragments.