My hierarchy is like this.A viewpager included 3 pages(all fragment),called
NewFragment,InvoledFragment and MeFragment,and outside the viewpager i set a Radiobutton to switch between Newfragment and another fragment named Hotfragment,for some reason i used hide(),show()to manage the switch,here is![enter image description here is my code
Page 1
Page 2
Page 3
RadioButton check listener
private class CheckedChangeListener implements RadioGroup.OnCheckedChangeListener {
#Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
switch (checkedId) {
case R.id.button_new:
mFragmentAtPos0 = newfrag;
fragTransaction = fragmentManager.beginTransaction();
if(!newfrag.isAdded()){
fragTransaction.hide(hotfrag).add(R.id.mainviewpager,newfrag).commit();
}
else{
fragTransaction.hide(hotfrag).show(newfrag).commit();
}
mFragmentAtPos0 = newfrag;
Toast.makeText(MainActivity.this, "NEW", Toast.LENGTH_SHORT).show();
break;
case R.id.button_hot:
fragTransaction = fragmentManager.beginTransaction();
if(!hotfrag.isAdded()){
fragTransaction.hide(newfrag).add(R.id.mainviewpager,hotfrag).commit();
}
else{
fragTransaction.hide(newfrag).show(hotfrag).commit();
}
mFragmentAtPos0 = hotfrag;
Toast.makeText(MainActivity.this, "HOT", Toast.LENGTH_SHORT).show();
break;
}
}
}
and the FragmentPagerAdapter
private class MyPagerAdapter extends FragmentPagerAdapter {
private final FragmentManager mFragmentManager;
public MyPagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
mFragmentManager = fm;
}
#Override
public Fragment getItem(int position) {
if(position == 0){
Log.d("check","111");
return mFragmentAtPos0;
}
else if(position == 1){
return(new InvolveFragment());
}
else {
return(new MeFragment());
}
}
#Override
public int getCount() {
return 3;
}
#Override
public int getItemPosition(Object object)
{
if (object instanceof NewFragment && mFragmentAtPos0 == hotfrag)
return POSITION_NONE;
return POSITION_UNCHANGED;
}
}
My problem is the HotFrag didn't show,but it worked (I put log code in hotfrag to check),and viewpager just works fine,I stucked here for several hours but still got no way to solve it.
There is a answer i found quite similar,it's here
Replace Fragment inside a ViewPager, but i worked with hide,show, not replace.
Anyone could help?Thanks a lot!
Related
I have problem with fragments in ViewPager. In my app I have 4 tabs implemented with FragmentStatePagerAdapter.
Here is my SectionsPageAdapter.java
public class SectionsPageAdapter extends FragmentStatePagerAdapter {
private String fragment0 = "";
private Fragment fragAt0;
private FragmentManager manager;
private String[] tabNames;
Context context;
public SectionsPageAdapter(FragmentManager fm, Context ctx, String lastFrag) {
super(fm);
context = ctx;
tabNames = ctx.getResources().getStringArray(R.array.tabs_name);
this.manager = fm;
this.listener = new fragmentChangeListener();
fragment0 = lastFrag;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return ActivityTypeFragment.newInstance(0);
case 1:
return EventsFragment.newInstance();
case 2:
return CalendarFragment.newInstance();
case 3:
if(fragAt0 == null){
switch(fragment0){
case "Chart":
fragAt0 = ChartFragment.newInstance(listener);
break;
case "Hour":
fragAt0 = HourChartFragment.newInstance(listener);
break;
case "Daily":
fragAt0 = DailyChartFragment.newInstance(listener);
break;
default:
fragAt0 = ChartFragment.newInstance(listener);
}
}
return fragAt0;
default:
return ActivityTypeFragment.newInstance(0);
}
}
#Override
public int getCount() {
return 4;
}
#Nullable
#Override
public CharSequence getPageTitle(int position) {
return tabNames[position];
}
#Override
public int getItemPosition(#NonNull Object object) {
if (object instanceof ChartFragment && fragAt0 instanceof DailyChartFragment
|| object instanceof HourChartFragment && fragAt0 instanceof DailyChartFragment)
{
return POSITION_NONE;
}
else if (object instanceof DailyChartFragment && fragAt0 instanceof ChartFragment
|| object instanceof HourChartFragment && fragAt0 instanceof ChartFragment){
return POSITION_NONE;
}
else if (object instanceof HourChartFragment && fragAt0 instanceof ChartFragment)
{
return POSITION_NONE;
}
else if (object instanceof ChartFragment && fragAt0 instanceof HourChartFragment
|| object instanceof DailyChartFragment && fragAt0 instanceof HourChartFragment)
{
return POSITION_NONE;
}
return POSITION_NONE;
}
public interface nextFragmentListener {
void fragment0Changed(String newFragmentIdentification, FragmentManager fragmentManager);
}
private nextFragmentListener listener;
private final class fragmentChangeListener implements nextFragmentListener {
#Override
public void fragment0Changed(String fragment, FragmentManager fm) {
fragment0 = fragment;
manager.beginTransaction().remove(fragAt0).commitNow();
switch (fragment) {
case "Chart":
fragAt0 = ChartFragment.newInstance(listener);
break;
case "Daily":
fragAt0 = DailyChartFragment.newInstance(listener);
break;
case "Hour":
fragAt0 = HourChartFragment.newInstance(listener);
break;
}
SectionsPageAdapter.this.notifyDataSetChanged();
}
}
public String getFragmentName()
{
return fragment0;
}
}
So in last tab I have a fragment - ChartFragment, where I have Spinner with options to select another chart. And when user selects another chart, this another chart reloads in this tab by calling method fragment0Changed that I have in my SectionsPageAdapter
Class. I found this solution in https://stackoverflow.com/a/27304530/1573414.
Here is my function onItemSelected which I have in every fragment that I want to be appeared in Charts tab
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (view != null) {
String selectedMenu = ((TextView) view).getText().toString();
if (selectedMenu == getString(R.string.best_appealing_chart)) {
LoadBestAppealingChart();
} else if (selectedMenu == getString(R.string.hour_chart)) {
chartListener.fragment0Changed("Hour", getActivity().getSupportFragmentManager());
}
else
{
chartListener.fragment0Changed("Daily", getActivity().getSupportFragmentManager());
}
}
else {
LoadBestAppealingChart();
}
}
So, everything works fine until I rotate device, after that selecting another chart does not work. App throwing the exception:
java.lang.IllegalStateException: Fragment host has been destroyed
at android.support.v4.app.FragmentManagerImpl.ensureExecReady(FragmentManager.java:2183)
at android.support.v4.app.FragmentManagerImpl.execSingleAction(FragmentManager.java:2211)
at android.support.v4.app.BackStackRecord.commitNow(BackStackRecord.java:643).
I’ve read the article https://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html,but in my case I have nor asynchronus methods where I would replace a fragment, nor I call it in Activity lifecycle methods. I make a fragment transaction in a onSelectionChanged method of my spinner. What I tried: using commitNowAllowingStateLoss() do not make things better, it just didn’t throw an exception, fragments still do not reloading; passing getSupportFragmentManager() parameter to fragment0Changed method doesn’t help either, but doing this does not throw any exception even if i use simple commitNow() method. But what helps is when after rotation I choose first or second tab of my app then go to my fourth tab(Charts tab), then I can switch(reload) my fragments without any problem. Interesting thing that switching to third tab(Calendar tab) does not help either. After switching to third tab and returning to fourth tab switching fragments still do not reloaded.
I think that its a common mistake on Android to communicate with Fragment passing listener. Listener on Fragment must be implemented by the Activity or a child Fragment. So you have to make your Activity or Fragment implements NextFragmentListener... Second, never make a reference to a Fragment on ViewPager, you are leaking the old fragment0
public class SubAdapter extends FragmentStatePagerAdapter
{
/* just pass the behavior in super() call so every time resume method will call so that you can reload any thing you want
But you should use subclass extending */
private String tile_list[];
public SubAdapter (FragmentManager fm, String[] tile_list)
{
super(fm,FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
this.tile_list = tile_list;
}
#Override
public Fragment getItem(int position)
{
Fragment fragment;
switch (position)
{
case 0:
fragment=new new_fragment();
break;
default:
fragment=new new_fragment();
}
return fragment;
}
#Override
public int getCount()
{
return tile_list.length;
}
#Nullable
#Override
public CharSequence getPageTitle(int position)
{
return tile_list[position];
}
}
I am working on an application that has tabs with viewpager. I am using fragmentstatepageradapter for handling the viewpager tabs. I am replacing the first fragment with nested fragments using the childFragmentmanager and adding the current fragment to the backstack. The control flow is as follows.
First tab fragment A -> Replaced with Fragment B -> Replaced with Fragment C ->Replaced with Fragment D.
Now when i click on a specific button in Fragment D, I need to go back to the fragment B, clearing the Fragments C,D from the backstack.
I tried
getSupportFragmentManager().popBackStack(
getSupportFragmentManager().getBackStackEntryAt(0).getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
But unsuccessfull. Can someone tell me the correct method to clear the backstack and navigate to fragment B from fragment D.?
This is my fragmentstatepageradapter
public class HomeViewPagerAdapter extends FragmentStatePagerAdapter {
CharSequence Titles[]; // This will Store the Titles of the Tabs which are
Context mcontext; // Going to be passed when ViewPagerAdapter is
int[] images; // created
int NumbOfTabs;
private Fragment mFragmentAtPos0;
SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
Fragment homefragment;
Context context;
FragmentManager fragmentmanager;
public HomeViewPagerAdapter(Context context, FragmentManager fm,
CharSequence mTitles[], int mNumbOfTabsumb) {
super(fm);
this.Titles = mTitles;
this.NumbOfTabs = mNumbOfTabsumb;
this.context = context;
this.fragmentmanager = fm;
}
#Override
public Fragment getItem(int position) {
// TODO Auto-generated method stub
if (position == 0)
{
SharedPreferences LoginPref = this.context.getSharedPreferences("login_credentials", Context.MODE_PRIVATE);
if (LoginPref.contains("login_pref") && LoginPref.getString("login_pref", "").equals("loggedin")) {
homefragment = new SearchHomeFragment();
} else {
homefragment = new HomeScreenFragment();
}
return homefragment;
} else if (position == 1)
{
HomeCategoriesFragment homecategoriesfragment = new HomeCategoriesFragment();
return homecategoriesfragment;
} else if (position == 2)
{
HomePriceComparisonFragment homepricecomparefragment = new HomePriceComparisonFragment();
return homepricecomparefragment;
} else if (position == 3)
{
HomeEnterFeedFragment homeenterfeedfragment = new HomeEnterFeedFragment();
return homeenterfeedfragment;
} else {
HomeMoreFragment homemorefragment = new HomeMoreFragment();
return homemorefragment;
}
}
#Override
public int getItemPosition(Object object) {
Log.e("getItemPosition", "getItemPosition");
if (object instanceof HomeScreenFragment)
return POSITION_NONE;
else
return POSITION_UNCHANGED;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
registeredFragments.put(position, fragment);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
registeredFragments.remove(position);
if(MyTabFragment.shouldDestroy) {
super.destroyItem(container, position, object);
MyTabFragment.shouldDestroy=false;
}
}
public void resetScreendata() {
if (homefragment != null) {
homefragment.getChildFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}
public void resettoSearch() {
if (homefragment != null) {
homefragment.getChildFragmentManager().popBackStack("search", FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}
public Fragment getRegisteredFragment(int position) {
return registeredFragments.get(position);
}
#Override
public CharSequence getPageTitle(int position) {
// TODO Auto-generated method stub
return Titles[position];
}
#Override
public int getCount() {
// TODO Auto-generated method stub
return NumbOfTabs;
}
}
And this is how i am replacing the fragment.
final SearchHomeFragment newFragment = new SearchHomeFragment();
FragmentTransaction transaction =getChildFragmentManager().beginTransaction();
transaction.setCustomAnimations(R.anim.slide_in_right,R.anim.slide_out_left,R.anim.slide_in_left,R.anim.slide_out_right);
transaction.replace(R.id.fragment_mainLayout, newFragment);
transaction.addToBackStack("search");
transaction.commit();
While you are adding fragments to backstack give them a name.
fragmentTransaction.addToBackStack("fragmentA");
fragmentTransaction.addToBackStack("fragmentB");
fragmentTransaction.addToBackStack("fragmentC");
fragmentTransaction.addToBackStack("fragmentD");
Then add this code on button click:
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FragmentManager fm = getActivity()
.getSupportFragmentManager();
fm.popBackStack ("fragmentB", FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
});
It looks like your code is popping everything off the stack of fragments. Try using a specific name or id for fragment B instead of using the id for the the very first fragment in the stack.
In my application I have a View pager that rotates 4 Fragments. Some of those fragments have other fragments inside them. The first time Viewpager starts a Fragment it works fine, but if I rotate around and return to the previous fragment, the inner fragments fail to show ( and I have noticed that getActivity() in them returns null).
For example, the app starts, I move through viewpager and see all the main fragments and they appear fine, then I scroll back, every main fragment that has inner fragments does not show properly.
I use "FragmentStatePagerAdapter" for my ViewPager and the code is below (variables names are for example).
private class ContentPagerAdapter extends FragmentStatePagerAdapter {
public ContentPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Parcelable saveState() {
return null;
}
#Override
public Fragment getItem(int pos) {
Fragment fragment = null;
try {
if (pos == POS1) {
if (fragment1 == null) {
fragment1 = Fragment1.newInstance();
}
fragment = fragment1;
} else if (pos == POS2) {
if (fragment2 == null) {
fragment2 = Fragment2.newInstance(something);
}
fragment = fragment2;
} else if (pos == POS3) {
if ( fragment3 == null ) {
fragment3 = Fragment3.newInstance(something3);
}
fragment = fragment3;
} else { //default
if (fragment4 == null) {
fragment4 = Fragment4.newInstance(something4);
}
fragment = fragment4;
}
}catch (Exception e) {
Log.e(AppConstants.APP_TAG, "Cannot create fragment", e);
finish();
}
return fragment;
}
#Override
public int getCount() {
int count = 4;
return count;
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
When instantiating the PagerAdapter, use getChildFragmentManager() instead of getFragmentManager(). If more info needed, similiar problem was solved here ViewPager in TabFragment not loading second time
I want to display another Fragment when the phone isn't connected to a WiFi network. Normally I'm displaying MainFragment, when no connection is available ErrorFragment.
I'm using mWifi.isConnected() to check if there is a connection.
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
if (mWifi.isConnected()) {
new PostTask(1).execute();
} else {
MainFragment.firstPageListener.onSwitchToNextFragment();
}
}
This is my code (inside MainFragment) to switch to the ErrorFragment, but it's giving me
11-23 20:57:29.588: E/AndroidRuntime(4025): java.lang.IllegalStateException: Recursive entry to executePendingTransactions
This is in my MainActivity (I'm using a ViewPager):
public class SectionsPagerAdapter extends FragmentPagerAdapter {
private final class FirstPageListener implements FirstPageFragmentListener {
public void onSwitchToNextFragment() {
mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit();
if (mFragmentAtPos0 instanceof MainFragment) {
mFragmentAtPos0 = new ErrorFragment(listener);
} else { // Instance of NextFragment
mFragmentAtPos0 = new MainFragment(listener);
}
notifyDataSetChanged();
}
}
private String[] titles = { "SEARCH", "LIST" };
private final FragmentManager mFragmentManager;
public Fragment mFragmentAtPos0;
private Context context;
FirstPageListener listener = new FirstPageListener();
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
mFragmentManager = fm;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0: // Fragment # 0
if (mFragmentAtPos0 == null) {
mFragmentAtPos0 = new MainFragment(listener);
}
return mFragmentAtPos0;
case 1: // Fragment # 1
return new ListFragment();
}
return null;
}
#Override
public int getCount() {
return titles.length;
}
#Override
public int getItemPosition(Object object) {
if (object instanceof MainFragment && mFragmentAtPos0 instanceof ErrorFragment) {
return POSITION_NONE;
}
if (object instanceof MainFragment && mFragmentAtPos0 instanceof MainFragment) {
return POSITION_NONE;
}
return POSITION_UNCHANGED;
}
#Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
}
public interface MainFragmentListener {
void onSwitchToNextFragment();
}
I tried to change Fragments with:
FragmentManager fragmentManager2 = getFragmentManager();
FragmentTransaction fragmentTransaction2 = fragmentManager2.beginTransaction();
ErrorFragment fragment2 = new ErrorFragment();
fragmentTransaction2.addToBackStack("xyz");
fragmentTransaction2.hide(MainFragment.this);
fragmentTransaction2.add(android.R.id.content, fragment2);
fragmentTransaction2.commit();
But when I swipe to the other Tab, the Fragments are overlapping... What should do? Thanks!
(The thumbs down is the ErrorFragment, the Button and On/Off Switch, is in ListFragment)
Try to remove
fragmentTransaction2.hide(MainFragment.this);
and change
fragmentTransaction2.add(android.R.id.content, fragment2);
to
fragmentTransaction2.replace(android.R.id.content, fragment2);
I've looked at so many questions here that I don't even know exactly what I'm looking for.
I have a simple app that uses a ViewPager. It has 3 tabs and there is a fragment for each tab. The first fragment contains a ListView. I want to be able to click on an element in the ListView and have that bring me to a different fragment.
So basically I want to remove the fragment that contained the ListView once an element is clicked and add in a new fragment. I've tried to do this in a few different ways with none working.
The last thing I tried was to edit the TabsPageAdapter once an element was clicked which pretty much works except when I press the back button it exits the app. Also it doesn't seem like the cleanest way of doing this.
TabsPagerAdapter
public class TabsPagerAdapter extends FragmentStatePagerAdapter {
SherlockFragment mf;
TalkingPointsFragment tpf;
ContactFragment gf;
int mode = 0;
public TabsPagerAdapter(FragmentManager fm) {
super(fm);
mf = new CanvassFragment();
tpf = new TalkingPointsFragment();
gf = new ContactFragment();
}
public TabsPagerAdapter(FragmentManager fm, int mode)
{
super(fm);
if(mode == 0)
{
mf = new CanvassFragment();
tpf = new TalkingPointsFragment();
gf = new ContactFragment();
}
else if(mode == 1)
{
mf = new ContactFragment();
tpf = new TalkingPointsFragment();
gf = new ContactFragment();
}
}
#Override
public SherlockFragment getItem(int index) {
switch (index) {
case 0:
return mf;
case 1:
return tpf;
case 2:
return gf;
}
return null;
}
#Override
public int getCount() {
// get item count - equal to number of tabs
return 3;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object)
{
}
}
The onclick code:
ViewPager viewp = (ViewPager) getActivity().findViewById(R.id.pager);
TabsPagerAdapter mAdapter = new TabsPagerAdapter(getActivity().getSupportFragmentManager(),1);
viewp.setAdapter(mAdapter);
mAdapter.notifyDataSetChanged();
layout_main.xml
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:actionBarTabStyle="#drawable/actionbar_tab_indicator">
I FragmentStatePagerAdapter too and when a user selects map from the ActionBar, I add the GoogleMapsFragment on top of the FragmentStatePagerAdapter:
// create a new map
mapsFragment = GoogleMapFragment.newInstance();
// Then we add it using a FragmentTransaction.
FragmentTransaction fragmentTransaction = getSupportFragmentManager()
.beginTransaction();
fragmentTransaction.add(android.R.id.content, mapsFragment,
FRAGMENT_MAP_TAG);
fragmentTransaction.commit();
For your case you would probably need to add it to the backstack too, which I have not because in my app the user has to navigate back using the ActionBar.
Thought this approach could work for you too when a user selects an item from your list.
Of course this has the disadvantage of not being able to use the FragmentStatePagerAdapter until the user navigates back. So I am not sure wether that would be acceptable for your app.
Ok, so this took a bit more code that I imagined. Hope you get the idea:
public class MainClass extends FragmentActivity implements CanvassCallback {
// save a single reference to ViewPager and TabsPagerAdapter
private ViewPager mViewPager;
private TabsPagerAdapter mAdapter;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.mViewPager = (ViewPager) findViewById(R.id.pager);
this.mAdapter = new TabsPagerAdapter(getSupportFragmentManager(), this);
mViewPager.setAdapter(mAdapter);
...
}
// from the CanvassCallback interface
public void itemSelected() {
mAdapter.canvassSelected();
mAdapter.notifyDataSetChanged();
}
#Override
public void onBackPressed() {
if (mViewPager.getCurrentItem() == 0 && mAdapter.isCanvassSelected() {
mAdapter.canvassSelected();
mAdapter.notifyDataSetChanged();
}
else {
super.onBackPressed();
}
}
}
Mockup of your CanvassFragment showing the callback
public class CanvassFragment extends SherlockFragment {
public interface CanvassCallback {
public void itemSelected();
}
private CanvassCallback canvassCallback;
public void setCanvassCallback(CanvassCallback canvassCallback) {
this.canvassCallback = canvassCallback;
}
...
// The onClick of your item
private void onClick() {
// notify your activity that an item was selected
canvassCallback.itemSelected();
}
}
The registeredFragments are not strictly needed but I think it provides some value if your need to call methoods on your Fragment from activity.
public class TabsPagerAdapter extends FragmentStatePagerAdapter {
// see upvoted answer from http://stackoverflow.com/questions/8785221/retrieve-a-fragment-from-a-viewpager
SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
private boolean canvassSelected = false;
private CanvassCallback canvassCallback;
public TabsPagerAdapter(FragmentManager fm, CanvassCallback canvassCallback) {
super(fm);
this.canvassCallback = canvassCallback;
}
public void canvassSelected() {
canvassSelected = !canvassSelected;
}
public boolean isCanvassSelected() {
return canvassSelected;
}
#Override
public SherlockFragment getItem(int index) {
switch (index) {
case 0:
if (canvassSelected)
return new ContactFragment();
CanvassFragment canvassFragment = new CanvassFragment();
// this ensures that your Activity gets notified when an item is clicked
canvassFragment.setCanvassCallback(canvassCallback);
return canvassFragment;
case 1:
return new TalkingPointsFragment();
case 2:
return new ContactFragment();
}
return null;
}
#Override
public int getCount() {
// get item count - equal to number of tabs
return 3;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Log.d(TAG, "instantiateItem " + position);
Fragment fragment = (Fragment) super.instantiateItem(container, position);
registeredFragments.put(position, fragment);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
Log.d(TAG, "destroyItem " + position);
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
}