Android Fragment, going back without recreating/reloading Fragment - android

I've seen quite a few questions on SO about Fragments and I still can't seem to figure out if what I want to do is possible, and more so if my design pattern is just flawed and I need to re-work the entire process. Basically, like most questions that have been asked, I have an ActionBar with NavigationTabs (using ActionBarSherlock), then within each Tab there is a FragementActivity and then the FragmentActivities push new Fragments when a row is selected (I'm trying to re-create an iOS Project in Android and it's just a basic Navigation based app with some tabs that can drill down into specific information). When I click the back button on the phone the previous Fragment is loaded but the Fragment re-creates itself (so the WebServices are called again for each view) and this isn't needed since the information won't change in a previous view when going backwards. So basically what I want to figure out is how do I setup my Fragments so that when I push the back button on the phone, the previous Fragment is just pulled up with the previous items already created. Below is my current code :
//This is from my FragmentActivity Class that contains the ActionBar and Tab Selection Control
#Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
// TODO Auto-generated method stub
int selectedTab = tab.getPosition();
if (selectedTab == 0) {
SalesMainScreen salesScreen = new SalesMainScreen();
ft.replace(R.id.content, salesScreen);
}
else if (selectedTab == 1) {
ClientMainScreen clientScreen = new ClientMainScreen();
ft.replace(R.id.content, clientScreen);
}.....
//This is within the ClientMainScreen Fragment Class, which handles moving to the Detail Fragment
row.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//Do something if Row is clicked
try{
String selectedClientName = clientObject.getString("ClientName");
String selectedClientID = clientObject.getString("ClientID");
String selectedValue = clientObject.getString("ClientValue");
transaction = getFragmentManager().beginTransaction();
ClientDetailScreen detailScreen = new ClientDetailScreen();
detailScreen.clientID = selectedClientID;
detailScreen.clientName = selectedClientName;
detailScreen.clientValue = selectedValue;
int currentID = ((ViewGroup)getView().getParent()).getId();
transaction.replace(currentID,detailScreen);
transaction.addToBackStack(null);
transaction.commit();
}
catch (Exception e) {
e.printStackTrace();
}
}
});....
//And then this is the Client Detail Fragment, with the method being called to Call the Web Service and create thew (since what is displayed on this screen is dependent on what is found in the Web Service
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup group, Bundle saved) {
return inflater.inflate(R.layout.clientdetailscreen, group, false);
}
#Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//Setup Preferences File Link
this.preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
//initialize the table object
mainTable = (TableLayout)getActivity().findViewById(R.id.mainTable);
//setup the detail table
setupRelatedClientSection();
}
The Client Detail Screen can then drill down one more time, using the same method as the Client Main Screen but when I go back from that new screen to the Detail Screen the seuptRelatedClientSection() method is called again and so the entire Fragment is rebuilt when really I just want to pull up a saved version of that screen. Is this possible with my current setup, or did I approach this the wrong way?

Try using fragementTransaction.add instead of replace

I believe that you are looking for show() and hide().
I think you can still add them to the backstack.
transaction.hide(currentFragment);
transaction.show(detailScreen);
transaction.addToBackStack(null);
transaction.commit();
I didnt have my code to look at but i believe this is how it would go... Try it out unless someone else has a better way.
I have not tried the backstack with show() hide() but i believe that it takes the changes that are made before the transactions commit and will undo them if the back button is pressed. Please get back to me on this cause i am interested to know.
You also have to make sure that the detail fragment is created before you call this. Since it is based on the click of someitem then you should probably create the details fragment every time you click to make sure the correct details fragment is created.

I'm posting this answer for people who may refer this question in future.
Following code will demonstrate how to open FragmentB from FragmentA and going back to FragmentA from FragmentB (without refreshing FragmentA) by pressing back button.
public class FragmentA extends Fragment{
...
void openFragmentB(){
FragmentManager fragmentManager =
getActivity().getSupportFragmentManager();
FragmentB fragmentB = FragmentB.newInstance();
if (fragmentB.isAdded()) {
return;
} else {
fragmentManager.
beginTransaction().
add(R.id.mainContainer,fragmentB).
addToBackStack(FragmentB.TAG).
commit();
}
}
}
public class FragmentB extends Fragment{
public static final String TAG =
FragmentB.class.getSimpleName();
...
public static FragmentB newInstance(){
FragmentB fragmentB = new FragmentB();
return fragmentB;
}
#Override
public void onResume() {
super.onResume();
// add this piece of code in onResume method
this.getView().setFocusableInTouchMode(true);
this.getView().requestFocus();
}
}
In your MainActivity override onBackPressed()
class MainActivity extends Activity{
...
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0) {
getFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
}

You're right, there has been a number of previous questions / documentation on the topic ;)
The documentation on Fragments, specifically the section about Transactions and Saving State, will guide you to the answer.
http://developer.android.com/guide/components/fragments.html#Transactions
http://developer.android.com/guide/components/activities.html#SavingActivityState
Android - Fragment onActivityResult avoid reloading
Fragments can have support for onSaveInstanceState but not onRestoreInstanceState, so if you want to save a reference to the table views, save them to the Bundle and you can access the saved view in your onActivityCreated method. You could also use the Fragments back stack.
This guide/tutorial has very detailed instructions/examples on the back stack and retaining fragment state.
Good luck

Related

How to refresh a previous fragment on back press without disturbing the back stack

