android - Nested retained FragmentTabHost doesn't attach tabs to new Activity -
i have been playing around fragmenttabhost android v4 support library while , came upon serious issue couldn't resolve. target requirements follows.
1) fragment
include tabs built fragments
include nested fragments
well, hierarchy this:
android.support.v4.app.fragment
(hostfragment)android.support.v4.app.fragmenttabhost
(tabhost)android.support.v4.app.fragment
(tab1fragment)android.support.v4.app.fragment
(tab1fragment1)android.support.v4.app.fragment
(tab1fragment2)android.support.v4.app.fragment
(tab1fragment3)
android.support.v4.app.fragment
(tab2fragment)android.support.v4.app.fragment
(tab2fragment1)android.support.v4.app.fragment
(tab2fragment2)
hostfragment + tabhost
setup done according says in documentation here.
2) upon screen rotation retain status of hostfragment
recreation of setup resources consuming operation , besides layout order nothing changes on screen no work needed display screen. return different layout
in oncreateview
callback of tab1
, tab2
, reattach existing fragments same id's.
to accomplish 1 think setting hostfragment.setretaininstance(true)
work and, partially, does. nothing getting recreated, tabs preserved should, worked fine. let's point.
the problem
the big problem came bit later. appears tab1fragment1
(and other tabxfragmenty
matter) not attached new activity
created upon rotation. attached on first run of activity
, nothing being done when user rotates screen.
the consequences
this causes 2 big issues me (might there more):
- initial
activity
leaked becausefragments
intabhost
attached no reason @ all. - when call
getactivity()
on oftabxfragmenty
oldactivity
, dreadful "cannot perform operation after onsavedinstance" exception.
this leads serious issues when want parent activity. also, doesn't happen when setting hostfragment.setretaininstance(false)
get's recreated , works fine.
the question
is issue seeing? couldn't find on topic , have reviewed code hundred times.
i have tried changing com.example.android.supportv4.app.fragmenttabsfragmentsupport
v4 examples, did set retain it's instance , added logging information in 1 of nested fragments on every onattach
call verify , can see there still same issue. puzzled. help.
recently i've been working on tabhost
fragment
s , found same issue. basically, need control fragment
s being attached/detached, in case, within ontabchanged()
event.
i have tabinfo
class store following information each tab
- the name of
fragment
(for identification purposes). - the
fragment
attach. - the
tabhost.tabspec
specification, if want remove tab, there's info recreating rest of themtabhost
bit tricky removing tabs. - the
bundle
associated (basically, saved instance prior configuration change).
also, need keep track of lasttab
, newtab
opened tabhost
doesn't have native way of knowing tabhost
have been closed, declaring variable class-wide. , how handle right now, i'll try add many comments can:
@override public void ontabchanged(final string tag) { // info tab triggered using tag final tabinfo newtab = mtabinfo.get(tag); // if there's been tab change... if (lasttab != newtab) { // you'll have make transaction replacing fragment final fragmenttransaction ft = this.getsupportfragmentmanager().begintransaction(); // if last tab has fragment associated if ((lasttab != null) && (lasttab.getfragment() != null)) { // in case i've additional level of complexion, have nested fragment // inside content fragment. have remove first prior detaching // parent fragment. not needed if have 1 fragment content. final fragment loginfrag = (fragment) lasttab.getfragment().getactivity().getsupportfragmentmanager().findfragmentbyid(lasttab.getloginfragid()); ft.remove(loginfrag); // , trick: calling detach() instead of remove() // seems versions there's problem makes not apply it, // calling remove remove fragment ft.remove(lasttab.getfragment()); } // you've detached old fragment, have attach new 1 if (newtab != null) { if (newtab.getfragment() == null) { // inflate new content if it's first time tab has been fired final tabfragmentinflater tabinf = new tabfragmentinflater(); newtab.setfragment(fragment.instantiate(this, tabinf.getclass().getname(), newtab.getargs())); ft.add(r.id.realtabcontent, newtab.getfragment(), newtab.gettag()); } else { // if not, attach fragment ft.attach(newtab.getfragment()); } } ft.commit(); this.getsupportfragmentmanager().executependingtransactions(); lasttab = newtab; } }
---- edit ----
answering questions:
i indeed call
newtabspec()
ontabhost
, it's in separate method didn't include because includedontabchanged()
callback. have create tabs normally,ontabchanged()
method gets fired when click on tabs. creation, this:private void inittabhost(final bundle args) { final tabhost th = (tabhost) findviewbyid(android.r.id.tabhost); th.setup(); // have hashmap called fragmap key define tab's name // , value, have integer unique identifier // know inflate when call tabfragmentinflater (i // add code below). can add id or tag also. (final string tablabel : fragmap.keyset()) { final tabhost.tabspec tabspec = th.get().newtabspec(tabname).setindicator(tabname); // here initialize tabinfo object tab, include additional // handling info: name of tab, tab spec, unique id explained above, // forth argument irrelevant in example, , args (saved instance) final tabinfo tabinfo = new tabinfo(tabname, tabspec, fragmap.get(tablabel), r.id.somelayout, args); // not tabhost's `addtab()` method, i'll call inside // method (see below) myclass.addtab(this, th, tabspec, tabinfo); // have able keep tracking of info mtabinfo.put(tabinfo.gettag(), tabinfo); } // tab creation, force start on first tab this.ontabchanged(firsttabnametag); th.setontabchangedlistener(this); }
the addtab()
method simple, inflates object , detaches if needed.
private static void addtab(final myclass activity, final tabhost tabhost, final tabhost.tabspec tabspec, final tabinfo tabinfo) { tabspec.setcontent(new tabfactory(activity)); // empty view final string tag = tabspec.gettag(); // here check if there's fragment tab, in // saved state. if happens, deactivate it, because our // former state tab "not shown". tabinfo.setfragment(activity.getsupportfragmentmanager().findfragmentbytag(tag)); if ((tabinfo.getfragment() != null) && (!tabinfo.getfragment().isdetached())) { final fragmenttransaction ft = activity.getsupportfragmentmanager().begintransaction(); ft.detach(tabinfo.getfragment()); ft.commit(); activity.getsupportfragmentmanager().executependingtransactions(); } // actually, there's call "official" `addtab()` `tabhost` tabhost.addtab(tabspec); }
so there's tabfragmentinflater left. it's
fragment
inflates according layout depending on unique id mentioned above, it's this:@override public view oncreateview(final layoutinflater inflater, final viewgroup container, final bundle savedinstancestate) { fragid = getarguments().getint("fragid"); if (view != null) { viewgroup parent = (viewgroup) view.getparent(); if (parent != null) parent.removeview(view); } try { switch (fragid) { case 1: // first tab... final linearlayout fraglayout = (linearlayout) inflater.inflate(r.layout.myfirsttab_fragment_layout, container, false); ... return fraglayout; case 2: // second tab fraglayout = (linearlayout) inflater.inflate(r.layout.mysecondtab_fragment_layout, container, false); ... return fraglayout; ... } } catch (final inflateexception e) { return view; } return null; }
Comments
Post a Comment