I'm creating a simple Activity, which contains two tabs. I'm following the documentation, so I'm using Fragment.
In my Activity there are only two tabs. The first one is a Map (MapFragment), while the second is a simple List (ListFragment)
All works good, my only problem is that I can't manage correctly the map.
In the onTabSelected callback I have use the following code:
public void onTabSelected(Tab tab, FragmentTransaction ft) {
if (mFragment == null) {
mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs);
ft.add(android.R.id.content, mFragment, mTag);
} else {
ft.attach(mFragment);
if(mTag.compareToIgnoreCase("map")==0)
setUpMapIfNeeded((MapFragment)mFragment);
}
}
where map is the tag of my first Activity and the mthod setUpMapIfNeeded is:
private static void setUpMapIfNeeded(MapFragment mMapFragment) {
// Do a null check to confirm that we have not already instantiated the map.
if (mMap == null) {
mMap = mMapFragment.getMap();
// Check if we were successful in obtaining the map.
if (mMap != null) {
mMap.addMarker(new MarkerOptions().position(new LatLng(0, 0)).title("Marker"));
}
}
}
So the marker is added in the map only if I select the tab.
The questione is: How can I modify my code for performing the addMarker action also when the activity is started (without pressing explicitly the first tab)?
I have solved adding this code:
#Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
FragmentManager fm = getFragmentManager();
Fragment f = fm.findFragmentByTag("map");
if(f!= null && f.isAdded())
setUpMapIfNeeded((MapFragment)f);
}
I would think that you could:
Refactor the content of onTabSelected to call a addMapFragment(FragmentTransaction ft), just put the whole code inside the addMapFragment method and return the FragmentTransaction. onTabSelected then becomes:
public void onTabSelected(Tab tab, FragmentTransaction ft) {
// if map tab
ft = addMapFragment(ft);
}
This will make it easy to handle the map and markers in onCreate:
protected void onCreate(Bundle savedInstanceState) {
...
if (savedInstanceState != null) {
// not first startup - map should already be created with markers
} else {
FragmentTransaction ft = getFragmentMangager.beginTransaction();
ft = addMapFragment(ft);
ft.commit();
}
}
Related
Whenever I want to change fragment depending on the item selected I use:
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment).commit();
But now I want to make it so that I can simply show the different fragments instead of creating new ones every time I re-select them, so I found .show instead of .replace, but the problem with .show is that I can not declare where I want that fragment to be shown... or perhaps I'm maybe doing it wrong:
getSupportFragmentManager().beginTransaction().show(fragment).commit();
If you want to use show/hide method, you need to add a fragment before this, as you can see the container is available here. I just showed an example of how to show or hide one fragment. If you have a lot of fragments you can for example save the list and then hide others in the loop. If interested, I added from below.
public void showFragment(Fragment fragment) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if (fragment.isAdded()) {
fragmentTransaction.show(fragment);
} else {
fragmentTransaction.add(R.id.container, fragment);
}
fragmentTransaction.commit();
}
public void hideFragment(Fragment fragment) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if (fragment.isAdded()) {
fragmentTransaction.hide(fragment);
}
fragmentTransaction.commit();
}
If there are many fragments
public class MainActivity extends AppCompatActivity {
HashMap<String, Fragment> fragments = new HashMap<>();
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/*
* call this method when you want to add a new fragment
* Example: addFragment(new A(), "TAG_A")
* */
public void addFragment(Fragment fragment, String fragmentTag) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
/*Before add fragment we hide others*/
for (String fTag : fragments.keySet()) {
Fragment fragmentInStack = fragments.get(fTag);
if (fragmentInStack.isAdded() && !fragmentInStack.isHidden()) {
fragmentTransaction.hide(fragmentInStack);
}
}
if (!fragment.isAdded()) {
fragments.put(fragmentTag, fragment);
fragmentTransaction.add(R.id.container, fragment, fragmentTag);
} else {
fragmentTransaction.show(fragment);
}
fragmentTransaction.commit();
}
/*
* call this method when you want to show fragment which are on the list
* Example: We have current fragment C and you want to show fragment A, that to call showFragment("TAG_A")
* */
public void showFragment(String fragmentTag) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
/*hide others fragment besides the one we need to show*/
for (String fTag : fragments.keySet()) {
Fragment fragmentInStack = fragments.get(fTag);
if (!fTag.equals(fragmentTag) && fragmentInStack.isAdded()) {
fragmentTransaction.hide(fragmentInStack);
}
}
Fragment fragment = fragments.get(fragmentTag);
if (fragment != null && fragment.isAdded()) {
fragmentTransaction.show(fragment);
}
fragmentTransaction.commit();
}
}
I have an app built using TabBarSherlock and the Support library to add ActionBar support to pre 3.0 devices. I can't remember what tutorial I followed to create the Tabs and the Listener but I have the following code.
Firstly creating the Tabs (Inside a SherlockFragmentActivity):
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
/*--------Setup News Tab--------*/
Tab tab1 = actionBar.newTab()
.setText("News")
.setTabListener(new TabListener<TabFragment>(
this, "tab1", TabFragment.class));
Bundle newsBundle = new Bundle();
newsBundle.putInt("news_id", newsID);
tab1.setTag(newsBundle);
actionBar.addTab(tab1);
/*------------------------------*/
// This is repeated 3 more times to total 4 Tabs.
Then I have a classCalled TabListener which is used in each of these Tabs to detect when they have been selected.
public class TabListener<T extends Fragment> implements ActionBar.TabListener{
private TabFragment mFragment;
private final Activity mActivity;
private final String mTag;
private final Class<T> mClass;
public TabListener(Activity activity, String tag, Class<T> clz) {
mActivity = activity;
mTag = tag;
mClass = clz;
}
public void onTabSelected(Tab tab, FragmentTransaction ft) {
// Check if the fragment is already initialised
if (mFragment == null) {
Log.v("FRAGMENT", "FRAGMENT NEEDS TO BE CREATED");
mFragment = (TabFragment) Fragment.instantiate(mActivity, mClass.getName(), (Bundle)tab.getTag());
ft.add(android.R.id.content, mFragment, mTag);
} else {
Log.v("FRAGMENT", "FRAGMENT ALREADY CREATED");
ft.show(mFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (mFragment != null) {
ft.hide(mFragment);
}
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
}
With the class TabFragment containing a ViewPager for each Tab. My issue is that when selecting a Tab other than the first one the content inside the Fragment does not show. From the logs in place when the Fragment is initialised I can tell the views are being created just not being shown, it's just a blank area showing the background.
Instead of using show() and hide(), use attach() and detach(). Using show/hide does not remove the view hierarchy from the screen, simply hides it, so there might be issues related to that.
You are not detaching fragment rather you are just hiding it. so you should detach it so that other fragment should be attached in your onTabUnSelected.
FragmentManager will automatically restore whatever fragment (and history) was currently displayed upon a configuration change. Call findFragmentByTag to see if an instance of the target fragment already exists before creating and attaching a new instance.
Example:
public void onTabSelected(Tab tab, FragmentTransaction ft) {
SherlockFragment preInitializedFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag);
// Check if the fragment is already initialized
if (mFragment == null && preInitializedFragment == null) {
// If not, instantiate and add it to the activity
mFragment = (SherlockFragment) SherlockFragment.instantiate(mActivity, mClass.getName());
ft.add(android.R.id.content, mFragment, mTag);
} else if (mFragment != null) {
// If it exists, simply attach it in order to show it
ft.attach(mFragment);
} else if (preInitializedFragment != null) {
ft.attach(preInitializedFragment);
mFragment = preInitializedFragment;
}
}
and you onTabUnSelected should be like this
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (mFragment != null) {
// Detach the fragment, because another one is being attached
ft.detach(mFragment);
}
}
Content is empty on tab bar then write below code
final ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(actionBar.NAVIGATION_MODE_STANDARD);
In my Android app I'm using an Action Bar with tabs to switch between fragments that display the content of the tabs. Everything is working fine until an orientation change: Then Android starts to draw the widgets on top of each other so that the contents of the fragments get mixed up. My TabListener:
private class TabListener implements ActionBar.TabListener {
private final Activity mActivity;
private final String mTag;
public TabListener(Activity activity, String tag) {
mActivity = activity;
mTag = tag;
}
#Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
Fragment mFragment = MyActivity.this.getSupportFragmentManager().findFragmentByTag(mTag);
if (mFragment == null) {
mFragment = new MyFragment();
ft.add(android.R.id.content, mFragment, mTag);
} else {
ft.attach(mFragment);
}
}
#Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
Fragment mFragment = MyActivity.this.getSupportFragmentManager().findFragmentByTag(mTag);
if (mFragment != null) {
ft.detach(mFragment);
}
}
#Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
// Do nothing
}
}
The only thing I noticed was that on every orientation change, onTabUnselected() won't be called, but still onTabSelected() is called (so the current tab will be attached twice without being detached in between). If that's the problem, I do not know how to fix it. If that's not the problem, I do not know where to look. I would be glad if someone has a suggestion.
Sidenote: I'm using the Action Bar from ActionBarSherlock. The problem appears on all Android versions I tested with (2.3, 4.0, 4.1).
I am not sure but here are some steps you can follow
Take Bundle reference before your Activity onCreate() method.
Bundle b1;
In your onCreate() method , put the Bundle value in b1
#Override
public void onCreate(Bundle savedInstanceState) {b1=savedInstanceState;
............................
}
Use this b1 in your onTabSelected method as
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
Fragment mFragment = MyActivity.this.getSupportFragmentManager().findFragmentByTag(mTag);
if (b1!=null){
ft.detach(mFragment);
//and the rest code
................}
}
Its an conclusion , which I have concluded with my working with fragments, but I have not done it with TabListener. So tell me when you are done , or any other solution.
I using sherlock bar (http://actionbarsherlock.com/). There is code of TabListener:
public class TabListener implements ActionBar.TabListener {
private String mTag;
private Fragment mFragment;
public TabListener(String tag) {
mTag = tag;
// Check to see if we already have a fragment for this tab, probably
// from a previously saved state. If so, deactivate it, because our
// initial state is that a tab isn't shown.
mFragment = getSupportFragmentManager().findFragmentByTag(mTag);
if (mFragment != null && !mFragment.isDetached()) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.detach(mFragment);
ft.commit();
}
}
public void onTabSelected(Tab tab) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (mFragment == null) {
if((tab.getPosition()==0) || (tab.getPosition()==1)) {
mFragment = new MainListFragment(ctx, ONLINE_TYPE);
} else {
mFragment = new SimpleFragmentf();
}
ft.add(com.lib.reader.R.id.root2, mFragment, mTag);
ft.addToBackStack(null);
ft.commit();
} else {
ft.attach(mFragment);
ft.commit();
}
}
public void onTabUnselected(Tab tab) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.detach(mFragment);
ft.commit();
}
#Override
public void onTabReselected(Tab tab) {
//To change body of implemented methods use File | Settings | File Templates.
}
}
There is part of View's code:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
LinearLayout v = (LinearLayout)inflater.inflate(R.layout.main, container, false);
v.addView(pager);
return v;
}
All work, but when I selected any tab in second time - I get error:
The specified child already has a parent. You must call removeView() on the child's parent first.
I think, that error in attach function, but how solve this?
You can not add a view to a viewpager like that. Check the demos of Jake Wharton for proper implementation of viewpagers for example.
https://github.com/JakeWharton/Android-ViewPagerIndicator
I have 3 fragments and an activity. I want to enable tabs on the ActionBar and assign a Fragment to each of the 3 tabs. How do I hook that up correctly?
ORIGINAL POST
I have an app that I'm developing using the Google I/O app as a guide. I've implemented tabs into the ActionBar. They seem to be working until the tablet's orientation changes. For example, all 3 tabs have a Fragment. I can switch between them just fine, but when I change the orientation, whatever the Fragment I was last viewing stays visible, but clicking the tabs no longer changes the view... like they have become disconnected. As expected, going back to the original orientation does not "fix" it.
I've looked into saving and restoring state, but I'm not seeing how those would help.
EDIT
module level:
Fragment mFragmentA = (Fragment) new AFragmentTab();
Fragment mFragmentB = (Fragment) new BFragmentTab();
Fragment mFragmentC = (Fragment) new CFragmentTab();
I have something like this in the activity's onCreate:
ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
actionBar.setDisplayShowTitleEnabled(false);
ActionBar.Tab tabA = actionBar.newTab().setText("text a");
ActionBar.Tab tabB = actionBar.newTab().setText("text b");
ActionBar.Tab tabC = actionBar.newTab().setText("text c");
tabA.setTabListener(this);
tabB.setTabListener(this);
tabC.setTabListener(this);
actionBar.addTab(tabA);
actionBar.addTab(tabB);
actionBar.addTab(tabC);
and a TabListener like this:
EDIT this is removed
class MyTabListener implements ActionBar.TabListener {
private Fragment mFragment;
// Called to create an instance of the listener when adding a new tab
public MyTabListener(Fragment fragment) {
mFragment = fragment;
}
public void onTabSelected(Tab tab, FragmentTransaction ft) {
ft.add(R.id.fragment_content, mFragment, null);
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
ft.remove(mFragment);
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
// do nothing
}
}
EDIT
I've moved the TabListener. Instead of it being a separate class, I implement the TabListener on the Activity. Then on the Selected and Unselected methods I have something like:
public void onTabSelected(Tab tab, FragmentTransaction ft) {
switch (tab.getPosition()) {
case 0:
ft.add(R.id.fragment_content, mFragmentA, null);
break;
case 1:
ft.add(R.id.fragment_content, mFragmentB, null);
break;
case 2:
ft.add(R.id.fragment_content, mFragmentC, null);
break;
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
switch (tab.getPosition()) {
case 0:
ft.remove(mFragmentA);
break;
case 1:
ft.remove(mFragmentB);
break;
case 2:
ft.remove(mFragmentC);
break;
}
}
It's still doing the same thing. I really don't know what's happening.
It looks like my answer can be found here: http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/FragmentTabs.html
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.apis.app;
import com.example.android.apis.R;
import android.app.ActionBar;
import android.app.ActionBar.Tab;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.widget.Toast;
/**
* This demonstrates the use of action bar tabs and how they interact
* with other action bar features.
*/
public class FragmentTabs extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActionBar bar = getActionBar();
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
bar.addTab(bar.newTab()
.setText("Simple")
.setTabListener(new TabListener<FragmentStack.CountingFragment>(
this, "simple", FragmentStack.CountingFragment.class)));
bar.addTab(bar.newTab()
.setText("Contacts")
.setTabListener(new TabListener<LoaderCursor.CursorLoaderListFragment>(
this, "contacts", LoaderCursor.CursorLoaderListFragment.class)));
bar.addTab(bar.newTab()
.setText("Apps")
.setTabListener(new TabListener<LoaderCustom.AppListFragment>(
this, "apps", LoaderCustom.AppListFragment.class)));
bar.addTab(bar.newTab()
.setText("Throttle")
.setTabListener(new TabListener<LoaderThrottle.ThrottledLoaderListFragment>(
this, "throttle", LoaderThrottle.ThrottledLoaderListFragment.class)));
if (savedInstanceState != null) {
bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("tab", getActionBar().getSelectedNavigationIndex());
}
public static class TabListener<T extends Fragment> implements ActionBar.TabListener {
private final Activity mActivity;
private final String mTag;
private final Class<T> mClass;
private final Bundle mArgs;
private Fragment mFragment;
public TabListener(Activity activity, String tag, Class<T> clz) {
this(activity, tag, clz, null);
}
public TabListener(Activity activity, String tag, Class<T> clz, Bundle args) {
mActivity = activity;
mTag = tag;
mClass = clz;
mArgs = args;
// Check to see if we already have a fragment for this tab, probably
// from a previously saved state. If so, deactivate it, because our
// initial state is that a tab isn't shown.
mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag);
if (mFragment != null && !mFragment.isDetached()) {
FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
ft.hide(mFragment);
ft.commit();
}
}
public void onTabSelected(Tab tab, FragmentTransaction ft) {
if (mFragment == null) {
mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs);
ft.add(android.R.id.content, mFragment, mTag);
} else {
ft.show(mFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (mFragment != null) {
ft.hide(mFragment);
}
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
Toast.makeText(mActivity, "Reselected!", Toast.LENGTH_SHORT).show();
}
}
}
The link provided by #Metallicraft is dead so here is some help for those who still stumbles upon this post. Go here for help with implementing tabs.
To see/read the original sample you can go to following folder if you've installed android development environment: android-sdk\samples\android-14\ApiDemos\src\com\example\android\apis\app\FragmentTabs.java
Also if you have problem with fragments overlapping after orientation change its properly related to savedinstancestate passed on. Here is a copy paste from a google tutorial that explains the fragment overlapping issue and how to avoid it:
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
public class MainActivity extends FragmentActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.news_articles);
// Check that the activity is using the layout version with
// the fragment_container FrameLayout
if (findViewById(R.id.fragment_container) != null) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
return;
}
// Create an instance of ExampleFragment
HeadlinesFragment firstFragment = new HeadlinesFragment();
// In case this activity was started with special instructions from an Intent,
// pass the Intent's extras to the fragment as arguments
firstFragment.setArguments(getIntent().getExtras());
// Add the fragment to the 'fragment_container' FrameLayout
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, firstFragment).commit();
}
}
}
Create a new Android Sample project, choose the Support4Demos sample. You will find the FragmentTabs sample there.
I had more or less the same problem, but the solutions presented above did not seem to work out in my situation. Eventually I found the following solution:
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
if (mFragment == null) {
mFragment = Fragment.instantiate(mActivity, mClass.getName());
ft.replace(android.R.id.content, mFragment, mTag); // Use replace iso add
}
else {
ft.attach(mFragment);
}
}
This worked great for me! I had this problem where the activities were overlapping everytime I changed the tabs:
mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag);
if (mFragment != null && !mFragment.isDetached()) {
FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
ft.detach(mFragment);
ft.commit();
Thanks again!
#Override
public void onTabSelected(ActionBar.Tab tab, android.support.v4.app.FragmentTransaction fragmentTransaction) {
// Check if the fragment is already initialized
if (mFragment == null) {
// If not, instantiate and add it to the activity
// Toast.makeText(getApplicationContext(),"TAb "+tab.getPosition(),Toast.LENGTH_LONG).show();
if(tab.getPosition() == 1) {
mFragment = new HolderFragment();
fragmentTransaction.add(android.R.id.content, mFragment, mTag);
}
else if(tab.getPosition() == 0)
{
mFragment = new FragmentKnowledge();
fragmentTransaction.add(android.R.id.c`enter code here`ontent, mFragment, mTag);
}
/*else
{
mFragment = Fragment.instantiate(mActivity, mClass.getName());
fragmentTransaction.add(android.R.id.content, mFragment, mTag);
}
*/
} else {
// If it exists, simply attach it in order to show it
fragmentTransaction.attach(mFragment);
}
}