I have a ViewPager, defined in an Activity, and many Fragments sequentially shown in the ViewPager. In these fragments there are dynamically constructed checkboxes and radiobuttons, which the user is supposed to manipulate. On the very moment that the user swipes to the next page I need the user data to be retrieved and stored in the Application object. I can't figure out what the standard way of doing this is. Since there are many Fragments I opted for using the FragmentStatePagerAdapter. Any help would be welcome, thanks in advance!
Update-1:
I do have this:
pageAdapter = new MyPageAdapter(getSupportFragmentManager());
pager = (ViewPager) findViewById(R.id.viewpager);
pager.setAdapter(pageAdapter);
// detects viewpager page change
pager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
Log.i("TAG", "onPageSelected");
int index = pager.getCurrentItem();
MyPageAdapter adapter = ((MyPageAdapter) pager.getAdapter());
QuestionFragment fragment = (QuestionFragment) adapter.getItem(position);
if (fragment.rdbtn != null) {
for (int i = 0; i < fragment.rdbtn.length; i++) {
if (fragment.rdbtn[i].isChecked())
Log.i("TAG", "checked");
else
Log.i("TAG", "not checked");
}
}
// fragment.refresh();
}
});
When checking the debugger, after starting up, the ViewPager instantiates Fragments 0 and 1 (standard behavior). When the user manipulates fragment-0 and swipes, the handler is indeed called but with position=1, not 0. And the public elements I want to read are null!
UPDATE-2
I notice in the debugger that the data I need is stored in adapter.mCurrentPrimaryItem.
How to retrieve CurrentPrimaryItem in the code?!
You can implement a pageChangeListener in your activity and set this to the viewPager.
Then you can have a class abstract BaseFragment extends Fragment and declare an abstract method, say, getData() that every fragment in the ViewPager extends and overrides the method.
And in onPageSelected() of the activity you can access those data.
Related
I'm building an app that uses a Viewpager with a FragmentStatePagerAdapter.
The FragmentStatePagerAdapter gets an array of objects witch is later used to map each object data to a fragment.
The adapter loads 3 fragments into memory (onCreateView is called 3 times when I'm on a page), it loads the current fragment and the next two.
I have the following issue:
I must change the content of the next fragment based on content change in the current fragment I'm in.
I've tried to modify the array in the FragmentStatePagerAdapter and then call notifydatasetchanged on the adapter, but the adapter doesn't load again the next page.
what is the best way to pull this off?
some code and scenario:
the current fragment I am on contains users data, the logged user can follow him or unfollow.
here is the onClick code:
if(mListener!=null)
mListener.onTweetUserFollowingStatusChanged(tweet.getUser());
the callback from listener in the activity with the viewpage:
#Override
public void onTweetUserFollowingStatusChanged(User user) {
DataManager.getInstance().onTweetUserFollowStatusChanged(user);
List<Tweet> affectedTweets = mPagerAdapter.getTweetsWithUser(user);
for (Tweet affectedTweet: affectedTweets
) {
boolean affectedTweetCurrentUserFollowStats = affectedTweet.isFollowingTweetUser();
affectedTweet.setFollowingTweetUser(!affectedTweetCurrentUserFollowStats);
}
mPagerAdapter.notifyDataSetChanged();
}
The next page containes the same user.
So if I follow him on the curent page I want the follow button from the next page to display "UNFOLLOW"
I have found the solution;
Original problem:
after updating the array in the FragmentStateAdapter I needed a way to update the UI of the already instantiated fragments in the Adapter.
So I after updating the array in the FragmentStateAdapter just call notifyDataSetChange() and add this code to your adapter.
mPagerAdapter.registerDataSetObserver(new DataSetObserver() {
#Override
public void onChanged() {
super.onChanged();
FragmentStatePagerAdapter fragmentPagerAdapter = (FragmentStatePagerAdapter) VP_tweets.getAdapter();
for(int i = 0; i < fragmentPagerAdapter.getCount(); i++) {
TweetFragment viewPagerFragment = (TweetFragment) VP_tweets.getAdapter().instantiateItem(VP_tweets, i);
if(viewPagerFragment != null && viewPagerFragment.isAdded()) {
viewPagerFragment.updateUI();
}
}
}
});
I'm trying to implement 3 slides composed of 3 fragments (or 3 layouts) with ViewPager and I want to know which slide I currently show in order to display the appropriate content. In simpler words, I want content 1 on slide 1, content 2 on slide 2 and so on.
Here is my actual code from my Activity (from android official doc) :
public class SliderActivity extends FragmentActivity {
private static final int NUM_PAGES = 3;
private ViewPager mPager;
private PagerAdapter mPagerAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_slider);
// Instantiate a ViewPager and a PagerAdapter.
mPager = (ViewPager) findViewById(R.id.pager);
mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
}
#Override
public void onBackPressed() {
if (mPager.getCurrentItem() == 0) {
// If the user is currently looking at the first step, allow the system to handle the
// Back button. This calls finish() on this activity and pops the back stack.
super.onBackPressed();
} else {
// Otherwise, select the previous step.
mPager.setCurrentItem(mPager.getCurrentItem() - 1);
}
}
// A simple pager adapter that represents 3 ScreenSlidePageFragment objects, in sequence.
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
public ScreenSlidePagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
System.out.println("POSITION = " + position); // Or mPager.getCurrentItem()
return new SlidesFragment();
}
#Override
public int getCount() {
return NUM_PAGES;
}
}
}
No matter how hard I try, getCurrentItem() or position still prints wrong values. Only the second slide sends a number to the console. It's 2 when I swipe right and 0 when swipe to left.
What am I doing wrong ?
You definitely want to use the position value in getItem, do not call getCurrentItem because the pager will create items that are not the current item and not on-screen. The pager has an offscreen page limit of 2 by default. So, when it is first created, getItem will be called with a position of 0 and then again immediately with position of 1. The fragment at position 1 is not the current item, but it is being created offscreen so the user can start sliding over and see it. Then when you complete the swipe, getItem is called with a position of 2 to pre-load the next and final slide.
To accomplish "I want content 1 on slide 1, content 2 on slide 2", your SlidesFragment needs to take an argument (store in the arguments bundle) the position that tells it which content to display. Or, more likely, you have 3 different fragment types you would create based on the position.
Need some help with my problem of updating pages while using viewpager. I am using a simple viewpager with FragmentStatePagerAdapter. All I want to do is get access to the current fragment/view so as I can update some textviews in my fragment/view. I searched around in the forum and came to know few things
- One of the ways to handle this is by setting tag in instantiateItem() call back of the adapter and retreive the view by findViewbyTag. I could not understand how to implement this and I am also not sure if that will work for FragmentStatePagerAdapter.
- I explored other options as suggested in various forums, but cannot make them work.
My code is very much same as in a android http://developer.android.com/training/animation/screen-slide.html. Basic components are the same (Fragment activity xml with some display components including a textview, viewpager xml with just a view pager in it, a Fragment class and the main FragmentActivity class). in my FragmentActivity class I have added a pageChangelistener to my viewpager so as I can do my textview changes during onPageSelected().
Any help is is appreciated.
Adding the code for reference.
Public class myActivity extends FragmentActivity
//Variable declarations
protected void onCreate(Bundle savedInstanceState) {
ViewPager mPager = (ViewPager) findViewById(R.id.pager);
mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
View CurrView;
OnPageChangeListener pageChangelistener = new OnPageChangeListener() {
#Override
public void onPageSelected(int pageSelected) {
doTextViewChnges();//access the text view and update it based on pageSelected
---THIS IS WHERE I AM STUCK IN TRYING TO GET THE TEXTVIEW IN MY CURRENT FRAGMWNT/VIEW-------
}
mPager.setOnPageChangeListener(pageChangelistener);
}
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
public ScreenSlidePagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
#Override
public android.support.v4.app.Fragment getItem(int position) {
return ScreenSlidePageFragment.create(position, <other parameters I want to pass>);
}
#Override
public int getCount() {
return NUM_PAGES;
}
}
in your OnPageChangeListener:
OnPageChangeListener pageChangelistener = new OnPageChangeListener() {
#Override
public void onPageSelected(int pageSelected) {
ScreenSlidePageFragment currentFragment = (ScreenSlidePageFragment) mPagerAdapter.getItem(pageSelected)
doTextViewChnges();//access the text view and update it based on pageSelected
---THIS IS WHERE I AM STUCK IN TRYING TO GET THE TEXTVIEW IN MY CURRENT FRAGMWNT/VIEW-------
}
i assume you want to access your fragments from your activity and update the views.Fragments are parts of activities and their views can be accessed by them. Fragments are created dynamically in viewadapters and there is no straight forward and easy way to Tag them. You may simply access a fragment by its index number.first one is 0 second one is 1 and so on...
//access your fragment(in your case your first fragment)
//this part should be inside your doTextViewChnges()
Fragment fragment = (Fragment ) adapterViewPager
.getFragment(0);
if (fragment != null) {
//call a public method inside your fragment to update your text view
fragment .updateYourTextView();
}
Edit: inside your fragment create the following method and update your textview from there.
void updateYourTextView() {
yourTextView.setText("yourtext");
}
I am having a hard time figuring out the next thing.
What I have: I have a viewpager and several pages in it. In this question only two of them is important, lets call them Fragment1 and Fragment2 and they are next to each other. Fragment1 contains a listview which is filled with data from the internet (external database). Fragment2 contains a simple button.
My goal: If I click on the button in Fragment2, I add a new item to the external database. I would like to update/refresh the listview in the Fragment1 with this new item.
The notifyDataChanged() doesnt work in my case, however so far I was convinced that it reinstantiates every pages.. I am going to introduce my problem the clearest way I can, so lets see the code I have, this is my ViewPager adapter:
class MyPagerAdapter extends FragmentStatePagerAdapter {
public List<String> fragmentsA;
public MyPagerAdapter(FragmentManager fm) {
super(fm);
fragmentsA = fragments;
}
#Override
public Fragment getItem(int position) {
return Fragment.instantiate(context, fragmentsA.get(position));
}
#Override
public CharSequence getPageTitle(int position) {
return mEntries.get(position % CONTENT.length).toUpperCase();
}
#Override
public int getCount() {
return mEntries.size();
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
Fragment1 onCreateView() (shortly):
public View onCreateView(LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) {
getData();
View view = inflater.inflate(R.layout.latestapps_tab, container, false);
lw = (ListView) view.findViewById(R.id.lw);
context = getActivity().getApplicationContext();
act = this.getActivity();
m_adapter = new ItemAdapter();
lw.setAdapter(m_adapter);
return view;
}
I create the ViewPager and the adapter, I set the adapter for the ViewPager afterwards I fill the my viewpager with my fragments in my Main class. After this I am goint to have a fully functional ViewPager with 2 fragments.
pager = (ViewPager)findViewById( R.id.viewpager );
adapter = new MyPagerAdapter(getSupportFragmentManager());
indicator = (TabPageIndicator)findViewById( R.id.indicator );
pager.setAdapter( adapter );
indicator.setViewPager( pager );
pager.setCurrentItem(INITIAL_PAGE);
pager.setOffscreenPageLimit(3);
//adding fragments to the pager
fragments.add( Fragment1.class.getName());
fragments.add( Fragment2.class.getName());
In the Fragment1 I have a listview with some textviews in every list item. The loading works perfectly: I create the ArrayLists and I fill thoes lists with data from the external database. After loading is done, I fill the listviews with these tons of data.
In Fragment 2 I click on the button and I would like that listview to be updated so a new row should be created in the listview with some data from the external database. (of course writing into the database works)
My guess, that I might not refresh the ArrayLists or I dont reinstantiate the Fragment1, so the getDataFromSQL() method never turns only if I exit and launch the application again or I swipe so much in the ViewPager that the Fragment1 gets detached. So I cannot update or refresh the Fragment1. Could someone help in this questionL?
EDIT
I managed to make it happen with delivering a message to the fragment2 to update itself. But I am not sure if it is a good solution and there is not a better way, i.e. just refreshing somehow the whole fragment.
SOLUTION
Okay I think it must have been my laziness but I solved it now. For everyone who still wants to refresh a fragment from another one or just make conection between fragments, I tell you the appropriate approach:
You have to implement your own listener which helps you communicate between the fragments through the holder activity. This can be found here: http://developer.android.com/training/basics/fragments/communicating.html . Very simple and useful.
You have to retrieve the fragment, which is again simple: Retrieve a Fragment from a ViewPager These Q offers several acceptable way, I used the SpareArray solution.
Thank you for the help anyway!
you need be able to get your fragments from your activity, to do that you need to get the fragment from your adapter, you will need to add a couple methods to your page adapter
public Fragment getFragment(ViewPager container, int position, FragmentManager fm) {
String name = makeFragmentName(container.getId(), position);
return fm.findFragmentByTag(name);
}
private String makeFragmentName(int viewId, int index) {
return "android:switcher:" + viewId + ":" + index;
}
then from your activity make the following method
public Fragment getFragmentbyPosition(int position) {
return adapter.getFragment(pager, position, getSupportFragmentManager());
}
now on fragment2 call the following:
Fragment1 fragment1 = (Fragment1) ((MyActivity)getActivity()).getFragmentbyPosition(0);
now you will be able to call public methods on fragment1 from fragment 2, so just use that in your onClick and tell fragment1 to update it's listview.
now the reason makeFragmentName works is that is how the FragmentPagerAdapter creates the tag for the fragments it makes.
I am having trouble implementing a feature in my Android app.
Here's the setup:
ItemPagerActivity: An activity that contains a fragment that displays a pager.
ItemPagerFragment: The fragment containing a pager that loads other fragments. A cursor is used to load the fragments.
ItemFragment: The fragment in the pager, which performs an asynchronous task to load its data.
What I want is the following:
as a I swipe pages, the data in the currently displayed ItemFragment is communicated to the ItemPagerActivity (specifically, the name of the item will be used as the activity's title).
I've defined a listener in ItemFragment that notifies when the data is loaded:
public class ItemFragment ... {
public interface OnItemLoadedListener {
public void onItemLoaded(Item item);
}
private Collection<OnItemLoadListener> listeners;
private class LoadItemTask extends AsyncTask<...> {
...
public void onPostExecute(Item item) {
notifyItemLoaded(item);
...
}
}
}
If this fragment was wrapped by an Activity, then I could set the activity's title simply by doing the following:
public class ItemActivity {
public void onCreate(...) {
...
ItemFragment fragment = new ItemFragment();
fragment.registerItemLoadedListener(new ItemLoadedListener() {
public void onItemLoaded(Item item) {
setTitle("Item: " + item.getName());
}
});
...
}
}
So that's easy enough, and works as expected: when the activity starts, it creates the fragment, which loads the item, which notifies the activity, and the title is updated correctly.
But with ItemPagerFragment, the fragments are loaded pre-emptively: swiping to Fragment 3 may mean that Fragment 4 and Fragment 5 are created. Receiving notifications from the ItemFragment class when items are loaded is not correct here because the fragment displayed may not match the fragment that performed the last load.
Now the ViewPager class has a OnPageChangeListener which could be a solution: when I swipe, this listener is invoked with the current page number. From that page number, I need to (somehow) get the fragment representing that page from the adapter, get the Item data out of the fragment, and notify listeners that the Item is now loaded:
public class ItemPagerFragment ... {
private Collection<OnItemLoadedListener> listeners;
public View onCreateView(...) {
...
ViewPager pager = (ViewPager) view.findViewById(R.id.pager):
pager.setOnPageChangeListener(new OnPageChangeListener() {
public void onPageChange(int pageNumber) {
ItemFragment fragment = getItemFragment(pageNumber);
Item item = fragment.getLoadedItem();
notifyItemLoaded(item);
}
});
...
}
}
The ItemPagerActivity class would then register as a listener on the ItemPagerFragment class as follows:
class ItemPagerActivity ... {
public void onCreate(...) {
...
ItemPagerFragment fragment = new ItemPagerFragment();
fragment.registerOnItemLoadedListener(new OnItemLoadedListener() {
public void onItemLoaded(Item item) {
setTitle("Item: " + item.getName());
}
});
...
}
}
This looks good, but there are a number of problems:
The OnPageChangeListener may be invoked before a fragment has loaded its data (i.e., the fragment is swiped into view before the item has asynchronously loaded). So the call to fragment.getLoadedItem() may return null.
The OnPageChangeListener is not invoked for the initial page (only when a page changes, e.g. after a swipe action) so the activity title will be incorrect for the initial page.
The ViewPager class allows for only one OnPageChangeListener. This is a problem because I am also using the ViewPageIndicator library, which wants to assign a listener to the ViewPager.
I'm assuming that this pattern (notifying the activity of the data in a fragment that has been swiped into view) might be common, so I am wondering if there are any good solutions for this pattern, and to the three specific problems that I have identified above.
...so I am wondering if there are any good solutions for this pattern,
and to the three specific problems that I have identified above.
I don't know if I would call it a pattern but the OnPageChangeListener is the way to go.
The OnPageChangeListener may be invoked before a fragment has loaded
its data (i.e., the fragment is swiped into view before the item has
asynchronously loaded). So the call to fragment.getLoadedItem() may
return null.
First, your code should handle the "no data available situation" from the start. Your AsyncTasks will have the job of loading the data and also update the title only if the fragment for which they are working is the visible one(a position field in the ItemFragment tested against the ViewPager's getCurrentItem() method). The OnPageChangeListener will handle the update of the title after the data was loaded, as the user switches between pages and the data is available(it will return null if no data is available). To get the ItemFragment for a certain position you could use the code below:
ItemFragment itf = getSupportFragmentManager()
.findFragmentByTag(
"android:switcher:" + R.id.theIdOfTheViewPager + ":"
+ position);
if (itf != null) {
Item item = fragment.getLoadedItem();
notifyItemLoaded(item);
}
The OnPageChangeListener is not invoked for the initial page (only
when a page changes, e.g. after a swipe action) so the activity title
will be incorrect for the initial page.
See above.
The ViewPager class allows for only one OnPageChangeListener. This is
a problem because I am also using the ViewPageIndicator library, which
wants to assign a listener to the ViewPager
I admit I don't have much knowledge on the ViewPagerIndicator library but at a quick look on its site I saw:
(Optional) If you use an OnPageChangeListener with your view pager you
should set it in the indicator rather than on the pager directly.
titleIndicator.setOnPageChangeListener(mPageChangeListener);
I don't see where is the limitation.
For my purposes, it worked to use ViewPager.OnPageChangeListener.onPageSelected() in conjunction with Fragment.onActivityCreated() to perform an action when the Fragment is visible. Fragment.getUserVisibleHint() helps too.