Same fragment called multiple time in ViewPager - android

I have a View pager. The user can choose how many differents pages he can have.
The pages are all the same layout but it will just load different data.
Here is my fragment adapter :
public class FragmentAdapter extends FragmentPagerAdapter
{
private final List<Fragment> lstFragment = new ArrayList<>();
private final List<String> lstTitles = new ArrayList<>();
public FragmentAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
return lstFragment.get(i);
}
#Nullable
#Override
public CharSequence getPageTitle(int position) {
return lstTitles.get(position);
}
#Override
public int getCount() {
return lstTitles.size();
}
public void AddFragment (Fragment fragment , String title)
{
lstFragment.add(fragment);
lstTitles.add(title);
}
}
And here is the code to call the fragment multiple time :
FragAdapter = new FragmentAdapter(getSupportFragmentManager());
viewPager = (ViewPager) findViewById(R.id.main_tabs_pager);
toolbar = (Toolbar) findViewById(R.id.main_page_toolbar);
tabLayout = (TabLayout) findViewById(R.id.main_tabs);
String[] Fragments = {"Frag1", "Frag2", "Frag3", "Frag4"};
for (int i=0; i<Fragments.length; i++)
{
((FragmentAdapter) FragAdapter).AddFragment(new MenuFragment(),Fragments[i]);
}
viewPager.setAdapter(FragAdapter);
tabLayout = (TabLayout) findViewById(R.id.main_tabs);
tabLayout.setupWithViewPager(viewPager);
So it works fine. But the only problem is that I don't know how to know the difference in code between the differents fragments.
Exemple :
The frag1 must load 5 pictures about the sea
The frag2 must load 8 pictures about the sun
How can I tell the fragment what to do? I tried to pass in the constructeur the arguments by exemple
public MenuFragment(int numberofpictures, String picturesthemes)
{
// Required empty public constructor
}
but the constructors must be empty because it is not called again when fragment is destroyed and recreated...
does anyone has an idea? thanks
UPDATE
I don't know if that is the good way but here is the way I did it :
In main activity I created :
for (int i=0; i<Fragments.length; i++)
{
Bundle parameters = new Bundle();
parameters.putInt("myInt", i);
Fragment menuFragment = new MenuFragment();
menuFragment.setArguments(parameters);
((FragmentAdapter) FragAdapter).AddFragment(menuFragment, Fragments[i]);
}
Which give a everyfragment the the int i which is a reference to the title.
Then I simply wrote this function :
public String getName (int i)
{
return Fragments[i];
}
which return the title based on the int that the fragment got thanks to the bundle
Then, In the MenuFragment() I used this :
private void fillinthelist()
{
myInt = getArguments().getInt("myInt");
String test = ((MainActivity) getActivity()).getName(myInt);
ListOfProgrammes.add(new Modele_carte(test));
}
so it gets the int from the bundle and make a like to it thanks to the function in MainActivity
Is it the good way to do it? It seems to work

You can attach a Bundle containing the parameters with setArguments(Bundle) :
Bundle parameters = new Bundle();
parameters.putInt("myInt", <int_value>);
Fragment menuFragment = new MenuFragment();
menuFragment.setArguments(arguments);
((FragmentAdapter) FragAdapter).AddFragment(menuFragment, Fragments[i]);
A common practice is to build and attach the Bundle in a fragment's class static factory method.
The fragment can use getArguments() to retrieve the parameters.
private int myInt;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myInt = getArguments().getInt("myInt");
}

Related

Viewpager gets the wrong page

