Child fragment in View Pager Fragment disappears after swap - android

So this is a bit of a complex question, but I will try to make it as easy to follow as possible.
To start, I have a main activity with a view pager. There are 2 pages in it, with 2 actionbar tabs linking each page.
I also have a drawer that has 4 options, the first 2 are the 2 pages in the view pager, and the second 2 are pages that aren't. If you select one of the second 2, I configured my ViewPager to swap out the fragments with 2 other ones. All of these fragments are singletons, so that switching back and forth doesn't cause any memory issues.
On switching to the second set of fragments, one of the fragments has a frame in it that I use to swap out child fragments when a button is selected. This is where the problem lies. After that fragment loads the first time, it works fine. But if you use the drawer to switch to the first set, and then back to the second again, the child fragment is gone. It then tends to crash after you do anything else after that.
This is the crash log for it
01-10 15:55:05.272: E/MessageQueue-JNI(21034): java.lang.NullPointerException: Attempt to invoke virtual method 'void android.support.v4.app.FragmentManagerImpl.performPendingDeferredStart(android.support.v4.app.Fragment)' on a null object reference
If I play around a bit with the code , I get something like this. These two errors are the ones I seem to produce the most:
01-10 16:00:13.072: E/AndroidRuntime(22227): java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.ClassLoader android.support.v4.app.FragmentActivity.getClassLoader()' on a null object reference
I can continue to call replace() on the frame view, so it's not going away. Therefore I'm guessing that's not the issue. It
Here is some relevant code for it.
The Fragment's code:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.calculator, container, false);
final FragmentManager fragmentManager = getChildFragmentManager();
if(afFrag == null && topFrag == null){
afFrag = CalculatorAfFragment.getInstance();
topFrag = CalculatorTopFragment.getInstance();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.calc_frame, afFrag, "afFrag").commit();
}
RadioGroup typeGroup = (RadioGroup) view.findViewById(R.id.type_buttons);
typeGroup.setOnCheckedChangeListener(new OnCheckedChangeListener()
{
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (checkedId == R.id.af_button){
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.calc_frame, afFrag, "afFrag").commit();
}
else if (checkedId == R.id.top_button){
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.calc_frame, topFrag, "topFrag").commit();
}
}
});
return view;
The FragmentStatePagerAdapter code:
#Override
public Fragment getItem(int position) {
if(tools){
return toolsFragmentsList.get(position);
} else {
return guidesFragmentsList.get(position);
}
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
public void swapFragments(int position){
if (tools){
tools = false;
stores = false;
notifyDataSetChanged();
this.setPrimaryItem(mPager, position-1, getItem(position - 1));
} else {
tools = true;
notifyDataSetChanged();
this.setPrimaryItem(mPager, position-4, getItem(position - 4));
}
}

Try setting a unique ID on the ViewPager via mPager.setId(View.generateViewId()).

Related

Remove or Clear All Fragments From ViewPager

I'm populating view pager through fragments likes this
pagerAdapter = new PagerAdapter(getSupportFragmentManager());
mViewPager = (ViewPager) findViewById(R.id.container);
for (int i = 0; i < 10; i++) {
newfragment fragobj = new newfragment();
fragobj.setArguments(bundle);
pagerAdapter.addFragment(fragobj, "Category");
}
mViewPager.setAdapter(pagerAdapter);
mViewPager.setCurrentItem(page);
I want to clear all fragments and add new fragments after clearing. If I add new fragments now it ads on with old fragments.
I tried many stack overflow answers but none of them worked for me.
the fragment was already getting removed and destroyed. the issue was of viewpages.
Only this worked for me
myViewPager.setSaveFromParentEnabled(false);
Cheers !
Add this method in ViewPagerAdapter and call it when you want to clear fragments from viewPager
public void clear() {
FragmentTransaction transaction = manager.beginTransaction();
for (Fragment fragment : mFragmentList) {
transaction.remove(fragment);
}
mFragmentList.clear();
transaction.commitAllowingStateLoss();
}
mViewPager : is the view you are using to set you Fragment
mViewPager = (YourViewPager) findViewById(R.id.myPager);
TABLE : is just a Integer list of the position of all my Fragments
public void destroyAllItem() {
int mPosition = mViewPager.getCurrentItem();
int mPositionMax = mViewPager.getCurrentItem()+1;
if (TABLE.size() > 0 && mPosition < TABLE.size()) {
if (mPosition > 0) {
mPosition--;
}
for (int i = mPosition; i < mPositionMax; i++) {
try {
Object objectobject = this.instantiateItem(mViewPager, TABLE.get(i).intValue());
if (objectobject != null)
destroyItem(mViewPager, TABLE.get(i).intValue(), objectobject);
} catch (Exception e) {
Log.i(TAG, "no more Fragment in FragmentPagerAdapter");
}
}
}
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
if (position <= getCount()) {
FragmentManager manager = ((Fragment) object).getFragmentManager();
FragmentTransaction trans = manager.beginTransaction();
trans.remove((Fragment) object);
trans.commit();
}
}
With ViewPager2 (1.0.0) and FragmentStateAdapter I also found the current view not to be recreated after updating the data set using notifyDataSetChanged, even when the data corresponding to the current view is changed. Apparently, notifyDataSetChanged doesn't perform a very thorough investigation of what has been changed and assumes the current view to be still correct.
The workaround I found was to simply set the same adapter that I had already set for the previous data set again:
viewPager2.setAdapter(myFragmentStateAdapter);
Apparently, setting an adapter makes it flush the cached views, which makes very good sense of course. Luckily, it doesn't check whether it's the same adapter.
I use navController fragment and in one fragment I also had viewPager with few slides set in a "parent fragment, which was part of the nav tree".
Sadly after the parent fragment was destoryed, the frags created within it for view pager were not getting cleared and they were restored while resuming the app, even tho they couldn't be shown!!
I've set adapter to null in parent's fragment onDestroy()-. and it clears all frags used inside viewPager.
If you have similiar problem, this wokrs 100%.

Showing a fragment with different parameters in tabs of a view pager

I want to show a same fragment for example fragment having activities of a day of week in a viewpager for all days with different data. I will be giving the dayNumber parameter to each fragment being instantiated and showing related activities. The problem is I see same fragment in each tab no matter what parameter I passed. I think the last fragment added or instantiated by a pager overrides all the other tab fragments instance. Because when I open a list item in expendableList View it is opened in all fragments of the pager.
This is how I am using the pager and fragment.
Pager
mPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
// When swiping between pages, select the
// corresponding tab.
if (bar.getNavigationMode() == ActionBar.NAVIGATION_MODE_TABS)
if (bar.getSelectedNavigationIndex() != position)
bar.setSelectedNavigationItem(position);
// should be changed when some solution comes.
if (tab == 0) {
Fragment ev;
if ((ev = (Fragment) mPagerAdapter.instantiateItem(mPager,
0)) instanceof frTimetable)
((frTimetable) ev).refresh(day.Monday);
} else if (tab == 1) {
Fragment ac;
if ((ac = (Fragment) mPagerAdapter.instantiateItem(mPager,
1)) instanceof frTimetable)
((frTimetable) ac).refresh(day.Tuesday);
}
tab = position;
}
Fragement
{
//class other methods
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fr_timetable, container, false);
ExpandList = (ExpandableListView) view.findViewById(R.id.expActivityView);
//I will change the list items in refresh method of the fragment for a day type
ExpListItems = new ArrayList<Items>(Timetable_Provider.getAllActivites());
ExpAdapter = new ExpandListAdapter(getActivity(), ExpListItems);
ExpandList.setAdapter(ExpAdapter);
return view;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
instantiateItem() is certainly not what you want. That is called by ViewPager, not by a consumer of a ViewPager.
Normally, you would provide the data to the ViewPager as part of setting up the pages, inside of your PagerAdapter. For example, this sample app uses the arguments Bundle to pass the page number of the page to the Fragment that is the implementation of the page.
If the data inside a page needs to be updated, ideally the page itself determines on its own that this is needed and handles it. Or, use an event bus (e.g., LocalBroadcastManager, greenrobot's EventBus, Square's Otto) to publish information that relevant pages can pick up. There is no great way to get at an existing page from outside of the page itself using FragmentPagerAdapter or FragmentStatePagerAdapter (e.g., to have an activity push data into a page), which is one of the reasons I wrote ArrayPagerAdapter.

