I want to make a quiz application. So far I have 3 activities - home, quiz, score. Since the quiz activity contains multiple equivalent views ( image header, question and 4 answer buttons ), I did some reading and decided that
ViewPager with FragmentStatePagerAdapter show do the trick. So I made an xml template and inflated couple of test views and it was all looking good, until I started handling the user interaction.
I want to simulate a toggle button and there is only one correct answer to each question, so selecting one button should deselect the previous one ( if any ). When the button is pressed I change my Question model, then I find all 4 buttons with findViewById and reset their color filter. Then I set that filter back on my selected button. To determine which question model to update I use the current fragment position, which I have set ( using setTag, in fragment's onCreate ) in my template root view.
This is how I call my fragmets:
public Fragment getItem(int position) {
Question question = Repository.findById(position);
int correctAnswerBtnId;
switch (question.getCorrectAnswerIndex()) {
case 0: correctAnswerBtnId = R.id.quiz_answer_0_btn; break;
case 1: correctAnswerBtnId = R.id.quiz_answer_1_btn; break;
case 2: correctAnswerBtnId = R.id.quiz_answer_2_btn; break;
case 3: correctAnswerBtnId = R.id.quiz_answer_3_btn; break;
this.ACTIVITY_ROOT.setTag(question.getID());
Fragment fragment = new QuestionFragment();
Bundle args = new Bundle();
args.putSerializable(QuestionFragment.QUESTION, question);
fragment.setArguments(args);
return fragment;
}
My QuestionFragment onCreateView is as per documentation:
public View onCreateView(
LayoutInflater inflater,
ViewGroup container,
Bundle questionData) {
this.rootView = inflater.inflate(
R.layout.layout_question_template,
container,
false);
Bundle args = getArguments();
this.question = (Question) args.getSerializable(QuestionFragment.QUESTION);
populateInflatable();
rootView.findViewById(R.id.layout_question_template_root).setTag(this.question.getID());
return rootView;
}
In populateInflatable I use this.rootView to fintViewById and populate it with my question data. Then I change the color of a button, if there is selected one from the Question.
On button click I call selectAnserButton :
public void selectAnswerButton(View selectedButton) {
int questionId =
(int) this.activityRoot.findViewById(
R.id.layout_question_template_root).getTag(); //??
unSelectAllButtons();
changeColor(selectedButton);
Repository.findById(questionId).selectAnswer(selectedButton.getId());
}
Where unSelectAllButtons represents buttonToUnSelect.getBackground().clearColorFilter(); on the four buttons. and Repository is just a static class with example question data.
It all goes terribly wrong, when I have more then one view. On each fragment I inflate the same xml with same View IDs, as I have defined them. And as I now understand calling findViewById retrieves not one, but all views with that Id from my current, but also from my previous and next fragment as well. So every time I want to select my current fragment's view, I also modify the same view in the previous and next fragments as well. You can imagine how this is problematic. This makes me feel I have a fundamental mistake, because I don't think there is supposed to be more then one View with same ID.
I really don't understand how I should do this using ViewPager. At this point it feels like I'm trying to make a wood carving, but instead I am hacking the framework to pieces. There must be a better way to do this with ViewPager.
RESOLVED: Thanks to Soo Chun Jung for pointing me to the answer. In short what got it working for me was:
Passing my Question model id to each fragment with Bundle.
Storing each fragment in inside an ArrayMap with fragment position as key and fragment as value.
Getting each individual fragment from my selectAnswer function is now easy: first get the current fragment's position with myViewPager.getCurrentItem, then calling getter function which returns a fragment on the current position.
Now that I have the fragment I can easily change its button's because they are kept as private fields, assigned in the 'onCreateView` method.
Hope it's helpful~
adapter
class CustomAdapter extends FragmentPagerAdapter {
private final String[] TITLES = {"A", "B"};
private final String TAG = CustomAdapter.class.getSimpleName();
private final ArrayList<Fragment> mFragments;
private final FragmentManager fm;
public CustomAdapter(FragmentManager fm) {
super(fm);
mFragments = new ArrayList<>(getCount());
this.fm = fm;
}
#Override
public CharSequence getPageTitle(int position) {
return TITLES[position];
}
#Override
public int getCount() {
return TITLES.length;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
Log.d(TAG, "destroyItem position = " + position);
mFragments.remove(object);
super.destroyItem(container, position, object);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Object object = super.instantiateItem(container, position);
mFragments.add((Fragment) object);
return object;
}
#Override
public Fragment getItem(int position) {
Log.d(TAG, "getItem position = " + position);
if (position == 0) {
return MyFragmentA.newInstance();
} else if (position == 1) {
return MyFragmentB.newInstance();
}
return null;
}
public MyFragmentA getMyFragmentA() {
synchronized (mFragments) {
for (Fragment f : mFragments) {
if (f instanceof MyFragmentA) {
return (MyFragmentA) f;
}
}
}
return null;
}
public MyFragmentB getMyFragmentB() {
synchronized (mFragments) {
for (Fragment f : mFragments) {
if (f instanceof MyFragmentB) {
return (MyFragmentB) f;
}
}
}
return null;
}
}
Fragment class
public class MyFragmentB extends Fragment {
...
public updateYourUI(){
//update something
}
}
Usage
mPager = (CustomViewPager) findViewById(R.id.pager);
mAdapter = new CustomAdapter(getChildFragmentManager());
mPager.setAdapter(mAdapter);
mAdapter.getMyFragmentB().updateYourUI();
for your comment below If you only have one kind Fragment. You can modify some function like this.
public static MyFragmentB newInstance(int ID) {
MyFragmentB fragment = new MyFragmentB();
Bundle bundle = new Bundle();
bundle.putInt("ID", ID);
fragment.setArguments(bundle);
return fragment;
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
myID = getArguments().getInt("ID");
....
}
public int getMyID() {
return myID;
}
public MyFragmentB getMyFragmentByID(String id) {
synchronized (mFragments) {
for (Fragment f : mFragments) {
if (f instanceof MyFragmentB) {
MyFragmentB temp = (MyFragmentB)f;
if(temp.getID.equals(id){
return (MyFragmentB) f;
}
}
}
}
return null;
}
Related
enter image description here
Before reading the question, please refer to image.
I am using viewpager to show the fragment.
Problem
In the fragment, I have used two edittext lets say editText1, editText2 now the problem is how I will get the editText data. I can only get the editText values when user click on next button but the next button is outside of fragment. How do I access the editText outside the fragment.
Before downvoting the question, let me know the reason so that I can improve my question.
Fragment java class
// newInstance constructor for creating fragment with arguments
public static BpDetails newInstance(int page) {
BpDetails fragmentFirst = new BpDetails();
Bundle args = new Bundle();
args.putInt("someInt", page);
fragmentFirst.setArguments(args);
return fragmentFirst;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
page = getArguments().getInt("someInt", 0);
}
// Inflate the view for the fragment based on layout XML
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.bp_details, container, false);
Log.i("View ",view.toString());
Log.i("DOB is ",Long.toString(Constants.dob));
systolic =(EditText) view.findViewById(R.id.systolic);
diastolic =(EditText) view.findViewById(R.id.diastolic);
return view;
}
ViewPager Activity
vpPager = (ViewPager) findViewById(R.id.view_pager);
adapterViewPager = new MyPagerAdapter(getSupportFragmentManager());
vpPager.setAdapter(adapterViewPager);
Fragment fragment=adapterViewPager.getItem(prevPage);
if (fragment.getClass().equals(BpDetails.class)){
Log.i("Call ","Yes");
}
findViewById(R.id.btn_prev).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// checking for last page
// if last page home screen will be launched
int current = getItem(-1);
if (current!=0)
prevPage=current-1;
if (current < 4) {
// move to next screen
vpPager.setCurrentItem(current);
} else {
//final reached.
}
}
});
findViewById(R.id.btn_next).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// checking for last page
// if last page home screen will be launched
int current = getItem(+1);
if (current!=0)
prevPage=current-1;
System.out.println("Prev page "+prevPage);
if (current < 4) {
// move to next screen
Fragment prevFragment=adapterViewPager.getItem(prevPage);
} else {
//final reached.
}
}
});
}
private int getItem(int i) {
return vpPager.getCurrentItem() + i;
}
public static class MyPagerAdapter extends FragmentPagerAdapter {
private static int NUM_ITEMS = 4;
private static int mSelectedPosition;
public MyPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
//mSelectedPosition=selectedPosition;
}
// Returns total number of pages
#Override
public int getCount() {
return NUM_ITEMS;
}
// 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 BasicDetails.newInstance(0);
case 1:
return BpDetails.newInstance(1);
case 2:
return BslDetails.newInstance(2);
case 3:
return Summary.newInstance(3);
default:
return null;
}
}
}
Create two getters inside your fragment like this.
public String getSystolic(){
return this.systolic.getText().toString();
}
public String getDiastolic(){
return this.diastolic.getText().toString();
}
BpDetails fr = (BpDetails)myAdapter.getItem(myViewPager.getCurrentItem());
String systolicString = fr.getSystolic();
I had a similar issue. .getItem() instantiates a new Fragment, so upon calling myAdapter.getItem(...) you would be getting null for all elements in the Fragment, but not null for the Fragment.
When I fixed this, what I had to do was create another method inside of MyPagerAdapter called getInstantiatedFragment:
public Fragment getInstantiatedFragment(int position)
{
return fragments.get(position);
}
fragments is a new field for the class:
private ArrayList<Fragment> fragments = new ArrayList<>();
I would override getItem() (as you have done already) and change it to:
#Override
public Fragment getItem(int position)
{
switch (position) {
case 0:
BasicDetails basicDetails = BasicDetails.newInstance(0);
fragments.add(basicDetails);
return basicDetails;
...
}
where you're adding the fragment to fragments before returning, then you would call:
BpDetails fr = (BpDetails)myAdapter.getInstantiatedItem(myViewPager.getCurrentItem());
to get the instance of the created fragment and then call
String systolicString = fr.getSystolic();
if you're using the previous answer's method.
This is so that you can keep track of the instantiated fragments in fragments. I'm sure there are better ways.
I am currently using Material Design in an Android app that I am making. In this app, I am using the Material Design tab layout to display some information that I am receiving. However when I tap the tabs, the animation is not smooth, and it is very abrupt. Sliding to go to the other tab, however is very smooth.
mTabLayout = (TabLayout) findViewById(R.id.chem_tab_layout);
mGenericAdapter = new GenericPagerAdapter(getSupportFragmentManager());
mPager = (ViewPager) findViewById(R.id.view_pager);
mPager.setAdapter(mGenericAdapter);
//Notice how the Tab Layout links with the Pager Adapter
mTabLayout.setTabsFromPagerAdapter(mGenericAdapter);
//Notice how The Tab Layout and View Pager object are linked
mTabLayout.setupWithViewPager(mPager);
mPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout){
#Override
public void onPageSelected(int position) {
mGenericAdapter.notifyDataSetChanged();
}
});
That is my code for setting the adapter, etc.
This is my custom adapter code for the tabs:
class GenericPagerAdapter extends FragmentStatePagerAdapter {
public GenericPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
ChemGridActivity.MyFragment myFragment = new ChemGridActivity.MyFragment();
return myFragment;
}
#Override
public int getCount() {
return 3; //returns number of tabs that need to be created
}
#Override
public CharSequence getPageTitle(int position) {
if (position == 0) return "Chemistry";
if (position == 1) return "Mathematics";
if (position == 2) return "Physics";
else return null;
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
I feel that the choppy transition between tabs is caused by the overriden method onPageSelected method when I add onPageChangeListener. What do I add to this method to make tapping on tabs a smoother animation?
Without knowing much about the internals of your classes, I imagine the problem is not that you have a listener, but what you are doing inside that listener.
In the case of most adapters notifyDataSetChanged() will cause it to re-render the entire view again (including all pages).
Seeing as you haven't specified what the intent here with the notification is, it's hard to tell you how you can do this in an alternative way, but you do need to do something less intensive if you want the animation to remain smooth.
I suspect you just want to change which fragment is shown, in which case just use the FragmentManager where necessary, remembering to reuse fragments which have already been seen once.
EDIT Based on additional info in comments
#Override
public Fragment getItem(int position) {
//POSITION_SOMETHINHG would be one of a set of constants to indicate hwa to display
return ChemGridActivity.MyFragment.newInstance(ChemGridActivity.MyFragment.POSITION_SOMETHINHG);
}
public class ChemGridActivity.MyFragment ... {
private static final String KEY_DISPLAY_TYPE = "KEY_DISPLAY_TYPE";
public static final int POSITION_SOMETHINHG = 11111;
public static MyFragment newInstance(int display) {
MyFragment f = new MyFragment();
Bundle bund = new Bundle();
bund.putInt(KEY_DISPLAY_TYPE, display);
f.setArguments(bund);
return f;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (args != null) {
mDisplay = args.getInt(KEY_DISPLAY_TYPE, 0);
}
}
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.my_layout, container, false);
//TODO: change something based on mDisplay
return view;
}
I have a FragmentStatePagerAdapter which shows the details of current Fragment in the next Fragment. Here is the Page Adapter
public class CustomerPagerAdapter extends FragmentStatePagerAdapter
{
public SectionsPagerAdapter(FragmentManager fm)
{
super(fm);
}
#Override
public Fragment getItem(int position) {
Gson gson = new Gson();
Customer selectedCustomer = mCustomers.get(position);
String serializedCustomer = gson.toJson(selectedCustomer);
return CustomerDetailsFragment.newInstance(serializedCustomer);
}
#Override
public int getCount()
{
return mCustomers.size();
}
#Override
public CharSequence getPageTitle(int position)
{
Customer selectedCustomer = mCustomers.get(position);
String CustomerTitle = selectedCustomer.getFirstName() + " " + selectedCustomer.getLastName();
return CustomerTitle;
}
}
And here is the Fragment where the detail are displayed
public static class CustomerDetailsFragment extends Fragment {
private Customer passedInCustomer;
public CustomerDetailsFragment() {
// Required empty public constructor
}
public static CustomerDetailsFragment newInstance(String serializedCustomer){
CustomerDetailsFragment fragment = new CustomerDetailsFragment();
if (serializedCustomer != null && !serializedCustomer.isEmpty()){
Bundle args = new Bundle();
args.putString("customerInfo", serializedCustomer);
fragment.setArguments(args);
}
return fragment;
}
private void getPosition(){
Bundle args = getArguments();
if (args != null && args.containsKey("customerInfo")){
String serializedCustomer = (getArguments().getString("customerInfo"));
Gson gson = new Gson();
passedInCustomer = gson.fromJson(serializedCustomer, Customer.class);
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
mRootView = inflater.inflate(R.layout.fragment_Customer_details, container, false);
if (passedInCustomer != null) {
showCustomerInfo();
}
return mRootView;
}
}
The problem stems from the fact that getItem is called twice for each swipe and the information that need to be displayed are contained in an List so when getItem is called twice the second object in the list is fetched and displayed in the screen where the first object was supposed to be displayed.
Has anyone dealt with displaying an nth number of items in a scrollable ViewPager where you have to create the Fragments on demand. If yes, can you give me suggestions how to deal with this.
Thanks
I notice that this problem is showing only when I have same fragments with diferent values ( what is probably in the most case ). So after long searching for right answer, i decided to in getItem() method every odd time return one fragment and every even time return another fragment ( These fragment are completely same, except their names:
#Override
public Fragment getItem(int position) {
if(position % 2 == 0){
return FragmentGalleryImage.newInstance(images.get(position), activity);
}else{
return FragmentGalleryImage2.newInstance(images.get(position), activity);
}
}
And i solved problem nice and smooth.
I solved the problem by calling on pager.getCurrentItem() to get the position of the currently displayed item.
I have created two fragments in a ViewPager , when I click on first one , Second fragment is taking the click.
This issue puts me in another position, when I create two instance from same fragment but with different data.
{
#Override
public Fragment getItem(int index) {
switch (index) {
case 1:
return FragmentBrandList.getInstance(tabs.getBrandList2(), 19,
title);
case 0:
return FragmentBrandList.getInstance(tabs.getBrandList1(), 19,
title);
}
return null;
}
#Override
public int getCount() {
return 2;
}
}
After creating ViewPager , both the fragments get created correctly , but when I click on any thing in the first fragment , the click event gets fired in second fragment not in the first fragment.
EDIT
public class TabsPagerAdapter extends FragmentPagerAdapter {
public TabsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int index) {
switch (index) {
case 1:
return FragmentBrandList.getInstance(tabs.getBrandList2(), 19,
title);
case 0:
return FragmentBrandList.getInstance(tabs.getBrandList1(), 19,
title);
}
return null;
}
#Override
public int getCount() {
return 2;
}
in FragmentBrandList
public class FragmentBrandList extends Fragment {
ArrayList<Brand> brandList = new ArrayList<Brand>();
int discoverID;
RecyclerView listView;
LinearLayoutManager mLayoutManager;
public static FragmentBrandList getInstance(ArrayList<Brand> brandList,
int discoverID, String title) {
FragmentBrandList frag = new FragmentBrandList();
Bundle b = new Bundle();
b.putSerializable("brandList", brandList);
b.putInt("discoverID", discoverID);
b.putString("title", title);
frag.setArguments(b);
return frag;
}
public FragmentBrandList() {
}
String title = "";
View v;
boolean isInflated = false;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (v == null) {
v = inflater.inflate(R.layout.fragment_list_view_brownbg,
container, false);
isInflated = true;
} else {
isInflated = false;
((ViewGroup) v.getParent()).removeView(v);
}
return v;
}
MainActivity activity;
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (isInflated) {
activity = (MainActivity) getActivity();
initView();
}
}
public void initView(){
title = getArguments().getString("title");
discoverID = getArguments().getInt("discoverID");
listView = (RecyclerView) v.findViewById(R.id.listView);
mLayoutManager = new LinearLayoutManager(getActivity());
listView.setItemAnimator(new DefaultItemAnimator());
listView.setHasFixedSize(true);
listView.setLayoutManager(mLayoutManager);
listView.setAdapter(new BrandListRecAdapter(getActivity(),
R.layout.single_item_listview, brandList));
}
#Override
public void onResume() {
// handle on click
((BrandListRecAdapter) listView.getAdapter())
.setOnItemClickListener(new ItemClickListener() {
#Override
public void onItemClickListener(final int pos, View v) {
activity.replaceCurrentFragment(
FragmentBrandDetails.getInstance(
brandList.get(pos), "bank"), true,
true);
}}
EDIT
i think problem cause
when create second fragment , listview.onclick is overwrite first one !!
how can solve this peb?
EDIT
thank you to every one try to help me
problem is already because i use same adapter and same fragment
when second fragment created it is overwrite on item click
so when click in item is called second one !!!
Just put this android:clickable="true" in every fragment layout, and this will not happen again.
This is just an educated guess, but because a ViewPager will always create at least one extra Fragment on either side of the currently visible fragment, you may be creating two virtually identical Fragments in parallel, assigning them both onItemClickListeners in onResume and as such they are both responding to item clicks when an item is pressed on either Fragment.
You could try moving the onItemClickListener to the ViewHolder in your Adapter, rather assigning it in onResume. In addition, I wonder what a Brand object looks like in your RecyclerView, and whether it wouldn't be simpler to pass the current ViewPager page as a parameter in getInstance, and use this to access an Array containing the information necessary to fill your RecyclerView rows.
Here is a very brief example of how your ViewHolder may look:
class MyRecyclerViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener
public MyRecyclerViewHolder(View itemView) {
itemView.setOnClickListener(this);
//etc.
Problem
A Fragment is not reattached to its hosting ViewPager after returning from another fragment.
Situation
One Activity hosting a Fragment whose layout holds a ViewPager (PageListFragment in the example below). The ViewPager is populated by a FragmentStateViewPagerAdapter. The single Fragments hosted inside the pager (PageFragment in the example below) can open sub page lists, containing a new set of pages.
Behaviour
All works fine as long as the back button is not pressed. As soon as the user closes one of the sub PageLists the previous List is recreated, but without the Page that was displayed previously. Swiping through the other pages on the parent PageList still works.
Code
A sample application can be found on github:
Activity
public class MainActivity extends FragmentActivity {
private static final String CURRENT_FRAGMENT = MainActivity.class.getCanonicalName() + ".CURRENT_FRAGMENT";
public static final String ARG_PARENTS = "Parents";
public void goInto(String mHostingLevel, String mPosition) {
Fragment hostingFragment = newHostingFragment(mHostingLevel, mPosition);
addFragment(hostingFragment);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
addBaseFragment();
}
private void addBaseFragment() {
Fragment hostingFragment = newHostingFragment("", "");
addFragment(hostingFragment);
}
private Fragment newHostingFragment(String mHostingLevel, String oldPosition) {
Fragment hostingFragment = new PageListFragment();
Bundle args = new Bundle();
args.putString(ARG_PARENTS, mHostingLevel + oldPosition +" > ");
hostingFragment.setArguments(args);
return hostingFragment;
}
private void addFragment(Fragment hostingFragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragmentSpace, hostingFragment, CURRENT_FRAGMENT);
transaction.addToBackStack(null);
transaction.commit();
}
}
PageListFragment
public class PageListFragment extends Fragment {
private String mParentString;
public PageListFragment() {
// Required empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_hosting, container, false);
}
#Override
public void onResume() {
mParentString = getArguments().getString(MainActivity.ARG_PARENTS);
ViewPager viewPager = (ViewPager) getView().findViewById(R.id.viewPager);
viewPager.setAdapter(new SimpleFragmentStatePagerAdapter(getFragmentManager(),mParentString));
super.onResume();
}
private static class SimpleFragmentStatePagerAdapter extends FragmentStatePagerAdapter {
private String mHostingLevel;
public SimpleFragmentStatePagerAdapter(FragmentManager fm, String hostingLevel) {
super(fm);
this.mHostingLevel = hostingLevel;
}
#Override
public android.support.v4.app.Fragment getItem(int position) {
PageFragment pageFragment = new PageFragment();
Bundle args = new Bundle();
args.putString(MainActivity.ARG_PARENTS, mHostingLevel);
args.putInt(PageFragment.ARG_POSITION, position);
pageFragment.setArguments(args);
return pageFragment;
}
#Override
public int getCount() {
return 5;
}
}
}
PageFragment
public class PageFragment extends Fragment {
public static final String ARG_POSITION = "Position";
private String mHostingLevel;
private int mPosition;
public PageFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View contentView = inflater.inflate(R.layout.fragment_page, container, false);
setupTextView(contentView);
setupButton(contentView);
return contentView;
}
private void setupTextView(View contentView) {
mPosition = getArguments().getInt(ARG_POSITION);
mHostingLevel = getArguments().getString(MainActivity.ARG_PARENTS);
TextView text = (TextView) contentView.findViewById(R.id.textView);
text.setText("Parent Fragments " + mHostingLevel + " \n\nCurrent Fragment "+ mPosition);
}
private void setupButton(View contentView) {
Button button = (Button) contentView.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
openNewLevel();
}
});
}
protected void openNewLevel() {
MainActivity activity = (MainActivity) getActivity();
activity.goInto(mHostingLevel, Integer.toString(mPosition));
}
}
After a lengthy investigation it turns out to be a problem with the fragment manager.
When using a construct like the one above the fragment transaction to reattach the fragment to the page list is silently discarded. It is basically the same problem that causes a
java.lang.IllegalStateException: Recursive entry to executePendingTransactions
when trying to alter the fragments inside the FragmentPager.
The same solution, as for problems with this error, is also applicable here. When constructing the FragmentStatePagerAdapter supply the correct child fragment manager.
Instead of
viewPager.setAdapter(new SimpleFragmentStatePagerAdapter(getFragmentManager(),mParentString));
do
viewPager.setAdapter(new SimpleFragmentStatePagerAdapter(getChildFragmentManager(),mParentString));
See also: github
What Paul has failed to mention is, if you use getChildFragmentManager, then you will suffer the "blank screen on back pressed" issue.
The hierarchy in my case was:
MainActivity->MainFragment->TabLayout+ViewPager->AccountsFragment+SavingsFragment+InvestmentsFragment etc.
The problem I had was that I couldn't use childFragmentManagerfor the reason that a click on the item Account view (who resides inside one of the Fragments of the ViewPager) needed to replace MainFragment i.e. the entire screen.
Using MainFragments host Fragment i.e. passing getFragmentManager() enabled the replacing, BUT when popping the back-stack, I ended up with this screen:
This was apparent also by looking at the layout inspector where the ViewPager is empty.
Apparently looking at the restored Fragments you would notice that their View is restored but will not match the hierarchy of the popped state. In order to make the minimum impact and not force a re-creation of the Fragments I re-wrote FragmentStatePagerAdapter with the following changes:
I copied the entire code of FragmentStatePagerAdapter and changed
#NonNull
#Override
public Object instantiateItem(#NonNull ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
...
}
with
#NonNull
#Override
public Object instantiateItem(#NonNull ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.detach(f);
mCurTransaction.attach(f);
return f;
}
}
...
}
This way I am effectively making sure that that the restored Fragments are re-attached to the ViewPager.
Delete all page fragments, enabling them to be re-added later
The page fragments are not attached when you return to the viewpager screen as the FragmentStatePagerAdapter is not re-connecting them. As a work-around, delete all the fragments in the viewpager after popbackstack() is called, which will allow them to be re-added by your initial code.
[This example is written in Kotlin]
//Clear all fragments from the adapter before they are re-added.
for (i: Int in 0 until adapter.count) {
val item = childFragmentManager.findFragmentByTag("f$i")
if (item != null) {
adapter.destroyItem(container!!, i, item)
}
}