Fragment Navigation Inside a Tabbed Action Bar - android

The Problem
Before I describe my current setup I will describe my problem. I need to have hierarchal navigation between fragments inside separate tabs. Say I drill down on level in a tab, I then switch tabs and then go back to the first, original tab, I need it to be on the drilled down level and then have the ability to go back up to the top level of that tab.
Now onto my setup.
I have an application with one activity called MainActivity. This activity is what controls my tabbed action bar. I have followed many different tutorials to handle the switching of Fragments when a tab is selected and here is the code below that works drawing your attention to the onTabSelected and onTabUnselected methods.
MainActivity -- Tab Listener
public static class MyTabListener<T extends Fragment> implements ActionBar.TabListener {
private final Activity mActivity;
private final String mTag;
private final Class<T> mClass;
private final Bundle mArgs;
private Fragment mFragment;
public MyTabListener(Activity activity, String tag, Class<T> clz) {
this(activity, tag, clz, null);
}
public MyTabListener(Activity activity, String tag, Class<T> clz, Bundle args) {
mActivity = activity;
mTag = tag;
mClass = clz;
mArgs = args;
// Check to see if we already have a fragment for this tab, probably
// from a previously saved state. If so, deactivate it, because our
// initial state is that a tab isn't shown.
mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag);
if (mFragment != null && !mFragment.isDetached()) {
FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
ft.detach(mFragment);
ft.commit();
}
}
public void onTabSelected(Tab tab, FragmentTransaction ft) {
Log.e("FRAGMENT","Selected Fragment With Tag " + mTag);
if (mFragment == null) {
mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs);
ft.add(android.R.id.content, mFragment, mTag);
} else {
ft.attach(mFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (mFragment != null) {
ft.detach(mFragment);
}
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
Toast.makeText(mActivity, "Reselected!", Toast.LENGTH_SHORT).show();
}
}
When I am on Tab1 viewing Fragment1', when a button is pressed I then show anotherFragmentcalledFragment2` which I need the ability to go back from. The code below works if I just stay on that tab however when navigating away from the tab and back to it, everything messes up.
MainActivity -- Showing second fragment
Fragment tdFrag = Fragment.instantiate(this, SecondFragment.class.getName());
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(android.R.id.content, tdFrag, "secondfrag");
ft.addToBackStack(null);
ft.commit();
My Thoughts
I think it must be the way I detach the fragments and handle the switching over when a new tab is pressed. Maybe I need to implement some way of storing each individual backstack for each tab and loading the fragments from that instead of the system back stack?
Any help would be great. I have tried to explain it as thoroughly and simply as possible.
Thanks in advance.

Ok so after a bit of experimentation I decided to implement a custom backstack using enumerated types.
I have two types of enumerated types: a ViewType and CurrentTab type. The CurrentTab type tells me what tab I'm currently viewing and the ViewType tells me what view is being viewed in these tabs. I have an instance of a ViewType for each tab I have to store where I am in the hieerarchy.
To implement a backstack I override the method thats called when the back button is pressed.
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
return true;
}
return super.onKeyDone(keyCode,event);
}
So in where it checks for the back button I put my code to check the current tab and the separate view types. That way I know where I am and if I push back I know where I need to go.
I hope this helps any questions just post a comment.

Related

Android SupportActionBar tabs disappear under Fragment