How do I use get a ViewPager to let go of it's fragments when switching orientation?

My application has a layout which differs based on the orientation and device size. On larger devices it is composed of two views visible at the same time. On smaller devices the layout is a ViewPager that lets the user switch between the two panes. This is a typical pattern.
The problem is that the ViewPager appears to stick around, and keeps its fragments. However, the same fragment instance is not kept for TextRenderFragment. Instead, it appears that the class is created anew every time. Since the fragments are kept inside the ViewPager, I end up having two sets of content views. This causes problems restoring state because the two sets of views don't share a Bundle.
Given that this is such a common pattern, I expected to find a discussion about how to do this properly, but have been unable. I would either like to have the ViewPager let go of its fragments, or find a way to use the same content fragment instances in both the ViewPager and the static layout, depending on orientation.
The construction looks like this:
#Override
public View onCreateView(final LayoutInflater inflater,
final ViewGroup container, final Bundle saved_instance_state) {
if (saved_instance_state != null) {
current_book = saved_instance_state.getInt("book");
current_chapter = saved_instance_state.getInt("chapter");
}
// Inflate the layout for this fragment
final View v = inflater.inflate(R.layout.fragment_text_render,
container, false);
_configure_content_fragments(v);
return v;
}
private void _configure_content_fragments(final View v) {
final ViewPager vp = (ViewPager) v.findViewById(R.id.tr_view_pager);
bl = new DocLoader();
boolean add_fragments = false;
final FragmentManager mgr = getFragmentManager();
primary_content_fragment = (PrimaryContent) mgr
.findFragmentByTag("primary_content_fragment");
secondary_content_fragment = (SecondaryContent) mgr
.findFragmentByTag("secondary_content_fragment");
if (primary_content_fragment == null) {
primary_content_fragment = new PrimaryContent();
secondary_content_fragment = new SecondaryContent();
primary_content_fragment.setNavLocation(current_book,
current_chapter);
add_fragments = true;
}
if (add_fragments && vp == null) {
final android.support.v4.app.FragmentTransaction tx = getFragmentManager()
.beginTransaction();
tx.add(R.id.tr_primary_content_container, primary_content_fragment,
"primary_content_fragment");
tx.add(R.id.tr_secondary_content_container,
secondary_content_fragment, "secondary_content_fragment");
tx.commit();
}
}
The initialization of the ViewPager looks like this:
#Override
public void onStart() {
super.onStart();
_configure_view_pager(getView());
}
private void _configure_view_pager(final View v) {
final ViewPager vp = (ViewPager) v.findViewById(R.id.tr_view_pager);
if (vp != null) {
final FragmentManager mgr = getFragmentManager();
final FragmentStatePagerAdapter adapter = new FragmentStatePagerAdapter(
mgr) {
#Override
public int getCount() {
return 2;
}
#Override
public Fragment getItem(final int item) {
switch (item) {
case 0:
return primary_content_fragment;
case 1:
return secondary_content_fragment;
}
return null;
}
};
vp.setAdapter(adapter);
}
}
After spending much time on this problem, and receiving no answers here, it appears that there is no easy way to do this. You could interrogate the FragmentManager for the parent and manually deal with things that way, but I have decided that doing things like this whole thing is the wrong approach to the general problem.
We have changed our approach to use the DrawerLayout. This fixes all of the problems that I was experiencing, and provides a nice UI.

