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];
}
}
Related
Following this example I'm trying to replace one fragement with another within a single tab of a ViewPager. I'm getting this exception:
java.lang.IllegalStateException: Can't change tag of fragment DailyWordFragment
class ViewPagerAdapter extends FragmentPagerAdapter {
private final FragmentManager fragmentManager;
Fragment fragment;
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
fragmentManager = manager;
}
#Override
public Fragment getItem(int position) {
switch (position){
case 0:
if(fragment == null){
fragment = DailyWordFragment.newInstance();
}
return fragment;
case 1:
if(fragment == null){
fragment = WordListFragment.newInstance(new WordListFragment.OnWordItemAddListener() {
#Override
public void onWordItemAddListener() {
fragmentManager.beginTransaction().remove(fragment).commit();
fragment = AddWordFragment.newInstance();
notifyDataSetChanged();
}
});
}
return fragment;
default:
return null;
}
}
#Override
public int getItemPosition(Object object) {
if(object instanceof WordListFragment && fragment instanceof AddWordFragment)
return POSITION_NONE;
return POSITION_UNCHANGED;
}
#Override
public int getCount() {
return 2;
}
}
According to other posts this exception seems to be caused by adding the same fragment repeatedly, or getting miscounting the number of fragments but I can't see where either of these are occurring if at all? Clearly I'm missing something. I would appreciate any help. Thank you.
Have you tried before you initialize the Fragment to set it to null?
#Override
public Fragment getItem(int position) {
fragment = null
switch (position){
case 0:
if(fragment == null){
fragment = DailyWordFragment.newInstance();
}
return fragment;
case 1:
if(fragment == null){
fragment = WordListFragment.newInstance(new WordListFragment.OnWordItemAddListener() {
#Override
public void onWordItemAddListener() {
fragmentManager.beginTransaction().remove(fragment).commit();
fragment = AddWordFragment.newInstance();
notifyDataSetChanged();
}
});
}
return fragment;
default:
return null;
}
}
To SWAP fragments in one tab of a View Pager you have to do it in a different way:
You need a fragment that goes in the ViewPager that acts as a container. which is managed by the ViewPager. This Fragment is allways there in the tab, never swaps or changes.
Inside that Fragment you have 2 child fragments that you can swap using transactions with the ChildFragmentManager. But the ViewPager doesn't even know about their existance.
I have an activity, which includes ViewPager with three fragments. In the second fragment i have ViewPager with two different fragments too.
This my MainActivityPagerAdapter which connected with ViewPager in MainActivity:
public class MainActivityPagerAdapter extends FragmentStatePagerAdapter implements LoginView.LoginStateChangeListener {
private static final String TAG = MainActivityPagerAdapter.class.getName();
private MapFragment mapFragment = new MapFragment();
private UserProfileFragment userProfileFragment = new UserProfileFragment();
private CompanyProfileFragment CompanyProfileFragment = new DiveCenterProfileFragment();
private NotificationsFragment notificationsFragment = new notificationsFragment();
public MainActivityPagerAdapter(FragmentManager manager) {
super(manager);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return mapFragment;
case 1:
return notificationsFragment;
case 2:
switch (MyApplication.getInstance().getSharedPreferenceHelper().getActiveUserType()) {
case DIVECENTER:
return companyProfileFragment;
case DIVER:
case INSTRUCTOR:
return userProfileFragment;
case NONE:
return userProfileFragment;
}
default:
return null;
}
}
#Override
public int getCount() {
return 3;
}
#Override
public int getItemPosition(Object object) {
Log.i(TAG, "getItemPosition");
return POSITION_NONE;
}
#Override
public CharSequence getPageTitle(int position) {
return null;
}
}
This my pager adapter for ViewPager at the second fragment:
public class NotificationsPagerAdapter extends FragmentStatePagerAdapter {
private PersonalNotificationsFragment personalNotificationsFragment = new PersonalNotificationsFragment();
private ActivityNotificationsFragment activityNotificationsFragment = new ActivityNotificationsFragment();
public NotificationsPagerAdapter(FragmentManager manager) {
super(manager);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return personalNotificationsFragment;
case 1:
return activityNotificationsFragment;
default:
return null;
}
}
#Override
public int getCount() {
return 2;
}
public void setPersonalNotificationsFragment(PersonalNotificationsFragment personalNotificationsFragment) {
this.personalNotificationsFragment = personalNotificationsFragment;
}
public void setActivityNotificationsFragment(ActivityNotificationsFragment activityNotificationsFragment) {
this.activityNotificationsFragment = activityNotificationsFragment;
}
#Override
public CharSequence getPageTitle(int position) {
return null;
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
So, there are two user types in app:
Simple user
Company
When user changing his type i need to refresh all fragments in MainActivity including fragments inside second fragment. For this I'm using notifyDataSetChanged() for MainActivityViewPager, it work correctly, but fragments inside second fragment don't update(onCreateView don't called, and i see only empty views).
In what may be a problem?
Use getChildFragmentManager() for inner viewPager
Use getChildFragmentManager() instead of getFragmentManager() for changing fragments inside the viewPager
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
There are some fragments in my activity.
private MainFragment mMainFragment;
... //other fragments
Here's my PagerAdapter.
private class MainPagerAdapter extends FragmentPagerAdapter {
...
#Override
public Fragment getItem(int position) {
if (DEBUG) {
Log.w(TAG, "position: " + position);
}
if (position == 0) {
mMainFragment = MainFragment.newInstance(PAGE_MAIN_INDEX);
return mMainFragment;
}
}
#Override
public int getCount() {
return FRAGMENT_COUNT;
}
}
Sometimes, when my app goes to background, the system kills the activity, then if my app return to foreground and system try restore my page. It looks everything is fine, every view shows correctly. But when I try to refresh my fragment, my app is crash. Log shows the fragment is null. I don't why. Any ideas?
Here's my Fragment.onCreate(Bundle savedInstanceState)
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mActivity = (SubmitTenderActivity) getActivity();
mInflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mResources = getResources();
}
getItem() does not get called again when a Fragment is being restored by the framework, so your mMainFragment is null after restoring. instantiateItem() method is called for every Fragment that is being restored by the framework so you must reassign mMainFragment again in this method. Change your code as following should solve your problem:
private class MainPagerAdapter extends FragmentPagerAdapter {
...
#Override
public Fragment getItem(int position) {
if (DEBUG) {
Log.w(TAG, "position: " + position);
}
if (position == 0) {
mMainFragment = MainFragment.newInstance(PAGE_MAIN_INDEX);
return mMainFragment;
}
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Object obj = super.instantiateItem(container, position);
if (position == 0) {
mMainFragment = (MainFragment)obj;
}
return obj;
}
#Override
public int getCount() {
return FRAGMENT_COUNT;
}
}
Try something like this
#Override
public Fragment getItem(int position) {
switch(position){
case 0:
return new Fragment1();
case 1:
return new Fragment2();
default:
break;
}
return null;
}
Its better to use below method in fragment:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mActivity = activity;
}
I would think you should move the return mMainFragment out of the if statement in method getItem.
Why don't you try this? Its working fine for me.
public class UpdateTabsAdapter extends FragmentPagerAdapter {
public UpdateTabsAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int index) {
switch (index) {
case 0:
return new AcademicDocumentsFragment();
case 1:
return new EmployementDocumentsFragment();
case 2:
return new UploadPhotoDetailsFragment();
}
return null;
}
#Override
public int getCount() {
return 3;
}
}
I have a ViewPager with 2 pages. The first page is a list which have different layouts depending on the type and orientation of the device. In case of portrait mode the list is a classic list and in landscape mode a grid should be shown.
For those two kind of layouts I created 2 different fragments: ResultListFragment and ResultGridFragment.
Here is my pager adapter:
public static class GSPagerAdapter extends FragmentStatePagerAdapter {
private Context mContext;
private FragmentManager mFragmentManager;
private AbstractResultFragment mResultFragment;
public GSPagerAdapter(FragmentManager fm, Context context) {
super(fm);
mContext = context;
mFragmentManager = fm;
}
#Override
public Fragment getItem(int i) {
Log.v("pager", "GSPagerAdapter.getItem(" + i + ")");
switch (i) {
case 0: {
// if the device is in portrait mode, use a list
if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ) {
Log.v("pager","return new list fragment");
mResultFragment = new ResultListFragment();
return mResultFragment;
// else use a grid
} else {
Log.v("pager","return new grid fragment");
mResultFragment = new ResultGridFragment();
return mResultFragment;
}
}
case 1: {
Log.v("pager", "return the new filter fragment");
return new FilterFragment();
}
default: {
Log.v("pager", "return null");
return null;
}
}
}
#Override
public CharSequence getPageTitle(int i) {
switch (i) {
case 0:
return "Result";
case 1:
return "Filter";
}
return "";
}
#Override
public int getCount() {
return 2;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Log.v("pager","instantiateItem: "+position);
return super.instantiateItem(container, position);
}
#Override
public int getItemPosition(Object object){
Log.v("pager","getItemPosition: "+object);
return POSITION_NONE;
}
}
OnResume of the Activity:
public void onResume() {
super.onResume();
Log.v("lifecycle", "MainActivity.onResume()");
if(mViewPager != null)
mGSPagerAdapter.notifyDataSetChanged();
}
What do I do wrong? I have read several questions and answers but I cannot figure out what is the problem.