I'm using the Support Package v7 for adding Action Bar Tabs to my app.
When the tab's Fragment has a root view with height="fill_parent" the Fragment is covering my tabs.
When I’m changing the Fragment's root view to height="wrap_content" I can see the tab but my Fragment styling is affected.
My code for loading Fragments is the same as in Android Docs:
public class PicoTabListener<T extends Fragment> implements TabListener {
private Fragment mFragment;
private final Activity mActivity;
private final String mTag;
private final Class<T> mClass;
/** Constructor used each time a new tab is created.
* #param activity The host Activity, used to instantiate the fragment
* #param tag The identifier tag for the fragment
* #param clz The fragment's Class, used to instantiate the fragment
*/
public PicoTabListener(Activity activity, String tag, Class<T> clz) {
mActivity = activity;
mTag = tag;
mClass = clz;
}
/* The following are each of the ActionBar.TabListener callbacks */
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());
ft.add(android.R.id.content, mFragment, mTag);
} else {
// If it exists, simply attach it in order to show it
ft.attach(mFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (mFragment != null) {
// Detach the fragment, because another one is being attached
ft.detach(mFragment);
}
}
#Override
public void onTabReselected(Tab arg0, FragmentTransaction arg1) {
// do nothing
}
Isn't tab should always be in the top of the screen as the first layer?
Isn't tab should always be in the top of the screen as the first layer ?
No.
If you were using ActionBarSherlock, I think what you are doing would work. And if you use the native action bar implementation, I think what you are doing would work. However, you cannot do this with AppCompat:
ft.add(android.R.id.content, mFragment, mTag);
Use some container inside of your layout, like a FrameLayout, as the target of the transaction, not android.R.id.content. See this issue for more.

ActionBar Tabs with support library

The problem I'm having is the Action Bar will not show on Android 2.3.7, but will work fine on 4.x+. The rest of my application works fine with the support v7 and v4 libraries, it's just this one area which is giving me trouble.
Here is what it should look like, as seen on 4.3:
And here is what it looks like on 2.3.7:
Inside my onCreate method (of the class which inherits from ActionBarActivity), I have this:
// setup action bar for tabs
ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowTitleEnabled(true);
Tab tab = actionBar.newTab()
.setText(R.string.details)
.setTabListener(new TabListener<DetailsFragmentOne>(
this, "one", DetailsFragmentOne.class));
actionBar.addTab(tab);
tab = actionBar.newTab()
.setText(R.string.grades)
.setTabListener(new TabListener<DetailsFragmentTwo>(
this, "one", DetailsFragmentTwo.class));
actionBar.addTab(tab);
And here is my TabListener, an inner class:
/**
* This is copied almost verbatim from the ActionBar Tabs API Guide.
* #param <T>
*/
public class TabListener<T extends Fragment> implements ActionBar.TabListener {
private Fragment mFragment;
private final Activity mActivity;
private final String mTag;
private final Class<T> mClass;
/** Constructor used each time a new tab is created.
* #param activity The host Activity, used to instantiate the fragment
* #param tag The identifier tag for the fragment
* #param clz The fragment's Class, used to instantiate the fragment
*/
public TabListener(Activity activity, String tag, Class<T> clz) {
mActivity = activity;
mTag = tag;
mClass = clz;
}
/* The following are each of the ActionBar.TabListener callbacks */
public void onTabSelected(Tab tab, FragmentTransaction ft) {
FragmentTransaction sft = ((FragmentActivity) mActivity).getSupportFragmentManager().beginTransaction();
mFragment = getSupportFragmentManager().findFragmentByTag(mTag);
// 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());
// calling commit() here because we're not using the provided FragmentTransaction
sft.replace(android.R.id.content, mFragment, mTag).commit();
} else {
// If it exists, simply attach it in order to show it
// calling commit() here because we're not using the provided FragmentTransaction
sft.replace(android.R.id.content, mFragment).commit();
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
FragmentTransaction sft = ((FragmentActivity) mActivity).getSupportFragmentManager().beginTransaction();
mFragment = getSupportFragmentManager().findFragmentByTag(mTag);
if (mFragment != null) {
// calling commit() here because we're not using the provided FragmentTransaction
sft.replace(android.R.id.content, mFragment).commit();
}
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
FragmentTransaction sft = ((FragmentActivity) mActivity).getSupportFragmentManager().beginTransaction();
mFragment = getSupportFragmentManager().findFragmentByTag(mTag);
if (mFragment != null) {
// calling commit() here because we're not using the provided FragmentTransaction
sft.replace(android.R.id.content, mFragment).commit();
}
}
}
I have seen these two other questions and attempted to implement the answers, but am still having the issue.
Implementing a TabListener using the Support Library
Implementing ActionBar tabs with v4 Fragments API
edit:
As requested, the theme which is being applied is simply the support library's AppCompat.Light.DarkActionBar theme with no overrides, seen below:
<style name="Theme.MyTheme" parent="#style/Theme.AppCompat.Light.DarkActionBar">
</style>
If you remove the background of your DetailFragment, the ActionBar and Tabs actually appear behind the DetailFragment. Instead of android.R.id.content, create your own container in your main layout and use R.id.yourcontent when calling replace in your FragmentTransaction. By making this change, it worked for me on 2.3.3 and 4+.
It appears that 2.3.3 adds the ActionBar to the root view element where 4+ is adding it outside of the root view.
sft.replace(R.id.yourcontent, mFragment).commit();
You should read the offcial document.
You must not call commit() for the fragment transaction in each of these callbacks—the system calls it for you and it may throw an exception if you call it yourself. You also cannot add these fragment transactions to the back stack.

Replacing ListFragment with another within ActionBar - new listfragment replaces other fragments, IllegalStateException when pressing back

Okay, so I have a problem.
I have an activity, which controls an ActionBar. In it's onCreate i populate the actionbar like this:
protected void onCreate(Bundle savedInstanceState) {
//...some other things
ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
actionBar.setDisplayShowTitleEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
siteSpecificFragmentManager.populateActionBar(this, actionBar);
}
The last line refers to my method that populates action bar with tabs depending on what tabs I want to show, which ones I want to hide, etc, etc... An example I'm using looks like this. CategoriesFragment is a ListFragment that has no corresponding layout xml, and OfflineFragment is a simple Fragment with a layout xml.
public void populateActionBar(Activity activity, ActionBar bar) {
Tab tab = bar.newTab().setText(R.string.tab_sites).setTabListener(
new TabListener<CategoriesFragment>(activity,"tag1",CategoriesFragment.class));
bar.addTab(tab);
tab = bar.newTab().setText(R.string.tab_offline).setTabListener(
new TabListener<OfflineFragment>(activity,"tag2", OfflineFragment.class));
bar.addTab(tab);
}
TabListener looks like this:
public class MyFragmentListener<T extends Fragment> implements
android.app.ActionBar.TabListener {
public MyFragmentListener(Activity activity, String tag, Class<T> tClass) {
this(activity, tag, tClass, null);
}
public MyFragmentListener(Activity activity, String tag, Class<T> tClass,
Bundle bundle) {
this.activity = activity;
this.tag = tag;
this.tClass = tClass;
this.bundle = bundle;
fragment = activity.getFragmentManager().findFragmentByTag(tag);
if(fragment != null && !fragment.isDetached()){
FragmentTransaction fragmentTransaction = activity.getFragmentManager().beginTransaction();
fragmentTransaction.detach(fragment);
fragmentTransaction.commit();
}
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
if(fragment == null){
fragment = Fragment.instantiate(activity, tClass.getName(),bundle);
ft.add(android.R.id.content,fragment, tag);
} else{
ft.attach(fragment);
}
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if(fragment != null){
ft.detach(fragment);
}
}
}
Ok, all set, let's try whether it works. When the activity starts, I see two tabs on ActionBar, I can switch between both without any problems. First tab contains the list, second tab contains some other things.
When I select an item from the list, I want to push a new ListFragment, called SubcategoryFragment onto it. Here's how I'm doing it:
public void onListItemClick(ListView l, View v, int position, long id) {
Bundle bundle = new Bundle();
bundle.putString("chosen", category[position]);
Fragment fragment = new SubcategoryFragment();
fragment.setArguments(bundle);
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.replace(getId(), fragment);
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
fragmentTransaction.addToBackStack("category_to_subcategory");
fragmentTransaction.commit();
}
And the problems are ->
1: When I select an item in the first tab, my fragment is replaced by the subcategory fragment. However, the list of the subcategory fragment overlays the content of the second, offline tab, the list drawn over the contents.
2: When I press back after switching from category to subcategory, the subcategory fragment pops, and I'm returned to category list. That's okay.
When I select a category, to make a subcategoryfragment, switch to the second tab, and press back, the list disappears. When I click back to 1st tab, the Category fragment is not visible.
3: When I do the things mentioned above, and instead of pressing back in the second tab, i switch back to first tab, and then press back, I'm greeted with
06-15 20:11:12.151: E/AndroidRuntime(18971): java.lang.IllegalStateException: Fragment already added: CategoriesFragment{414d9350 #0 id=0x1020002 fanfictionnet_categories}
So, as you can see, something doesn't work as I'd like it to. What am I doing wrong?

Cross-fragment communication

I am working on an app that has an "artist list", a list of a good chunk of music bands, performers and/or composers. They are sorted by genre and relative popularity.
From the main activity you go to a "band list" activity that has two tabs in it. One of them is a "genres" tab that has a list of the general music genres and when you click on each genre you get sent to the respective activity that also has two tabs in it. The first one is the "All" tabs and the other one's the "Popular" tab. Each of the tabs holds a fragment that contains a filtered sqlite database (by "filtered" I mean I have one big database that gets filtered to show what I want in different fragments rather than using a new database for each fragment/genre). So now I have 7 genres and 14 classes; 2 ("all" and "popular") for each genre.
What I would like to do now is reduce the number of classes to only two (all/popular) or, if possible, even one, by recycling the same fragment for all lists via filtering the database again. In order to do this I need to send a string from the "genres" fragment, containing the position of the pressed list item (genre) to the activity which contains the "All" and "Popular" tabs (fragments), which will then send that string further, to the fragments, which will use it to filter the sqlite database.
I need to add that I have, of course, read the Android Developers guide on fragment communication, but I do not know how to use their approach in my code, since I have created the fragments a little differently, including not defining them in the xml, so because of that I don't know how to use their getSupportFragmentManager().findFragmentById(R.id.article_fragment); method and some more things like that.
Here is the TabListener via which I am creating all fragments:
public class TabListener<T extends Fragment> implements ActionBar.TabListener {
private Fragment mFragment;
private final Activity mActivity;
private final String mTag;
private final Class<T> mClass;
public TabListener(Activity activity, String tag, Class<T> clz) {
mActivity = activity;
mTag = tag;
mClass = clz;
}
public void onTabSelected(Tab tab, FragmentTransaction ft) {
if (mFragment == null) {
mFragment = Fragment.instantiate(mActivity, mClass.getName());
ft.add(android.R.id.content, mFragment, mTag);
} else {
ft.attach(mFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (mFragment != null) {
ft.detach(mFragment);
}
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
}
Thanks,
Aleksa
If you created the fragments programmatically and added them with a tag, u can use the support FragmentManager method: findFragmentByTag(String tag)
After some more trying I eventually gave up on setArguments() and getArguments() and used this in the parent activity:
public int getPosition(){ return position; }
...and this in the child fragment:
position=((ParentActivity) getActivity()).getPosition();
I don't know if it is the most efficient method around but it works great and makes sending some simple data to a fragment a trivial thing for newcomers like me as it should be. :)

Using Fragments and TabListener the right way

My problem is about fragments with tabs. I have got one activity which basically has an empty layout and I add fragments to it dynamically. As soon as I start the activity I add a fragment with a listview to it and when I click on an item of the list I remove the fragment with the listview and add another fragment with details about it. Also some tabs are visible now. Clicking on one of these tabs shows other details about the item from the listview.
I do have working code but it seems that I'm hacking it altogether.
Here is some bits of it:
Activity:
// In onCreate:
#Override
protected void onCreate(Bundle savedInstanceState) {
// Check that the activity is using the layout version with
// the fragment_container FrameLayout
if (findViewById(R.id.fragment_container) != null) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
return;
}
// Create an instance of FList
fList = new FList();
// Add the fragment to the 'fragment_container' FrameLayout
getFragmentManager().beginTransaction().add(R.id.fragment_container,fList).commit();
}
}
// After clicking on an item of the listview changeFragment() is called
public void changeFragment() {
FragmentTransaction ft = getFragmentManager().beginTransaction();
fSpeed = new FSpeed();
ft.add(R.id.fragment_container, fSpeed);
// this might be necessary
if (actionBar != null)
actionBar.selectTab(tabSpeed);
ft.commit();
}
// In the activity I also setup the ActionBar with tabs
private void setupActionBar() {
tabSpeed = actionBar.newTab();
// and a few more tabs...
tabSpeed.setTabListener(this);
actionBar.addTab(tabSpeed);
// But the tabs are not visible because of the navigation mode
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
}
// Here is the tabListener - very hacky!
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft)
if (fm == null)
fm = getFragmentManager();
Fragment f = null;
switch (tab.getPosition()) {
case 0:
if (fSpeed == null)
fSpeed = new FSpeed();
f = findFragment(fSpeed);
if (f != null) {
ft.setCustomAnimations(R.anim.fragment_alpha_0_1, R.anim.fragment_alpha_1_0);
ft.remove(f);
ft.add(R.id.fragment_container, fSpeed);
} else {
Log.d(TAG, "fSpeed is " + f);
return;
}
break;
case 1:
// more tabs - similar code
}
// the findFragment() method
private Fragment findFragment(Fragment selectedFragment) {
if (fm.findFragmentById(R.id.fragment_container) == fSpeed && selectedFragment != fSpeed)
return fSpeed;
// more ifs for other fragments
else
return null;
}
This all worked fine for the minute but after switching x times between the tabs things started looking funny, e.g. fList was visible, tab icons disappeared, ... I could track the bugs down and add some null checks but I think I went the wrong way. This is why I changed the TabListener with the following one:
// Still in activity
public static class MyTabListener<T extends Fragment> implements TabListener {
private Fragment mFragment;
private final Activity mActivity;
private final String mTag;
private final Class<T> mClass;
/**
* * Constructor used each time a new tab is created.
* * * #param
* activity * The host Activity, used to instantiate the fragment
* * #param
* tag * The identifier tag for the fragment
* * #param clz * The
* fragment's Class, used to instantiate the fragment
*/
public MyTabListener(Activity activity, String tag, Class<T> clz) {
mActivity = activity;
mTag = tag;
mClass = clz;
}
/* The following are each of the ActionBar.TabListener callbacks */
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());
ft.add(R.id.fragment_container, mFragment, mTag);
} else {
// If it exists, simply attach it in order to show it
ft.attach(mFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (mFragment != null) {
ft.detach(mFragment);
}
}
// The tabs are now assigned to MyTabListener
tabSpeed.setTabListener(new MyTabListener<FSpeed>(this, "speed", FSpeed.class));
Here is where the problem started. After clicking on an item from the listview with the new TabListener onTabSelected and onTabUnSelected are called in turns endlessly.
Question:
My question is how do you go from a fragment with a listview to another fragment after clicking on an item of the listview (and still use the same activity). Is my own written method changeFragment() the wrong approach? Still I would like to use the MyTabListener.
Note 1: I bought Commonsware's book to learn more about fragments but I couldn't find a more complex example with different fragments working together and also couldn't find how the back button is handled, or overridden. For example after clicking on the back button if fragment1, 2 or 3 are visible always show fragment4. If anybody found one in the book could you please tell me the chapter (name)/ page? If there isn't it would be very kind of the Common guys to provide one in the next update or so.
Note 2: Global variables were used in the project like fSpeed, tabSpeed, ...
Note 3: If you need more code or explanation please let me know in the comments. Thanks for helping!
Is my own written method changeFragment() the wrong approach?
You are adding a fragment, not replacing one. To replace a fragment, use replace(), not add().

Categories

Resources