I want to use 3 fragments within an Android App, I red:
Creating-and-Using-Fragments
.But I want to use the viewpager for swiping and displaying the fragments like explained in:
ViewPager-with-FragmentPagerAdapter
.But this code or the default code of Android Studio Example, use newInstance to create the instances, each time it is needed and destroy when not needed.
// Returns the fragment to display for that page
#Override
public Fragment getItem(int position) {
switch (position) {
case 0: // Fragment # 0 - This will show FirstFragment
return FirstFragment.newInstance(0, "Page # 1");
case 1: // Fragment # 0 - This will show FirstFragment different title
return FirstFragment.newInstance(1, "Page # 2");
case 2: // Fragment # 1 - This will show SecondFragment
return SecondFragment.newInstance(2, "Page # 3");
default:
return null;
}
}
but I want to create once for all :
// Within an activity
private FragmentA fragmentA;
private FragmentB fragmentB;
private FragmentC fragmentC;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
fragmentA = FragmentA.newInstance("foo");
fragmentB = FragmentB.newInstance("bar");
fragmentC = FragmentC.newInstance("baz");
}
}
and only hide/show them, as in the example:
// ...onCreate stays the same
// Replace the switch method
protected void displayFragmentA() {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (fragmentA.isAdded()) { // if the fragment is already in container
ft.show(fragmentA);
} else { // fragment needs to be added to frame container
ft.add(R.id.flContainer, fragmentA, "A");
}
// Hide fragment B
if (fragmentB.isAdded()) { ft.hide(fragmentB); }
// Hide fragment C
if (fragmentC.isAdded()) { ft.hide(fragmentC); }
// Commit changes
ft.commit();
}
But how to do that with a FragmentPagerAdapter
public Fragment getItem(int position) no longer has to be like that
Also, how to access a data
public double [][] tableaux;
that is in the MainActivity from one Fragment.
Data will be persistant if I assign a pointer of the Fragment I just created in the MainActivity onCreate to point on the MainActivity.tableaux
You can return you pre-initialized fragments in getItem method.
#Override
public Fragment getItem(int position) {
switch (position) {
case 0: return fragmentA;
case 1: return fragmentB;
case 2: return fragmentC;
default: return null;
}
UPDATE: #Alok is right. You should not have fragment references in Activity. And I suggest instead of increasing offscreen page limit using setOffscreenPageLimit, you should consider saving & restoring fragment states using public void onSaveInstanceState(Bundle) and savedInstanceState argument of public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState).
#Lotfi you don't need to save your fragment inside activity, it is prone to leaks .
Also you can assign id to each of your fragment and later when you need to reuse that fragment you can ask fragment manager to return the fragment by passing ie. by calling fragment manager method findFragmentByID() inside your getItem().
Related
I currently got a ViewPager that has 3 tabs. Each tab is a fragment. I made a custom dialogFragment to login. I want that logindialog to open only when i go on my 3rd tab. In my onCreateView, i created a new object of my logindialog and show. My problem is, whenever i switch from my 1st tab to 2nd, the dialog also appears and i don't want that.
This is my viewpager adapter
public Fragment getItem(int position) {
switch (position) {
case 0:
return frag1;
case 1:
return frag2;
case 2:
return frag3;
default:
return null;
}
}
My 3rd Frag
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
root = inflater.inflate(R.layout.fragment_tab_annonce, container, false);
DialogFragment loginDialog = new DialogFragment();
loginDialog .show(getActivity().getSupportFragmentManager(), "customLogin");
return root;
}
ViewPager will create and retain "offscreen" pages as a performance optimization (with one offscreen page in either direction by default). What this means in practice is that when you are on Page 1, Page 2 has already been created off-screen. When you switch from Page 1 to Page 2, now Page 3 is created off-screen (and Page 1 is retained in case you want to switch back to it).
That means that onCreateView() is simply not the right place to show the error message.
You could perhaps create your own subclass of ViewPager.SimpleOnPageChangeListener and override onPageSelected() to show the dialog.
ViewPager.OnPageChangeListener listener = new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
if (position == 2) {
// show dialog
}
}
};
viewPager.addOnPageChangeListener(listener);
I'm using a FragmentPagerAdapter in the MainActivity. I use res\layout\fragment_text.xml file to create two fragments each with different data entered by the user. When I try to findViewById(), It returns data from a different fragment. Here is the applicable code:
//this is from my public class SectionsPagerAdapter extends FragmentPagerAdapter
#Override
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page.
switch (position){
case 0: return MultipleChoiceSingleAnswerFragment.newInstance(position + 1);
**case 1: return TextFragment.newInstance("1", "2");**
**case 2: return TextFragment.newInstance("10", "20");**
case 3: return ScoreFragment.newInstance("100", "200");
default: return null;
}
}
//here I collect the fragment tags as they are being instantiated
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment newFragment = (Fragment) super.instantiateItem(container, position);
// save the appropriate fragment tag depending on position
fragmentTags[position] = newFragment.getTag();//store in array of Strings
return newFragment;
}
In an onclick listener I try to retrieve the fragment and show text that the user entered, into an EditText view, using a toast. This code is called in another Fragment that has just a TextView and a Button
public void onScorePressed(){
int pageCount = mSectionsPagerAdapter.getCount();
**TextFragment textfragment = (TextFragment) getSupportFragmentManager().findFragmentByTag(fragmentTags[1]);
CharSequence text = textfragment.getAnswer()**;<<<<<main problem in call,see below
int duration = Toast.LENGTH_SHORT;
Context context = getApplicationContext();
Toast toast = Toast.makeText(context, text, duration);
toast.show();
}
In line above `textfragment.getAnswer()' calls this method which exists in two instances of the same fragment class:
public class TextFragment extends Fragment {
//other code for building a Fragment
.....
//This method return text entered by user as well as the mParam1 used in the instantiation above in Fragment getItem case 1:;
public CharSequence getAnswer(){
EditText txt = (EditText) root.findViewById(R.id.text_answer);
return "mParam1=" + mParam1 + "text_answer=" + txt.getText();
}
The getAnswer method returns
"mParam1=1 text_answer=text entered"
from the second TextFragment instantiated in case 2 above. This shows that I'm actually getting the correct TextFragment because mParam1 matches the parameter passed in the instantiation. I need a better way to get the EditText View's text. Any help is appreciated. Thanks.
Posts I used in my research:
https://stackoverflow.com/a/12805033/9226205
https://developer.android.com/guide/components/fragments.html
https://developer.android.com/reference/android/support/v4/app/FragmentManager.html
You don't have tags set for your fragments and that's why you can't retrieve the fragments by tag. As far as I know, fragment tag can only be set in the XML layout or when the fragment is created using transaction's add or replace methods. I would expect a NullPointerExeption in
fragmentTags[position] = newFragment.getTag();
since the tag was not previously set.
As a solution, I suggest an array that holds references to your fragments, instead of an array of tags:
Fragment[] fragments = new Fragment[NUMBER_OF_FRAGMENTS];
So getItem method could look like this:
#Override
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page.
Fragment fragment;
switch (position){
case 0:
fragment = MultipleChoiceSingleAnswerFragment.newInstance(position + 1);
break;
case 1:
fragment = TextFragment.newInstance("1", "2");
break;
case 2:
fragment = TextFragment.newInstance("10", "20");
break;
case 3:
fragment = ScoreFragment.newInstance("100", "200");
default:
fragment = null;
}
fragments[position]=fragment;
return fragment;
}
Then you can completely omit instantiateItem method because it is not needed.
Finally, in onScorePressed method you get the reference to the right fragment by
TextFragment textfragment = fragments[i];
Hope this helps.
The background
I have single activity in my App which loads 2 fragments based on some menu item selection
public class ActivityMain extends AppCompatActivity{
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState == null) {
loadFragment(1); // DEFAULT FRAGMENT, AT THE BEGINNING
}
}
..........................
..........................
// This method is called above, ALSO onItemClick in the Navigation Drawer (code not included for brevity)
public void loadFragment(int position) {
Fragment fragment = null;
switch (position) {
case 1:
fragment = new Fragment1();
break;
case 2:
fragment = new Fragment2();
break;
default:
break;
}
if (fragment != null) {
getFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment, "frag_" + position).addToBackStack(null).commit();
}
}
}
"Fragment1" is a simple fragment with a fixed text in a TextView. "Fragment2" uses SlidingTabLayout to load a Fragment "FragmentViewPager" in a viewpager using FragmentStatePagerAdapter.
The issue I am facing:
Even if I remove "Fragment2" from the Activity's Frame Layout (using getFragmentManager().beginTransaction().remove), Fragment "FragmentViewPager" does not get destroyed, rather it resumes every time Activity resumes.
Question
Why FragmentViewPager is not destroyed with "Fragment2"?
If you are using FragmentStatePagerAdapter then this will not destroy fragment and when you swipe and come back to that fragment it will show old data without refresh. Because of not destroyed by viewpager.
I have a PagerAdapter which creates 3 fragments.
In the MainActivity I set the ViewPager like this:
ViewPager pager = (ViewPager) findViewById(R.id.pager);
pager.setOffscreenPageLimit(2);
pager.setAdapter(new PagerAdapter(getSupportFragmentManager()));
the pager.setOffScreenPageLimit(2) is from here https://stackoverflow.com/a/11852707/1662033, to make sure OnViewCreated is called once for each fragment.
Here is my PagerAdapter class:
public class PagerAdapter extends FragmentPagerAdapter {
public PagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return "Home";
case 1:
return "Live";
case 2:
return "Gallery";
default:
return null;
}
}
#Override
public int getCount() {
return 3;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new HomeFragment();
case 1:
return new LiveFragment();
case 2:
return new GalleryFragment();
default:
return null;
}
}
}
In the current code: all of the fragments's onCreateView, onActivityCreated etc are called once, at the beginning and that's it.
The issue I am having is - in one of the fragments (LiveFragment) I have a custom view which connects to a camera and shows the live stream.
What I want is - to inflate the view of LiveFragment only when the user navigates to the fragment, instead of how its now - its inflated at the beginning with the other fragments.
Is there a way to call onCreateView only when fragment is chosen?
FragmentPagerAdapter creates all the Fragments and has all of them in memory at all time. i.e. All your Fragments are created only once and you can navigate around them.
FragmentStatePagerAdapter creates and has only 3 Fragments (the current Fragment and the left and right Fragments to the current one) in memory at any given time, by default. You cannot reduce that number. However, you can increase the number Fragments in memory by using the viewpager.setOffScreenPageLimit().
Since you have only 3 Fragments, all your 3 fragments are created when the Viewpager is initialised. You can track which Fragment is currently visible on the screen using viewpager.addOnPageChangeListener(). Using this you can change the View of your LiveFragment from dummy one to actual View only when the Fragment is currently visible.
public static class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm, HomeActivity activity) {
super(fm);
}
#Override
public Fragment getItem(int i) {
switch (i) {
case 0:
return new FragmentOne();
case 1:
return new FragmentTwo();
case 2:
return new FragmentThree();
default:
return null;
}
}
#Override
public int getCount() {
return 3;
}
#Override
public CharSequence getPageTitle(int position) {
switch (position)
{
case 0 : return "Fragment 1";
case 1 : return "Fragment 2";
case 2 : return "Fragment 3";
//TODO
default : return "";
}
}
}
The above is the adapter for view pager. The problem is :
when clicking Tab 1, onResume() of FragmentOne and FragmentTwo get called.
going Tab1 to Tab 2, onResume() of FragmentThree called.
going Tab2 to Tab 3, onPause() of FragmentOne called.
going Tab3 to Tab 2, onResume() of FragmentOne called.
Why all the Fragments are called in this way?
The answer is in the way how ViewPager works. By default it stores three fragments: the one which is shown currently, one previous and one next (if present). If you want to prevent Pager from loading extra Fragments you may call its method setOffscreenPageLimit(0), but actually I wouldn't recommend doing it. So it is not an issue it is working in a way it is intended to work.
UPD: By the way if you want to make your Pager more memory efficient, you may use FragmentStatePagerAdapter instead of FragmentAdapter, it actually destroys fragments instead of simply detaching them.
UDP2: I'm terribly sorry but offscreenPageLimit can be not fewer than 1. So in your case since your Fragment shows a ProgressDialog it passes its host Activity as context. That's why ProgressDialog shows up when adjacent Fragment is shown. So in onResume() method of your Fragment you should check UservisibleHint of the Fragment in order to check if this is the Fragment, shown to user. If it is true then show dialog if not, then dismiss it. Like this:
#Override
public void onResume() {
super.onResume();
if (getUserVisibleHint()) {
progressDialog = ProgressDialog.show(getActivity(), "blah-blah-blah", "blah-blah-blah");
}
else {
if (progressDialog != null && progressDialog.isShowing()) progressDialog.dismiss();
}
}