I have looked in several topics like this but didn't see how to fix my problem
I have a resturant for say.. with dynamic number of categories.. I put all the categories in a list.. and create fragments by from those categories
so I cant just
case 0 : fragment0
case 1 :frament 1
because I dont know how much categories I have untill runtime
class MyPageAdapter extends FragmentStatePagerAdapter {
private List<MyFragment> fragments = new ArrayList<>();
private List<menuCat> Categories = new ArrayList<>();
public MyPageAdapter(FragmentManager fm, List<menuCat> Categories) {
super(fm);
this.Categories = Categories;
for (int i = 0; i<Categories.size();i++)
{
fragments.add(MyFragment.newInstance(Categories.get(i)));
}
}
#Override
public String getPageTitle(int position)
{
return Categories.get(position).catName();
}
#Override
public MyFragment getItem(int position) {
return this.fragments.get(position);
}
#Override
public int getCount() {
return this.fragments.size();
}
MyFragment.class
public class MyFragment extends Fragment {
public static final MyFragment newInstance(menuCat category)
{
Bundle bun = new Bundle();
bun.putString("category", category.toJson());
MyFragment f = new MyFragment();
f.setArguments(bun);
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_my, container, false);
String json = getArguments().getString("category");
menuCat category = menuCat.fromJson(json);
System.out.println(category.catName());
ArrayList<Card> cards = new ArrayList<Card>();
for(menuItem item : category.getItems())
{
Card card = new Card(getActivity());
// Create a CardHeader
CardHeader header = new CardHeader(getActivity());
// Add Header to card
header.setTitle(item.getName());
card.setTitle(item.getPrice());
card.addCardHeader(header);
CardThumbnail thumb = new CardThumbnail(getActivity());
//thumb.setDrawableResource(listImages[i]);
//card.addCardThumbnail(thumb);
cards.add(card);
}
CardArrayAdapter mCardArrayAdapter = new CardArrayAdapter(getActivity(), cards);
CardListView listView = (CardListView) getActivity().findViewById(R.id.myList);
if (listView != null) {
listView.setAdapter(mCardArrayAdapter);
}
return v;
}
}
this is my adapter, my problem is for example
if I am in page 1 and I need page 3 data.. if I will go to page 2.. it will display data of page 3 OR if I go to page 5 then go backward.. to 4 it will also show data of page 3.. I mean it gets me like the data of the next page instead of current one.
Creating all of your fragments in the constructor is very poor design, as you're creating references to these objects which will later be attached to an Activity, but when they are detached, you continue to hold the reference. In the end, this is going to cause you a lot of frustration with memory leaks.
Is it not possible to simply remove fragments and change your methods to the following:
#Override
public MyFragment getItem(int position) {
return MyFragment.newInstance(Categories.get(position));
}
#Override
public int getCount() {
return Categories.size();
}
I'm not certain this will solve all of your problems, but it is a start.

Using viewpager with specific fragments

