简体   繁体   中英

Using Google map within a fragment

I'm trying to use a Google map in a project I'm working on and am having trouble getting the map to work as a fragment. I want to use the map as a fragment and add the fragment to my main activity (with the assumption that I could reuse this fragment somewhere else if I needed) but the app either crashes or fails to do anything other than load the map, depending on what I try and change.

Here's the tutorial that I used as a basis for the code in my MapsFragment.java file. https://developers.google.com/maps/documentation/android-api/current-places-tutorial

Here's the basic tag I'm using to try and display the map from within my activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:id="@+id/fragment_container"
          android:orientation="vertical"
          android:layout_width="match_parent"
          android:layout_height="match_parent">

<Button
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:text="text"/>

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          android:id="@+id/map_fragment"
          android:name="com.google.android.gms.maps.SupportMapFragment"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          tools:context="[path info].MapsFragment"/>
</LinearLayout>

    @Override
    public void onConnected(Bundle connectionHint) {
        getDeviceLocation();
        SupportMapFragment mapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.map_fragment);
        mapFragment.getMapAsync(this);
    }

With this XML the map will load, but it doesn't seem to call any of the methods within my Fragment class. It certainly doesn't call onCreate, onCreateView, or onConnected.

And here's the code for my fragment class, MapsFragment.java

import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.location.Location;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.location.LocationListener;

import java.util.Date;

public class MapsFragment extends Fragment implements
        OnMapReadyCallback,
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener,
        LocationListener {

    private static final String TAG = MapsActivity.class.getSimpleName();
    private GoogleMap mMap;

    // A default location (Sydney, Australia) and default zoom to use when location permission is
    // not granted.
    private final LatLng mDefaultLocation = new LatLng(-33.8523341, 151.2106085);
    private static final int DEFAULT_ZOOM = 15;
    private static final int PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1;
    private boolean mLocationPermissionGranted;


    private Location mCurrentLocation;
    private Marker mCurrentLocationMarker;

    private CameraPosition mCameraPosition;

    private GoogleApiClient mGoogleApiClient;
    private LocationRequest mLocationRequest;
    private static final long UPDATE_INTERVAL_IN_MILLISECONDS = 10000;
    private static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS =
            UPDATE_INTERVAL_IN_MILLISECONDS / 2;

    // Keys for storing activity state.
    private static final String KEY_CAMERA_POSITION = "camera_position";
    private static final String KEY_LOCATION = "location";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // initialize GoogleAPIClient
        Activity parentActivity = this.getActivity();
        if (parentActivity != null) {
            mGoogleApiClient = new GoogleApiClient.Builder(this.getActivity())
                    .addConnectionCallbacks(this)
                    .addOnConnectionFailedListener(this)
                    .addApi(LocationServices.API)
                    .build();
            createLocationRequest();

        }
    }

    public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.activity_maps, container, false);
    }

    @Override
    public void onResume() {
        super.onResume();
        //setUpMapIfNeeded();
        if (mGoogleApiClient.isConnected()) {
            getDeviceLocation();
        } else {
            mGoogleApiClient.connect();
        }
    }

    @Override
    public void onPause() {
        super.onPause();
    }

    @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;

        if (mCurrentLocation != null) {
            LatLng latLng = new LatLng(mCurrentLocation.getLatitude(), mCurrentLocation.getLongitude());    // maybe add default zoom?
            mMap.addMarker(new MarkerOptions().position(latLng).title("You"));  //(latLng).title("Marker"));
            mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, DEFAULT_ZOOM));
        } else {
            Log.d(TAG, "Current location is null.  Using defaults.");
            mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(mDefaultLocation, 10));     //zoom to Sydney, Australia
        }
    }

    @Override
    public void onConnected(Bundle connectionHint) {
        getDeviceLocation();
        SupportMapFragment mapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.map_fragment);
        mapFragment.getMapAsync(this);
    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult result) {
        // Refer to the reference doc for ConnectionResult to see what error codes might
        // be returned in onConnectionFailed.
        Log.d(TAG, "Play services connection failed: ConnectionResult.getErrorCode() = "
        + result.getErrorCode());
    }

    @Override
    public void onConnectionSuspended(int cause) {
        Log.d(TAG, "Play services connection suspended");
    }

    @Override
    public void onLocationChanged(Location location) {
        mCurrentLocation = location;
        //updateMarkers();
        if (mCurrentLocationMarker != null) {
            mCurrentLocationMarker.remove();
        }
        LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
        // DEBUG
        Date date = new Date();
        int hour = date.getHours();
        int minutes = date.getMinutes();
        String time = String.valueOf(hour) + ":" + String.valueOf(minutes);
        //
        mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, DEFAULT_ZOOM));
        // might need mMap.animateCamera(CameraUpdateFactory.zoomTo(11)) to animate transition. Not sure.
        MarkerOptions markerOptions = new MarkerOptions();
        markerOptions.position(latLng);
        markerOptions.title(time);
        mCurrentLocationMarker = mMap.addMarker(markerOptions);

        Log.d(TAG, latLng.toString() + ", Time: " + time);
    }

    private void getDeviceLocation() {
        if (ContextCompat.checkSelfPermission(this.getContext(),
            Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
        {
            mLocationPermissionGranted = true;
        } else {
            ActivityCompat.requestPermissions(this.getActivity(),
                new String[] {Manifest.permission.ACCESS_FINE_LOCATION},
                PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
        }

        if (mLocationPermissionGranted) {
            mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
            LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,
            mLocationRequest, this);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
    @NonNull String permissions[],
    @NonNull int[] grantResults) {
        mLocationPermissionGranted = false;
        switch (requestCode) {
            case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    mLocationPermissionGranted = true;
                }
            }
        }
        updateLocationUI();
    }

    @SuppressWarnings("MissingPermission")
    private void updateLocationUI() {
        if (mMap == null) {
            return;
        }

        if (mLocationPermissionGranted) {
            mMap.setMyLocationEnabled(true);
            mMap.getUiSettings().setMyLocationButtonEnabled(true);
        } else {
            mMap.setMyLocationEnabled(false);
            mMap.getUiSettings().setMyLocationButtonEnabled(false);
            mCurrentLocation = null;
        }
    }

    private void createLocationRequest() {
        mLocationRequest = new LocationRequest();

        mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
        mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    }
}

