I already have a viewpager that works but I have a small bug. The user can add or delete the pages himself. In these pages I have an edittext (in fact I have more but it is the same case for all) which is the name of the page. If the user creates a page that it calls "page 1", deletes it and creates another page immediately after it, which it calls "page 2", the fragment will display the data on page 1 ". Why ? I call notifyDataSetChanged (); After creating and deleting a page so why the fragment is not refreshed? If I leave the activity and I restart it the "page 2" that the user created will display the data of "page 2". What more do I need to do to properly refresh the fragment?
I use this library : link
My onCreate method
viewPager = (ViewPager) findViewById(R.id.viewpager);
viewPagerTab = (SmartTabLayout) findViewById(R.id.viewpagertab);
pages = new FragmentPagerItems(this);
int i = 0;
for (Points points : Pref.getList_points()) {
pages.add(FragmentPagerItem.of(points.getName(), FragmentConfigurationPoints.class, new Bundler().putInt("param1", i).get()));
i++;
}
adapter = new FragmentPagerItemAdapter(getSupportFragmentManager(), pages){
#Override
public int getItemPosition(Object object) {
return PagerAdapter.POSITION_NONE;
}
};
viewPager.setAdapter(adapter);
viewPagerTab.setViewPager(viewPager);
My methods to add a page
pages.add(FragmentPagerItem.of(
Pref.getList_points().get(Pref.get_size() - 1).getName(),
FragmentConfigurationPoints.class,
new Bundler().putInt("param1", Pref.get_size() - 1).get()
));
adapter.notifyDataSetChanged();
viewPagerTab.setViewPager(viewPager);
viewPager.setCurrentItem(Pref.get_size() - 1, true);
Reset adapter with new Data should force to load new data.
viewPager.setAdapter(adapter);
notifyDataSetChanged() won't properly do what you need.
this method refresh data inside adapter not Adapter itself.
check out the reference
void notifyDataSetChanged ()
/*Notifies the attached observers that the underlying data has been changed and any View reflecting the data set should refresh itself.*/
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 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.
My app is a news app, with news listing fragment, NewsDetailsFragment (which contains menu, search, etc). I am using the single activity model and each screen is represented by a Fragment. NewsDetailsFragment includes a ViewPager (gallery).
When I navigate to NewsDetailsFragment initially, the gallery is loaded, now if I load a new instance of NewsDetailsFragment fragment, to choose another news from the menu, doing a search, etc. The ViewPager in the 2nd NewsDetailsFragment won't display the images, although the Log shows that everything works fine, data is there, adapter calls getView()...
So basically if there is 1 loaded gallery ViewPager, any more NewsDetailsFragment won't show their galleries, only if I press Back till I have no fragment (hence no ViewPager running), only then if I open a new instance of NewsDetailsFragment it will load the gallery.
I hope I explained clearly. I tried my best and I'm clueless now.
This is how I pass the gallery data to the ViewPager after it is loaded (code excerpt from NewsDetailsFragment):
ViewPager pager = (ViewPager) getView().findViewById(R.id.vpAlbum);
ArticleAlbumAdapter adapter = new ArticleAlbumAdapter(getActivity().getSupportFragmentManager(), article.album);
pager.setAdapter(adapter);
ArticleAlbumAdapter:
public class ArticleAlbumAdapter extends FragmentStatePagerAdapter
{
private List<String> urlList;
public ArticleAlbumAdapter(FragmentManager fm, List<String> urlList)
{
super(fm);
this.urlList = urlList;
Log.d("ArticleAlbumAdapter()", "urlList=" + urlList);
}
#Override
public Fragment getItem(int position)
{
Log.d("ArticleAlbumAdapter.getItem()", "position=" + position + ", url=" + urlList.get(position));
Fragment image = new AlbumImageFragment();
Bundle bundle = new Bundle();
bundle.putString("url", urlList.get(position));
bundle.putInt("pos", position);
image.setArguments(bundle);
return image;
}
#Override
public int getCount()
{
return urlList.size();
}
}
Pass the ViewPager your current Fragment's getChildFragmentManager() rather than the FragmentManager of the activity. Based on your description, I'm assuming your main screen which creates the first ViewPager is also a Fragment. Since the ViewPager is a child to your Fragment and it in turn is creating child Fragments, it needs the child FragmentManager rather than the main one.
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.
My app has a ListView on startup. The user can either manually select an item in the ListView to go to a details screen or swipe using a ViewPager between the different details screens. The ViewPager's fragments are setup like this:
Listing
Detail 1
Detail 2
Detail 3
Detail 4
...
It's my understanding, when the Listing fragment is loaded, the ViewPager will execute Detail 1's code, for performance. The same when Detail 1 is loaded, Detail 2's code will execute.
The problem I'm running into is that I'm setting the title of each detail fragment in onActivityCreated, however, when the Listing fragment is loaded, it is displaying Detail 1's title. So I moved the code to onPageSelected of the ViewPager, which works if the user is swiping, but if the user manually selects an item in the ListView the title is never set.
I'm not sure if there is an event that is only fired when a user manually selects an item in the ListView and not when they are swiping or if I need to rethink my apps' setup. For example, instead of using this code in the Listing fragment's onListItemClick event:
final Intent listing = new Intent(getActivity().getApplicationContext(), Details.class);
startActivity(listing);
I need to somehow use the ViewPager.
mViewPager = (ViewPager)findViewById(R.id.viewpager);
mMyFragmentPagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(mMyFragmentPagerAdapter);
mViewPager.setSaveEnabled(false);
mViewPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
String title = GetTitle(position);
getSupportActionBar().setTitle(title);
}
#Override
public void onPageScrolled(int position, float offset, int offsetPixel) {
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
You'll probably want to override the method getPageTitle(int) in your MyFragmentPagerAdapter class. The documentation states:
This method may be called by the ViewPager to obtain a title string to
describe the specified page. This method may return null indicating no
title for this page. The default implementation returns null.
So rather than returning null, make sure you return the actual page title. You get passed in the position/index of the page the title is requested for, so a simple switch-case statement should suffice. Alternatively, you could set up an interface for your pages and query the relevant page for its title.