I am creating an app that is using a viewpager to slide between 4 specific fragments.
All the examples of viewpager I have read so far, create new fragments each time the getPosition method of FragmentPagerAdapter is called. So it's something like:
return FragmentA.newInstance();
What I have done is the following:
In the main activity
public static final int FRAGMENTS = 4;
public static final String FRAGMENT_LIST ="LIST";
public static final String FRAGMENT_SETTINGS = "SETTINGS";
public static final String FRAGMENT_MAP = "MAP";
public static final String FRAGMENT_TICKET = "TICKET";
MainAdapter _adapter;
ViewPager _pager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
.add(new FragmentMap(), FRAGMENT_MAP)
.add(new FragmentList(), FRAGMENT_LIST)
.add(new FragmentTicket(), FRAGMENT_TICKET)
.add(new FragmentSettings(), FRAGMENT_SETTINGS)
.commit();
}
_adapter = new MainAdapter(getSupportFragmentManager());
_pager = (ViewPager) findViewById(R.id.pager);
_pager.setAdapter(_adapter);
}
and in the adapter:
public class MainAdapter extends FragmentPagerAdapter {
FragmentManager _manager;
public MainAdapter(FragmentManager fm) {
super(fm);
_manager = fm;
}
#Override
public Fragment getItem(int position) {
return _manager.getFragments().get(position);
}
#Override
public int getCount() {
return ActivityMain.FRAGMENTS;
}
}
This raises an exception because the adapter is trying to change the tag of each fragment in getItem
My questions are:
a) Is it incorrect to always use the same fragment every time? I have seen no example that uses the above method or a similar one, they always create a new instance in the getItem method
b) If I wish for fragments to have some persistence, then does that mean that I should store the data that should be held by each fragment in static variables and always create new instances that use those variables?
a) You must create a new instance in the getItem() method, this method is not called every time you switch fragment from your viewpager.
I recommend you to use your adapter like
public class MainAdapter extends FragmentPagerAdapter {
public MainAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
case 0 :
return new FragmentMap();
case 1 :
return new FragmentList();
case 2 :
return new FragmentTicket();
case 3 :
return new FragmentSettings();
default :
return null;
}
#Override
public int getCount() {
return ActivityMain.FRAGMENTS;
}
}
b) Fragments in FragmentPagerAdapter are persistents, and they will be recreate only if you switch several fragment in your ViewPager. You can set the refresh limit by _pager.setOffscreenPageLimit(3); for exemple if you never want to recreate your fragments in your case.
For more information : http://developer.android.com/reference/android/support/v4/view/ViewPager.html#setOffscreenPageLimit(int)
a) I think it's perfectly ok. They always create ne instance because it is easier and in many cases (like image gallery) it is better.
b) Static variables get lost if you send the app to backround and then return to it. Shared prefferences or bundle should do the work.
You should use FragmentStatePagerAdapter. If you fragmentstatepager adapter, you can remove or add fragment dynamically.

ViewPager shows duplicate page error