I am using one activity for tabactivity with viewpager and attached fragmentA.when i want to move fragmentB ,replaced fragmentB with present fragmentA.I need to find solution for onbackpress in FragmentB refresh fragmentA wihtout changing Backstack because i used nearly ten fragments in one tab.
Is there any method in fragment life cycle
When Fragment B over Fragment A, fragment A is onPause(). After backpress, fragment A is onResume(). So you can create a boolean to observe this change.
In Fragment A
private boolean isBackFromB;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
isBackFromB=false;
}
#Override
public void onPause() {
super.onPause();
isBackFromB = true;
}
#Override
public void onResume() {
super.onResume();
updateUI();
if (isBackFromB) //Do something
}
View pager does not always hold it's fragments in memory, so you have to handle 2 scenarios:
1 - fragment is recreated - just read data from wherever you hold them and show in ordinary way.
2 - fragment is modified from outside while still has been held by ViewPager, and as I understand this is what concerns you.
What you can do is to create this listener and then notify fragment that it was shown again and possibly should reload it's data.
More complex answer for communicating between different application's components is using some event bus like Otto.
override onBackPressed() method in Activity, add all fragment in backstack.
#Override
public void onBackPressed() {
FragmentManager manager =getFragmentManager();
int count = manager.getBackStackEntryCount();
if (count == 0) {
super.onBackPressed();
} else {
manager.popBackStack();
}
}

Callback when Fragment is replaced and when it is visible again

I've been researching this topic but so far no luck. Basically I'm replacing a fragment (A) with another one (B) using FragmentTransaction.replace. In this other fragment (B) I have a 'Cancel' button in the toolbar which when pressed pops back to the previous transaction (A) by calling getActivity().getSupportFragmentManager().popBackStackImmediate().
The problem is I need to update the Activity toolbar to display a different title when I'm showing fragment A and fragment B. I can't seem to find a method which gets called in fragment A whenever I go from A -> B -> A to inform me that it is visible again. The idea is to set the toolbar title in this callback which I cannot seem to find.
Can anyone point me in the right direction please?
Cheers.
Edit:
Method I call to replace the fragment with another one is as follows:
public static void replaceFragment(FragmentActivity parentActivity, int fragmentToReplaceId, Fragment withFragment, Integer enterAnim, Integer exitAnim)
{
FragmentManager fragmentManager;
FragmentTransaction transaction;
fragmentManager = parentActivity.getSupportFragmentManager();
transaction = fragmentManager.beginTransaction();
if ( (null != enterAnim) &&
(null != exitAnim) )
{
transaction.setCustomAnimations(enterAnim, exitAnim);
}
transaction.replace(fragmentToReplaceId, withFragment);
transaction.addToBackStack(null);
transaction.commit();
}
You can inform by overriding method onResume() in fragment and sending the message to activity or changing the Toolbar directly.
#Override
public void onResume() {
super.onResume();
((AppCompatActivity) getActivity()).getSupportActionBar().setTitle("Title");
}
In one activity, when replace A ---> B (A and B both are fragments), can use this call-back:
#Override
public void onAttachFragment(Fragment fragment) {
}
Solved by creating a simple static method in fragment A as follows:
public static void updateActivityTitle(FragmentActivity activity)
{
activity.setTitle(kActivityTitle);
}
Then I'm calling this method in fragment B as follows:
// cancel button has been pressed
private void cancel()
{
INV_CustomersFragment.updateActivityTitle(getActivity());
getActivity().getSupportFragmentManager().popBackStackImmediate();
}
Not ideal but it gets the job done. Any other solution involving a proper callback would be better
Update:
A better solution is the one described by #Rani at Fragment's onResume() not called when popped from backstack. This is more elegant and more maintainable, in fact I implemented this solution in my project. Compared to an equivalent solution for iOS this is still messy if you ask me, still seems the way to go.

How to save an Activity's Fragment's transactional states?

Related Question.
I put together a simple app that goes like this:
Activity -> FirstFragment
Activity: onCreate() -> createFirstFragment()
FirstFragment firstFragment = (FirstFragment) getSupportFragmentManager().findFragmentByTag(FirstFragment.TAG);
if (firstFragment == null)
{
firstFragment = FirstFragment.newInstance();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.firstFragmentContainer, firstFragment, FirstFragment.TAG)
.hide(firstFragment)
//.addToBackStack(null)
.commit();
}
Plain and simple, during onCreate() add and hide a fragment so that I can do show/hide animations later.
Now, my question is this: why does the Activity/FragmentManager not remember this transaction (regardless of whether I .addToBackStack() or setRetainInstance(true) on the fragment) when the activity is killed and recreated? You can test this by checking the Do not keep activities developer option. Start the app, firstFragment is hidden as expected, minimize and come back, and viola! firstFragment is there for all the world to see!
I would expect that this sort of thing would be managed by Android, or do I need to specifically record all my transactions and repeat them when the app is recreated?
Any help/advice would be greatly appreciated!
Edit: Also see related logged bug
Use FragmentStatePagerAdapter like below in your main activity. This internally calls 'onSaveInstanceState' of the fragments and hence keeps the track of the changes you made and retains the transactional states
class MyAdapter extends FragmentStatePagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
// your code here
}
#Override
public int getCount() {
// returns no. of fragments count. in my case it is 4
return 4;
}
onCreate() in mainactivity generally looks like this:
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView (R.layout.scrollabletabs_main);
viewPager = (ViewPager) findViewById (R.id.pager);
FragmentManager fragManager = getSupportFragmentManager();
viewPager.setAdapter(new MyAdapter(fragManager));
}
From
http://developer.android.com/training/basics/fragments/fragment-ui.html it is mentioned that,
Note: When you remove or replace a fragment and add the transaction to the back stack, the fragment that is removed is stopped (not destroyed). If the user navigates back to restore the fragment, it restarts. If you do not add the transaction to the back stack, then the fragment is destroyed when removed or replaced.

communication flow between fragments and activity

