Now im into my first question, after not finding an answer in the internet. Usually this should be a really easy thing, but it took me hours and hours so please excuse me if its a beginner question. Also i want to make sure im going the right way before spending more days just to see that in the end it just not works as i want, the way i implement it atm.
The problem:
My App has 3 Tabs with 3 fragments as content, one for every tab. I use Tabs because all users i asked want Tabs and not a Navigation Drawer and i listen to users, not to google (sorry). Also the 3 tabs have very different "roles" (Language Guide, Currency convertor, and own Notebook for own phrases etc) and it makes sense to split it this way (imo). If someone has other ideas, go ahead, im open :-)
The tab-navigation works as intended so far. But now i want to build the further navigation for the single Tabs an im not sure anymore wich way to go... Theres just so many way for a simple thing like this...
The first Tab has some imageButtons to further navigate into this section (See Screenshot 1), to choose a category in the language section. Of course this means the tabs should be stay in place to be able to easy switch to the Converter i.E, just the fragment from tab 1 should get switched to the new fragment (see Screenshot 2), and so on. The Buttons that you see in the below screenshot have clicklisteners that change current fragment to the new one (like "Daily")
For this i open another fragment and add it to the backstack. Maybe you already can guess what happens now. If i press the backbutton, i come back to the first fragment of course. Problems start when i switch tabs when im inside a fragment opened on tab 1. The added fragment is still in backstack what means if i press back button on Tab 2 or 3, the displayed content gets mixes up somehow with the other fragment ones. See Screenshot 3
Do i make an error somehere or can i simply tell the backstack to "reset" when switching tabs so this problem doesnt occur?
Thank you for info and sorry for language and spelling errors, english isnt my main language.
Tablistener Class to switch the tab fragments:
public class MyTabListener implements ActionBar.TabListener {
Fragment fragment;
public MyTabListener(Fragment fragment) {
this.fragment = fragment;
}
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
ft.replace(R.id.fragment_container, fragment);
}
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
ft.remove(fragment);
}
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
// nothing done here
}
}
The Fragments class from first fragment look like this (not full code):
public static class FragmentTab1 extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
final View view = inflater.inflate(R.layout.tab, container, false);
ImageButton catBtn_Daily = (ImageButton) view.findViewById(R.id.catBtn_Daily);
ImageButton catBtn_OnTheRoad = (ImageButton) view.findViewById(R.id.catBtn_OnTheRoad);
ImageButton catBtn_Shopping = (ImageButton) view.findViewById(R.id.catBtn_Shopping);
ImageButton catBtn_Restaurant = (ImageButton) view.findViewById(R.id.catBtn_Restaurant);
ImageButton catBtn_Romance = (ImageButton) view.findViewById(R.id.catBtn_Romance);
ImageButton catBtn_Emergency = (ImageButton) view.findViewById(R.id.catBtn_Emergency);
// attach an OnClickListener
catBtn_Daily.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Create new fragment and transaction
Fragment newFragment = new FragmentTab4();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
transaction.setTransition(4097);
transaction.commit();
}
});
Mainactivity:
public class MainActivity extends Activity {
ActionBar.Tab tab1, tab2, tab3;
Fragment fragmentTab1 = new FragmentTab1();
Fragment fragmentTab2 = new FragmentTab2();
Fragment fragmentTab3 = new FragmentTab3();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tab_test);
ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
tab1 = actionBar.newTab().setText("Home");
tab2 = actionBar.newTab().setText("Calculator");
tab3 = actionBar.newTab().setText("Notebook");
tab1.setTabListener(new MyTabListener(fragmentTab1));
tab2.setTabListener(new MyTabListener(fragmentTab2));
tab3.setTabListener(new MyTabListener(fragmentTab3));
actionBar.addTab(tab1);
actionBar.addTab(tab2);
actionBar.addTab(tab3);
Cannot post pics yet, rep too low....
Please see here (third pic is called Untitled-3.png)
Screen 1: https://www.dropbox.com/s/3av3pf6a17p2w42/Untitled-1.png
Screen 2: https://www.dropbox.com/s/3av3pf6a17p2w42/Untitled-2.png
Screen 3: https://www.dropbox.com/s/3av3pf6a17p2w42/Untitled-3.png
you might be better off not using the fragment back stack at all. you can maintain your own stack and override onBackPressed() to provide the appropriate behavior.
#Override protected void onBackPressed()
{
if(myStack.isEmpty())
super.onBackPressed(); // default handling finishes the activity
else
{
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, myStack.pop());
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
transaction.commit();
}
}
Actually it sounds like you might want to consider a completely different approach. If I am understanding correctly you might want to consider using a ViewPager with a FragmentPagerAdapter (see the google documentation code sample here: FragmentPagerAdapter).
Essentially the viewpager presents the fragments under your tabs, the adapter provides the list of fragments to show and you select the current "page" by calling setCurrentItem on the viewpager (when the user clicks a tab). Additionally users will be able to swipe between pages (you would update the selected tab by listening to the pager events see google documentation code sample here: ViewPager).
This approach avoids the whole backstack issue and sounds more like what you are after.
Hope it helps.
You can try following:
public void onTabSelected(YourTab tab) {
Fragment fragment = new YourTabFragment();
replaceRootFragment(fragment);
}
public void replaceRootFragment(Fragment fragment) {
if (getSupportFragmentManager().getBackStackEntryCount() != 0) {
int id = getSupportFragmentManager().getBackStackEntryAt(0).getId();
try {
getSupportFragmentManager().popBackStackImmediate(id, FragmentManager.POP_BACK_STACK_INCLUSIVE);
} catch (IllegalStateException e) {
return;
}
}
getSupportFragmentManager().beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.replace(R.id.container, fragment)
.commit();
}
Call onTabSelected when you click on the tab.
Related
Here the summary of my 'project' on Android 4.x and the problem I can't solve:
i have an actionBar with 2 tabs (i know it is deprecated):
...
Fragment fragmentTab1 = new FragmentTab1();
Fragment fragmentTab2 = new FragmentTab2();
Fragment fragmentTab3 = new FragmentTab3();
ActionBar.Tab tab1, tab2;
ActionBar actionBar = getActionBar();
tab1 = actionBar.newTab().setText("tab1");
tab2 = actionBar.newTab().setText("tab2");
tab1.setTabListener(new MyTabListener(fragmentTab1));
tab2.setTabListener(new MyTabListener(fragmentTab2));
actionBar.addTab(tab1);
actionBar.addTab(tab2);
...
I would like to change the fragment of a tab and refresh what the user sees, doing that in a method (not only when the tab is (re)selected).
It sounds simple, but impossible to find a easy way to do that.
After many fails, this is what I try (i remove the tab and add it with a new fragment):
void myMethod () {
getActionBar().removeTabAt(0);
tab1 = actionBar.newTab().setText("tab3");
tab1.setTabListener(new MyTabListener(fragmentTab3));
getActionBar().addTab(tab1, 0);
...
And then this awful thing to desesperatly try to update what is seen...
...
getActionBar().setSelectedNavigationItem(tab1.getPosition()+1);
getActionBar().setSelectedNavigationItem(tab1.getPosition());
}
Everything goes as wanted, except that the reselected tab1 is empty... :(
If I select with my finger tab2 then tab1 (so manually in place of programmaticaly), then tab1 appears as wanted...
So, how can I change programmaticaly the content of a tab and refresh what is seen in an actionBar?
Already a big thanks for your time!
You should depend on TabListener methods, don't do getActionBar().setSelectedNavigationItem(). There are good code samples on the Internet.
1) Google webpage # Creating Swipe Views with Tabs. Code snippet:
ActionBar.TabListener tabListener = new ActionBar.TabListener() {
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
// show the given tab
}
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
// hide the given tab
}
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
// probably ignore this event
}
};
Notes: Refresh your data in onTabSelected(). You may check for tags or text in tab parameter.
2) Another good link for you # Android Fragment Tabs Example. Code snippet from the link:
public class TabListener implements ActionBar.TabListener {
private Fragment fragment;
public TabListener(Fragment fragment) {
this.fragment = fragment;
}
// When a tab is tapped, the FragmentTransaction replaces
// the content of our main layout with the specified fragment;
// that's why we declared an id for the main layout.
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
ft.replace(R.id.activity_main, fragment);
}
Notes: Again, refresh your data in onTabSelected(). I have to warn you that TabListener is deprecated in the new Lollipop API.
EDIT:
Sample code to set Tabs to the correct Fragments.
// Setting tab listeners.
bmwTab.setTabListener(new TabListener(bmwFragmentTab));
toyotaTab.setTabListener(new TabListener(toyotaFragmentTab));
fordTab.setTabListener(new TabListener(fordFragmentTab));
Good luck and have fun...
I have an activity with a relative layout; And have two different fragments. I show one fragment and switch to the next fragment on a button click vice verse.
// When button menu is clicked
OnClickListener btnClick = new OnClickListener() {
#Override
public void onClick(View arg0) {
if(currentType==0){
InitThisFragment(1);
}else{
InitThisFragment(0);
}
}
};
public void InitThisFragment(int type){
Fragment newFragment;
if(type==0){
newFragment=new MainFragment();
}else{
newFragment=new JourneyFragment();
}
currentType=type;
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.abs_fragment_container, newFragment);
transaction.addToBackStack(null);
transaction.commit();
}
R.id.abs_fragment_container is a Relative Layout
BOTH THE FRAGMENT CONTAIN GOOGLE MAPS INSIDE THEM;
But it is not working as expected.
By default i add the first fragment [which works fine].
Then after an onClick event the 2nd fragment comes[which also works
fine]
But on my next click the app crashes;
Please help I am new to Google Maps and Fragments;
yeah of course your app crashes because the fragment with the map already exists look at this thread it helped me a lot: Duplicate ID, tag null, or parent id with another fragment for com.google.android.gms.maps.MapFragment
Especially the second answer with 41 ups is easy to understand
And yes I would have posted this one in a comment if I would have enough reputation for it!
I've settings activity, which has 3 tabs (each tab contains fragment) + in the main activity there is also buttons row - with buttons OK, and Cancel.When I press OK, I would like to do something like this:
1) get all my custom variables from all fragments
2) save them to shared prefs
But how to get access to fragment variables? I tried this:
Adding tabs in main activity:
ActionBar.Tab tab1 = actionbar.newTab().setText(res.getString(R.string.actSettingsTab1));
tab1.setTabListener(new MyTabsListener(new Tab1Fragment(), "tab1"));
actionbar.addTab(tab1);
//...similar for all tabs
This is my tab listener:
class MyTabsListener implements ActionBar.TabListener {
private Fragment fragment;
private String tag;
public MyTabsListener(Fragment fragment, String tag) {
this.fragment = fragment;
this.tag = tag;
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
// do nothing
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
ft.replace(R.id.fragment_container, fragment, tag);
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
ft.remove(fragment);
}
}
And this is how I would like to get variables from fragments:
Tab1Fragment tab1 = (Tab1Fragment) fm.findFragmentByTag("tab1");
Tab2Fragment tab2 = (Tab1Fragment) fm.findFragmentByTag("tab2");
Tab3Fragment tab3 = (Tab1Fragment) fm.findFragmentByTag("tab3");
but it's strange - findFragmentByTag returns fragment only for selected tab, otherwise returns null. So when I have selected tab1 and press ok, findFragmentByTag return fragment for tab1, but null for others.
Maybe I'm doing something wrong, or whole my tring is goind wrong way. How to retrieve values from all fragments in parent activity and save them to shared preferences?
Separate the process into two steps:
1) Gather all settings from all the fragments in real time (i.e. as they are changed by the user). Use Listener pattern, so that fragments will expose an interface by which they will notify attached listener when user modifies any of the settings for which given fragment is responsible for. Let the activity attach itself as a listener to all fragments, and capture changes (store them in a structure that suits you).
2) Let the activity save settings using SharedPreferences when OK button is clicked. It does not have to access fragments as it always about any settings user has changed.
This way you do not have to access all the fragments at once, which is impossible if they are removed from the memory (as they are not visible anyway at a given time). Instead, you can re-assign the activity as a listener to selected fragment every time it gets selected/displayed.
I wouldn't recommend actually accessing the variables from your fragments. You could make a public method in those fragments that does the shared preferences saving that you want. Ex:
Tab1Fragment tab1 = (Tab1Fragment) fm.findFragmentByTag("tab1");
Tab2Fragment tab2 = (Tab1Fragment) fm.findFragmentByTag("tab2");
Tab3Fragment tab3 = (Tab1Fragment) fm.findFragmentByTag("tab3");
tab1.savePreferences();
tab2.savePreferences();
tab3.savePreferences();
ActionBar.Tab tab1 = actionbar.newTab().setText(res.getString(R.string.actSettingsTab1));
tab1.setTabListener(new MyTabsListener(new Tab1Fragment(), "tab1"));
actionbar.addTab(tab1);
//...similar for all tabs
this does not add your Fragment to the backstack therefore you get only the selected tab and the others are null.
I think the best you can do is iterate through the tabs of the ActionBar using getTabCount() and then you should be able to get your content with getTabAt(int index). getCustomView()
Since Tab Activity is deprecated I'm trying to implement tabs with fragments. As you can see in the lots of StackOverFlow questions back stack is an issue when you work with fragments and which has its own back stack.
So the thing I'm trying to do is, there is a fragment in each tab and this fragment can call another fragment within the same tab and its also the same for the other tabs.
Since there is only one activity then there is only one back stack for whole application. So I need to create my custom back stack which is separated for each tab. It's also the same common idea in the other questions. I need to find a way to create custom back stack but I couldnt find any example to take a look.
Is there any tutorial or any example piece of code doing something similar ? Thanks in advance.
There is a backstack for the whole application, but there is also a backstack for fragments.
Perhaps have a read of this:
http://developer.android.com/guide/components/fragments.html#Transactions
When you perform a fragment transaction (add, replace or remove), you can add that transaction to the backstack.
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragmentContainer, fragment);
ft.addToBackStack(null);
ft.commit();
And now when you press back, the latest fragment will be 'popped' from the fragment backstack.
You could also override the onBackPressed(), and manage your fragment backstack there. (I'm currently having trouble trying to work out how to do this efficiently).
Anyway, there are several calls available from the FragmentManager to do with the backstack. The most useful to me being:
FragmentManager.getBackStackEntryCount()
and
FragmentManager.PopBackStack()
Sorry for late answer, but this might help somebody. I have added functionality like this in my project. I used fragment tabhost to add backstacks within it. Main logic will remain same in others.
Basically I took,
Stack<String> fragmentStack = new Stack<>();
boolean bBackPress = false;
String strPrevTab;
FragmentTabHost tabHost;
strPrevTab = "tag"; // Add tag for tab which is selected by default
tabHost.setOnTabChangedListener( new TabHost.OnTabChangeListener() {
#Override
public void onTabChanged( String tabId ) {
if ( !bBackPress ) {
if ( fragmentStack.contains( tabId ) ) {
fragmentStack.remove( tabId );
}
fragmentStack.push( strPrevTab );
strPrevTab = tabId;
}
}
} );
#Override
public void onBackPressed() {
if ( fragmentStack.size() == 0 ) {
finish();
} else {
strPrevTab = fragmentStack.pop();
bBackPress = true;
tabHost.setCurrentTabByTag( strPrevTab );
bBackPress = false;
}
}
Hope this helps!
I have an app with three tabs (ActionBar Tabs), each one with one fragment at a time.
TabListener
TabsActivity
Tab1 -> ListFragment1 -> ListFragment2 -> Fragment3
Tab2 -> Tab2Fragment
Tab3 -> Tab3Fragment
The problem is when I create the FragmentTransaction (inside OnListItemClicked) from ListFragment1 to ListFragment2, the fragments inside the other tabs also change to ListFragment2.
In the end, I want to change fragments only inside on tab and preserve the state of the other tabs.
I'm already saving the state (OnSavedInstance()).
Do you know what I'm missing here?
Some of the code:
public class TabsActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tabs);
// setup Action Bar for tabs
ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// instantiate fragment for the tab
Fragment networksFragment = new NetworksFragment();
// add a new tab and set its title text and tab listener
actionBar.addTab(actionBar.newTab().setText("Tab1")
.setTabListener(new TabsListener(ListFragment1)));
// instantiate fragment for the tab
Fragment historyFragment = new HistoryFragment();
// add a new tab and set its title text and tab listener
actionBar.addTab(actionBar.newTab().setText("Tab2")
.setTabListener(new TabsListener(Tab2Fragment)));
// instantiate fragment for the tab
Fragment settingsFragment = new SettingsFragment();
// add a new tab and set its title text and tab listener
actionBar.addTab(actionBar.newTab().setText("Tab3")
.setTabListener(new TabsListener(Tab3Fragment)));
}
}
public class TabsListener implements ActionBar.TabListener {
private Fragment frag;
// Called to create an instance of the listener when adding a new tab
public TabsListener(Fragment networksFragment) {
frag = networksFragment;
}
#Override
public void onTabReselected(Tab arg0, FragmentTransaction arg1) {
// TODO Auto-generated method stub
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
ft.add(R.id.fragment_container, frag, null);
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
ft.remove(frag);
}
}
public class ListFragment1 extends ListFragment {
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
getListView().setItemChecked(position, true);
ListFragment2 fragment2 = ListFragment2.newInstance(position);
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.fragment_container, fragment2);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(null);
ft.commit();
}
}
You're not missing anything (or I'm missing it too).
I searched long and hard for a way to do this "properly" but I couldn't find anything. What I ended up doing is writing my own backstack logic.
Unfortunately my employer owns my code so I can't share any of that verbatim, but here was my approach:
Create an enum with one entry for each of your tabs. Let's call it TabType.
Now create an instance variable tabStacks of type HashMap<TabType, Stack<String>>. Now you can instantiate one stack for each tab - each stack is a list of tags, as specified by Fragment.getTag(). This way you don't have to worry about storing references to Fragments and whether they're going to disappear on you when you rotate the device. Any time you need a reference to a Fragment, grab the right tag off the stack and use FragmentManager.findFragmentByTag().
Now whenever you want to push a Fragment onto a tab, generate a new tag (I used UUID.randomUUID().toString()) and use it in your call to FragmentTransaction.add(). Then push the tag on top of the stack for the currently displayed tab.
Be careful: when you want to push a new fragment on top of an old one, don't remove() the old one, since the FragmentManager will consider it gone and it will be cleaned up. Be sure to detach() it, and then attach() it later. Only use remove() when you're permanently popping a Fragment, and only use add() the first time you want to show it.
Then, you'll have to add some relatively simple logic to your TabListener. When a tab is unselected, simply peek() at its stack and detatch() the associated Fragment. When a tab is selected, peek() at the top of that stack and attach() that fragment.
Lastly, you'll have to deal with Activity lifecycle quirks (like orientation changes). Persist your Map of Stacks as well as the currently selected tab, and unpack it again in your onCreate(). (You don't get this packing and unpacking for free, but it's pretty easy to do.) Luckily your TabType enum is Serializable so it should be trivial to put into a Bundle.