I spent whole day for searching the right answer, but couldn't find any.
I have a ViewPager which has listview on each fragment, and it keep showing duplicate data on first and second page. (sometimes other two pages, i.e. second and third)
Let's say I have A,B,C,D,E listviews on each fragment, but when I started my ViewPager, it shows B,B,C,D,E. I guess this is because FragmentStatePagerAdapter is automatically loads previous and next page, and in my case, it seems like last loaded data is over writing the previous data.
Here's my adapter :
private class MyPagerAdapter extends FragmentStatePagerAdapter {
private ArrayList<MyFragment> mMyFragList;
public MyPagerAdapter(FragmentManager fm, ArrayList<MyFragment> list) {
super(fm);
mMyFragList = list;
}
#Override
public int getCount() {
return mMyFragList.size();
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
#Override
public MyFragment getItem(int position) {
return mMyFragList.get(position);
}
}
and ViewPager initialize part :
public void updateView() {
setPagerFragment();
mPager = (ViewPager) mView.findViewById(R.id.menu_viewpager);
mAdapter = new MyAdapter(mActivity.getSupportFragmentManager(), mMyFrag);
mPager.setAdapter(mAdapter);
}
public void setPagerFragment() {
for(int i=0; i<mListData.size(); i++) {
// mListData is an ArrayList of MyPojo object
MyFragment fragment = MyFragment.newInstance(i, mListData.get(i));
mMyFrag.add(fragment);
}
}
This updateView() function is called from another class as a callback, and I checked mListData has correct data before and after assign to Fragment.
Also, here's the newInstance() in MyFragment :
public static MyFragment newInstance(int page, MyPojo data) {
MyFragment fragment = new MyFragment();
Bundle bundle = new Bundle();
String jsonObj = gson.toJson(data);
bundle.putInt(ARG_PAGE_NUMBER, page);
bundle.putString(ARG_PAGE_DATA, jsonObj);
fragment.setArguments(bundle);
return fragment;
}
When I checked page and data in newInstance() and onCreate() of MyFragment, the page index and its data were correct.
I tried all the variation of above code, i.e. using FragmentPagerAdapter or initialize and destroy fragment manually in adapter and etc, but none of them worked for me.
Is there anyone who can share your wisdom?

Android ViewPager get view nullpointer exception

I have support.v4 ViewPager, which I fill with fragments, by using FragmentStatePagerAdapter.
The displaying of the fragments in ViewPager works as expected, but the problem is getting the fragment views when calling the getView() function in the FragmentActvitiy class. I can access all the 15 fragments and get arguments, etc.., but when calling getView() function on fragment I get a nullpointer exception, becase the third fragment getView() is null.
MyPageAdapter adapter = (MyPageAdapter) pager.getAdapter();
Fragment fragment;
for (int i=0; i<15; i++) {
fragment = adapter.getItem(i);
View v = fragment.getView();
System.out.println("--- FRAGMENT = " + fragment + " VIEW = " + v);
}
Output:
--- FRAGMENT = MyPrvaPomocFragment{40ede4c8 #0 id=0x7f06000d} VIEW = android.support.v4.app.NoSaveStateFrameLayout{40f11cb0 V.E..... ......I. 0,0-540,850}
--- FRAGMENT = MyPrvaPomocFragment{40edf7c0 #1 id=0x7f06000d} VIEW = android.support.v4.app.NoSaveStateFrameLayout{40f16f80 V.E..... ......ID 540,0-1080,850}
--- FRAGMENT = MyPrvaPomocFragment{40ee0998} VIEW = null
FragmentStatePageAdapter class code:
public class MyPageAdapter extends FragmentStatePagerAdapter {
private List<Fragment> fragments;
public MyPageAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments = fragments;
}
#Override
public Fragment getItem(int position) {
return this.fragments.get(position);
}
#Override
public int getCount() {
return this.fragments.size();
}
#Override
public int getItemPosition(Object object){
return PagerAdapter.POSITION_NONE;
}
}
Fragment class code:
public class MyPrvaPomocFragment extends Fragment {
public static final String EXTRA_QUESTION_NUMBER = "EXTRA_QUESTION_NUMBER";
public static final String EXTRA_QUESTION = "EXTRA_QUESTION";
...
public static final MyPrvaPomocFragment newInstance(String questionNumber, String question, ..., int correctAnswer) {
MyPrvaPomocFragment f = new MyPrvaPomocFragment();
Bundle bdl = new Bundle(1);
bdl.putString(EXTRA_QUESTION_NUMBER, questionNumber);
bdl.putString(EXTRA_QUESTION, question);
bdl.putInt(EXTRA_CORRECT_ANSWER, correctAnswer);
...
f.setArguments(bdl);
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
String question = getArguments().getString(EXTRA_QUESTION);
String firstAnswer = getArguments().getString(EXTRA_FIRST_ANSWER);
...
View v = inflater.inflate(R.layout.fragment_prva_pomoc, container, false);
TextView textViewQuestion = (TextView) v.findViewById(R.id.textViewQuestion);
CheckBox checkBox1 = (CheckBox) v.findViewById(R.id.checkBox1);
CheckBox checkBox2 = (CheckBox) v.findViewById(R.id.checkBox2);
...
textViewQuestion.setText(question);
checkBox1.setText(firstAnswer);
checkBox2.setText(secondAnswer);
return v;
}
}
FragmentActivity class code:
public class KvizPrvaPomoc extends FragmentActivity {
ViewPager pager;
MyPageAdapter pageAdapter;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.kviz_prva_pomoc);
pager = (ViewPager)findViewById(R.id.viewpagerquiz);
pageAdapter = new MyPageAdapter(getSupportFragmentManager(), getFragments());
pager.setAdapter(pageAdapter);
}
private List<Fragment> getFragments(){
String question;
String firstAnswer;
String secondAnswer;
...
List<Fragment> fragmentList = new ArrayList<Fragment>();
for (int j=0; j<someList.size(); j++) {
...
fragmentList.add(MyPrvaPomocFragment.newInstance(questionNumber, question, firstAnswer, ...));
}
return fragmentList;
}
}
I have read through numerous SO questions/answers, but I have no idea what could be wrong.
This is the expected behavior; FragmentStatePagerAdapter destroys fragments once you scroll more than one fragment away from them in either direction. This is done to conserve memory.
Fragments' views are removed from memory by view pager automatically. This is done to manage resources on mobiles.
You should not try to access visual elements of the fragments from the activity anyway. Only fragment should use its own visual elements, not its parent activity directly.
If you need to manipulate fragments, you should change the data they hold and when the view is recreated (when the user swipes to the fragment), it should restore the view properly based on the data.