Fragments view is null when orientation changed

Im having some problems when it comes to porting my app from the normal activity style to the fragment style. Im beginning to notice that when a fragment gets recreated, or popped from the backstack it loses its views. When I say that Im talking about a listview in particular. What im doing is im loading items into the listview, then rotating the screen. When it goes back through, it gets a nullpointerexception. I debug it and sure enough the listview is null. Here is the relevant code to the fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstanceState) {
return inflater.inflate(R.layout.sg_question_frag, viewGroup, false);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
list = (ListView)getActivity().findViewById(R.id.sgQuestionsList);
if (savedInstanceState != null) {
catId = savedInstanceState.getInt("catId");
catTitle = savedInstanceState.getString("catTitle");
}
populateList(catId, catTitle);
}
And here is how it is called (keep in mind there are a few other fragments that im working with as well)
#Override
public void onTopicSelected(int id, String catTitle) {
// TODO Auto-generated method stub
FragmentManager fm = this.getSupportFragmentManager();
SGQuestionFragment sgQuestFrag = (SGQuestionFragment) fm.findFragmentByTag("SgQuestionList");
FragmentTransaction ft = fm.beginTransaction();
//If the fragment isnt instantiated
if (sgQuestFrag == null) {
sgQuestFrag = new SGQuestionFragment();
sgQuestFrag.catId = id;
sgQuestFrag.catTitle = catTitle;
//Fragment isnt there, so we have to put it there
if (mDualPane) {
//TO-DO
//If we are not in dual pane view, then add the fragment to the second container
ft.add(R.id.sgQuestionContainer, sgQuestFrag,"SgQuestionList").commit();
} else {
ft.replace(R.id.singlePaneStudyGuide, sgQuestFrag, "SqQuestionList").addToBackStack(null).commit();
}
} else if (sgQuestFrag != null) {
if (sgQuestFrag.isVisible()) {
sgQuestFrag.updateList(id, catTitle);
} else {
sgQuestFrag.catId = id;
sgQuestFrag.catTitle = catTitle;
ft.replace(R.id.sgQuestionContainer, sgQuestFrag, "SgQuestionList");
ft.addToBackStack(null);
ft.commit();
sgQuestFrag.updateList(id, catTitle);
}
}
fm.executePendingTransactions();
}
What I would ultimately want it to do is to completely recreate the activity, forget the fragments and everything and just act like the activity was started in landscape mode or portrait mode. I dont really need the fragments there, I can recreate them progmatically with some saved variables
If you want to get a reference to a view from within a Fragment always look for that View in the View returned by the getView() method. In your case, at the time you look for the ListView the Fragment's view probably isn't yet attached to the activity so the reference will be null. So you use:
list = (ListView) getView().findViewById(R.id.sgQuestionsList);

