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()
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...
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.
I want to create a feed like the one in the facebook app, and I want to have actionbarsherlock navigation tabs that will be used to filter the feed - a tab for people, a tab for places and a tab for items.
Right now I've set it up so that each tab opens up a fragment. How can I make them open up a list view instead?
ActionBar.Tab itemsFeedTab = actionBar.newTab();
ActionBar.Tab peopleFeedTab = actionBar.newTab();
ActionBar.Tab placesFeedTab = actionBar.newTab();
Fragment itemsFeedFragment = new FeedItems();
Fragment peopleFeedFragment = new FeedPeople();
Fragment placesFeedFragment = new FeedPlaces();
itemsFeedTab.setTabListener(new MyTabsListener(itemsFeedFragment));
peopleFeedTab.setTabListener(new MyTabsListener(peopleFeedFragment));
placesFeedTab.setTabListener(new MyTabsListener(placesFeedFragment));
actionBar.addTab(itemsFeedTab, 0, true);
actionBar.addTab(peopleFeedTab, 1, false);
actionBar.addTab(placesFeedTab, 2, false);
class MyTabsListener implements ActionBar.TabListener {
public Fragment fragment;
public MyTabsListener(Fragment fragment){
this.fragment = fragment;
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
// TODO Auto-generated method stub
ft.replace(R.id.home, fragment);
}
If your fragments contain only ListViews then its better to use ListFragment
Well the answer is hidden in your question , you are trying to open ListView instead of Fragment.
Why don't you open a ListFragment and you are done.
I switched part of my App from Activities to Fragments so that I can use the neat ActionBar tabs.
However, after completing the transition I ran into an issue: whenever I switch to another tab, that Fragment gets created all over again. Both onCreate and onCreateView get called every time I get to a tab.
I have 4 tabs, each of which is meant to open one of these fragments:
Fragment ShopFragment = new WebActivity();
Fragment SearchFragment = new SearchActivity(context);
Fragment StoreFragment = new StoreLocatorActivity(context, this);
Fragment BlogsFragment = new BlogsActivity(context, this);
Here's my code for the listener:
class MyTabsListener implements ActionBar.TabListener {
public Fragment fragment;
public MyTabsListener(Fragment fragment) {
this.fragment = fragment;
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
ft.hide(fragment);
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
ft.replace(R.id.fragment_container, fragment);
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}
}
Could someone please point me in the right direction?
When you call FragmentTransaction.replace(...), Android will effectively perform a sequence of FragmentTransaction.remove(...) (for all Fragments currently added to that container) and FragmentTransaction.add(...) (for your supplied Fragment). Removing a Fragment from the FragmentManager will cause the Fragment to be destroyed and its state will no longer be managed. Most noticeably, when you re-add the Fragment all of the views will have been reset. Note: since you are reusing the same Fragment instance, the Fragment will still keep the value any instance variables.
One solution to this problem would be to use FragmentTransaction.detach(Fragment) and FragmentTransaction.attach(Fragment) when switching. This will cause the Fragment views to be recreated (onDestroyView() & onCreateView() will be called), but the instance state bundle will be saved and given back to you between calls and so the view state can be maintained. This is the approach taken by FragmentPagerAdapter when it tries to switch between Fragments.
Alternatively, you could allow the Fragments to be destroyed, but maintain their saved state for them independently. This would use less memory, at the expense of a slower switching time. Methods of note would be FragmentManager.saveFragmentInstanceState(Fragment) and FragmentManager.setInitialSavedState(Fragment.SavedState), in conjuction with adding/removing. This is the approach taken by FragmentStatePagerAdapter.
You can have a look at the source for FragmentPagerAdapter and the source for FragmentStatePagerAdapter for implementation hints.
There is the show/hide option just so the fragments would not need to be repainted/recreated and the onCreate() and onCreateView() won't be reinvoked.
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.