I'm trying to make a simple reminder app. When creating a new reminder I have the following setup:
MainEditActivity.java: 2 placeholder FrameLayouts for the following fragments:
EditNameFragment.java
custom Action Bar (Cancel, OK button)
EditText for the reminder name
CheckBox to toggle one of the following fragments
EditDateFragment.java OR EditLocationFragment.java (both have a lot of views)
MainEditActivity.java:
private LocationFragment mLocationFragment;
private DateFragment mDateFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_new_remainer_main); // 2 frame layouts
if (savedInstanceState == null) {
Fragment newFragment = new NewReminderFragment();
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(R.id.reminder_title_fragment, newFragment).commit();
mDateFragment = new DateFragment();
ft = getFragmentManager().beginTransaction();
ft.add(R.id.date_or_location_fragment, mDateFragment).commit();
}
}
public void onCheckBoxClick(View view)
{
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if(mLocationFragment == null)
mLocationFragment = new LocationFragment();
CheckBox checkBox = (CheckBox)findViewById(R.id.checkBoxID);
if(checkBox != null)
{
if(checkBox.isChecked()) {
fragmentTransaction.replace(R.id.date_or_location_fragment, mLocationFragment);
} else {
fragmentTransaction.replace(R.id.date_or_location_fragment, mDateFragment);
}
fragmentTransaction.commit();
}
#Override
public void handleEvent(EventInfo event) {
// here I get all the data (name, data, time, location, etc)
}
EditNameFragment.java:
private EventHandler mEventHandler;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mEventHandler = (EventHandler)activity;
}
View doneButton = actionBarButtons.findViewById(R.id.doneBtnTextID);
doneButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
createNewReminder();
}
});
private void createNewReminder()
{
// Event info object to store all information (reminder name, data, time, etc)
EventInfo eventInfo = new EventInfo();
// access either Date or Location Fragment (depending on which is visible)
// FIXME: access the fragments and get the data
// let the activity know to display the new reminder
mEventHandler.handleEvent(eventInfo);
}
Question: I started off using only 1 MainActivity with all elements and a ViewFlipper. Then I read that fragments are better to eventually adjust to tablets better. Is this the right way to lay this out?
Question: I know how to get the data from EditNameFragment.java. But how do I get the data that the user entered from EditDataFragment or EditLocationFragment?
Do I need an instance of those two Fragments in the MainActivity to access their Views? Or do I need another callback like I did with EventHandler? If yes, would the rule be to have a callback for each fragment?
I guess I'm struggling a bit with the communication flow between those components. And I know fragment to fragment communication isn't a good design
Fragments are self-contained, modular components that would make sense if presented by themselves independently of the other. The canonical example is a list of newspaper articles and the display for an article...both could be presented either independently or separately and it would make sense. In your case, I think having multiple fragments is probably unnecessary because they do not make sense independently of the other (or you would end up with a half-defined event). It seems as if you have one fragment to edit the date and one fragment to edit the name, but conceptually, the two fragments really edit one "object," which is the event. Thus, this should probably be done within one fragment with a ViewFlipper.
Have you seen this?
http://developer.android.com/training/basics/fragments/communicating.html
You need instances of all three fragments in the main activity, and you need to define interfaces for each fragment and implement them in the main activity.

Separate Back Stack for each tab in Android using Fragments

