I have a Fragment (lets call it RootFragment) containing aViewPager2 that switches between two fragments on tab1 and tab2. Only considering the fragment on tab2, it starts as showing Fragment 1, which contains a button. When the button is pressed, I'd like Fragment 2 to show up over Fragment 1 and added to the backstack for that page.
Here is the code for the RootFragment.
public class RootFragment extends Fragment {
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.root_fragment, container, false);
ViewPager2 pager = view.findViewById(R.id.myPager);
FragmentStateAdapter pagerAdapter = new ScreenSlidePagerAdapter(this);
pager.setAdapter(pagerAdapter);
TabLayout tabLayout = view.findViewById(R.id.myTabLayout);
new TabLayoutMediator(tabLayout, pager, (tab, position) -> {
if (position == 0) {
tab.setText("tab1");
} else {
tab.setText("tab2");
}
}).attach();
return view;
}
private static class ScreenSlidePagerAdapter extends FragmentStateAdapter {
public ScreenSlidePagerAdapter(Fragment fragment) {
super(fragment);
}
#NonNull
#Override
public Fragment createFragment(int position) {
if (position == 0) {
return new FragmentTab1(); // don't care about this right now
} else {
return new Fragment1();
}
}
#Override
public int getItemCount() {
return 2;
}
}
}
My initial thought would be to use a FragmentManager transaction in Fragment1 when the button is pressed to add Fragment 2 and update the backstack. However, I'm unsure what the correct container argument would be to use in that transaction.
Is the best way to achieve this to create a dummy Fragment just containing a FragmentContainerView as the Fragment instantiated by the ViewPager2's getFragment, have that fragment initially replace itself with Fragment1, then use that parent container for the transaction? Or is there a better way that avoids adding a dummy Fragment?
Related
In Dashboard Activity there is a Fragment on top which contains 3 fragments as view pagers inside it.The view pagers become blank and unresponsive (as shown in 2nd screenshot) in these cases:
When app is loaded from backstack
When font setting is changed while app is in background and called again from app stack
When the notification of my app is clicked and the click action redirects to the Dashboard Activity
The code for Fragment for tabs is:
public class MainActivityGraphViewPager extends Fragment {
public MainActivityGraphViewPager() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.fragment_main_activity_graph_pager, container, false);
ViewPager viewPager = v.findViewById(R.id.fgraph_pager);
TabLayout tabx = v.findViewById(R.id.tab_layout_dashboard);
tabx.setupWithViewPager(viewPager, true);
viewPager.setAdapter(new SectionPagerAdapterFeaturedgraphs(getActivity().getSupportFragmentManager()));
viewPager.setCurrentItem(1, false);
viewPager.setOffscreenPageLimit(3);
return v;
}
private class SectionPagerAdapterFeaturedgraphs extends FragmentPagerAdapter {
public SectionPagerAdapterFeaturedgraphs(FragmentManager supportFragmentManager) {
super(supportFragmentManager);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new ExpenseManagerPieChart();
case 1:
return new BudgetandFitness();
case 2:
return new ExpendsFragments();
default:
return null;
}
}
#Override
public int getCount() {
return 3;
}
}}
The MainActivityGraphViewPager.class is called in Dashboard Activity's onCreate():
MainActivityGraphViewPager mainActivityGraphPager=new MainActivityGraphViewPager();
this.getSupportFragmentManager().beginTransaction().replace(R.id.graph_pagers, mainActivityGraphPager).commit();
What can be the possible solutions to remove this unresponsiveness?
I have three Fragments attached to ViewPager. If i am Fragment1, and i have a button on Fragment1, i want on button click of that 'Fragment', access the Fragment3 but on the same time if i slide the page,i don't want to access the Fragment3. while sliding i want to access only up to Fragment2. How can i do this.
TabPagerAdapter.java:-
public class TabPagerAdapter extends FragmentStatePagerAdapter
{
public TabPagerAdapter(FragmentManager fragmentManager)
{
super(fragmentManager);
}
#Override
public Fragment getItem(int position)
{
switch (position)
{
case 0:
return new Fragment1();
case 1:
return new Fragment2();
case 2:
return new Fragment3();
}
return null;
}
#Override
public int getCount()
{
return 3;
}
I have implemented something very similar: Previously I had four fragments in my ViewPager, but then decided that I want to integrate two of those fragments more intimately by reducing the number of fragment that I can reach by sliding and switching back and forth between two fragments by using a button. The implementation took me a while, though. I did not manage to mess with the index of my fragmentList properly. In the end, the trick was to take out those two fragments from the ViewPager altogether and add a HolderFragment instead. Within this fragment I implemented the fragment transaction for hiding one and showing the other fragment.
Here is the code from my HolderFragment:
public class HolderFragment extends Fragment {
private Context context;
private Fragment currentFragment;
#Override
public void onAttach(Context context) {
super.onAttach(context);
this.context = context;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.fragment_holder, container, false);
OneFragment oneFragment =
(OneFragment) Fragment.instantiate(context, OneFragment.class.getName());
OtherFragment otherFragment =
(OtherFragment) Fragment.instantiate(context, OtherFragment.class.getName());
// oneFragment selected on view creation
FragmentManager fm = this.getChildFragmentManager();
fm.beginTransaction().add(R.id.holder_fragment_holder, oneFragment).commit();
fm.beginTransaction().add(R.id.holder_fragment_holder, otherFragment).hide(mediaFragment)
.commit();
currentFragment = oneFragment;
return view;
}
public void switchFragments(Fragment fragment) {
if (!fragment.equals(currentFragment)) {
this.getChildFragmentManager().beginTransaction().hide(currentFragment).commit();
currentFragment = fragment;
this.getChildFragmentManager().beginTransaction().show(currentFragment).commit();
}
}
}
Then, whenever the switch button is pressed just call holderFragment.switchFragments(oneFragment) or holderFragment.switchFragments(otherFragment) from the activity that holds the ViewPager (and a reference to the HolderFragment).
Try this
mViewPager.setCurrentItem(your_position, true);
I have an app that uses SectionsPagerAdapter to show 3 fragments within an activity (this part was automatically generated by Android Studio after selecting a tabbed activity). Right now, this is how I select what fragment to show:
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
if (position == 0) {
return new Fragment1();
} else if (position == 1) {
return new Fragment2();
} else {
return new Fragment3();
}
}
#Override
public int getCount() {
return 3;
}
}
I would like my app to start on Fragment 2 (i.e. position 1), to allow the user to either swipe right to go back to the first fragment, or to swipe left to move on to the next fragment.
How can I set the position to start at? I couldn't find any method to set the position number, is there one or should I do this another way?
If it helps, this is my onCreateView method of my MainActivity class:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
return rootView;
}
I don't think an Activity has onCreateView method. Its a method of a fragment. Your activity will have public void onCreate(Bundle savedInstanceState)
If my assumptions are not wrong, in the layout of your main activity you will have a android.support.v4.view.ViewPager.
and in your code you might declare your view pager variable and set the SectionsPagerAdapter to it. After that just do viewPager.setCurrentItem(1);
How can I set the position to start at?
Call setCurrentItem() on the ViewPager.
My application has to swipe in between fragments when a button is pressed. If I was hosting swipe tab and view pager inside an activity, I would do something like this.
((ParentActivityName) getActivity()).setCurrentItem(2, true);
Now I have a parent Fragment that hosts the slide tabs. It has the following method to set current child Fragment to viewpager.
public void setCurrentItem (int item, boolean smoothScroll) {
pager.setCurrentItem(item, smoothScroll);
}
On Click of "Next" button in one of the sliding tab Fragments, I am trying to call the method as
new FragUserRegistration().setCurrentItem(1,true);
But is simply returning a null object reference error. Any help would be much appreciated.
I worked it out simply by calling viewpager from parent Fragment to each sliding tab fragments and then the associated setCurrentItem method.
viewPager = (ViewPager)getActivity().findViewById(R.id.pager);
viewPager.setCurrentItem(int position, true);
//four swipe-able fragments so position - -> 1-3 (total count 4)
I am not sure where your click happens to set the current item.
Maybe you'll find this useful:
Your MainFragment:
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
//init view
MainViewPageAdapter mainViewPageAdapter = new MainViewPageAdapter(getChildFragmentManager());
yourViewPager.setAdapter(mainViewPageAdapter);
return view;
}
private class MainViewPageAdapter extends FragmentStatePagerAdapter {
private static final int MAX_COUNT = 2;
public MainViewPageAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Fragment fragment = null;
switch (position) {
case 1:
fragment = YourChildFragmentOne.getInstance();
break;
case 2:
fragment = YourChildFragmentTwo.getInstance();
break;
}
return fragment;
}
#Override
public int getCount() {
return MAX_COUNT;
}
}
In your ChildFragment(s):
(or create an abstract ChildFragment, which handles the click listener and create two instances of the abstract one)
private OnChildFragmentClickListener mClickListener;
#Override
public void onAttach(Context context) {
super.onAttach(context);
mClickListener = (OnChildFragmentClickListener) getParentFragment();
}
//call somewhere in your ChildFragment mClickListener.onChildFragClick(index);
public interface OnChildFragmentClickListener{
void onChildFragClick(int index);
}
Now let your MainFragment implement OnChildFragmentClickListener and call there:
#Override
public void onChildFragClick(int index){
yourViewPager.setCurrentItem(index);
}
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)
}
}