How can i use getSupportFragmentManager() in a class extended by fragment? - android

i have a class which is extended from a fragment ... and i want to getSupportFragmentManager() for finding the map fragment...
this is the fragment xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" tools:context="myproject.BlankFragment">
<!-- TODO: Update blank fragment layout -->
<TextView android:layout_width="match_parent" android:layout_height="match_parent"
android:text="this is ths map " />
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent" android:id="#+id/mapFrag"
tools:context="pk.edu.riu.mrcab.mrcabv1.MapsActivity"
android:name="com.google.android.gms.maps.SupportMapFragment"
/>
this is the fragment class
public class BlankFragment extends Fragment {
private GoogleMap mMap;
public BlankFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_blank, container, false);
return rootView;
}
private void setUpMapIfNeeded() {
if (mMap == null) {
mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map))
.getMap();
if (mMap != null) {
}
}
}

Whenever you are trying to access a nested Fragment, you should call getChildFragmentManager() instead of getSupportFragmentManager(). Please note that you should use the support library Fragment, unless you're supporting API 17+.

Did you try
FragmentManager fm = getActivity().getSupportFragmentManager();
// and then depending on your case
fm.findFragmentById(R.id.yourId);
// or
fm.findFragmentByTag("YOUR_TAG");
// or
fm.getFragments(); // and then search the right one
This is the way I'm usually doing it.

use this
private FragmentActivity myContext;
override onAttach method of your fragment :
#Override
public void onAttach(Activity activity) {
myContext=(FragmentActivity) activity;
super.onAttach(activity);
}
and call this method when you need
FragmentManager fragManager = myContext.getSupportFragmentManager(); //if lib you are using is support v4
or use
FragmentManager fragManager = myContext.getFragmentManager();
or else you can directly call this method.
getFragmentManager()

Related

binary xml file line #0 error when inflating the layout [duplicate]

I have an application with three tabs.
Each tab has its own layout .xml file. The main.xml has its own map fragment. It's the one that shows up when the application first launches.
Everything works fine except for when I change between tabs. If I try to switch back to the map fragment tab, I get this error. Switching to and between other tabs works just fine.
What could be wrong here?
This is my main class and my main.xml, as well as a relevant class that I use ( you will also find the error log at the bottom )
main class
package com.nfc.demo;
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;
public class NFCDemoActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActionBar bar = getActionBar();
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
bar.addTab(bar
.newTab()
.setText("Map")
.setTabListener(
new TabListener<MapFragment>(this, "map",
MapFragment.class)));
bar.addTab(bar
.newTab()
.setText("Settings")
.setTabListener(
new TabListener<SettingsFragment>(this, "settings",
SettingsFragment.class)));
bar.addTab(bar
.newTab()
.setText("About")
.setTabListener(
new TabListener<AboutFragment>(this, "about",
AboutFragment.class)));
if (savedInstanceState != null) {
bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
}
// setContentView(R.layout.main);
}
#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.detach(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.attach(mFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (mFragment != null) {
ft.detach(mFragment);
}
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
Toast.makeText(mActivity, "Reselected!", Toast.LENGTH_SHORT)
.show();
}
}
}
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<fragment
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mapFragment"
android:name="com.google.android.gms.maps.MapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
relevant class ( MapFragment.java )
package com.nfc.demo;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class MapFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
return inflater.inflate(R.layout.main, container, false);
}
public void onDestroy() {
super.onDestroy();
}
}
error
android.view.InflateException: Binary XML file line #7:
Error inflating class fragment
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:704)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:746)
at android.view.LayoutInflater.inflate(LayoutInflater.java:489)
at android.view.LayoutInflater.inflate(LayoutInflater.java:396)
at com.nfc.demo.MapFragment.onCreateView(MapFragment.java:15)
at android.app.Fragment.performCreateView(Fragment.java:1695)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:885)
at android.app.FragmentManagerImpl.attachFragment(FragmentManager.java:1255)
at android.app.BackStackRecord.run(BackStackRecord.java:672)
at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1435)
at android.app.FragmentManagerImpl$1.run(FragmentManager.java:441)
at android.os.Handler.handleCallback(Handler.java:725)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5039)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.IllegalArgumentException:
Binary XML file line #7: Duplicate id 0x7f040005, tag null, or
parent id 0xffffffff with another fragment for
com.google.android.gms.maps.MapFragment
at android.app.Activity.onCreateView(Activity.java:4722)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:680)
... 19 more
The answer Matt suggests works, but it causes the map to be recreated and redrawn, which isn't always desirable.
After lots of trial and error, I found a solution that works for me:
private static View view;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (view != null) {
ViewGroup parent = (ViewGroup) view.getParent();
if (parent != null)
parent.removeView(view);
}
try {
view = inflater.inflate(R.layout.map, container, false);
} catch (InflateException e) {
/* map is already there, just return view as it is */
}
return view;
}
For good measure, here's "map.xml" (R.layout.map) with R.id.mapFragment (android:id="#+id/mapFragment"):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mapLayout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.SupportMapFragment" />
</LinearLayout>
I hope this helps, but I can't guarantee that it doesn't have any adverse effects.
Edit: There were some adverse effects, such as when exiting the application and starting it again. Since the application isn't necessarily completely shut down (but just put to sleep in the background), the previous code i submitted would fail upon restarting the application. I've updated the code to something that works for me, both going in & out of the map and exiting and restarting the application, I'm not too happy with the try-catch bit, but it seem to work well enough. When looking at the stack trace it occurred to me that I could just check if the map fragment is in the FragmentManager, no need for the try-catch block, code updated.
More edits: It turns out that you need that try-catch after all. Just checking for the map fragment turned out not to work so well after all. Blergh.
The problem is that what you are trying to do shouldn't be done. You shouldn't be inflating fragments inside other fragments. From Android's documentation:
Note: You cannot inflate a layout into a fragment when that layout
includes a <fragment>. Nested fragments are only supported when added
to a fragment dynamically.
While you may be able to accomplish the task with the hacks presented here, I highly suggest you don't do it. Its impossible to be sure that these hacks will handle what each new Android OS does when you try to inflate a layout for a fragment containing another fragment.
The only Android-supported way to add a fragment to another fragment is via a transaction from the child fragment manager.
Simply change your XML layout into an empty container (add an ID if needed):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mapFragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
</LinearLayout>
Then in the Fragment onViewCreated(View view, #Nullable Bundle savedInstanceState) method:
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
FragmentManager fm = getChildFragmentManager();
SupportMapFragment mapFragment = (SupportMapFragment) fm.findFragmentByTag("mapFragment");
if (mapFragment == null) {
mapFragment = new SupportMapFragment();
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.mapFragmentContainer, mapFragment, "mapFragment");
ft.commit();
fm.executePendingTransactions();
}
mapFragment.getMapAsync(callback);
}
I had the same issue and was able to resolve it by manually removing the MapFragment in the onDestroy() method of the Fragment class. Here is code that works and references the MapFragment by ID in the XML:
#Override
public void onDestroyView() {
super.onDestroyView();
MapFragment f = (MapFragment) getFragmentManager()
.findFragmentById(R.id.map);
if (f != null)
getFragmentManager().beginTransaction().remove(f).commit();
}
If you don't remove the MapFragment manually, it will hang around so that it doesn't cost a lot of resources to recreate/show the map view again. It seems that keeping the underlying MapView is great for switching back and forth between tabs, but when used in fragments this behavior causes a duplicate MapView to be created upon each new MapFragment with the same ID. The solution is to manually remove the MapFragment and thus recreate the underlying map each time the fragment is inflated.
I also noted this in another answer [1].
This is my answer:
1, Create a layout xml like following:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/map_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
2, in the Fragment class, add a google map programmatically.
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* A simple {#link android.support.v4.app.Fragment} subclass. Activities that
* contain this fragment must implement the
* {#link MapFragment.OnFragmentInteractionListener} interface to handle
* interaction events. Use the {#link MapFragment#newInstance} factory method to
* create an instance of this fragment.
*
*/
public class MapFragment extends Fragment {
// TODO: Rename parameter arguments, choose names that match
private GoogleMap mMap;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_map, container, false);
SupportMapFragment mMapFragment = SupportMapFragment.newInstance();
mMap = mMapFragment.getMap();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.add(R.id.map_container, mMapFragment).commit();
return view;
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
Log.d("Attach", "on attach");
}
#Override
public void onDetach() {
super.onDetach();
}
}
As mentioned by #Justin Breitfeller, #Vidar Wahlberg solution is a hack which might not work in future version of Android.
#Vidar Wahlberg perfer a hack because other solution might "cause the map to be recreated and redrawn, which isn't always desirable". Map redraw could be prevented by maintaining the old map fragment, rather than creating a new instance every time.
#Matt solution doesn't work for me (IllegalStateException)
As quoted by #Justin Breitfeller, "You cannot inflate a layout into a fragment when that layout includes a . Nested fragments are only supported when added to a fragment dynamically."
My solution:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_map_list, container, false);
// init
//mapFragment = (SupportMapFragment)getChildFragmentManager().findFragmentById(R.id.map);
// don't recreate fragment everytime ensure last map location/state are maintain
if (mapFragment == null) {
mapFragment = SupportMapFragment.newInstance();
mapFragment.getMapAsync(this);
}
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
// R.id.map is a layout
transaction.replace(R.id.map, mapFragment).commit();
return view;
}
Declare SupportMapFragment object globally
private SupportMapFragment mapFragment;
In onCreateView() method put below code
mapFragment = (SupportMapFragment) getChildFragmentManager()
.findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
In onDestroyView() put below code
#Override
public void onDestroyView() {
super.onDestroyView();
if (mapFragment != null)
getFragmentManager().beginTransaction().remove(mapFragment).commit();
}
In your xml file put below code
<fragment
android:id="#+id/map"
android:name="com.abc.Driver.fragment.FragmentHome"
class="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Above code solved my problem and it's working fine
I would recommend replace() rather than attach()/detach() in your tab handling.
Or, switch to ViewPager. Here is a sample project showing a ViewPager, with tabs, hosting 10 maps.
Another solution:
if (view == null) {
view = inflater.inflate(R.layout.nearbyplaces, container, false);
}
That's it, if not null you don't need to reinitialize it removing from parent is unnecessary step.
You are returning or inflating layout twice, just check to see if you only inflate once.
I have lost hours today to find the reason, fortunately this issue is not because of MapFragment implementation, fnfortunately, this does not work because nested fragments are only supported through support library from rev 11.
My implementation has an activity with actionbar (in tabbed mode) with two tabs (no viewpager), one having the map and the other having a list of entries. Of course I've been quite naive to use MapFragment inside my tab-fragments, et voila the app crashed everytime I switched back to map-tab.
( The same issue I also would have in case my tab-fragment would inflate any layout containing any other fragment ).
One option is to use the MapView (instead of MapFragment), with some overhead though ( see MapView Docs as drop-in replacement in the layout.xml, another option is to use support-library up from rev. 11 but then take programmatic approach since nested fragments are neither supported via layout. Or just working around programmatically by explicitely destroying the fragment (like in the answer from Matt / Vidar), btw: same effect is achieved using the MapView (option 1).
But actually, I did not want to loose the map everytime I tab away, that is, I wanted to keep it in memory and cleanup only upon activity close, so I decided to simply hide/show the map while tabbing, see FragmentTransaction / hide
For those who are still running into this issue, the best way to make sure you don't get this error with a Map in a Tab is to make the Fragment extend SupportMapFragment instead of nesting a SupportMapFragment inside the Fragment used for the Tab.
I just got this working using a ViewPager with a FragmentPagerAdapter, with the SupportMapFragment in the third Tab.
Here is the general structure, note there is no need to override the onCreateView() method, and there is no need to inflate any layout xml:
public class MapTabFragment extends SupportMapFragment
implements OnMapReadyCallback {
private GoogleMap mMap;
private Marker marker;
public MapTabFragment() {
}
#Override
public void onResume() {
super.onResume();
setUpMapIfNeeded();
}
private void setUpMapIfNeeded() {
if (mMap == null) {
getMapAsync(this);
}
}
#Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
setUpMap();
}
private void setUpMap() {
mMap.setMyLocationEnabled(true);
mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
mMap.getUiSettings().setMapToolbarEnabled(false);
mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() {
#Override
public void onMapClick(LatLng point) {
//remove previously placed Marker
if (marker != null) {
marker.remove();
}
//place marker where user just clicked
marker = mMap.addMarker(new MarkerOptions().position(point).title("Marker")
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA)));
}
});
}
}
Result:
Here is the full class code that I used to test with, which includes the placeholder Fragment used for the first two Tabs, and the Map Fragment used for the third Tab:
public class MainActivity extends AppCompatActivity implements ActionBar.TabListener{
SectionsPagerAdapter mSectionsPagerAdapter;
ViewPager mViewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
final ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
actionBar.setSelectedNavigationItem(position);
}
});
for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
actionBar.addTab(actionBar.newTab().setText(mSectionsPagerAdapter.getPageTitle(i)).setTabListener(this));
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
mViewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
}
#Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
}
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return PlaceholderFragment.newInstance(position + 1);
case 1:
return PlaceholderFragment.newInstance(position + 1);
case 2:
return MapTabFragment.newInstance(position + 1);
}
return null;
}
#Override
public int getCount() {
// Show 3 total pages.
return 3;
}
#Override
public CharSequence getPageTitle(int position) {
Locale l = Locale.getDefault();
switch (position) {
case 0:
return getString(R.string.title_section1).toUpperCase(l);
case 1:
return getString(R.string.title_section2).toUpperCase(l);
case 2:
return getString(R.string.title_section3).toUpperCase(l);
}
return null;
}
}
public static class PlaceholderFragment extends Fragment {
private static final String ARG_SECTION_NUMBER = "section_number";
TextView text;
public static PlaceholderFragment newInstance(int sectionNumber) {
PlaceholderFragment fragment = new PlaceholderFragment();
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
fragment.setArguments(args);
return fragment;
}
public PlaceholderFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
text = (TextView) rootView.findViewById(R.id.section_label);
text.setText("placeholder");
return rootView;
}
}
public static class MapTabFragment extends SupportMapFragment implements
OnMapReadyCallback {
private static final String ARG_SECTION_NUMBER = "section_number";
private GoogleMap mMap;
private Marker marker;
public static MapTabFragment newInstance(int sectionNumber) {
MapTabFragment fragment = new MapTabFragment();
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
fragment.setArguments(args);
return fragment;
}
public MapTabFragment() {
}
#Override
public void onResume() {
super.onResume();
Log.d("MyMap", "onResume");
setUpMapIfNeeded();
}
private void setUpMapIfNeeded() {
if (mMap == null) {
Log.d("MyMap", "setUpMapIfNeeded");
getMapAsync(this);
}
}
#Override
public void onMapReady(GoogleMap googleMap) {
Log.d("MyMap", "onMapReady");
mMap = googleMap;
setUpMap();
}
private void setUpMap() {
mMap.setMyLocationEnabled(true);
mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
mMap.getUiSettings().setMapToolbarEnabled(false);
mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() {
#Override
public void onMapClick(LatLng point) {
Log.d("MyMap", "MapClick");
//remove previously placed Marker
if (marker != null) {
marker.remove();
}
//place marker where user just clicked
marker = mMap.addMarker(new MarkerOptions().position(point).title("Marker")
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA)));
Log.d("MyMap", "MapClick After Add Marker");
}
});
}
}
}
I respect all the answers but i found this one liner solution:
If n Is the Number of tabs then:
mViewPager.setOffscreenPageLimit(n);
Example:
In case mentioned :
mViewPager.setOffscreenPageLimit(2);
View pager implements a queue so, you don't have to let it remove that fragment. onCreateView is called only once.
I am a bit late to the party but Non of these answer helped me in my case. I was using Google map as SupportMapFragment and PlaceAutocompleteFragment both in my fragment. As all the answers pointed to the fact that the problem is with SupportMapFragment being the map to be recreated and redrawn.But after digging found out my problem was actually with PlaceAutocompleteFragment
So here is the working solution for those who are facing this problem because of SupportMapFragment and SupportMapFragment
//Global SupportMapFragment mapFragment;
mapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.mapFragment);
FragmentManager fm = getChildFragmentManager();
if (mapFragment == null) {
mapFragment = SupportMapFragment.newInstance();
fm.beginTransaction().replace(R.id.mapFragment, mapFragment).commit();
fm.executePendingTransactions();
}
mapFragment.getMapAsync(this);
//Global PlaceAutocompleteFragment autocompleteFragment;
if (autocompleteFragment == null) {
autocompleteFragment = (PlaceAutocompleteFragment) getActivity().getFragmentManager().findFragmentById(R.id.place_autoCompleteFragment);
}
And in onDestroyView clear the SupportMapFragment and SupportMapFragment
#Override
public void onDestroyView() {
super.onDestroyView();
if (getActivity() != null) {
Log.e("res","place dlted");
android.app.FragmentManager fragmentManager = getActivity().getFragmentManager();
android.app.FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.remove(autocompleteFragment);
fragmentTransaction.commit();
//Use commitAllowingStateLoss() if getting exception
autocompleteFragment = null;
}
}
Nested fragments are not currently supported.
Try Support Package, revision 11.
Have you been trying to reference your custom MapFragment class in the layout file?
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<fragment
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mapFragment"
android:name="com.nfc.demo.MapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
If you will use only Vidar Wahlberg answer, you get error when you open other activity (for example) and back to map. Or in my case open other activity and then from new activity open map again( without use back button).
But when you combine Vidar Wahlberg solution and Matt solution you will have not exceptions.
layout
<com.example.ui.layout.MapWrapperLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/map_relative_layout">
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/root">
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.SupportMapFragment" />
</RelativeLayout>
</<com.example.ui.layout.MapWrapperLayout>
Fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setHasOptionsMenu(true);
if (view != null) {
ViewGroup parent = (ViewGroup) view.getParent();
if (parent != null){
parent.removeView(view);
}
}
try {
view = inflater.inflate(R.layout.map_view, null);
if(view!=null){
ViewGroup root = (ViewGroup) view.findViewById(R.id.root);
...
#Override
public void onDestroyView() {
super.onDestroyView();
Fragment fragment = this.getSherlockActivity().getSupportFragmentManager().findFragmentById(R.id.map);
if (fragment != null)
getFragmentManager().beginTransaction().remove(fragment).commit();
}
I had this in viewPager and the crash was because any fragment had to have its own tag, duplicate tags or ids for same fragment are not allowed.
I think there was some bugs in previous App-Compat lib for child Fragment. I tried #Vidar Wahlberg and #Matt's ans they did not work for me. After updating the appcompat library my code run perfectly without any extra effort.
Things to Note here is your app will crash badly in either of two cases:-
1) In order to reuse fragment with Maps again MapView Fragment must be removed when
your fragment showing Maps got replaced with other fragment in onDestroyView callback.
else when you try to inflate same fragment twice Duplicate ID, tag null, or parent id with another fragment for com.google.android.gms.maps.MapFragment error will happen.
2) Secondly you must not mix app.Fragment operations with
android.support.v4.app.Fragment API operations eg.do not use
android.app.FragmentTransaction to remove v4.app.Fragment type
MapView Fragment. Mixing this will again result into crash from fragment side.
Here is sample code snippet for correct usage of MapView
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnMapClickListener;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
import com.serveroverload.yago.R;
/**
* #author 663918
*
*/
public class HomeFragment extends Fragment implements LocationListener {
// Class to do operations on the Map
GoogleMap googleMap;
private LocationManager locationManager;
public static Fragment newInstance() {
return new HomeFragment();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.home_fragment, container, false);
Bundle bdl = getArguments();
// setuping locatiomanager to perfrom location related operations
locationManager = (LocationManager) getActivity().getSystemService(
Context.LOCATION_SERVICE);
// Requesting locationmanager for location updates
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, 1, 1, this);
// To get map from MapFragment from layout
googleMap = ((MapFragment) getActivity().getFragmentManager()
.findFragmentById(R.id.map)).getMap();
// To change the map type to Satellite
// googleMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
// To show our current location in the map with dot
// googleMap.setMyLocationEnabled(true);
// To listen action whenever we click on the map
googleMap.setOnMapClickListener(new OnMapClickListener() {
#Override
public void onMapClick(LatLng latLng) {
/*
* LatLng:Class will give us selected position lattigude and
* longitude values
*/
Toast.makeText(getActivity(), latLng.toString(),
Toast.LENGTH_LONG).show();
}
});
changeMapMode(2);
// googleMap.setSatellite(true);
googleMap.setTrafficEnabled(true);
googleMap.setBuildingsEnabled(true);
googleMap.setMyLocationEnabled(true);
return v;
}
private void doZoom() {
if (googleMap != null) {
googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(
new LatLng(18.520430, 73.856744), 17));
}
}
private void changeMapMode(int mapMode) {
if (googleMap != null) {
switch (mapMode) {
case 0:
googleMap.setMapType(GoogleMap.MAP_TYPE_NONE);
break;
case 1:
googleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
break;
case 2:
googleMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
break;
case 3:
googleMap.setMapType(GoogleMap.MAP_TYPE_TERRAIN);
break;
case 4:
googleMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
break;
default:
break;
}
}
}
private void createMarker(double latitude, double longitude) {
// double latitude = 17.385044;
// double longitude = 78.486671;
// lets place some 10 random markers
for (int i = 0; i < 10; i++) {
// random latitude and logitude
double[] randomLocation = createRandLocation(latitude, longitude);
// Adding a marker
MarkerOptions marker = new MarkerOptions().position(
new LatLng(randomLocation[0], randomLocation[1])).title(
"Hello Maps " + i);
Log.e("Random", "> " + randomLocation[0] + ", " + randomLocation[1]);
// changing marker color
if (i == 0)
marker.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_AZURE));
if (i == 1)
marker.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_BLUE));
if (i == 2)
marker.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_CYAN));
if (i == 3)
marker.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_GREEN));
if (i == 4)
marker.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA));
if (i == 5)
marker.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_ORANGE));
if (i == 6)
marker.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_RED));
if (i == 7)
marker.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_ROSE));
if (i == 8)
marker.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_VIOLET));
if (i == 9)
marker.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_YELLOW));
googleMap.addMarker(marker);
// Move the camera to last position with a zoom level
if (i == 9) {
CameraPosition cameraPosition = new CameraPosition.Builder()
.target(new LatLng(randomLocation[0], randomLocation[1]))
.zoom(15).build();
googleMap.animateCamera(CameraUpdateFactory
.newCameraPosition(cameraPosition));
}
}
}
/*
* creating random postion around a location for testing purpose only
*/
private double[] createRandLocation(double latitude, double longitude) {
return new double[] { latitude + ((Math.random() - 0.5) / 500),
longitude + ((Math.random() - 0.5) / 500),
150 + ((Math.random() - 0.5) * 10) };
}
#Override
public void onLocationChanged(Location location) {
if (null != googleMap) {
// To get lattitude value from location object
double latti = location.getLatitude();
// To get longitude value from location object
double longi = location.getLongitude();
// To hold lattitude and longitude values
LatLng position = new LatLng(latti, longi);
createMarker(latti, longi);
// Creating object to pass our current location to the map
MarkerOptions markerOptions = new MarkerOptions();
// To store current location in the markeroptions object
markerOptions.position(position);
// Zooming to our current location with zoom level 17.0f
googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(position,
17f));
// adding markeroptions class object to the map to show our current
// location in the map with help of default marker
googleMap.addMarker(markerOptions);
}
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// TODO Auto-generated method stub
}
#Override
public void onProviderEnabled(String provider) {
// TODO Auto-generated method stub
}
#Override
public void onProviderDisabled(String provider) {
// TODO Auto-generated method stub
}
#Override
public void onDestroyView() {
// TODO Auto-generated method stub
super.onDestroyView();
locationManager.removeUpdates(this);
android.app.Fragment fragment = getActivity().getFragmentManager()
.findFragmentById(R.id.map);
if (null != fragment) {
android.app.FragmentTransaction ft = getActivity()
.getFragmentManager().beginTransaction();
ft.remove(fragment);
ft.commit();
}
}
}
XML
<fragment
android:id="#+id/map"
android:name="com.google.android.gms.maps.MapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Result looks like this :-
Hope it will help Somebody.
In this solution you do not need to take static variable;
Button nextBtn;
private SupportMapFragment mMapFragment;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
if (mRootView != null) {
ViewGroup parent = (ViewGroup) mRootView.getParent();
Utility.log(0,"removeView","mRootView not NULL");
if (parent != null) {
Utility.log(0, "removeView", "view removeViewed");
parent.removeAllViews();
}
}
else {
try {
mRootView = inflater.inflate(R.layout.dummy_fragment_layout_one, container, false);//
} catch (InflateException e) {
/* map is already there, just return view as it is */
e.printStackTrace();
}
}
return mRootView;
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
FragmentManager fm = getChildFragmentManager();
SupportMapFragment mapFragment = (SupportMapFragment) fm.findFragmentById(R.id.mapView);
if (mapFragment == null) {
mapFragment = new SupportMapFragment();
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.mapView, mapFragment, "mapFragment");
ft.commit();
fm.executePendingTransactions();
}
//mapFragment.getMapAsync(this);
nextBtn = (Button) view.findViewById(R.id.nextBtn);
nextBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Utility.replaceSupportFragment(getActivity(),R.id.dummyFragment,dummyFragment_2.class.getSimpleName(),null,new dummyFragment_2());
}
});
}`
Try setting an id (android:id="#+id/maps_dialog") for your mapView parent layout. Works for me.
Anyone coming here now that's getting this type of error when opening a Dialog or other Fragment with the Google Places AutocompleteSupportFragment, try this one-liner (I don't know how safe this is but it works for me):
autocompleteFragment.getFragmentManager().beginTransaction().remove(autocompleteFragment).commit();
before you dismiss/destroy your fragment.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.google.android.gms.maps.MapView
android:id="#+id/mapview"
android:layout_width="100dip"
android:layout_height="100dip"
android:layout_alignParentTop="true"
android:layout_alignRight="#+id/textView1"
android:layout_marginRight="15dp" >
</com.google.android.gms.maps.MapView>
Why don't you insert a map using the MapView object instead of MapFragment ? I am not sure if there is any limitation in MapView,though i found it helpful.

getMapAsync in Fragment

I am using Sliding Tabs with different fragments. In map fragment I cannot use Nested Fragment as XML. Below is Java code and XMLfile.
I am stuck at getMapAsync() method So How Can I get Map using getMapAsync without Any exception? I will really appreciate your cooperation.
Food Fragment Java:
public class FoodFragment extends Fragment {
private SupportMapFragment mapfragment;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view= inflater.inflate(R.layout.food_layout, null);
mapfragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.maps_frame);
if (mapfragment == null) {
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
mapfragment = SupportMapFragment.newInstance();
fragmentTransaction.replace(R.id.maps_frame,mapfragment).commit();
}
mapfragment.getMapAsync((OnMapReadyCallback) getActivity().getApplicationContext());
return view;
}
}
Food XML file,
<FrameLayout
android:layout_width="match_parent"
android:layout_height="2in"
android:id="#+id/maps_frame"
android:background="#color/colorAccent"
android:layout_alignParentBottom="true">
</FrameLayout>
Your ApplicationContext cannot be casts as OnMapReadyCallback you can create new instance of OnMapReadyCallback() as an anonymous class instead like here is what you might be searching for
mapfragment.getMapAsync(new OnMapReadyCallback() {
#Override public void onMapReady(GoogleMap googleMap) {
if (googleMap != null) {
// your additional codes goes here
.....
}
}
You can refer this example as well if you need.

Google Maps API v2 Custom MapFragment + SimpleFragment

I need to create a simple application which contain
1. Custom MapFragment which can display current location (LocationListener?), two markers, etc.
2. Simple empty Fragments.
The application must not use support v4 (sdk 11+) / FragmentActivity.
Empty fragment.
XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/black" >
</RelativeLayout>
Class:
public class FirstFragment extends Fragment{
public FirstFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.first_fragment, container, false);
return view;
}
}
Map fragment (try to make it singleton).
XML:
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.MapFragment"/>
Class
public class ThirdMapFragment extends MapFragment {
public static MapFragment instance;
#Override
public View onCreateView(LayoutInflater arg0, ViewGroup arg1, Bundle arg2) {
View view;
view = arg0.inflate(R.layout.map_fragment, arg1, false);
return view;
}
public static MapFragment getInstance() {
if (instance == null) {
instance = newInstance();
}
// NullPointerException in getMap() ???
// instance.getMap().addMarker(new MarkerOptions().position(new LatLng(41.1, 39)));
return instance;
}
#Override
public void onCreate(Bundle arg0) {
super.onCreate(arg0);
}
}
Main activity:
public class ActivityMain extends Activity implements OnClickListener {
FragmentManager fragmentManager;
FragmentTransaction transaction;
FirstFragment firstFragment;
MapFragment mapFragment;
// Buttons
Button b4;
int i = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
fragmentManager = getFragmentManager();
transaction = fragmentManager.beginTransaction();
b4 = (Button) findViewById(R.id.button4);
b1.setOnClickListener(this);
firstFragment = new FirstFragment();
mapFragment = ThirdMapFragment.getInstance();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
#Override
public void onClick(View v) {
makeTransaction(v.getId());
}
private void makeTransaction(int id) {
i++;
transaction = fragmentManager.beginTransaction();
if (i == 1) {
transaction.replace(R.id.relativeLayout, firstFragment).commit();
} else if (i == 2) {
transaction.replace(R.id.relativeLayout, secondFragment).commit();
} else {
transaction.replace(R.id.relativeLayout, mapFragment).commit();
i = 0;
}
}
}
R.id.relativeLayout - Layout in main.xml
But I do not see the map (empty screen with zoom buttons) and how to make LocationListener? Thx.
if you are using the support library you have to use the SupportMapFragment class and not the MapFragment for the map fragment. your activity should be a FragmentActivity for the support of the library. as well as for the Fragment manager. you have to getSupportFragmentManager().
Take a look at this code:
import com.google.android.gms.common.GooglePlayServicesUtil;
import android.os.Bundle;
import com.google.android.gms.maps.*;
public class Map extends android.support.v4.app.FragmentActivity
{
private GoogleMap map;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.map);
if (GooglePlayServicesUtil.isGooglePlayServicesAvailable(getApplicationContext()) == 0)
{
map = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap();
map.setMapType(GoogleMap.MAP_TYPE_HYBRID);
}
}}
If you still don't see a map after you made those changes. Check your API Console that you have turn on the Google Map ANDROID API V2 and not just the Google Map API V2 that could cause the same result as you describe.
the view is not created yet that's why getMap() returns null, map doesn't exist, put your code in onResume
Put in the manifest, the key for android?
For example:
<application
android:icon="#drawable/android"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<meta-data android:name="com.google.android.maps.v2.API_KEY"
android:value="AIzaSyB2s6isdfsghDfgdfdfgDFGDG-yi4-aAYUma0Owc"/>
https://developers.google.com/maps/documentation/android/start

Duplicate ID, tag null, or parent id with another fragment for com.google.android.gms.maps.MapFragment

I have an application with three tabs.
Each tab has its own layout .xml file. The main.xml has its own map fragment. It's the one that shows up when the application first launches.
Everything works fine except for when I change between tabs. If I try to switch back to the map fragment tab, I get this error. Switching to and between other tabs works just fine.
What could be wrong here?
This is my main class and my main.xml, as well as a relevant class that I use ( you will also find the error log at the bottom )
main class
package com.nfc.demo;
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;
public class NFCDemoActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActionBar bar = getActionBar();
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
bar.addTab(bar
.newTab()
.setText("Map")
.setTabListener(
new TabListener<MapFragment>(this, "map",
MapFragment.class)));
bar.addTab(bar
.newTab()
.setText("Settings")
.setTabListener(
new TabListener<SettingsFragment>(this, "settings",
SettingsFragment.class)));
bar.addTab(bar
.newTab()
.setText("About")
.setTabListener(
new TabListener<AboutFragment>(this, "about",
AboutFragment.class)));
if (savedInstanceState != null) {
bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
}
// setContentView(R.layout.main);
}
#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.detach(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.attach(mFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (mFragment != null) {
ft.detach(mFragment);
}
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
Toast.makeText(mActivity, "Reselected!", Toast.LENGTH_SHORT)
.show();
}
}
}
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<fragment
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mapFragment"
android:name="com.google.android.gms.maps.MapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
relevant class ( MapFragment.java )
package com.nfc.demo;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class MapFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
return inflater.inflate(R.layout.main, container, false);
}
public void onDestroy() {
super.onDestroy();
}
}
error
android.view.InflateException: Binary XML file line #7:
Error inflating class fragment
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:704)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:746)
at android.view.LayoutInflater.inflate(LayoutInflater.java:489)
at android.view.LayoutInflater.inflate(LayoutInflater.java:396)
at com.nfc.demo.MapFragment.onCreateView(MapFragment.java:15)
at android.app.Fragment.performCreateView(Fragment.java:1695)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:885)
at android.app.FragmentManagerImpl.attachFragment(FragmentManager.java:1255)
at android.app.BackStackRecord.run(BackStackRecord.java:672)
at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1435)
at android.app.FragmentManagerImpl$1.run(FragmentManager.java:441)
at android.os.Handler.handleCallback(Handler.java:725)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5039)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.IllegalArgumentException:
Binary XML file line #7: Duplicate id 0x7f040005, tag null, or
parent id 0xffffffff with another fragment for
com.google.android.gms.maps.MapFragment
at android.app.Activity.onCreateView(Activity.java:4722)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:680)
... 19 more
The answer Matt suggests works, but it causes the map to be recreated and redrawn, which isn't always desirable.
After lots of trial and error, I found a solution that works for me:
private static View view;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (view != null) {
ViewGroup parent = (ViewGroup) view.getParent();
if (parent != null)
parent.removeView(view);
}
try {
view = inflater.inflate(R.layout.map, container, false);
} catch (InflateException e) {
/* map is already there, just return view as it is */
}
return view;
}
For good measure, here's "map.xml" (R.layout.map) with R.id.mapFragment (android:id="#+id/mapFragment"):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mapLayout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.SupportMapFragment" />
</LinearLayout>
I hope this helps, but I can't guarantee that it doesn't have any adverse effects.
Edit: There were some adverse effects, such as when exiting the application and starting it again. Since the application isn't necessarily completely shut down (but just put to sleep in the background), the previous code i submitted would fail upon restarting the application. I've updated the code to something that works for me, both going in & out of the map and exiting and restarting the application, I'm not too happy with the try-catch bit, but it seem to work well enough. When looking at the stack trace it occurred to me that I could just check if the map fragment is in the FragmentManager, no need for the try-catch block, code updated.
More edits: It turns out that you need that try-catch after all. Just checking for the map fragment turned out not to work so well after all. Blergh.
The problem is that what you are trying to do shouldn't be done. You shouldn't be inflating fragments inside other fragments. From Android's documentation:
Note: You cannot inflate a layout into a fragment when that layout
includes a <fragment>. Nested fragments are only supported when added
to a fragment dynamically.
While you may be able to accomplish the task with the hacks presented here, I highly suggest you don't do it. Its impossible to be sure that these hacks will handle what each new Android OS does when you try to inflate a layout for a fragment containing another fragment.
The only Android-supported way to add a fragment to another fragment is via a transaction from the child fragment manager.
Simply change your XML layout into an empty container (add an ID if needed):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mapFragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
</LinearLayout>
Then in the Fragment onViewCreated(View view, #Nullable Bundle savedInstanceState) method:
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
FragmentManager fm = getChildFragmentManager();
SupportMapFragment mapFragment = (SupportMapFragment) fm.findFragmentByTag("mapFragment");
if (mapFragment == null) {
mapFragment = new SupportMapFragment();
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.mapFragmentContainer, mapFragment, "mapFragment");
ft.commit();
fm.executePendingTransactions();
}
mapFragment.getMapAsync(callback);
}
I had the same issue and was able to resolve it by manually removing the MapFragment in the onDestroy() method of the Fragment class. Here is code that works and references the MapFragment by ID in the XML:
#Override
public void onDestroyView() {
super.onDestroyView();
MapFragment f = (MapFragment) getFragmentManager()
.findFragmentById(R.id.map);
if (f != null)
getFragmentManager().beginTransaction().remove(f).commit();
}
If you don't remove the MapFragment manually, it will hang around so that it doesn't cost a lot of resources to recreate/show the map view again. It seems that keeping the underlying MapView is great for switching back and forth between tabs, but when used in fragments this behavior causes a duplicate MapView to be created upon each new MapFragment with the same ID. The solution is to manually remove the MapFragment and thus recreate the underlying map each time the fragment is inflated.
I also noted this in another answer [1].
This is my answer:
1, Create a layout xml like following:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/map_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
2, in the Fragment class, add a google map programmatically.
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* A simple {#link android.support.v4.app.Fragment} subclass. Activities that
* contain this fragment must implement the
* {#link MapFragment.OnFragmentInteractionListener} interface to handle
* interaction events. Use the {#link MapFragment#newInstance} factory method to
* create an instance of this fragment.
*
*/
public class MapFragment extends Fragment {
// TODO: Rename parameter arguments, choose names that match
private GoogleMap mMap;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_map, container, false);
SupportMapFragment mMapFragment = SupportMapFragment.newInstance();
mMap = mMapFragment.getMap();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.add(R.id.map_container, mMapFragment).commit();
return view;
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
Log.d("Attach", "on attach");
}
#Override
public void onDetach() {
super.onDetach();
}
}
As mentioned by #Justin Breitfeller, #Vidar Wahlberg solution is a hack which might not work in future version of Android.
#Vidar Wahlberg perfer a hack because other solution might "cause the map to be recreated and redrawn, which isn't always desirable". Map redraw could be prevented by maintaining the old map fragment, rather than creating a new instance every time.
#Matt solution doesn't work for me (IllegalStateException)
As quoted by #Justin Breitfeller, "You cannot inflate a layout into a fragment when that layout includes a . Nested fragments are only supported when added to a fragment dynamically."
My solution:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_map_list, container, false);
// init
//mapFragment = (SupportMapFragment)getChildFragmentManager().findFragmentById(R.id.map);
// don't recreate fragment everytime ensure last map location/state are maintain
if (mapFragment == null) {
mapFragment = SupportMapFragment.newInstance();
mapFragment.getMapAsync(this);
}
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
// R.id.map is a layout
transaction.replace(R.id.map, mapFragment).commit();
return view;
}
Declare SupportMapFragment object globally
private SupportMapFragment mapFragment;
In onCreateView() method put below code
mapFragment = (SupportMapFragment) getChildFragmentManager()
.findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
In onDestroyView() put below code
#Override
public void onDestroyView() {
super.onDestroyView();
if (mapFragment != null)
getFragmentManager().beginTransaction().remove(mapFragment).commit();
}
In your xml file put below code
<fragment
android:id="#+id/map"
android:name="com.abc.Driver.fragment.FragmentHome"
class="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Above code solved my problem and it's working fine
I would recommend replace() rather than attach()/detach() in your tab handling.
Or, switch to ViewPager. Here is a sample project showing a ViewPager, with tabs, hosting 10 maps.
Another solution:
if (view == null) {
view = inflater.inflate(R.layout.nearbyplaces, container, false);
}
That's it, if not null you don't need to reinitialize it removing from parent is unnecessary step.
You are returning or inflating layout twice, just check to see if you only inflate once.
I have lost hours today to find the reason, fortunately this issue is not because of MapFragment implementation, fnfortunately, this does not work because nested fragments are only supported through support library from rev 11.
My implementation has an activity with actionbar (in tabbed mode) with two tabs (no viewpager), one having the map and the other having a list of entries. Of course I've been quite naive to use MapFragment inside my tab-fragments, et voila the app crashed everytime I switched back to map-tab.
( The same issue I also would have in case my tab-fragment would inflate any layout containing any other fragment ).
One option is to use the MapView (instead of MapFragment), with some overhead though ( see MapView Docs as drop-in replacement in the layout.xml, another option is to use support-library up from rev. 11 but then take programmatic approach since nested fragments are neither supported via layout. Or just working around programmatically by explicitely destroying the fragment (like in the answer from Matt / Vidar), btw: same effect is achieved using the MapView (option 1).
But actually, I did not want to loose the map everytime I tab away, that is, I wanted to keep it in memory and cleanup only upon activity close, so I decided to simply hide/show the map while tabbing, see FragmentTransaction / hide
For those who are still running into this issue, the best way to make sure you don't get this error with a Map in a Tab is to make the Fragment extend SupportMapFragment instead of nesting a SupportMapFragment inside the Fragment used for the Tab.
I just got this working using a ViewPager with a FragmentPagerAdapter, with the SupportMapFragment in the third Tab.
Here is the general structure, note there is no need to override the onCreateView() method, and there is no need to inflate any layout xml:
public class MapTabFragment extends SupportMapFragment
implements OnMapReadyCallback {
private GoogleMap mMap;
private Marker marker;
public MapTabFragment() {
}
#Override
public void onResume() {
super.onResume();
setUpMapIfNeeded();
}
private void setUpMapIfNeeded() {
if (mMap == null) {
getMapAsync(this);
}
}
#Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
setUpMap();
}
private void setUpMap() {
mMap.setMyLocationEnabled(true);
mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
mMap.getUiSettings().setMapToolbarEnabled(false);
mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() {
#Override
public void onMapClick(LatLng point) {
//remove previously placed Marker
if (marker != null) {
marker.remove();
}
//place marker where user just clicked
marker = mMap.addMarker(new MarkerOptions().position(point).title("Marker")
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA)));
}
});
}
}
Result:
Here is the full class code that I used to test with, which includes the placeholder Fragment used for the first two Tabs, and the Map Fragment used for the third Tab:
public class MainActivity extends AppCompatActivity implements ActionBar.TabListener{
SectionsPagerAdapter mSectionsPagerAdapter;
ViewPager mViewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
final ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
actionBar.setSelectedNavigationItem(position);
}
});
for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
actionBar.addTab(actionBar.newTab().setText(mSectionsPagerAdapter.getPageTitle(i)).setTabListener(this));
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
mViewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
}
#Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
}
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return PlaceholderFragment.newInstance(position + 1);
case 1:
return PlaceholderFragment.newInstance(position + 1);
case 2:
return MapTabFragment.newInstance(position + 1);
}
return null;
}
#Override
public int getCount() {
// Show 3 total pages.
return 3;
}
#Override
public CharSequence getPageTitle(int position) {
Locale l = Locale.getDefault();
switch (position) {
case 0:
return getString(R.string.title_section1).toUpperCase(l);
case 1:
return getString(R.string.title_section2).toUpperCase(l);
case 2:
return getString(R.string.title_section3).toUpperCase(l);
}
return null;
}
}
public static class PlaceholderFragment extends Fragment {
private static final String ARG_SECTION_NUMBER = "section_number";
TextView text;
public static PlaceholderFragment newInstance(int sectionNumber) {
PlaceholderFragment fragment = new PlaceholderFragment();
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
fragment.setArguments(args);
return fragment;
}
public PlaceholderFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
text = (TextView) rootView.findViewById(R.id.section_label);
text.setText("placeholder");
return rootView;
}
}
public static class MapTabFragment extends SupportMapFragment implements
OnMapReadyCallback {
private static final String ARG_SECTION_NUMBER = "section_number";
private GoogleMap mMap;
private Marker marker;
public static MapTabFragment newInstance(int sectionNumber) {
MapTabFragment fragment = new MapTabFragment();
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
fragment.setArguments(args);
return fragment;
}
public MapTabFragment() {
}
#Override
public void onResume() {
super.onResume();
Log.d("MyMap", "onResume");
setUpMapIfNeeded();
}
private void setUpMapIfNeeded() {
if (mMap == null) {
Log.d("MyMap", "setUpMapIfNeeded");
getMapAsync(this);
}
}
#Override
public void onMapReady(GoogleMap googleMap) {
Log.d("MyMap", "onMapReady");
mMap = googleMap;
setUpMap();
}
private void setUpMap() {
mMap.setMyLocationEnabled(true);
mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
mMap.getUiSettings().setMapToolbarEnabled(false);
mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() {
#Override
public void onMapClick(LatLng point) {
Log.d("MyMap", "MapClick");
//remove previously placed Marker
if (marker != null) {
marker.remove();
}
//place marker where user just clicked
marker = mMap.addMarker(new MarkerOptions().position(point).title("Marker")
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA)));
Log.d("MyMap", "MapClick After Add Marker");
}
});
}
}
}
I respect all the answers but i found this one liner solution:
If n Is the Number of tabs then:
mViewPager.setOffscreenPageLimit(n);
Example:
In case mentioned :
mViewPager.setOffscreenPageLimit(2);
View pager implements a queue so, you don't have to let it remove that fragment. onCreateView is called only once.
I am a bit late to the party but Non of these answer helped me in my case. I was using Google map as SupportMapFragment and PlaceAutocompleteFragment both in my fragment. As all the answers pointed to the fact that the problem is with SupportMapFragment being the map to be recreated and redrawn.But after digging found out my problem was actually with PlaceAutocompleteFragment
So here is the working solution for those who are facing this problem because of SupportMapFragment and SupportMapFragment
//Global SupportMapFragment mapFragment;
mapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.mapFragment);
FragmentManager fm = getChildFragmentManager();
if (mapFragment == null) {
mapFragment = SupportMapFragment.newInstance();
fm.beginTransaction().replace(R.id.mapFragment, mapFragment).commit();
fm.executePendingTransactions();
}
mapFragment.getMapAsync(this);
//Global PlaceAutocompleteFragment autocompleteFragment;
if (autocompleteFragment == null) {
autocompleteFragment = (PlaceAutocompleteFragment) getActivity().getFragmentManager().findFragmentById(R.id.place_autoCompleteFragment);
}
And in onDestroyView clear the SupportMapFragment and SupportMapFragment
#Override
public void onDestroyView() {
super.onDestroyView();
if (getActivity() != null) {
Log.e("res","place dlted");
android.app.FragmentManager fragmentManager = getActivity().getFragmentManager();
android.app.FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.remove(autocompleteFragment);
fragmentTransaction.commit();
//Use commitAllowingStateLoss() if getting exception
autocompleteFragment = null;
}
}
Nested fragments are not currently supported.
Try Support Package, revision 11.
Have you been trying to reference your custom MapFragment class in the layout file?
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<fragment
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mapFragment"
android:name="com.nfc.demo.MapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
If you will use only Vidar Wahlberg answer, you get error when you open other activity (for example) and back to map. Or in my case open other activity and then from new activity open map again( without use back button).
But when you combine Vidar Wahlberg solution and Matt solution you will have not exceptions.
layout
<com.example.ui.layout.MapWrapperLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/map_relative_layout">
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/root">
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.SupportMapFragment" />
</RelativeLayout>
</<com.example.ui.layout.MapWrapperLayout>
Fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setHasOptionsMenu(true);
if (view != null) {
ViewGroup parent = (ViewGroup) view.getParent();
if (parent != null){
parent.removeView(view);
}
}
try {
view = inflater.inflate(R.layout.map_view, null);
if(view!=null){
ViewGroup root = (ViewGroup) view.findViewById(R.id.root);
...
#Override
public void onDestroyView() {
super.onDestroyView();
Fragment fragment = this.getSherlockActivity().getSupportFragmentManager().findFragmentById(R.id.map);
if (fragment != null)
getFragmentManager().beginTransaction().remove(fragment).commit();
}
I had this in viewPager and the crash was because any fragment had to have its own tag, duplicate tags or ids for same fragment are not allowed.
I think there was some bugs in previous App-Compat lib for child Fragment. I tried #Vidar Wahlberg and #Matt's ans they did not work for me. After updating the appcompat library my code run perfectly without any extra effort.
Things to Note here is your app will crash badly in either of two cases:-
1) In order to reuse fragment with Maps again MapView Fragment must be removed when
your fragment showing Maps got replaced with other fragment in onDestroyView callback.
else when you try to inflate same fragment twice Duplicate ID, tag null, or parent id with another fragment for com.google.android.gms.maps.MapFragment error will happen.
2) Secondly you must not mix app.Fragment operations with
android.support.v4.app.Fragment API operations eg.do not use
android.app.FragmentTransaction to remove v4.app.Fragment type
MapView Fragment. Mixing this will again result into crash from fragment side.
Here is sample code snippet for correct usage of MapView
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnMapClickListener;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
import com.serveroverload.yago.R;
/**
* #author 663918
*
*/
public class HomeFragment extends Fragment implements LocationListener {
// Class to do operations on the Map
GoogleMap googleMap;
private LocationManager locationManager;
public static Fragment newInstance() {
return new HomeFragment();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.home_fragment, container, false);
Bundle bdl = getArguments();
// setuping locatiomanager to perfrom location related operations
locationManager = (LocationManager) getActivity().getSystemService(
Context.LOCATION_SERVICE);
// Requesting locationmanager for location updates
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, 1, 1, this);
// To get map from MapFragment from layout
googleMap = ((MapFragment) getActivity().getFragmentManager()
.findFragmentById(R.id.map)).getMap();
// To change the map type to Satellite
// googleMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
// To show our current location in the map with dot
// googleMap.setMyLocationEnabled(true);
// To listen action whenever we click on the map
googleMap.setOnMapClickListener(new OnMapClickListener() {
#Override
public void onMapClick(LatLng latLng) {
/*
* LatLng:Class will give us selected position lattigude and
* longitude values
*/
Toast.makeText(getActivity(), latLng.toString(),
Toast.LENGTH_LONG).show();
}
});
changeMapMode(2);
// googleMap.setSatellite(true);
googleMap.setTrafficEnabled(true);
googleMap.setBuildingsEnabled(true);
googleMap.setMyLocationEnabled(true);
return v;
}
private void doZoom() {
if (googleMap != null) {
googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(
new LatLng(18.520430, 73.856744), 17));
}
}
private void changeMapMode(int mapMode) {
if (googleMap != null) {
switch (mapMode) {
case 0:
googleMap.setMapType(GoogleMap.MAP_TYPE_NONE);
break;
case 1:
googleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
break;
case 2:
googleMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
break;
case 3:
googleMap.setMapType(GoogleMap.MAP_TYPE_TERRAIN);
break;
case 4:
googleMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
break;
default:
break;
}
}
}
private void createMarker(double latitude, double longitude) {
// double latitude = 17.385044;
// double longitude = 78.486671;
// lets place some 10 random markers
for (int i = 0; i < 10; i++) {
// random latitude and logitude
double[] randomLocation = createRandLocation(latitude, longitude);
// Adding a marker
MarkerOptions marker = new MarkerOptions().position(
new LatLng(randomLocation[0], randomLocation[1])).title(
"Hello Maps " + i);
Log.e("Random", "> " + randomLocation[0] + ", " + randomLocation[1]);
// changing marker color
if (i == 0)
marker.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_AZURE));
if (i == 1)
marker.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_BLUE));
if (i == 2)
marker.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_CYAN));
if (i == 3)
marker.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_GREEN));
if (i == 4)
marker.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA));
if (i == 5)
marker.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_ORANGE));
if (i == 6)
marker.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_RED));
if (i == 7)
marker.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_ROSE));
if (i == 8)
marker.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_VIOLET));
if (i == 9)
marker.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_YELLOW));
googleMap.addMarker(marker);
// Move the camera to last position with a zoom level
if (i == 9) {
CameraPosition cameraPosition = new CameraPosition.Builder()
.target(new LatLng(randomLocation[0], randomLocation[1]))
.zoom(15).build();
googleMap.animateCamera(CameraUpdateFactory
.newCameraPosition(cameraPosition));
}
}
}
/*
* creating random postion around a location for testing purpose only
*/
private double[] createRandLocation(double latitude, double longitude) {
return new double[] { latitude + ((Math.random() - 0.5) / 500),
longitude + ((Math.random() - 0.5) / 500),
150 + ((Math.random() - 0.5) * 10) };
}
#Override
public void onLocationChanged(Location location) {
if (null != googleMap) {
// To get lattitude value from location object
double latti = location.getLatitude();
// To get longitude value from location object
double longi = location.getLongitude();
// To hold lattitude and longitude values
LatLng position = new LatLng(latti, longi);
createMarker(latti, longi);
// Creating object to pass our current location to the map
MarkerOptions markerOptions = new MarkerOptions();
// To store current location in the markeroptions object
markerOptions.position(position);
// Zooming to our current location with zoom level 17.0f
googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(position,
17f));
// adding markeroptions class object to the map to show our current
// location in the map with help of default marker
googleMap.addMarker(markerOptions);
}
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// TODO Auto-generated method stub
}
#Override
public void onProviderEnabled(String provider) {
// TODO Auto-generated method stub
}
#Override
public void onProviderDisabled(String provider) {
// TODO Auto-generated method stub
}
#Override
public void onDestroyView() {
// TODO Auto-generated method stub
super.onDestroyView();
locationManager.removeUpdates(this);
android.app.Fragment fragment = getActivity().getFragmentManager()
.findFragmentById(R.id.map);
if (null != fragment) {
android.app.FragmentTransaction ft = getActivity()
.getFragmentManager().beginTransaction();
ft.remove(fragment);
ft.commit();
}
}
}
XML
<fragment
android:id="#+id/map"
android:name="com.google.android.gms.maps.MapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Result looks like this :-
Hope it will help Somebody.
In this solution you do not need to take static variable;
Button nextBtn;
private SupportMapFragment mMapFragment;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
if (mRootView != null) {
ViewGroup parent = (ViewGroup) mRootView.getParent();
Utility.log(0,"removeView","mRootView not NULL");
if (parent != null) {
Utility.log(0, "removeView", "view removeViewed");
parent.removeAllViews();
}
}
else {
try {
mRootView = inflater.inflate(R.layout.dummy_fragment_layout_one, container, false);//
} catch (InflateException e) {
/* map is already there, just return view as it is */
e.printStackTrace();
}
}
return mRootView;
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
FragmentManager fm = getChildFragmentManager();
SupportMapFragment mapFragment = (SupportMapFragment) fm.findFragmentById(R.id.mapView);
if (mapFragment == null) {
mapFragment = new SupportMapFragment();
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.mapView, mapFragment, "mapFragment");
ft.commit();
fm.executePendingTransactions();
}
//mapFragment.getMapAsync(this);
nextBtn = (Button) view.findViewById(R.id.nextBtn);
nextBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Utility.replaceSupportFragment(getActivity(),R.id.dummyFragment,dummyFragment_2.class.getSimpleName(),null,new dummyFragment_2());
}
});
}`
Try setting an id (android:id="#+id/maps_dialog") for your mapView parent layout. Works for me.
Anyone coming here now that's getting this type of error when opening a Dialog or other Fragment with the Google Places AutocompleteSupportFragment, try this one-liner (I don't know how safe this is but it works for me):
autocompleteFragment.getFragmentManager().beginTransaction().remove(autocompleteFragment).commit();
before you dismiss/destroy your fragment.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.google.android.gms.maps.MapView
android:id="#+id/mapview"
android:layout_width="100dip"
android:layout_height="100dip"
android:layout_alignParentTop="true"
android:layout_alignRight="#+id/textView1"
android:layout_marginRight="15dp" >
</com.google.android.gms.maps.MapView>
Why don't you insert a map using the MapView object instead of MapFragment ? I am not sure if there is any limitation in MapView,though i found it helpful.

How do I add a Fragment to an Activity with a programmatically created content view

I want to add a Fragment to an Activity that implements its layout programmatically. I looked over the Fragment documentation but there aren't many examples describing what I need. Here is the type of code I tried to write:
public class DebugExampleTwo extends Activity {
private ExampleTwoFragment mFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FrameLayout frame = new FrameLayout(this);
if (savedInstanceState == null) {
mFragment = new ExampleTwoFragment();
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(frame.getId(), mFragment).commit();
}
setContentView(frame);
}
}
...
public class ExampleTwoFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
Button button = new Button(getActivity());
button.setText("Hello There");
return button;
}
}
This code compiles but crashes at start, probably because my FragmentTransaction.add() is incorrect. What is the correct way to do this?
It turns out there's more than one problem with that code. A fragment cannot be declared that way, inside the same java file as the activity but not as a public inner class. The framework expects the fragment's constructor (with no parameters) to be public and visible. Moving the fragment into the Activity as an inner class, or creating a new java file for the fragment fixes that.
The second issue is that when you're adding a fragment this way, you must pass a reference to the fragment's containing view, and that view must have a custom id. Using the default id will crash the app. Here's the updated code:
public class DebugExampleTwo extends Activity {
private static final int CONTENT_VIEW_ID = 10101010;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FrameLayout frame = new FrameLayout(this);
frame.setId(CONTENT_VIEW_ID);
setContentView(frame, new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
if (savedInstanceState == null) {
Fragment newFragment = new DebugExampleTwoFragment();
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(CONTENT_VIEW_ID, newFragment).commit();
}
}
public static class DebugExampleTwoFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
EditText v = new EditText(getActivity());
v.setText("Hello Fragment!");
return v;
}
}
}
Here is what I came up with after reading Tony Wong's comment:
public class DebugExampleTwo extends BaseActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addFragment(android.R.id.content,
new DebugExampleTwoFragment(),
DebugExampleTwoFragment.FRAGMENT_TAG);
}
}
...
public abstract class BaseActivity extends Activity {
protected void addFragment(#IdRes int containerViewId,
#NonNull Fragment fragment,
#NonNull String fragmentTag) {
getSupportFragmentManager()
.beginTransaction()
.add(containerViewId, fragment, fragmentTag)
.disallowAddToBackStack()
.commit();
}
protected void replaceFragment(#IdRes int containerViewId,
#NonNull Fragment fragment,
#NonNull String fragmentTag,
#Nullable String backStackStateName) {
getSupportFragmentManager()
.beginTransaction()
.replace(containerViewId, fragment, fragmentTag)
.addToBackStack(backStackStateName)
.commit();
}
}
...
public class DebugExampleTwoFragment extends Fragment {
public static final String FRAGMENT_TAG =
BuildConfig.APPLICATION_ID + ".DEBUG_EXAMPLE_TWO_FRAGMENT_TAG";
// ...
}
Kotlin
If you are using Kotlin make sure to take a look at what the Kotlin extensions by Google provide or just write your own.
public class Example1 extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DemoFragment fragmentDemo = (DemoFragment)
getSupportFragmentManager().findFragmentById(R.id.frame_container);
//above part is to determine which fragment is in your frame_container
setFragment(fragmentDemo);
(OR)
setFragment(new TestFragment1());
}
// This could be moved into an abstract BaseActivity
// class for being re-used by several instances
protected void setFragment(Fragment fragment) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.replace(android.R.id.content, fragment);
fragmentTransaction.commit();
}
}
To add a fragment into a Activity or FramentActivity it requires a
Container. That container should be a "Framelayout", which can be
included in xml or else you can use the default container for that
like "android.R.id.content" to remove or replace a fragment in
Activity.
main.xml
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- Framelayout to display Fragments -->
<FrameLayout
android:id="#+id/frame_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="#+id/imagenext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="16dp"
android:src="#drawable/next" />
</RelativeLayout>
After read all Answers I came up with elegant way:
public class MyActivity extends ActionBarActivity {
Fragment fragment ;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FragmentManager fm = getSupportFragmentManager();
fragment = fm.findFragmentByTag("myFragmentTag");
if (fragment == null) {
FragmentTransaction ft = fm.beginTransaction();
fragment =new MyFragment();
ft.add(android.R.id.content,fragment,"myFragmentTag");
ft.commit();
}
}
basically you don't need to add a frameLayout as container of your fragment instead you can add straight the fragment into the android root View container
IMPORTANT: don't use replace fragment as most of the approach shown here, unless you don't mind to lose fragment variable instance state during onrecreation process.
For attaching fragment to an activity programmatically in Kotlin, you can look at the following code:
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// create fragment instance
val fragment : FragmentName = FragmentName.newInstance()
// for passing data to fragment
val bundle = Bundle()
bundle.putString("data_to_be_passed", DATA)
fragment.arguments = bundle
// check is important to prevent activity from attaching the fragment if already its attached
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.add(R.id.fragment_container, fragment, "fragment_name")
.commit()
}
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">
<FrameLayout
android:id="#+id/fragment_container"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
FragmentName.kt
class FragmentName : Fragment() {
companion object {
fun newInstance() = FragmentName()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// receiving the data passed from activity here
val data = arguments!!.getString("data_to_be_passed")
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
}
}
If you are familiar with Extensions in Kotlin then you can even better this code by following this article.
public abstract class SingleFragmentActivity extends Activity {
public static final String FRAGMENT_TAG = "single";
private Fragment fragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
fragment = onCreateFragment();
getFragmentManager().beginTransaction()
.add(android.R.id.content, fragment, FRAGMENT_TAG)
.commit();
} else {
fragment = getFragmentManager().findFragmentByTag(FRAGMENT_TAG);
}
}
public abstract Fragment onCreateFragment();
public Fragment getFragment() {
return fragment;
}
}
use
public class ViewCatalogItemActivity extends SingleFragmentActivity {
#Override
public Fragment onCreateFragment() {
return new FragmentWorkShops();
}
}
For API level 17 or higher, View.generateViewId() will solve this problem. The utility method provides a unique id that is not used in build time.
This may help you
Defining a Fragment
create xml file for fragment view fragment_abc.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView" />
<Button
android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" />
</LinearLayout>
create fragment ABCFragment.java
import androidx.fragment.app.Fragment;
public class FooFragment extends Fragment {
// The onCreateView method is called when Fragment should create its View object hierarchy,
// either dynamically or via XML layout inflation.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle
savedInstanceState) {
// Defines the xml file for the fragment
return inflater.inflate(R.layout.fragment_abc, parent, false);
}
// This event is triggered soon after onCreateView().
// Any view setup should occur here. E.g., view lookups and attaching view listeners.
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
// Setup any handles to view objects here
// EditText etFoo = (EditText) view.findViewById(R.id.etFoo);
}
}
Add frameLayout in your activity
<FrameLayout
android:id="#+id/your_placeholder"
android:layout_width="match_parent"
android:layout_height="match_parent">
now in activity, add following method
protected void setFragment() {
// Begin the transaction
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
// Replace the contents of the container with the new fragment
ft.replace(R.id.fragment_container, new ABCFragment());
// or ft.add(R.id.your_placeholder, new ABCFragment());
// Complete the changes added above
ft.commit();
}
reference : https://guides.codepath.com/android/creating-and-using-fragments#defining-a-fragment

Categories

Resources