Updating fragments from Activity in a ViewPager

I'm new to Android developing and I would really appreciate some help here.
I'm using a fragment that contains a TextView and I'm using 5 instances of the same MyFragment class.
In the activity, i got a button and a ViewPager, and I need the button to update all the fragment instances content, whenever its clicked.
Here's the Activity
public class MainActivity extends FragmentActivity {
final static String[] CONTENT = {"a", "b"};
ViewPager pager;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
List<MyFragment> fragments = new Vector<MyFragment>();
for(int i = 0; i < 5; i++){
MyFragment fragment = new MyFragment(CONTENT);
fragments.add(fragment);
}
PagerAdapter adapter = new PagerAdapter(this.getSupportFragmentManager(), fragments);
pager = (ViewPager) findViewById(R.id.viewpager);
pager.setAdapter(adapter);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
//method that isn't working
PagerAdapter adapter = (PagerAdapter)pager.getAdapter();
for(int i = 0; i < 5; i++){
MyFragment fragment = (MyFragment) adapter.getItem(i);
fragment.textView.setText(fragment.content[1]);
}
}
});
}
}
The Fragment
public class MyFragment extends Fragment{
String[] content;
TextView textView;
public MyFragment(String[] content) {
this.content = content;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_content, container, false);
textView = (TextView) view.findViewById(R.id.textView1);
textView.setText(content[0]);
return view;
}
}
And the FragmentPagerAdapter
public class PagerAdapter extends FragmentPagerAdapter{
List<MyFragment> fragments;
public PagerAdapter(FragmentManager fm, List<MyFragment> fragments) {
super(fm);
this.fragments = fragments;
}
#Override
public Fragment getItem(int arg0) {
return fragments.get(arg0);
}
#Override
public int getCount() {
return fragments.size();
}
}
The OnClick method gives me a NullPointerException whenever i try to access a fragment from the adapter which is less than adapter.getCurrentItem() - 1, or more than adapter.getCurrentItem() + 1.
Any idea on how to update all the fragments at the same time?
Thanks in advance.
The easiest way to update those fragments is to use your code and set the number of fragments that the ViewPager holds in memory to the number of total fragments - 1(so all fragments are valid no matter at what page you are). In your case:
pager.setOffscreenPageLimit(4); // you have 5 elements
You can still use the method from my comment with the method onPageScrollStateChanged(so the update will start the moment the user starts swiping) to see when the user is starting to swipe the pager and update the fragments to the left and right of the currently visible fragment, but this will be a bit difficult to get right so I recommend to go with the first option.
Some points regarding your code containing fragments:
If you nest the fragment class make it static so you don't tie it to the activity object.
Don't create a constructor for a Fragment besides the default one. If the framework needs to recreate the fragment it will call the default constructor and if it is not available it will throw an exception. For example, try to change the orientation of the phone/emulator and see what happens(this is one of the cases when Android will recreate the fragments). Last, use a custom name for the ViewPager's adapter, you use PagerAdapter which is the name of the super class of FragmentViewPager and it's very confusing for someone reading your code.
If you need to pass data to the Fragment you could use a creation method like the one below:
public static MyFragment newInstance(String text) {
MyFragment f = new MyFragment();
Bundle b = new Bundle();
b.putString("content", text);
f.setArguments(b);
return f;
}
The text will be available in MyFragment by using getArguments().getString("content");

Categories

Resources