I've tried getting rid of the android:name name-value pair, and adding class="[fully qualified path name]" in the XML because I figured I needed a way to tie the class and the fragment together so that the code would be executed when the fragment is inflated. If I do this my class code is actually called, but I get the following error when getMapAsync is called within onConnected

java.lang.NullPointerException: Attempt to invoke virtual method 'void com.google.android.gms.maps.SupportMapFragment.getMapAsync(com.google.android.gms.maps.OnMapReadyCallback)' on a null object reference
                                                                         at [the path info].MapsFragment.onConnected(MapsFragment.java:163)
                                                                         at com.google.android.gms.common.internal.zzm.zzq(Unknown Source)
                                                                         at com.google.android.gms.internal.zzaal.zzo(Unknown Source)
                                                                         at com.google.android.gms.internal.zzaaj.zzvE(Unknown Source)
                                                                         at com.google.android.gms.internal.zzaaj.onConnected(Unknown Source)
                                                                         at com.google.android.gms.internal.zzaan.onConnected(Unknown Source)
                                                                         at com.google.android.gms.internal.zzzy.onConnected(Unknown Source)
                                                                         at com.google.android.gms.common.internal.zzl$1.onConnected(Unknown Source)
                                                                         at com.google.android.gms.common.internal.zzf$zzj.zzwZ(Unknown Source)
                                                                         at com.google.android.gms.common.internal.zzf$zza.zzc(Unknown Source)
                                                                         at com.google.android.gms.common.internal.zzf$zza.zzu(Unknown Source)
                                                                         at com.google.android.gms.common.internal.zzf$zze.zzxa(Unknown Source)
                                                                         at com.google.android.gms.common.internal.zzf$zzd.handleMessage(Unknown Source)
                                                                         at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                         at android.os.Looper.loop(Looper.java:148)
                                                                         at android.app.ActivityThread.main(ActivityThread.java:5417)
                                                                         at java.lang.reflect.Method.invoke(Native Method)
                                                                         at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
                                                                         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

I've been trying different things and trying to understand what exactly is causing my problem and how to get this working but to no avail. If this isn't the right way to go about what I'm trying to do, what should I do instead?

I tried looking at questions with similar sounding problems on this site but none of them seemed to have an answer for me.

Thanks for your help

what I have done is create another xml for only loading map layout xml

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.user.myApp.Fragment.MapFragment" />

after that inflate that map in my fragment

SupportMapFragment mapFragment;

if (mapFragment == null) {
        mapFragment = ((SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.map));


        // Check if we were successful in obtaining the map.
        if (mapFragment != null) {
            mapFragment.getMapAsync(this);

            if (mapFragment == null) {

                Toast.makeText(getContext(),"Sorry! unable to create maps", Toast.LENGTH_SHORT).show();
            }
        }
    }

connect your api on start

@Override
public void onStart() {
    super.onStart();
    googleApiClient.connect();
}

and following lines in your onCreateView after intializing the mapFragment

googleApiClient = new GoogleApiClient.Builder(getActivity())
            .addApi(LocationServices.API)
            .addApi(Places.GEO_DATA_API)
            .addApi(Places.PLACE_DETECTION_API)
            .build();

Tell me if you find any issues

add this piece of code in onCreate of your Activity

    FragmentManager myFragmentManager = getSupportFragmentManager();
    SupportMapFragment mapFragment = (SupportMapFragment) myFragmentManager
            .findFragmentById(R.id.map);
    mapFragment.getMapAsync(this);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM