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);
Related
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()
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.
I have a ViewPager with 10 pages. When I start the last (10th) page onCreateView() method of my fragment is called. When I swipe to the 9th page onCreateView() is called also. But when I back to the 10th page onCreateView() isn't called. What's wrong?
Try Extending FragmentStatePagerAdapter
That is because a FragmentPagerAdapter keeps in memory every fragment. Hence, when you visit the first time the fragment, onCreate will be invoked but the second time Android will looking for in memory, so it not need invoke onCreate.
If you need run the code in OnCreate every time fragment is displayed, you should move it to getItem(int id)
See offical documentation: http://developer.android.com/reference/android/support/v4/app/FragmentPagerAdapter.html
Nothing is wrong. The ViewPager already has the page, and so it does not need to create it.
I had the same problem, my solution was to assign again the adapter of the ViewPager instance, just like:
pager.setAdapter(adapter);
This causes a restart of the "mItems" property from the viewPager and removes the cache.
But I don't know if it's a safe solution
You can call the adapter getItem from onPageSelect, which is called also on swipes, and place your code inside the getItem, or even in the onPageSeelect itself.
CommonWare's answer is the best and works like charm:
simple add OnPageChangeListener to your ViewPager item, something like this:
ViewPager viewPager = null;
PagerAdapter pagerAdapter = null;
//Some code come here...
pagerAdapter = new PagerAdapter(); //Or any class derived from it
viewPager = (ViewPager)findViewById(R.id.container);//Connect it to XML
viewPager.setAdapter (mPagerAdapter); //Connect the two
//Next two lines are simply for fun...
//viewager.setPageTransformer(true, new DepthPageTransformer());
//viewPager.setPageTransformer(true, new PaymentZoomOutPageTransformer());
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
//This is the right place to connect the pages with a data struct!!!
#Override
public void onPageSelected(int position) {
// Here you can connect the current displayed page with some data..
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
//Here use the inflater to add views/pages
//Don't forget to do:
pagerAdapter.notifyDataSetChanged();
//When you're done...
We all know that when using ViewPager with Fragment and FragmentPagerAdapter we get 3 Fragment loaded: the visible one, and both on each of its sides.
So, if I have 7 Fragments and I'm iterating through them to see which 3 of them are the ones that are loaded, and by that I mean onCreateView() has already been called, how can I determine this?
EDIT: The Fragment doesn't have to be the one that the ViewPager is showing, just that onCreateView() has already been called.
Well logically, this would be a reasonable test if onCreateView has been called:
myFragment.getView() != null;
Assuming you a have a reference to all of the fragments in the pager iterate, them and check if they have a view.
http://developer.android.com/reference/android/app/Fragment.html#getView()
Update
The above answer assumes that your fragments always create a view, and are not viewless fragments. If they are then I suggest sub classing the fragment like so:
public abstract class SubFragment extends Fragment
{
protected boolean onCreateViewCalled = false;
public boolean hasOnCreateViewBeenCalled()
{
return onCreateViewCalled;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup Container, Bundle state){
onCreateViewCalled = true;
return null;
}
}
Just bear in mind that further sub classes will have to call super or set the flag themselves should they override onCreateView as well.
I added an interface to Fragment. Looks like:
protected OnCreateViewCallback createViewCallback = null;
public void setCreateViewCallback(OnCreateViewCallback createViewCallback) {
this.createViewCallback = createViewCallback;
}
public interface OnCreateViewCallback {
void onCreateView();
}
In my onCreateView():
//initialize your view.
if (createViewCallback != null) {
createViewCallback.onCreateView();
createViewCallback = null;
}
return mainView;
From my activity:
if (ocrFragment.getView() == null) {
ocrFragment.setCreateViewCallback(new MainScreenFragment.OnCreateViewCallback() {
#Override
public void onCreateView() {
ocrFragment.ocrImage(picture, false);
}
});
} else {
ocrFragment.ocrImage(picture, false);
}
If you are trying to perform something after onCreateView is called, use onViewCreated:
Called immediately after onCreateView(LayoutInflater, ViewGroup,
Bundle) has returned, but before any saved state has been restored in
to the view. This gives subclasses a chance to initialize themselves
once they know their view hierarchy has been completely created. The
fragment's view hierarchy is not however attached to its parent at
this point.
public void onViewCreated(View view, Bundle savedInstanceState) {
MyActivity myActivity = (MyActivity) getActivity();
MyActivity.newAsyncTask(mPar);
}
You could also check for Fragment.isVisible() because a Fragment is in visible state when it's in the offscreen page limit of a ViewPager.
Edit: But it just really depends on what you really want to achieve with your question. Perhaps some kind of update to all UIs in your Fragments when their UI is ready?
EDIT:
Just another addition, you could listen to onViewCreated() and set a flag. Or notify your Activity and do further work (getActivity() will return your Activity at this point). But really, better state what you want to accomplish with your question.
I have a ViewPager (extends FragmentPagerAdapter) which holds two Fragments. What I need is just refresh a ListView for each Fragment when I swipe among them. For this I have implemented ViewPager.OnPageChangeListener interface (namely onPageScrollStateChanged). In order to hold references to Fragments I use a HashTable. I store references to Fragments in HashTable in getItem() method:
#Override
public Fragment getItem(int num) {
if (num == 0) {
Fragment itemsListFragment = new ItemsListFragment();
mPageReferenceMap.put(num, itemsListFragment);
return itemsListFragment;
} else {
Fragment favsListFragment = new ItemsFavsListFragment();
mPageReferenceMap.put(num, favsListFragment);
return favsListFragment;
}
}
So when I swipe from one Fragment to another the onPageScrollStateChanged triggers where I use the HashTable to call required method in both Fragments (refresh):
public void refreshList() {
((ItemsListFragment) mPageReferenceMap.get(0)).refresh();
((ItemsFavsListFragment) mPageReferenceMap.get(1)).refresh();
}
Everything goes fine until orientation change event happens. After it the code in refresh() method, which is:
public void refresh() {
mAdapter.changeCursor(mDbHelper.getAll());
getListView().setItemChecked(-1, true); // The last row from a exception trace finishes here (my class).
}
results in IllegalStateException:
java.lang.IllegalStateException: Content view not yet created
at android.support.v4.app.ListFragment.ensureList(ListFragment.java:328)
at android.support.v4.app.ListFragment.getListView(ListFragment.java:222)
at ebeletskiy.gmail.com.passwords.ui.ItemsFavsListFragment.refresh(ItemsFavsListFragment.java:17)
Assuming the Content view is not created indeed I set the boolean variable in onActivityCreated() method to true and used if/else condition to call getListView() or not, which shown the activity and content view successfully created.
Then I was debugging to see when FragmentPagerAdapter invokes getItem() and it happens the method is not called after orientation change event. So looks like it ViewPager holds references to old Fragments. This is just my assumption.
So, is there any way to enforce the ViewPager to call getItem() again, so I can use proper references to current Fragments? May be some other solution? Thank you very much.
Then I was debugging to see when FragmentPagerAdapter invokes getItem() and it happens the method is not called after orientation change event. So looks like it ViewPager holds references to old Fragments.
The fragments should be automatically recreated, just like any fragment is on an configuration change. The exception would be if you used setRetainInstance(true), in which case they should be the same fragment objects as before.
So, is there any way to enforce the ViewPager to call getItem() again, so I can use proper references to current Fragments?
What is wrong with the fragments that are there?
I've spent some days searching for a solution for this problem, and many points was figured out:
use FragmentPagerAdapter instead of FragmentStatePagerAdapter
use FragmentStatePagerAdapter instead of FragmentPagerAdapter
return POSITION_NONE on getItemPosition override of FragmentPagerAdapter
don't use FragmentPagerAdapter if you need dynamic changes of Fragments
and many many many others...
In my app, like Eugene, I managed myself the instances of created fragments. I keep that in one HashMap<String,Fragment> inside some specialized class, so the fragments are never released, speeding up my app (but consuming more resources).
The problem was when I rotate my tablet (and phone). The getItem(int) wasn't called anymore for that fragment, and I couldn't change it.
I really spent many time until really found a solution, so I need share it with StackOverflow community, who helps me so many many times...
The solution for this problem, although the hard work to find it, is quite simple:
Just keep the reference to FragmentManager in the constructor of FragmentPagerAdapter extends:
public class Manager_Pager extends FragmentPagerAdapter {
private final FragmentManager mFragmentManager;
private final FragmentActivity mContext;
public Manager_Pager(FragmentActivity context) {
super( context.getSupportFragmentManager() );
this.mContext = context;
this.mFragmentManager = context.getSupportFragmentManager();
}
#Override
public int getItemPosition( Object object ) {
// here, check if this fragment is an instance of the
// ***FragmentClass_of_you_want_be_dynamic***
if (object instanceof FragmentClass_of_you_want_be_dynamic) {
// if true, remove from ***FragmentManager*** and return ***POSITION_NONE***
// to force a call to ***getItem***
mFragmentManager.beginTransaction().remove((Fragment) object).commit();
return POSITION_NONE;
}
//don't return POSITION_NONE, avoid fragment recreation.
return super.getItemPosition(object);
}
#Override
public Fragment getItem( int position ) {
if ( position == MY_DYNAMIC_FRAGMENT_INDEX){
Bundle args = new Bundle();
args.putString( "anything", position );
args.putString( "created_at", ALITEC.Utils.timeToStr() );
return Fragment.instantiate( mContext, FragmentClass_of_you_want_be_dynamic.class.getName(), args );
}else
if ( position == OTHER ){
//...
}else
return Fragment.instantiate( mContext, FragmentDefault.class.getName(), null );
}
}
Thats all. And it will work like a charm...
You can clear the saved instance state
protected void onCreate(Bundle savedInstanceState) {
clearBundle(savedInstanceState);
super.onCreate(savedInstanceState, R.layout.activity_car);
}
private void clearBundle(Bundle savedInstanceState) {
if (savedInstanceState != null) {
savedInstanceState.remove("android:fragments");
savedInstanceState.remove("android:support:fragments");
savedInstanceState.remove("androidx.lifecycle.BundlableSavedStateRegistry.key");
savedInstanceState.remove("android:lastAutofillId");
}
}