onPageSelected is called before onViewCreated - android

Thats a big problem for me right now because i need to call a method from an interface
all my fragments in my viewpager are implementing. I need to do something like this:
#Override
public void onPageSelected(int position) {
this.getActivity().getActionBar().setSelectedNavigationItem(position);
FragmentVisible fragment = (FragmentVisible) this.fragmentPager.instantiateItem(this.viewPager, position);
if (fragment != null) {
fragment.fragmentBecameVisible();
}
}
This works for the "normal startup" but when i rotate the screen i get nullpointer exceptions
because onPageSelected gets called before onViewCreated. I need my views to get updated everytime
a fragment gets visible. First i hoped onResume would get called everytime but it doesnt. For that
i implemented the interface:
public interface FragmentVisible {
public void fragmentBecameVisible();
}
Does someone has an idea how to solve this?

Per the FragmentPagerAdapter's setPrimaryItem() method (called when the ViewPager sets the current page), it calls setUserVisibleHint(true) for the current page's fragment. You can override that method in your Fragment and do your fragmentBecameVisible() method in there.

Related

Lifecycle problem when use FragmentStatePagerAdapter, Fragments

I'm using FragmentStatePagerAdapter, ViewPager.
I'm going to use onSaveInstanceState by overriding to save some states like cursor position of EditText in every fragment.
But when I choose first fragment and next choose second fragment, the onSaveInstanceState of first fragment is not called. If I choose first and next choose third fragment, then the onSaveInstanceState of the first fragment is called.
In this case of choosing first fragment and next second fragment, even the onPause of the first fragment is not called.
What's the reason? How can I solve this problem? I have researched about this problem whole day. But I haven't found solution and correct reason yet.
onSaveInstanceState has cases that it can be called, but how about onPause? Why doens't onPause be called?
I found a solution. I used setUserVisibleHint.
In fragment, I wrote save and restore logic in setUserVisibleHint.
It works well.
This is called when fragment is shown or hidden.
Also I used onViewStateRestored, onSaveInstanceState together for being destroyed cases.
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser) {
if (mInputFrom != null) {
if(isToFocus) {
mInputFrom.requestFocus();
mInputFrom.setSelection(fromCursor);
} else {
mInputOut.requestFocus();
mInputOut.setSelection(toCursor);
}
}
} else {
if (mInputFrom != null) {
fromCursor = mInputFrom.getSelectionStart();
toCursor = mInputOut.getSelectionStart();
}
}
}
setUservisibleHint was deprecated.
So other option is that we can use constructor of FragmentStatePagerAdapter(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
If we call super's constructor like above in constructor of our customized Adapter that extends FragmentStatePagerAdapter, then onPause, onResume of fragment will be called for every case of being hidden or shown.
First option is suitable for my case I think. So I used first option and has solved perfectly.

onPageSelected runs BEFORE onCreateView is called?

I'm using a ViewPager to cycle through a set of fragments, and I want to update each fragment after it slides onto the screen. Basically, I want the text to "fade in" after the fragment has settled.
I tried using the fragment's onStart and onResume methods, and while this works for most of the pages, it does NOT work for the second page, because for whatever dumb reason, the first page AND the second page have their onStart/onResume methods called at the same time (before the second page ever hits the screen).
Now I'm trying to get it to work with the onPageChangeListener's onPageSelected callback. That method looks like this:
#Override
public void onPageSelected(final int position) {
mCurrentPosition = position;
PageFragment fragment = (PageFragment) ((MainActivity.ScreenSlidePagerAdapter) mViewPager.getAdapter()).getItem(position);
fragment.onSelect();
}
And the onSelect method in the fragment looks like this:
public void onSelect(){
new android.os.Handler().postDelayed(
new Runnable() {
public void run() {
mSwitcher.setText("");
mNum = getArguments() != null ? getArguments().getInt("num") : 1;
Media currentMedia = slideshow.getMedia().get(mNum);
mSwitcher.setText(currentMedia.getDisplayName());
}
},
4000);
}
The problem with this way is that the line mSwitcher.setText(""); throws a NullPointerException
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextSwitcher.setText(java.lang.CharSequence)' on a null object reference
Which would suggest that the onCreateView method in that class has yet to run since that's where the mSwitcher variable is instantiated. Which seems bananas to me, since the view is already sliding onto the screen at this point.
Any ideas about how to solve this problem would be greatly appreciated. This is my first Android experience, and I've been trying to solve this stupid text-fade-in issue for a full week with no luck. At this point I'm almost ready to abandon mobile as a platform because of how painful every minor change has been so far.
ViewPager keeps the next page in memory & this is it's default behaviour. You could adjust it by calling like:
viewPager.setOffscreenPageLimit(2);
However this might not be useful as if you pass 0 in above method, viewPager will ignore it.
You are going in right direction. I believe now problem is in your ScreenSlidePagerAdapter. In getItem(int position) you might have something like
if(position == 1)
return new PageFragment();
instead change the adapter to something like following,
public class ScreenSlidePagerAdapter extends FragmentPagerAdapter {
private List<Fragment> mFragments = new ArrayList<>();
public ScreenSlidePagerAdapter(FragmentManager fm, List<Item> items) {
super(fm);
for (Item item : items) {
mFragments.add(new PageFragment());
}
}
#Override
public int getCount() {
return mFragments.size();
}
#Override
public Fragment getItem(int position) {
return mFragments.get(position); // Return from list instead of new PagerFragment()
}
}
I have the similar problem as yours, onPageSelected() is called before the fragments are initialized, but your description is not detailed enough, such as how you select the second page.
When adapter is fed with Fragments, or we say getCount() > 0, getItem() will whatever returns a Fragment, which is not null. But this doesn't mean it is initialized, at least it doesn't if you extend from FragmentStatePagerAdapter.
when adapter is fed with data and called notifyDataSetChange(), adapter will initialize the first two pages by default. If you call setCurrentItem() to move to other pages immediately after notifyDataSetChange() the issue might happen. During the runtime, setCurrentItem() -> onPageSelected() might be called before the fragments are initialized.
my solution is using view.post() when setCurrentItem(). e.g.
viewPager.post(() -> viewPager.setCurrentItem(index));

Save Data when ViewPager page changes

I'm trying to save data a user enters in a fragment to a file.
Scenario:
one viewpager and 7 fragments
A user starts in fragment 0 and can enter text into edittexts,
by swiping, using tabhost or pressing floating arrows the user can switch to other fragments.
I want to save alle entered text of the fragment the user leaves with the methods above.
I tried a OnPageChangeListener, but there i can't get the previous tab. I logged the values of the implementation methods onPageScrolled, onPageSelected, onPageScrollStateChanged.
Non of these seem to work for my needs.
onPageScrolled is called several times and shows only the current tab until it is of screen, the offset is different and not always starts by 0.0, so i can't use this reliably.
onPageSelected is the only reliable one but only returns the new current tab
onPageScrollStateChanged has no information i could use to determine the tab
I also looked into onInterceptTouchEvent in the ViewPager but this is also some times invoked several times (for MOVE events) and does not always work for every tab.
Is there a way to get this cost efficent? I want to store the data in an encrypted file and don't want to do this several times over.
Because the suggestions didn't work for my case I came up with another idea I wan't to share with others.
First instead of focusing on the ViewPager to suite my needs I thought wouldn't it be clever to led the fragment know if its changed and handle that instead.
So I created an abstract class extending the android Fragment with a boolean attribute dataChanged which I check every time the OnPageChangeListener calls onPageSelected (iterate over all fragments in the pager).
Naturally all Fragments in the pager should extend the abstract class. Furthermore I added abstract methods save() and load() to the abstract class.
So in onPageSelected(int position), after saving all changes for all fragments, which should only be one at a time, I load the data of the now selected fragment via the position attribute.
There was but one problem. If a fragment was paused and resumed the dataChanged attribute was always true if I set it in onTextChangeListeners, because of the automatic loading of widget values that android does. So I also override onResume to set the dataChanged to false.
Also every MyFragment has to handle the dataChanged attribute in the save() and load() method.
Abstract Fragment
public abstract class MyFragment extends Fragment {
private boolean dataChanged = false;
#Override
public void onResume() {
super.onResume();
setDataChanged(false);
}
public boolean isDataChanged() {
return dataChanged;
}
public void setDataChanged(boolean dataChanged) {
this.dataChanged = dataChanged;
}
public abstract void save();
public abstract void load();
}
OnPageChangeListener of ViewPager
fragmentViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
...
#Override
public void onPageSelected(int position) {
for(Fragment f : fragments) {
if(f instanceof MyFragment && ((MyFragment)f).isDataChanged()) {
((MyFragment) f).save();
}
}
if(fragmentViewPager.getCurrentItem() == position) {
Fragment fragment = getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.view_pager + ":" + fragmentViewPager.getCurrentItem());
if(fragment instanceof MyFragment) {
((MyFragment) fragment).load();
}
}
}
...
});

Updating listview fragment on viewpager tab changes

I have a ViewPager using a FragmentPagerAdapter for displaying three tabs, each represented by its ow fragment. One of these fragments contains a list, that should be updated on switching / swiping to that tab. But I don't find any way to make it happen. I tried using the onResume method, but the fragments seem not to be paused and resumed on tab change. I also tried using ViewPager.OnPageChangeListener in my MainActivity:
#Override
public void onPageSelected(int position)
{
FragmentRefreshInterface currentFragment = (FragmentRefreshInterface) mSectionsPagerAdapter.getItem(position);
currentFragment.onRefreshed();
}
And in the fragment I use the following:
#Override
public void onRefreshed()
{
List<Record> records = mRecordingService.getRecords();
mRecordAdapter.clear();
mRecordAdapter.add(record);
}
But using this code I can't access my RecordingService class that is used to provide the database functions (because mRecordingService seems to be null). I initialize it in the fragment like this:
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
mRecordingService = new RecordingService(getContext());
}
Using the onPageChangeListener is the correct way to do it. I believe the reason why your code is not working, is because you are calling getItem on your pager adapter: getItem() actually returns a new instance of the fragment. In order to get the current instance, you use instantiateItem() (which returns a reference to the fragment actually being used).
Change your code to look something like this:
#Override
public void onPageSelected(int position)
{
FragmentRefreshInterface currentFragment = (FragmentRefreshInterface) mSectionsPagerAdapter.instantiateItem(viewPager,position);
currentFragment.onRefreshed();
}
And it should work.
I suggest that the code you have in onRefreshed() go in onResume() instead. Fragment doesn't have an onRefreshed() method. You must be implementing another interface that declares this method.
Since you are storing data in a database, you should be use a CursorAdapter or subclass such as SimpleCursorAdapter. If you do this correctly, the ListView will automatically update when you add a record to the database. Then the service can add records without needing to access the service from the fragment.
In your MainActivity:
private FirstFragment firstFragment;
private WantedFragment wantedFragment;
private ThirdFragment thirdfragment;
In getItem
switch(postition){
//return first, wanted, third fragments depending on position
}
onPageSelected:
if(position == 1) // position of the wanted fragment
wantedfragment.onRefreshed()