Avoid recreating same view when perform tab switching

Current, I have 2 Fragments, which is switch-able through ActionBar's tab.
getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
ActionBar.Tab newTab = getSupportActionBar().newTab();
newTab.setText("history");
newTab.setTabListener(new TabListenerHistoryFragment>(this, "history",
HistoryFragment.class));
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
// Check if the fragment is already initialized
if (mFragment == null) {
// If not, instantiate and add it to the activity
mFragment = Fragment.instantiate(mActivity, mClass.getName());
mFragment.setRetainInstance(true);
ft.add(android.R.id.content, mFragment, mTag);
} else {
// If it exists, simply attach it in order to show it
ft.attach(mFragment);
}
}
I realize the first time of my Activity (This activity is holding 2 fragments) being launched, Fragments' methods will be called in the following sequence.
onCreate -> onCreateView -> onStart
When I perform Tab switching, and then Tab switching back to the same Fragment, the following methods will be called again.
onCreateView -> onStart
I just wish to retain the same GUI view state, when Tab is being switched back.
I want my chart continue to be zoomed into previous level.
I want my chart horizontal scroll stay at previous level.
I want my list continue scroll stay at previous level.
...
I know that I can save/restore simple variables using the following method when Tab switching
android fragment- How to save states of views in a fragment when another fragment is pushed on top of it
But, that is not something I want, as my GUI state is pretty difficult to describe within whole bunch of primitive values.
I try the following approach. Of course it won't work, as I am getting the following runtime error.
public class HistoryFragment extends Fragment {
View view = null;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (this.view != null) {
return this.view;
}
this.view = inflater.inflate(R.layout.history_activity, container, false);
}
}
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
I realize the following demo example is able to preserve its fragment GUI state (For instance, the position of vertical scroll of list) when there is Tab switching. But I guess, perhaps it is because they are using ListFragment? As I do not find they perform any special handling to preserve GUI state.
com.example.android.apis.app.FragmentTabs
com.example.android.apis.app.LoaderCursor.CursorLoaderListFragment
May I know, how I can avoid from recreating same view when perform tab switching?
I had the same problem, and tried to follow the suggestion in the error message.
I tried the following code, and it worked for me.
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) {
if (mMyView == null) {
mMyView = new MyView(getActivity());
} else {
((ViewGroup) mMyView.getParent()).removeView(mMyView);
}
return mPuzzleView;
}
I started searching for a simple solution for this many hours ago and finally stumbled across the answer by #roger which saved me lots of hair....
When using the ViewPager in other implementations, I could simply call:
mViewPager.setOffscreenPageLimit(//number of pages to cache);
So, I was very surprised it took me so many hours to resolve this. The example he gave wasn't entirely clear though, so for the sake of completeness, here is the code I use for the Fragments in my FragmentTabHost
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class FragmentExample extends Fragment {
private View rootView;
public FragmentExample() {
}
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
if (rootView == null) {
rootView = inflater.inflate(R.layout.fragment_example_layout, container, false);
// Initialise your layout here
} else {
((ViewGroup) rootView.getParent()).removeView(rootView);
}
return rootView;
}
}
I searched for the following key phrases which I'm adding here, in the hope that I may save someone else from the frustration I've just experienced!
FragmentTabHost save Fragment state
FragmentTabHost views recreated
FragmentTabHost cache Fragments
FragmentTabHost onCreateView Fragment destroyed
The following solution works for me. It prevents Fragment's onCreateView to be called when switching tabs.
Activity's onCreate should add all fragments and hide all except the one for the first tab:
ft.add(R.id.fragment_content, secondTabFragment);
ft.hide(secondTabFragment);
ft.add(R.id.fragment_content, firstTabFragment);
ft.show(firstTabFragment);
ft.commit();
currentFragment = firstTabFragment;
Activity's onTabSelected should just hide the current fragment and show the fragment corresponding to the chosen tab.
ft.hide(currentFragment);
ft.show(chosenFragment);
ft.commit();
currentFragment = chosenFragment;
Beware that changing the device orientation will restart your Activity and then recreate your Fragments. You can avoid that by adding this configChanges in your Manifest:
<activity android:configChanges="keyboardHidden|orientation" ...
View mMyView = null;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) {
if (state == null) {
mMyView = new MyView(getActivity());
} else {
container.removeView(mMyView);
}
return mMyView;
}
Update
I simply avoiding this problem, by using ViewPager instead of ActionBar's tab.
I faced the same issue, but what I did was, before attaching or detaching the fragement inside the callbacks of ActionBar.TabListener, call
fragmentManager.executePendingTransactions();
this solves the issue for me
#Override
public void onTabelected(Tab tab, FragmentTransaction ft, FragmentManager fm) {
fm.executePendingTransactions(); // **execute the pending transactions before adding another fragment.
if (mFragment == null) {
mFragment = Fragment.instantiate(mContext, mFragmentName);
ft.replace(android.R.id.tabcontent, mFragment, mTag);
} else {
ft.attach(mFragment);
}
}

Categories

Resources