I'm trying to implement tabs for navigation in an Android app. Since TabActivity and ActivityGroup are deprecated I would like to implement it using Fragments instead.
I know how to set up one fragment for each tab and then switch fragments when a tab is clicked. But how can I have a separate back stack for each tab?
For an example Fragment A and B would be under Tab 1 and Fragment C and D under Tab 2. When the app is started Fragment A is shown and Tab 1 is selected. Then Fragment A might be replaced with Fragment B. When Tab 2 is selected Fragment C should be displayed. If Tab 1 is then selected Fragment B should once again be displayed. At this point it should be possible to use the back button to show Fragment A.
Also, it is important that the state for each tab is maintained when the device is rotated.
BR
Martin
Read this before using this solution
Wow, I still can't believe this answer is the one with most votes in this thread. Please don't blindly follow this implementation. I wrote this solution in 2012 (when I was just a novice in Android). Ten years down the line, I can see there is a terrible issue with this solution.
I am storing hard reference to fragments to implement the navigation stack. It is a terrible practice and would result in memory leak. Let the FragmentManager saves the reference to fragments. Just store the fragment identifier if needed.
My answer can be used with above modification if needed. But I don't think we need to write a multi stacked navigation implementation from scratch. There is surely a much better readymade solution for this. I am not much into Android nowadays, so can't point to any.
I am keeping the original answer for the sake of completeness.
Original answer
I am terribly late to this question . But since this thread has been very informative and helpful to me I thought I better post my two pence here.
I needed a screen flow like this (A minimalistic design with 2 tabs and 2 views in each tab),
tabA
-> ScreenA1, ScreenA2
tabB
-> ScreenB1, ScreenB2
I had the same requirements in the past, and I did it using TabActivityGroup (which was deprecated at that time too) and Activities. This time I wanted to use Fragments.
So this is how I done it.
1. Create a base Fragment Class
public class BaseFragment extends Fragment {
AppMainTabActivity mActivity;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mActivity = (AppMainTabActivity) this.getActivity();
}
public void onBackPressed(){
}
public void onActivityResult(int requestCode, int resultCode, Intent data){
}
}
All fragments in your app can extend this Base class. If you want to use special fragments like ListFragment you should create a base class for that too. You will be clear about the usage of onBackPressed() and onActivityResult() if you read the post in full..
2. Create some Tab identifiers, accessible everywhere in project
public class AppConstants{
public static final String TAB_A = "tab_a_identifier";
public static final String TAB_B = "tab_b_identifier";
//Your other constants, if you have them..
}
nothing to explain here..
3. Ok, Main Tab Activity- Please go through comments in code..
public class AppMainFragmentActivity extends FragmentActivity{
/* Your Tab host */
private TabHost mTabHost;
/* A HashMap of stacks, where we use tab identifier as keys..*/
private HashMap<String, Stack<Fragment>> mStacks;
/*Save current tabs identifier in this..*/
private String mCurrentTab;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.app_main_tab_fragment_layout);
/*
* Navigation stacks for each tab gets created..
* tab identifier is used as key to get respective stack for each tab
*/
mStacks = new HashMap<String, Stack<Fragment>>();
mStacks.put(AppConstants.TAB_A, new Stack<Fragment>());
mStacks.put(AppConstants.TAB_B, new Stack<Fragment>());
mTabHost = (TabHost)findViewById(android.R.id.tabhost);
mTabHost.setOnTabChangedListener(listener);
mTabHost.setup();
initializeTabs();
}
private View createTabView(final int id) {
View view = LayoutInflater.from(this).inflate(R.layout.tabs_icon, null);
ImageView imageView = (ImageView) view.findViewById(R.id.tab_icon);
imageView.setImageDrawable(getResources().getDrawable(id));
return view;
}
public void initializeTabs(){
/* Setup your tab icons and content views.. Nothing special in this..*/
TabHost.TabSpec spec = mTabHost.newTabSpec(AppConstants.TAB_A);
mTabHost.setCurrentTab(-3);
spec.setContent(new TabHost.TabContentFactory() {
public View createTabContent(String tag) {
return findViewById(R.id.realtabcontent);
}
});
spec.setIndicator(createTabView(R.drawable.tab_home_state_btn));
mTabHost.addTab(spec);
spec = mTabHost.newTabSpec(AppConstants.TAB_B);
spec.setContent(new TabHost.TabContentFactory() {
public View createTabContent(String tag) {
return findViewById(R.id.realtabcontent);
}
});
spec.setIndicator(createTabView(R.drawable.tab_status_state_btn));
mTabHost.addTab(spec);
}
/*Comes here when user switch tab, or we do programmatically*/
TabHost.OnTabChangeListener listener = new TabHost.OnTabChangeListener() {
public void onTabChanged(String tabId) {
/*Set current tab..*/
mCurrentTab = tabId;
if(mStacks.get(tabId).size() == 0){
/*
* First time this tab is selected. So add first fragment of that tab.
* Dont need animation, so that argument is false.
* We are adding a new fragment which is not present in stack. So add to stack is true.
*/
if(tabId.equals(AppConstants.TAB_A)){
pushFragments(tabId, new AppTabAFirstFragment(), false,true);
}else if(tabId.equals(AppConstants.TAB_B)){
pushFragments(tabId, new AppTabBFirstFragment(), false,true);
}
}else {
/*
* We are switching tabs, and target tab is already has atleast one fragment.
* No need of animation, no need of stack pushing. Just show the target fragment
*/
pushFragments(tabId, mStacks.get(tabId).lastElement(), false,false);
}
}
};
/* Might be useful if we want to switch tab programmatically, from inside any of the fragment.*/
public void setCurrentTab(int val){
mTabHost.setCurrentTab(val);
}
/*
* To add fragment to a tab.
* tag -> Tab identifier
* fragment -> Fragment to show, in tab identified by tag
* shouldAnimate -> should animate transaction. false when we switch tabs, or adding first fragment to a tab
* true when when we are pushing more fragment into navigation stack.
* shouldAdd -> Should add to fragment navigation stack (mStacks.get(tag)). false when we are switching tabs (except for the first time)
* true in all other cases.
*/
public void pushFragments(String tag, Fragment fragment,boolean shouldAnimate, boolean shouldAdd){
if(shouldAdd)
mStacks.get(tag).push(fragment);
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
if(shouldAnimate)
ft.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);
ft.replace(R.id.realtabcontent, fragment);
ft.commit();
}
public void popFragments(){
/*
* Select the second last fragment in current tab's stack..
* which will be shown after the fragment transaction given below
*/
Fragment fragment = mStacks.get(mCurrentTab).elementAt(mStacks.get(mCurrentTab).size() - 2);
/*pop current fragment from stack.. */
mStacks.get(mCurrentTab).pop();
/* We have the target fragment in hand.. Just show it.. Show a standard navigation animation*/
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
ft.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right);
ft.replace(R.id.realtabcontent, fragment);
ft.commit();
}
#Override
public void onBackPressed() {
if(mStacks.get(mCurrentTab).size() == 1){
// We are already showing first fragment of current tab, so when back pressed, we will finish this activity..
finish();
return;
}
/* Each fragment represent a screen in application (at least in my requirement, just like an activity used to represent a screen). So if I want to do any particular action
* when back button is pressed, I can do that inside the fragment itself. For this I used AppBaseFragment, so that each fragment can override onBackPressed() or onActivityResult()
* kind of events, and activity can pass it to them. Make sure just do your non navigation (popping) logic in fragment, since popping of fragment is done here itself.
*/
((AppBaseFragment)mStacks.get(mCurrentTab).lastElement()).onBackPressed();
/* Goto previous fragment in navigation stack of this tab */
popFragments();
}
/*
* Imagine if you wanted to get an image selected using ImagePicker intent to the fragment. Ofcourse I could have created a public function
* in that fragment, and called it from the activity. But couldn't resist myself.
*/
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(mStacks.get(mCurrentTab).size() == 0){
return;
}
/*Now current fragment on screen gets onActivityResult callback..*/
mStacks.get(mCurrentTab).lastElement().onActivityResult(requestCode, resultCode, data);
}
}
4. app_main_tab_fragment_layout.xml (In case anyone interested.)
<?xml version="1.0" encoding="utf-8"?>
<TabHost
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/tabhost"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<FrameLayout
android:id="#android:id/tabcontent"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="0"/>
<FrameLayout
android:id="#+android:id/realtabcontent"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<TabWidget
android:id="#android:id/tabs"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0"/>
</LinearLayout>
</TabHost>
5. AppTabAFirstFragment.java (First fragment in Tab A, simliar for all Tabs)
public class AppTabAFragment extends BaseFragment {
private Button mGotoButton;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_one_layout, container, false);
mGoToButton = (Button) view.findViewById(R.id.goto_button);
mGoToButton.setOnClickListener(listener);
return view;
}
private OnClickListener listener = new View.OnClickListener(){
#Override
public void onClick(View v){
/* Go to next fragment in navigation stack*/
mActivity.pushFragments(AppConstants.TAB_A, new AppTabAFragment2(),true,true);
}
}
}
This might not be the most polished and correct way. But it worked beautifully in my case. Also I only had this requirement in portrait mode. I never had to use this code in a project supporting both orientation. So can't say what kind of challenges I face there..
If anyone want a full project, I have pushed a sample project to github.
We had to implement exactly that same behaviour that you describe for an app recently. The screens and overall flow of the application were already defined so we had to stick with it (it's an iOS app clone...). Luckily, we managed to get rid of the on-screen back buttons :)
We hacked the solution using a mixture of TabActivity, FragmentActivities (we were using the support library for fragments) and Fragments. In retrospective, I'm pretty sure it wasn't the best architecture decision, but we managed to get the thing working. If I had to do it again, I'd probably try to do a more activity-based solution (no fragments), or try and have only one Activity for the tabs and let all the rest be views (which I find are much more reusable than activities overall).
So the requirements were to have some tabs and nestable screens in each tab:
tab 1
screen 1 -> screen 2 -> screen 3
tab 2
screen 4
tab 3
screen 5 -> 6
etc...
So say: user starts in tab 1, navigates from screen 1 to screen 2 then to screen 3, he then switches to tab 3 and navigates from screen 4 to 6; if the switched back to tab 1, he should see screen 3 again and if he pressed Back he should return to screen 2; Back again and he is in screen 1; switch to tab 3 and he's in screen 6 again.
The main Activity in the application is MainTabActivity, which extends TabActivity. Each tab is associated with an activity, lets say ActivityInTab1, 2 and 3. And then each screen will be a fragment:
MainTabActivity
ActivityInTab1
Fragment1 -> Fragment2 -> Fragment3
ActivityInTab2
Fragment4
ActivityInTab3
Fragment5 -> Fragment6
Each ActivityInTab holds only one fragment at a time, and knows how to replace one fragment for another one (pretty much the same as an ActvityGroup). The cool thing is that it's quite easy to mantain separate back stacks for each tab this way.
The functionality for each ActivityInTab was quite the same: know how to navigate from one fragment to another and maintain a back stack, so we put that in a base class. Let's call it simply ActivityInTab:
abstract class ActivityInTab extends FragmentActivity { // FragmentActivity is just Activity for the support library.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_in_tab);
}
/**
* Navigates to a new fragment, which is added in the fragment container
* view.
*
* #param newFragment
*/
protected void navigateTo(Fragment newFragment) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
ft.replace(R.id.content, newFragment);
// Add this transaction to the back stack, so when the user presses back,
// it rollbacks.
ft.addToBackStack(null);
ft.commit();
}
}
The activity_in_tab.xml is just this:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/content"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:isScrollContainer="true">
</RelativeLayout>
As you can see, the view layout for each tab was the same. That's because it's just a FrameLayout called content that will hold each fragment. The fragments are the ones that have each screen's view.
Just for the bonus points, we also added some little code to show a confirm dialog when the user presses Back and there are no more fragments to go back to:
// In ActivityInTab.java...
#Override
public void onBackPressed() {
FragmentManager manager = getSupportFragmentManager();
if (manager.getBackStackEntryCount() > 0) {
// If there are back-stack entries, leave the FragmentActivity
// implementation take care of them.
super.onBackPressed();
} else {
// Otherwise, ask user if he wants to leave :)
showExitDialog();
}
}
That's pretty much the setup. As you can see, each FragmentActivity (or just simply Activity in Android >3) is taking care of all the back-stacking with it's own FragmentManager.
An activity like ActivityInTab1 will be really simple, it'll just show it's first fragment (i.e. screen):
public class ActivityInTab1 extends ActivityInTab {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
navigateTo(new Fragment1());
}
}
Then, if a fragment needs to navigate to another fragment, it has to do a little nasty casting... but it's not that bad:
// In Fragment1.java for example...
// Need to navigate to Fragment2.
((ActivityIntab) getActivity()).navigateTo(new Fragment2());
So that's pretty much it. I'm pretty sure this is not a very canonical (and mostly sure not very good) solution, so I'd like to ask seasoned Android developers what would be a better approach to acheive this functionality, and if this is not "how it's done" in Android, I'd appreciate if you could point me to some link or material that explains which is the Android way to approach this (tabs, nested screens in tabs, etc). Feel free to tear apart this answer in the comments :)
As a sign that this solution is not very good is that recently I had to add some navigation functionality to the application. Some bizarre button that should take the user from one tab into another and into a nested screen. Doing that programmatically was a pain in the butt, because of who-knows-who problems and dealing with when are fragments and activities actually instantiated and initialized. I think it would have been much easier if those screens and tabs were all just Views really.
Finally, if you need to survive orientation changes, it's important that your fragments are created using setArguments/getArguments. If you set instance variables in your fragments' constructors you'll be screwed. But fortunately that's really easy to fix: just save everything in setArguments in the constructor and then retrieve those things with getArguments in onCreate to use them.
The framework won't currently do this for you automatically. You will need to build and manage your own back stacks for each tab.
To be honest, this seems like a really questionable thing to do. I can't imagine it resulting in a decent UI -- if the back key is going to do different things depending on the tab I am, especially if the back key also has its normal behavior of closing the entire activity when at the top of the stack... sounds nasty.
If you are trying to build something like a web browser UI, to get a UX that is natural to the user is going to involve a lot of subtle tweaks of behavior depending on context, so you'll definitely need to do your own back stack management rather than rely on some default implementation in the framework. For an example try paying attention to how the back key interacts with the standard browser in the various ways you can go in and out of it. (Each "window" in the browser is essentially a tab.)
This can be easily achieved with ChildFragmentManager
Here is post about this with associated project. take a look,
http://tausiq.wordpress.com/2014/06/06/android-multiple-fragments-stack-in-each-viewpager-tab/
Storing strong references to fragments is not the correct way.
FragmentManager provides putFragment(Bundle, String, Fragment) and saveFragmentInstanceState(Fragment).
Either one is enough to implement a backstack.
Using putFragment, instead of replacing a Fragment, you detach the old one and add the new one. This is what the framework does to a replace transaction that is added to the backstack. putFragment stores an index to the current list of active Fragments and those Fragments are saved by the framework during orientation changes.
The second way, using saveFragmentInstanceState, saves the whole fragment state to a Bundle allowing you to really remove it, rather than detaching. Using this approach makes the back stack easier to manipulate, as you can pop a Fragment whenever you want.
I used the second method for this usecase:
SignInFragment ----> SignUpFragment ---> ChooseBTDeviceFragment
\ /
\------------------------/
I don't want the user to return to the Sign Up screen, from the third one, by pressing the back button. I also do flip animations between them (using onCreateAnimation), so hacky solutions won't work, atleast without the user clearly noticing something is not right.
This is a valid use case for a custom backstack, doing what the user expects...
private static final String STATE_BACKSTACK = "SetupActivity.STATE_BACKSTACK";
private MyBackStack mBackStack;
#Override
protected void onCreate(Bundle state) {
super.onCreate(state);
if (state == null) {
mBackStack = new MyBackStack();
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction tr = fm.beginTransaction();
tr.add(R.id.act_base_frg_container, new SignInFragment());
tr.commit();
} else {
mBackStack = state.getParcelable(STATE_BACKSTACK);
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(STATE_BACKSTACK, mBackStack);
}
private void showFragment(Fragment frg, boolean addOldToBackStack) {
final FragmentManager fm = getSupportFragmentManager();
final Fragment oldFrg = fm.findFragmentById(R.id.act_base_frg_container);
FragmentTransaction tr = fm.beginTransaction();
tr.replace(R.id.act_base_frg_container, frg);
// This is async, the fragment will only be removed after this returns
tr.commit();
if (addOldToBackStack) {
mBackStack.push(fm, oldFrg);
}
}
#Override
public void onBackPressed() {
MyBackStackEntry entry;
if ((entry = mBackStack.pop()) != null) {
Fragment frg = entry.recreate(this);
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction tr = fm.beginTransaction();
tr.replace(R.id.act_base_frg_container, frg);
tr.commit();
// Pop it now, like the framework implementation.
fm.executePendingTransactions();
} else {
super.onBackPressed();
}
}
public class MyBackStack implements Parcelable {
private final List<MyBackStackEntry> mList;
public MyBackStack() {
mList = new ArrayList<MyBackStackEntry>(4);
}
public void push(FragmentManager fm, Fragment frg) {
push(MyBackStackEntry.newEntry(fm, frg);
}
public void push(MyBackStackEntry entry) {
if (entry == null) {
throw new NullPointerException();
}
mList.add(entry);
}
public MyBackStackEntry pop() {
int idx = mList.size() - 1;
return (idx != -1) ? mList.remove(idx) : null;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
final int len = mList.size();
dest.writeInt(len);
for (int i = 0; i < len; i++) {
// MyBackStackEntry's class is final, theres no
// need to use writeParcelable
mList.get(i).writeToParcel(dest, flags);
}
}
protected MyBackStack(Parcel in) {
int len = in.readInt();
List<MyBackStackEntry> list = new ArrayList<MyBackStackEntry>(len);
for (int i = 0; i < len; i++) {
list.add(MyBackStackEntry.CREATOR.createFromParcel(in));
}
mList = list;
}
public static final Parcelable.Creator<MyBackStack> CREATOR =
new Parcelable.Creator<MyBackStack>() {
#Override
public MyBackStack createFromParcel(Parcel in) {
return new MyBackStack(in);
}
#Override
public MyBackStack[] newArray(int size) {
return new MyBackStack[size];
}
};
}
public final class MyBackStackEntry implements Parcelable {
public final String fname;
public final Fragment.SavedState state;
public final Bundle arguments;
public MyBackStackEntry(String clazz,
Fragment.SavedState state,
Bundle args) {
this.fname = clazz;
this.state = state;
this.arguments = args;
}
public static MyBackStackEntry newEntry(FragmentManager fm, Fragment frg) {
final Fragment.SavedState state = fm.saveFragmentInstanceState(frg);
final String name = frg.getClass().getName();
final Bundle args = frg.getArguments();
return new MyBackStackEntry(name, state, args);
}
public Fragment recreate(Context ctx) {
Fragment frg = Fragment.instantiate(ctx, fname);
frg.setInitialSavedState(state);
frg.setArguments(arguments);
return frg;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(fname);
dest.writeBundle(arguments);
if (state == null) {
dest.writeInt(-1);
} else if (state.getClass() == Fragment.SavedState.class) {
dest.writeInt(0);
state.writeToParcel(dest, flags);
} else {
dest.writeInt(1);
dest.writeParcelable(state, flags);
}
}
protected MyBackStackEntry(Parcel in) {
final ClassLoader loader = getClass().getClassLoader();
fname = in.readString();
arguments = in.readBundle(loader);
switch (in.readInt()) {
case -1:
state = null;
break;
case 0:
state = Fragment.SavedState.CREATOR.createFromParcel(in);
break;
case 1:
state = in.readParcelable(loader);
break;
default:
throw new IllegalStateException();
}
}
public static final Parcelable.Creator<MyBackStackEntry> CREATOR =
new Parcelable.Creator<MyBackStackEntry>() {
#Override
public MyBackStackEntry createFromParcel(Parcel in) {
return new MyBackStackEntry(in);
}
#Override
public MyBackStackEntry[] newArray(int size) {
return new MyBackStackEntry[size];
}
};
}
Disclaimer:
I feel this is the best place to post a related solution I have worked on for a similar type of problem that seems to be pretty standard Android stuff. It's not going to solve the problem for everyone, but it may help some.
If the primary difference between your fragments is only the data backing them up (ie, not a lot of big layout differences), then you may not need to actually replace the fragment, but merely swap out the underlying data and refresh the view.
Here's a description of one possible example for this approach:
I have an app that uses ListViews. Each item in the list is a parent with some number of children. When you tap the item, a new list needs to open with those children, within the same ActionBar tab as the original list. These nested lists have a very similar layout (some conditional tweaks here and there perhaps), but the data is different.
This app has several layers of offspring beneath the initial parent list and we may or may not have data from the server by the time a user attempts to access any certain depth beyond the first. Because the list is constructed from a database cursor, and the fragments use a cursor loader and cursor adapter to populate the list view with list items, all that needs to happen when a click is registered is:
1) Create a new adapter with the appropriate 'to' and 'from' fields that will match new item views being added to the list and the columns returned by the new cursor.
2) Set this adapter as the new adapter for the ListView.
3) Build a new URI based on the item that was clicked and restart the cursor loader with the new URI (and projection). In this example, the URI is mapped to specific queries with the selection args passed down from the UI.
4) When the new data has been loaded from the URI, swap the cursor associated with the adapter to the new cursor, and the list will then refresh.
There is no backstack associated with this since we aren't using transactions, so you will have to either build your own, or play the queries in reverse when backing out of the hierarchy. When I tried this, the queries were fast enough that I just perform them again in oNBackPressed() up until I am at the top of hierarchy, at which point the framework takes over the back button again.
If you find yourself in a similar situation, make sure to read the docs:
http://developer.android.com/guide/topics/ui/layout/listview.html
http://developer.android.com/reference/android/support/v4/app/LoaderManager.LoaderCallbacks.html
I hope this helps someone!
I had exactly the same problem and implemented an open source github project that covers stacked tab, back and up navigation and is well tested and documented:
https://github.com/SebastianBaltesObjectCode/PersistentFragmentTabs
This is a simple and small framework for navigation tabs and fragment switching and handling of up and back navigation. Each tab has its own stack of fragments. It uses ActionBarSherlock and is compatible back to API level 8.
This is a complex problem as Android only handles 1 back stack, but this is feasible. It took me days to create a library called Tab Stacker that does exactly what you are looking for: a fragment history for each tab. It is open source and fully documented, and can be included easily with gradle. You can find the library on github: https://github.com/smart-fun/TabStacker
You can also download the sample app to see that the behaviour corresponds to your needs:
https://play.google.com/apps/testing/fr.arnaudguyon.tabstackerapp
If you have any question don't hesitate to drop a mail.
I'd like to suggest my own solution in case somebody is looking and want to try and choose the best one for his/her needs.
https://github.com/drusak/tabactivity
The purpose of creating the library is quite banal - implement it like iPhone.
The main advantages:
use android.support.design library with TabLayout;
each tab has its own stack using FragmentManager (without saving fragments' references);
support for deep linking (when you need to open specific tab and specific fragment's level in it);
saving / restoring states of tabs;
adaptive lifecycle methods of fragments in tabs;
quite easy to implement for your needs.
A simple solution:
Every time you change tab/root view call:
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
It will clear the BackStack. Remember to call this before you change the root fragment.
And add fragments with this:
FragmentTransaction transaction = getFragmentManager().beginTransaction();
NewsDetailsFragment newsDetailsFragment = NewsDetailsFragment.newInstance(newsId);
transaction.add(R.id.content_frame, newsDetailsFragment).addToBackStack(null).commit();
Note the .addToBackStack(null) and the transaction.add could e.g. be changed with transaction.replace.
This thread was very very interesting and useful.
Thanks Krishnabhadra for your explanation and code, I use your code and improved a bit, allowing to persist the stacks, currentTab, etc... from change configuration (rotating mainly).
Tested on a real 4.0.4 and 2.3.6 devices, not tested on emulator
I change this part of code on "AppMainTabActivity.java", the rest stay the same.
Maybe Krishnabhadra will add this on his code.
Recover data onCreate:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.app_main_tab_fragment_layout);
/*
* Navigation stacks for each tab gets created..
* tab identifier is used as key to get respective stack for each tab
*/
//if we are recreating this activity...
if (savedInstanceState!=null) {
mStacks = (HashMap<String, Stack<Fragment>>) savedInstanceState.get("stack");
mCurrentTab = savedInstanceState.getString("currentTab");
}
else {
mStacks = new HashMap<String, Stack<Fragment>>();
mStacks.put(AppConstants.TAB_A, new Stack<Fragment>());
mStacks.put(AppConstants.TAB_B, new Stack<Fragment>());
}
mTabHost = (TabHost)findViewById(android.R.id.tabhost);
mTabHost.setup();
initializeTabs();
//set the listener the last, to avoid overwrite mCurrentTab everytime we add a new Tab
mTabHost.setOnTabChangedListener(listener);
}
Save the variables and put to Bundle:
//Save variables while recreating
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable("stack", mStacks);
outState.putString("currentTab", mCurrentTab);
//outState.putInt("tabHost",mTabHost);
}
If exist a previous CurrentTab, set this, else create a new Tab_A:
public void initializeTabs(){
/* Setup your tab icons and content views.. Nothing special in this..*/
TabHost.TabSpec spec = mTabHost.newTabSpec(AppConstants.TAB_A);
spec.setContent(new TabHost.TabContentFactory() {
public View createTabContent(String tag) {
return findViewById(R.id.realtabcontent);
}
});
spec.setIndicator(createTabView(R.drawable.tab_a_state_btn));
mTabHost.addTab(spec);
spec = mTabHost.newTabSpec(AppConstants.TAB_B);
spec.setContent(new TabHost.TabContentFactory() {
public View createTabContent(String tag) {
return findViewById(R.id.realtabcontent);
}
});
spec.setIndicator(createTabView(R.drawable.tab_b_state_btn));
mTabHost.addTab(spec);
//if we have non default Tab as current, change it
if (mCurrentTab!=null) {
mTabHost.setCurrentTabByTag(mCurrentTab);
} else {
mCurrentTab=AppConstants.TAB_A;
pushFragments(AppConstants.TAB_A, new AppTabAFirstFragment(), false,true);
}
}
I hope this helps other people.
I would recommend do not use backstack based on HashMap>
there is lots of bugs in "do not keep activities" mode.
It will not correctly restore the state in case you deeply in fragment's stack.
And also will be crached in nested map fragment (with exeption: Fragment no view found for ID) .
Coz HashMap> after background\foreground app will be null
I optimize code above for work with fragment's backstack
It is bottom TabView
Main activity Class
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.widget.ImageView;
import android.widget.TabHost;
import android.widget.TextView;
import com.strikersoft.nida.R;
import com.strikersoft.nida.abstractActivity.BaseActivity;
import com.strikersoft.nida.screens.tags.mapTab.MapContainerFragment;
import com.strikersoft.nida.screens.tags.searchTab.SearchFragment;
import com.strikersoft.nida.screens.tags.settingsTab.SettingsFragment;
public class TagsActivity extends BaseActivity {
public static final String M_CURRENT_TAB = "M_CURRENT_TAB";
private TabHost mTabHost;
private String mCurrentTab;
public static final String TAB_TAGS = "TAB_TAGS";
public static final String TAB_MAP = "TAB_MAP";
public static final String TAB_SETTINGS = "TAB_SETTINGS";
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
getActionBar().hide();
setContentView(R.layout.tags_activity);
mTabHost = (TabHost) findViewById(android.R.id.tabhost);
mTabHost.setup();
if (savedInstanceState != null) {
mCurrentTab = savedInstanceState.getString(M_CURRENT_TAB);
initializeTabs();
mTabHost.setCurrentTabByTag(mCurrentTab);
/*
when resume state it's important to set listener after initializeTabs
*/
mTabHost.setOnTabChangedListener(listener);
} else {
mTabHost.setOnTabChangedListener(listener);
initializeTabs();
}
}
private View createTabView(final int id, final String text) {
View view = LayoutInflater.from(this).inflate(R.layout.tabs_icon, null);
ImageView imageView = (ImageView) view.findViewById(R.id.tab_icon);
imageView.setImageDrawable(getResources().getDrawable(id));
TextView textView = (TextView) view.findViewById(R.id.tab_text);
textView.setText(text);
return view;
}
/*
create 3 tabs with name and image
and add it to TabHost
*/
public void initializeTabs() {
TabHost.TabSpec spec;
spec = mTabHost.newTabSpec(TAB_TAGS);
spec.setContent(new TabHost.TabContentFactory() {
public View createTabContent(String tag) {
return findViewById(R.id.realtabcontent);
}
});
spec.setIndicator(createTabView(R.drawable.tab_tag_drawable, getString(R.string.tab_tags)));
mTabHost.addTab(spec);
spec = mTabHost.newTabSpec(TAB_MAP);
spec.setContent(new TabHost.TabContentFactory() {
public View createTabContent(String tag) {
return findViewById(R.id.realtabcontent);
}
});
spec.setIndicator(createTabView(R.drawable.tab_map_drawable, getString(R.string.tab_map)));
mTabHost.addTab(spec);
spec = mTabHost.newTabSpec(TAB_SETTINGS);
spec.setContent(new TabHost.TabContentFactory() {
public View createTabContent(String tag) {
return findViewById(R.id.realtabcontent);
}
});
spec.setIndicator(createTabView(R.drawable.tab_settings_drawable, getString(R.string.tab_settings)));
mTabHost.addTab(spec);
}
/*
first time listener will be trigered immediatelly after first: mTabHost.addTab(spec);
for set correct Tab in setmTabHost.setCurrentTabByTag ignore first call of listener
*/
TabHost.OnTabChangeListener listener = new TabHost.OnTabChangeListener() {
public void onTabChanged(String tabId) {
mCurrentTab = tabId;
if (tabId.equals(TAB_TAGS)) {
pushFragments(SearchFragment.getInstance(), false,
false, null);
} else if (tabId.equals(TAB_MAP)) {
pushFragments(MapContainerFragment.getInstance(), false,
false, null);
} else if (tabId.equals(TAB_SETTINGS)) {
pushFragments(SettingsFragment.getInstance(), false,
false, null);
}
}
};
/*
Example of starting nested fragment from another fragment:
Fragment newFragment = ManagerTagFragment.newInstance(tag.getMac());
TagsActivity tAct = (TagsActivity)getActivity();
tAct.pushFragments(newFragment, true, true, null);
*/
public void pushFragments(Fragment fragment,
boolean shouldAnimate, boolean shouldAdd, String tag) {
FragmentManager manager = getFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
if (shouldAnimate) {
ft.setCustomAnimations(R.animator.fragment_slide_left_enter,
R.animator.fragment_slide_left_exit,
R.animator.fragment_slide_right_enter,
R.animator.fragment_slide_right_exit);
}
ft.replace(R.id.realtabcontent, fragment, tag);
if (shouldAdd) {
/*
here you can create named backstack for realize another logic.
ft.addToBackStack("name of your backstack");
*/
ft.addToBackStack(null);
} else {
/*
and remove named backstack:
manager.popBackStack("name of your backstack", FragmentManager.POP_BACK_STACK_INCLUSIVE);
or remove whole:
manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
*/
manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
ft.commit();
}
/*
If you want to start this activity from another
*/
public static void startUrself(Activity context) {
Intent newActivity = new Intent(context, TagsActivity.class);
newActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(newActivity);
context.finish();
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putString(M_CURRENT_TAB, mCurrentTab);
super.onSaveInstanceState(outState);
}
#Override
public void onBackPressed(){
super.onBackPressed();
}
}
tags_activity.xml
<
?xml version="1.0" encoding="utf-8"?>
<TabHost
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#android:id/tabcontent"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="0"/>
<FrameLayout
android:id="#+android:id/realtabcontent"
android:background="#drawable/bg_main_app_gradient"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<TabWidget
android:id="#android:id/tabs"
android:background="#EAE7E1"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0"/>
</LinearLayout>
</TabHost>
tags_icon.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/tabsLayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#drawable/bg_tab_gradient"
android:gravity="center"
android:orientation="vertical"
tools:ignore="contentDescription" >
<ImageView
android:id="#+id/tab_icon"
android:layout_marginTop="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/tab_text"
android:layout_marginBottom="3dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/tab_text_color"/>
</LinearLayout>

Categories

Resources