Desired Behavior
In case it affects any answers or suggestions, what I would like to ultimately see in this app is a navigation UI where one of the Fragment
is a map. I used a template to start with a navigation UI, which gave me several Fragment
s, one for each tab. Currently, I'm trying to use the Google API to put a MapView
inside of one of those Fragment
s.
Current Behavior
Generally, this isn't working (obviously). I have everything up and running, but when I pull up the tab with the map in it, the map is blank. Nothing crashes, and no errors are output:
I/Google Maps Android API: Google Play services client version: 12451000
I/Google Maps Android API: Google Play services package version: 201516040
D/NetworkSecurityConfig: No Network Security Config specified, using platform default
I/DynamiteModule: Considering local module com.google.android.gms.googlecertificates:0 and remote module com.google.android.gms.googlecertificates:4
Selected remote version of com.google.android.gms.googlecertificates, version >= 4
Other Details/Troubleshooting
I know that my API key works because I have another app in which the maps work as expected. I doubt I need a second API key, but I looked anyway and since it wasn't overly obvious how to create a second API key, I think that hunch is correct. Most of the code I'm using was copied from that working example; the primary difference is that other example uses a SupportMapFragment
and it has a class MapsActivity extends FragmentActivity
. Here, since I don't really want to deal with nesting fragments, I'm trying to use a MapView
inside that fragment.
What bits of my code that weren't copied largely came from online sources. None of what I've tried has produced better results.
MapView inside fragment is answering a different problem
Creating google maps v2 in a fragment using Navigation Drawer contains only things I already have
android MapView in Fragment gave me the idea to use MapsInitializer.initialize(this.getActivity());
but that didn't help
Mapview in separate fragment android has nothing new
how do i solve integrating google map in fragment in android uses a Fragment
not a MapView
I also know that onMapReady
is being called. Inside of that, I move the camera and that subsequently calls onCameraMoveStarted
. However, it does not enter onCameraMove
or onCameraIdle
.
Code
This is the code as it is, which uses some of the ideas from the above links. I tried to cut out anything that's unrelated (other fragments, etc)
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.retailtherapy">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/google_maps_key" />
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:windowSoftInputMode="stateVisible|adjustResize" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-feature
android:glEsVersion="0x00020000"
android:required="true"/>
</manifest>
activity_main.xml
<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:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
app:menu="@menu/bottom_nav_menu" />
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/mobile_navigation"
tools:context=".MainActivity" />
</androidx.constraintlayout.widget.ConstraintLayout>
mobile_navigation.xml
<navigation 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:id="@+id/mobile_navigation"
app:startDestination="@+id/navigation_map">
<fragment
android:id="@+id/navigation_map"
android:name="com.example.retailtherapy.ui.map.MapFragment"
android:label="Map"
tools:layout="@layout/fragment_map" />
</navigation>
fragment_map.xml
<RelativeLayout
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.map.MapFragment" >
<com.google.android.gms.maps.MapView
android:id="@+id/googleMap"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
MapFragment.java
public class MapFragment extends Fragment implements OnMapReadyCallback, LocationListener {
/*************************************************************************
*
* M E M B E R S
*
*/
// Location manager for getting current location
private LocationManager mLocationManager = null;
// The map object for reference for ease of adding points and such
private GoogleMap mGoogleMap = null;
private MapView mMapView = null;
// The camera position tells us where the view is on the map
private CameraPosition mCameraPosition = Constants.GetDefaultCameraPosition();
// Current latitude and longitude of user
private LatLng mCurrentLatLong = new LatLng(0.0, 0.0);
// These are flags that allow us to selectively not move the camera and
// instead wait for idle so the movement isn't jerky
private boolean mCameraMoving = false;
private boolean mPendingUpdate = false;
/*************************************************************************
*
* F R A G M E N T
*
*/
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View root = inflater.inflate(R.layout.fragment_map, container, false);
// The google map...
MapsInitializer.initialize(getActivity());
mMapView = (MapView) root.findViewById(R.id.googleMap);
mMapView.onCreate(savedInstanceState);
mMapView.getMapAsync(this);
// The required permissions...
final String[] REQUIRED_PERMISSIONS = {
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.INTERNET,
};
requestPermissions(REQUIRED_PERMISSIONS, 0);
return root;
}
@Override
public void onStart() throws SecurityException {
super.onStart();
// Location manager for getting info current location
mLocationManager = (LocationManager) getActivity().getSystemService(Context.LOCATION_SERVICE);
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0, this);
}
@Override
public void onResume() {
super.onResume();
mMapView.onResume();
}
@Override
public void onStop() {
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
mMapView.onDestroy();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mMapView.onSaveInstanceState(outState);
}
@Override
public void onLowMemory() {
super.onLowMemory();
mMapView.onLowMemory();
}
/*************************************************************************
*
* G O O G L E M A P S
*
*/
@Override
public void onMapReady(GoogleMap googleMap) {
mGoogleMap = googleMap;
// Assuming if here that we have permissions okay. Dangerous sure
// but I'm lazy...
mGoogleMap.setMyLocationEnabled(true);
// Store the location of the camera for UI purposes so we can go
// back to where we were
mGoogleMap.setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() {
@Override
public void onCameraMove() {
mCameraPosition = mGoogleMap.getCameraPosition();
}
});
mGoogleMap.setOnCameraMoveStartedListener(new GoogleMap.OnCameraMoveStartedListener() {
@Override
public void onCameraMoveStarted(int i) {
mCameraMoving = true;
}
});
mGoogleMap.setOnCameraIdleListener(new GoogleMap.OnCameraIdleListener() {
@Override
public void onCameraIdle() {
mCameraMoving = false;
if (mPendingUpdate) {
centerCameraOnLocation(false);
}
}
});
// Start with user centered in view
centerCameraOnLocation(true);
}
/*************************************************************************
*
* L O C A T I O N
*
*/
@Override
public void onLocationChanged(Location location) {
// Store the current location so if we start auto-center but aren't
// already, we know where to pan to
LatLng newLatLng = new LatLng(location.getLatitude(), location.getLongitude());
if (!newLatLng.equals(mCurrentLatLong)) {
mCurrentLatLong = new LatLng(location.getLatitude(), location.getLongitude());
centerCameraOnLocation(false);
}
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {}
@Override
public void onProviderDisabled(String provider) {}
@Override
public void onProviderEnabled(String provider) {}
/*************************************************************************
*
* M A P C A M E R A
*
*/
/*
* Updates the camera position based on current state. If the camera is
* already moving, then wait.
*/
private void centerCameraOnLocation(boolean animate) {
if (mGoogleMap == null) {
return;
}
if (mCameraMoving) {
mPendingUpdate = true;
return;
}
// Get the CameraPosition to update to based on whether we are auto
// centering or not.
if (mCameraPosition == null) {
mCameraPosition = Constants.GetDefaultCameraPosition();
}
// If this is the same as it was before, then don't reset it
if (mCameraPosition.equals(mGoogleMap.getCameraPosition())) {
return;
}
// Make the update so we can move the camera
CameraUpdate cameraUpdate = CameraUpdateFactory.newCameraPosition(mCameraPosition);
// Move the camera with or without animation. The time we don't want
// animation is when we're setting the position initially
if (animate) {
mGoogleMap.animateCamera(cameraUpdate);
}
else {
mGoogleMap.moveCamera(cameraUpdate);
}
mPendingUpdate = false;
}
}