Fragment in ViewPager returns empty object onResume

I use a FragmentPagerAdapter to switch from fragments. I need some functions to be called when a fragmentswitch is made and had some troubles with OnPause and OnResume, so as suggested by THIS question I have implemented an interface OnPageSelectListener :
public interface OnPageSelectListener {
void onPageSelected();
void onPageNotVisible();
}
It calls the function OnPageSelected whenever this page comes to the foreground. Works nice, except that I want to call a function on my adapter. I thought that would work, except that my adapter returns NULL all the times (even though it is initialized and data is loaded in my listview as prefered).
public class AfterCheckFragment extends Fragment implements OnPageSelectListener{
private ListView listView;
private List<Check> checkList;
private CheckListAdapter adapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_check, container, false);
System.out.println("VIEW create called");
//(.. some other stuff, not relevant for question..)
//initializing the adapter
listView = (ListView) view.findViewById(R.id.listView);
adapter = new CheckListAdapter(checkList,getActivity(),trainPosition);
listView.setAdapter(adapter);
adapter.handleButtonVisibility();
return view;
}
#Override
public void onPageSelected() {
if(this.adapter != null) {
System.out.println("adapter not null");
this.adapter.checkForActive();
}else{
System.out.println("Adapter is NULL");
}
}
#Override
public void onPageNotVisible() { //page is moved to backgroung
System.out.println("AFTER not active any more ");
}
}
Now is my question: Why does adapter (or any other object in the fragment) return null when I return to my fragment? When the fragmentPager is initialized the onActivityCreate function of the fragment is called one time, but after that not any more, and the adapter return null....
you have to call the onPageSelected() after initialization of the adapter and setAdapter() otherwise adapter will return null always
Here is why I think your CheckListAdapter (i'll call it listAdapter) is null:
You give the pagerAdapter to the ViewPager
The ViewPager asks the pagerAdapter for a new Fragment
The ViewPager tells the FragmentManager to use it
onPageSelected gets called
You try and use listAdapter. It hasn't been initialized yet at this point. (NPE)
The FragmentManager drags the Fragment through all its stages.
onCreateView gets called. Your listAdapter is created.
Don't try and use internal data of a fragment outside of it. It is meant to work as a standalone unit, it won't be very good if you use it differently. Since the fragment is initialized at a later stage, you can't use it like you intend.
You can try and do what you want to do in the fragment, rather than the pagerAdapter, or write a method in the hosting Activity and call it from the fragment when ready, or even launch an event.
ViewPager will create and destroy fragments as the user changes pages (see ViewPager.setOffscreenPageLimit()). So onActivityCreated() is only called on the fragment when it is being restored or set up for the first time. Hence, fragments can be created without ever having onActivityCreated() called.
Instead of onActivityCreated(), I would recommend overriding onViewCreated() and setting up your adapter there. No fragment can be displayed without having a view created, so this is a good place to do that kind of stuff.
If you have your OnPageSelectListener logic working, that's good. I found the best way to know when your fragment is actually in front of the user is by overriding setPrimaryItem() in the FragmentPagerAdapter. Getting the page out of view event is a little trickier, since you have to keep a reference to the fragment from the previous setPrimaryItem() call.
This is because Viewpager calls OnpageSelected way before Fragments in oncreateView()/onActivityCreated() is called .
The best way for you is to inflate your views in the constructor of the Fragment and set the Adapters.
Or
Use a member variable to store whether the Fragment is active or not. And use the variable in oncreateview() to call function on your adapter.
Why don't you use a viewpager.addOnPageChangeListener, in you pager , after setting its adapter and the setOffscreenPageLimit() instead of implements it on your fragment?
Heres a sample code:
viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
if(position == 1){ // if you want the second page, for example
//Your code here
}
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
Make it in your Activity, where you setup your ViewPager, of course.
for me i had to call this on my viewpager:
myViewPager.setSaveFromParentEnabled(false);

Categories

Resources