GoogleMap object is null when trying to migrate from older version Google Map code (Android apps Programming) - trouble migrating to newer version?

1

I am a newbie to Android Programming (have very little knowledge about Android programming). I am trying to read and code a project in a book, but it is an old project and most of the dependencies (Android Studio, SDK, Google Maps, ...) are outdated. I try to migrate the project to newer version but still has some problems and the program does not run.

Here is the MapsActivity.java file, most of the file deals with showing the the Point of Interest (POI) and show the map using Google Maps:

package hitlexamples.happywalk.activities;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Bundle;
import android.os.Looper;
import android.support.v4.app.FragmentActivity;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

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 java.util.ArrayList;
import java.util.Map;

import hitlexamples.happywalk.R;
import hitlexamples.happywalk.cluster.GeoClusterer;
import hitlexamples.happywalk.cluster.GeoItem;
import hitlexamples.happywalk.service.HappyWalkService;
import hitlexamples.happywalk.tasks.ThreadGetPoi;
import hitlexamples.happywalk.utilities.GlobalVariables;

public class MapsActivity extends FragmentActivity implements OnMapReadyCallback {

    /*  ------------ DEVELOPMENT NOTES ----------------------
    - TODO: Fix for devices with low DPI
    */

    private GoogleMap mMap; // Might be null if Google Play services APK is not available.
    private Handler uiHandler; //handler used by other classes to post tasks to the Ui Thread
    private Marker userMarker;
    private HappyWalkService hWService;

    /** This list contains all available POIs fetched at activity start **/
    private ArrayList<GeoItem> geoItems = new ArrayList<GeoItem>();
    private GeoClusterer clusterer;
    //Thread responsible for fetching POIs
    private Thread getpoi;

    //workflow variables
    private int requestCode = 99;

    // GETS AND SETS ------------------------------

    public void setGeoItems(ArrayList<GeoItem> geoItems) {
        this.geoItems = geoItems;
    }

    public Handler getUiHandler() {
        return uiHandler;
    }

    public GoogleMap getmMap() {
        return mMap;
    }

    // --------------------------------------------

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);
        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);

        uiHandler = new Handler(Looper.getMainLooper());
        //initialize getpoi as an empty thread; will be replaced when necessary.
        getpoi = new Thread();
        //(Re)start our service.
        Intent startHWService = new Intent(this, HappyWalkService.class);
        startService(startHWService);

        initializeMapComponents();
    }

    private ServiceConnection hwConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            /*
             This is called when the connection with the service has been established, giving us a service object we can use to interact with the service.  Because we have bound to a explicit service that we know is running in our own process, we can cast its IBinder to a concrete class and directly access it.
            */
            hWService = ((HappyWalkService.HappyWalkBinder)service).getService(MapsActivity.this);

            /* Now we check from where this activity is being initiated from */
            if (requestCode == GlobalVariables.AREQ_POI_DESCRIPTION_REQUEST) {
                /* if we come from POI description, we do nothing.
                * The camera should already be focused on the appropriate POI*/
                //revert requestCode
                requestCode = 99;
            }
            else {
                LatLng position;
                //If our service was previously running, let us focus the camera on the user's current position.
                if((position = hWService.getHwLocationListener().getActualposition()) !=null) {
                    updateUserMarkerPosition(position);
                    focusCameraOnPosition(position);
                }
                //if not, let us focus our camera onto the last known position
                else if ((position = hWService.getHwLocationListener().
                        getLastKnownPositionFromSharedPrefs()) != null){
                    focusCameraOnPosition(position);
                }
            }
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            // Because it is running in our same process, we should never
            // see this happen.
            hWService = null;
        }
    };

    private void initializeMapComponents() {

        setUpMapIfNeeded();

        // Initialize the clusterer
        float screenDensity = this.getResources().getDisplayMetrics().density;
        clusterer = new GeoClusterer(this, screenDensity, mMap.getCameraPosition().target);
        GeoClusterer.ClusterMarkerListener clustLis = clusterer.new ClusterMarkerListener();
        GeoClusterer.CameraChangeListener cameraLis = clusterer.new CameraChangeListener();
        mMap.setOnMarkerClickListener(clustLis);
        mMap.setOnCameraChangeListener(cameraLis);
    }

    /**
     * Here we have to bind to our HappyWalk service again, since we unbinded on pause.
     */
    @Override
    protected void onResume() {
        //check the request code
        /* if we are coming from a POI description, ignore this step
        otherwise, we will wrongly overwrite the requestCode*/
        if (getIntent().getExtras() != null
                && getIntent().getExtras().containsKey(GlobalVariables.BND_EXTRA_REQ_CODE_KEY)
                && requestCode != GlobalVariables.AREQ_POI_DESCRIPTION_REQUEST) {
            requestCode = getIntent().getExtras().
                    getInt(GlobalVariables.BND_EXTRA_REQ_CODE_KEY);
            getIntent().removeExtra(GlobalVariables.BND_EXTRA_REQ_CODE_KEY);
        }
        bindHwService();
        super.onResume();
    }

    /**
     * Here we stop the notification requests, the accelerometer collection,
     * and the threads that fetch POIs and draw markers.
     */
    @Override
    protected void onPause() {
        getpoi.interrupt();
        clusterer.stopMarkerUpdate();
        unBindHwService();
        super.onPause();
    }

    /**
     * Since this activity is a single instance, this is called whenever we have a new intent
     */
    @Override
    protected void onNewIntent(Intent intent) {
        //update the intent
        setIntent(intent);
        super.onNewIntent(intent);
    }

    @Override
    public void onMapReady(GoogleMap map) {
        // Place your logic here
        mMap = map;
        map.setIndoorEnabled(true);
        map.setBuildingsEnabled(true);
        map.getUiSettings().setZoomControlsEnabled(false);

    }

    // MAP MANIPULATION METHODS ---------------------------

    private void setUpMapIfNeeded() {
        // Do a null check to confirm that we have not already instantiated the map.
        if (mMap == null) {
            SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
            mapFragment.getMapAsync(this);
        }
    }

    /**
    Updates the position of the user marker
     */
    public void updateUserMarkerPosition(final LatLng position) {
        if (userMarker == null) {
            userMarker = mMap.addMarker(new MarkerOptions().
                    //position(position).
                    //icon(BitmapDescriptorFactory.fromResource(R.drawable.marker_user_position)));
                    position(position));
        }
        else {
            userMarker.setPosition(position);
        }
    }

    /**
     * This function changes the camera to a certain position. It is used by
     * the HwLocationListener to change the camera the first time the user changed location
     */
    public void focusCameraOnPosition(final LatLng position) {
        mMap.getUiSettings().setAllGesturesEnabled(false);
        CameraPosition cameraPosition = new CameraPosition.Builder()
                .target(position)
                .zoom(GlobalVariables.ZOOM_USER_OVERVIEW)
                .build();
        mMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition),1500,null);
        mMap.getUiSettings().setAllGesturesEnabled(true);
    }

    // ----------------------------------------
    // Workflow methods
    // ----------------------------------------

    /**
     * This class starts the thread necessary to request a fresh list of POIs on the App
     */
    public void refreshPOIs(double latitude, double longitude) {
        while (getpoi.isAlive()) {
            getpoi.interrupt();
        }
        getpoi = new ThreadGetPoi(this, latitude, longitude);
        getpoi.start();
    }

    /**
     * Performs clustering of the GeoItems in memory
     */
    public void refreshGeoItems() {
        /*
        refreshing the geoItems is kinda important, so we want it done
        ASAP and, thus, don't use our handler.
         */
        runOnUiThread(new Runnable() {
            public void run() {
                clusterer.refreshGeoItems(geoItems);
            }
        });
    }

    public void showPOIDetails(String idPoi) {
        Intent myIntent = new Intent(getApplicationContext(),
                POIDescription.class);
        myIntent.putExtra("idPoi", idPoi);
        startActivity(myIntent);
        requestCode = GlobalVariables.AREQ_POI_DESCRIPTION_REQUEST;
    }

    /**
     * This method binds our MapRouteActivity to the NotificationService
     */
    private void bindHwService() {
        bindService(new Intent(this, HappyWalkService.class), hwConnection, Context.BIND_AUTO_CREATE);
    }

    /**
     * This method unbinds our MapRouteActivity from the NotificationService
     */
    private void unBindHwService() {
        unbindService(hwConnection);
        hWService = null;
    }

    /**
     * This method is called when the user presses the "Exit" menu option
     */
    private void performExit() {
        //we don't need to unBind to the service, since we already do that onPause();
        stopService(new Intent(this, HappyWalkService.class));
        this.finish();
    }

    // ----------------------------------------
    // App Menu
    // ----------------------------------------
    /**
     * This method handles the options Menu
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.map_menu, menu);
        return true;
    }

    /**
     * This method handles menu items
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle item selection
        switch (item.getItemId()) {
            case R.id.menuExit:
                performExit();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
}

Here is the error that I encountered:

02/02 16:01:23: Launching 'app' on Pixel 3 API 27.
$ adb shell am start -n "hitlexamples.happywalk/hitlexamples.happywalk.activities.MapsActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER
Waiting for process to come online...
Connected to process 3972 on device 'Pixel_3_API_27 [emulator-5554]'.
Capturing and displaying logcat messages from application. This behavior can be disabled in the "Logcat output" section of the "Debugger" settings page.
I/FirebaseInitProvider: FirebaseApp initialization unsuccessful
I/zzbx: Making Creator dynamically
I/zygote: The ClassLoaderContext is a special shared library.
I/chatty: uid=10080(hitlexamples.happywalk) identical 1 line
I/zygote: The ClassLoaderContext is a special shared library.
W/zygote: Unsupported class loader
W/zygote: Skipping duplicate class check due to unsupported classloader
I/Google Maps Android API: Google Play services client version: 11020000
I/Google Maps Android API: Google Play services package version: 11580470
I/zygote: Do partial code cache collection, code=30KB, data=15KB
I/zygote: After code cache collection, code=26KB, data=14KB
    Increasing code cache capacity to 128KB
I/zygote: Background concurrent copying GC freed 14373(3MB) AllocSpace objects, 1(148KB) LOS objects, 49% free, 1904KB/3MB, paused 12us total 192.833ms
D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main
    Process: hitlexamples.happywalk, PID: 3972
    java.lang.RuntimeException: Unable to start activity ComponentInfo{hitlexamples.happywalk/hitlexamples.happywalk.activities.MapsActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'com.google.android.gms.maps.model.CameraPosition com.google.android.gms.maps.GoogleMap.getCameraPosition()' on a null object reference
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2778)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856)
        at android.app.ActivityThread.-wrap11(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
     Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'com.google.android.gms.maps.model.CameraPosition com.google.android.gms.maps.GoogleMap.getCameraPosition()' on a null object reference
        at hitlexamples.happywalk.activities.MapsActivity.initializeMapComponents(MapsActivity.java:134)
        at hitlexamples.happywalk.activities.MapsActivity.onCreate(MapsActivity.java:87)
        at android.app.Activity.performCreate(Activity.java:7009)
        at android.app.Activity.performCreate(Activity.java:7000)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1214)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2731)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856) 
        at android.app.ActivityThread.-wrap11(Unknown Source:0) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:164) 
        at android.app.ActivityThread.main(ActivityThread.java:6494) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) 
W/DynamiteModule: Local module descriptor class for com.google.android.gms.googlecertificates not found.
W/zygote: Unsupported class loader
W/zygote: Skipping duplicate class check due to unsupported classloader
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
W/zygote: Unsupported class loader
    Skipping duplicate class check due to unsupported classloader
Process 3972 terminated.

Can anyone help me pointing out the problem? Or if you think I provide inadequate information please tell me to post it further. I am really appreciated, since I do not have a lot of experience and this project is only the base architecture for me to apply knowledge in other field.

You can find the whole HappyWalk project at this git site: https://git.dei.uc.pt/dsnunes/happywalk.git

If anyone can help me migrate the whole project to newer version of Android and Google Maps I am really appreciated!

Edited: I have tried to modify the code as @Evan said, but I still encountered some errors (it only shows maps of the whole world, while I'm looking for the area near me). Can anyone help me with this error

02/10 10:10:45: Launching 'app' on Google Pixel 3.
$ adb shell am start -n "hitlexamples.happywalk/hitlexamples.happywalk.activities.MapsActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER
Waiting for process to come online...
Connected to process 20363 on device 'google-pixel_3-973X22WD3'.
Capturing and displaying logcat messages from application. This behavior can be disabled in the "Logcat output" section of the "Debugger" settings page.
I/mples.happywal: The ClassLoaderContext is a special shared library.
W/hitlexamples.happywalk: type=1400 audit(0.0:525): avc: denied { read } for comm=45474C20496E6974 name="u:object_r:vendor_default_prop:s0" dev="tmpfs" ino=24252 scontext=u:r:untrusted_app:s0:c181,c256,c512,c768 tcontext=u:object_r:vendor_default_prop:s0 tclass=file permissive=0
E/libc: Access denied finding property "vendor.debug.egl.profiler"
W/mples.happywal: Accessing hidden method Landroid/graphics/drawable/Drawable;->getOpticalInsets()Landroid/graphics/Insets; (light greylist, linking)
    Accessing hidden field Landroid/graphics/Insets;->left:I (light greylist, linking)
    Accessing hidden field Landroid/graphics/Insets;->right:I (light greylist, linking)
    Accessing hidden field Landroid/graphics/Insets;->top:I (light greylist, linking)
    Accessing hidden field Landroid/graphics/Insets;->bottom:I (light greylist, linking)
W/mples.happywal: Accessing hidden method Landroid/view/View;->computeFitSystemWindows(Landroid/graphics/Rect;Landroid/graphics/Rect;)Z (light greylist, reflection)
    Accessing hidden method Landroid/view/ViewGroup;->makeOptionalFitsSystemWindows()V (light greylist, reflection)
W/mples.happywal: Accessing hidden method Landroid/widget/TextView;->getTextDirectionHeuristic()Landroid/text/TextDirectionHeuristic; (light greylist, linking)
I/zzbz: Making Creator dynamically
W/mples.happywal: Unsupported class loader
W/mples.happywal: Skipping duplicate class check due to unsupported classloader
I/DynamiteModule: Considering local module com.google.android.gms.maps_dynamite:0 and remote module com.google.android.gms.maps_dynamite:222
    Selected remote version of com.google.android.gms.maps_dynamite, version >= 222
V/DynamiteModule: Dynamite loader version >= 2, using loadModule2NoCrashUtils
I/DynamiteLoaderV2: [71] Mapsdynamite
W/mples.happywal: Unsupported class loader
W/mples.happywal: Skipping duplicate class check due to unsupported classloader
I/Google Maps Android API: Google Play services client version: 12451000
I/Google Maps Android API: Google Play services package version: 20104028
W/mples.happywal: Accessing hidden field Ljava/nio/Buffer;->address:J (light greylist, reflection)
D/OpenGLRenderer: Skia GL Pipeline
I/Adreno: QUALCOMM build                   : 3f88ca2, I42f6fe38fb
    Build Date                       : 07/13/18
    OpenGL ES Shader Compiler Version: EV031.24.00.00
    Local Branch                     : 50.04
    Remote Branch                    : 
    Remote Branch                    : 
    Reconstruct Branch               : 
    Build Config                     : S P 4.0.10 AArch64
W/RenderThread: type=1400 audit(0.0:526): avc: denied { read } for name="u:object_r:vendor_default_prop:s0" dev="tmpfs" ino=24252 scontext=u:r:untrusted_app:s0:c181,c256,c512,c768 tcontext=u:object_r:vendor_default_prop:s0 tclass=file permissive=0
E/libc: Access denied finding property "ro.vendor.graphics.memory"
I/Adreno: PFP: 0x016ee170, ME: 0x00000000
I/ConfigStore: android::hardware::configstore::V1_0::ISurfaceFlingerConfigs::hasWideColorDisplay retrieved: 1
    android::hardware::configstore::V1_0::ISurfaceFlingerConfigs::hasHDRDisplay retrieved: 1
I/OpenGLRenderer: Initialized EGL, version 1.4
D/OpenGLRenderer: Swap behavior 2
E/libc: Access denied finding property "vendor.gralloc.enable_ahardware_buffer"
W/RenderThread: type=1400 audit(0.0:527): avc: denied { read } for name="u:object_r:vendor_default_prop:s0" dev="tmpfs" ino=24252 scontext=u:r:untrusted_app:s0:c181,c256,c512,c768 tcontext=u:object_r:vendor_default_prop:s0 tclass=file permissive=0
D/NetworkSecurityConfig: No Network Security Config specified, using platform default
W/DynamiteModule: Local module descriptor class for com.google.android.gms.googlecertificates not found.
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
I/DynamiteLoaderV2: [71] Googlecertificates
W/mples.happywal: Unsupported class loader
W/mples.happywal: Skipping duplicate class check due to unsupported classloader
E/SchedPolicy: set_timerslack_ns write failed: Operation not permitted
Process 20363 terminated.
java
android
google-maps
android-studio
runtime-error
asked on Stack Overflow Feb 2, 2020 by Minh Tran • edited Feb 10, 2020 by Minh Tran

1 Answer

1

I've downloaded your project and applied some changes in order to help you get it to run. Try modifying your code as below and make sure that you migrate your app to AndroidX using refactor.

MapsActivity.java

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

import androidx.appcompat.app.AppCompatActivity;

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 java.util.ArrayList;

import hitlexamples.happywalk.R;
import hitlexamples.happywalk.cluster.GeoClusterer;
import hitlexamples.happywalk.cluster.GeoItem;
import hitlexamples.happywalk.service.HappyWalkService;
import hitlexamples.happywalk.tasks.ThreadGetPoi;
import hitlexamples.happywalk.utilities.GlobalVariables;

public class MapsActivity extends AppCompatActivity implements OnMapReadyCallback {

    /*  ------------ DEVELOPMENT NOTES ----------------------
    - TODO: Fix for devices with low DPI
    */

    private GoogleMap mMap; // Might be null if Google Play services APK is not available.
    private Handler uiHandler; //handler used by other classes to post tasks to the Ui Thread
    private Marker userMarker;
    private HappyWalkService hWService;

    /**
     * This list contains all available POIs fetched at activity start
     **/
    private ArrayList<GeoItem> geoItems = new ArrayList<GeoItem>();
    private GeoClusterer clusterer;
    //Thread responsible for fetching POIs
    private Thread getpoi;

    //workflow variables
    private int requestCode = 99;

    // GETS AND SETS ------------------------------

    public void setGeoItems(ArrayList<GeoItem> geoItems) {
        this.geoItems = geoItems;
    }

    public Handler getUiHandler() {
        return uiHandler;
    }

    public GoogleMap getmMap() {
        return mMap;
    }

    // --------------------------------------------

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);

        uiHandler = new Handler(Looper.getMainLooper());
        //initialize getpoi as an empty thread; will be replaced when necessary.
        getpoi = new Thread();
        //(Re)start our service.
        Intent startHWService = new Intent(this, HappyWalkService.class);
        startService(startHWService);
        initializeMapComponents();
    }

    private ServiceConnection hwConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            /*
             This is called when the connection with the service has been established, giving us a service object we can use to interact with the service.  Because we have bound to a explicit service that we know is running in our own process, we can cast its IBinder to a concrete class and directly access it.
            */
            hWService = ((HappyWalkService.HappyWalkBinder) service).getService(MapsActivity.this);

            /* Now we check from where this activity is being initiated from */
            if (requestCode == GlobalVariables.AREQ_POI_DESCRIPTION_REQUEST) {
                /* if we come from POI description, we do nothing.
                 * The camera should already be focused on the appropriate POI*/
                //revert requestCode
                requestCode = 99;
            } else {
                LatLng position;
                //If our service was previously running, let us focus the camera on the user's current position.
                if ((position = hWService.getHwLocationListener().getActualposition()) != null) {
                    updateUserMarkerPosition(position);
                    focusCameraOnPosition(position);
                }
                //if not, let us focus our camera onto the last known position
                else if ((position = hWService.getHwLocationListener().
                        getLastKnownPositionFromSharedPrefs()) != null) {
                    focusCameraOnPosition(position);
                }
            }
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            // Because it is running in our same process, we should never
            // see this happen.
            hWService = null;
        }
    };

    private void initializeMapComponents() {
        setUpMapIfNeeded();
    }

    /**
     * Here we have to bind to our HappyWalk service again, since we unbinded on pause.
     */
    @Override
    protected void onResume() {
        //check the request code
        /* if we are coming from a POI description, ignore this step
        otherwise, we will wrongly overwrite the requestCode*/
        if (getIntent().getExtras() != null
                && getIntent().getExtras().containsKey(GlobalVariables.BND_EXTRA_REQ_CODE_KEY)
                && requestCode != GlobalVariables.AREQ_POI_DESCRIPTION_REQUEST) {
            requestCode = getIntent().getExtras().
                    getInt(GlobalVariables.BND_EXTRA_REQ_CODE_KEY);
            getIntent().removeExtra(GlobalVariables.BND_EXTRA_REQ_CODE_KEY);
        }
        bindHwService();
        super.onResume();
    }

    /**
     * Here we stop the notification requests, the accelerometer collection,
     * and the threads that fetch POIs and draw markers.
     */
    @Override
    protected void onPause() {
        getpoi.interrupt();
        clusterer.stopMarkerUpdate();
        unBindHwService();
        super.onPause();
    }

    /**
     * Since this activity is a single instance, this is called whenever we have a new intent
     */
    @Override
    protected void onNewIntent(Intent intent) {
        //update the intent
        setIntent(intent);
        super.onNewIntent(intent);
    }

    // MAP MANIPULATION METHODS ---------------------------

    private void setUpMapIfNeeded() {
        // Do a null check to confirm that we have not already instantiated the map.
        if (mMap == null) {
            // Try to obtain the map from the SupportMapFragment.
            ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMapAsync(this);
        }
    }

    /**
     * Updates the position of the user marker
     */
    public void updateUserMarkerPosition(final LatLng position) {
        if (userMarker == null) {
            userMarker = mMap.addMarker(new MarkerOptions().
                    //position(position).
                    //icon(BitmapDescriptorFactory.fromResource(R.drawable.marker_user_position)));
                            position(position));
        } else {
            userMarker.setPosition(position);
        }
    }

    /**
     * This function changes the camera to a certain position. It is used by
     * the HwLocationListener to change the camera the first time the user changed location
     */
    public void focusCameraOnPosition(final LatLng position) {
        mMap.getUiSettings().setAllGesturesEnabled(false);
        CameraPosition cameraPosition = new CameraPosition.Builder()
                .target(position)
                .zoom(GlobalVariables.ZOOM_USER_OVERVIEW)
                .build();
        mMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition), 1500, null);
        mMap.getUiSettings().setAllGesturesEnabled(true);
    }

    // ----------------------------------------
    // Workflow methods
    // ----------------------------------------

    /**
     * This class starts the thread necessary to request a fresh list of POIs on the App
     */
    public void refreshPOIs(double latitude, double longitude) {
        while (getpoi.isAlive()) {
            getpoi.interrupt();
        }
        getpoi = new ThreadGetPoi(this, latitude, longitude);
        getpoi.start();
    }

    /**
     * Performs clustering of the GeoItems in memory
     */
    public void refreshGeoItems() {
        /*
        refreshing the geoItems is kinda important, so we want it done
        ASAP and, thus, don't use our handler.
         */
        runOnUiThread(new Runnable() {
            public void run() {
                clusterer.refreshGeoItems(geoItems);
            }
        });
    }

    public void showPOIDetails(String idPoi) {
        Intent myIntent = new Intent(getApplicationContext(),
                POIDescription.class);
        myIntent.putExtra("idPoi", idPoi);
        startActivity(myIntent);
        requestCode = GlobalVariables.AREQ_POI_DESCRIPTION_REQUEST;
    }

    /**
     * This method binds our MapRouteActivity to the NotificationService
     */
    private void bindHwService() {
        bindService(new Intent(this, HappyWalkService.class), hwConnection, Context.BIND_AUTO_CREATE);
    }

    /**
     * This method unbinds our MapRouteActivity from the NotificationService
     */
    private void unBindHwService() {
        unbindService(hwConnection);
        hWService = null;
    }

    /**
     * This method is called when the user presses the "Exit" menu option
     */
    private void performExit() {
        //we don't need to unBind to the service, since we already do that onPause();
        stopService(new Intent(this, HappyWalkService.class));
        this.finish();
    }

    // ----------------------------------------
    // App Menu
    // ----------------------------------------

    /**
     * This method handles the options Menu
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.map_menu, menu);
        return true;
    }

    /**
     * This method handles menu items
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle item selection
        switch (item.getItemId()) {
            case R.id.menuExit:
                performExit();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

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

        // Initialize the clusterer
        float screenDensity = this.getResources().getDisplayMetrics().density;
        clusterer = new GeoClusterer(this, screenDensity, mMap.getCameraPosition().target);
        GeoClusterer.ClusterMarkerListener clustLis = clusterer.new ClusterMarkerListener();
        GeoClusterer.CameraChangeListener cameraLis = clusterer.new CameraChangeListener();
        mMap.setOnMarkerClickListener(clustLis);
    }
}

top-level build.gradle

buildscript {
    repositories {
        jcenter()
        maven {
            url 'https://maven.google.com/'
            name 'Google'
        }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.3'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
        maven {
            url 'https://maven.google.com/'
            name 'Google'
        }
    }
}

module-level build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion '29.0.2'
    useLibrary 'org.apache.http.legacy'

    defaultConfig {
        applicationId "hitlexamples.happywalk"
        minSdkVersion 16
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility = 1.8
        targetCompatibility = 1.8
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'com.google.android.gms:play-services-maps:17.0.0'
    implementation "androidx.preference:preference:1.1.0"
    implementation project(':com.ubhave.sensormanager')
}

activity_maps.xml

<fragment 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" android:id="@+id/map" tools:context=".activities.MapsActivity"
    android:name="com.google.android.gms.maps.SupportMapFragment" />

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="hitlexamples.happywalk" >

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
    <!--
 The ACCESS_COARSE/FINE_LOCATION permissions are not required to use
         Google Maps Android API v2, but are recommended.
    -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/happywalklogo"
        android:label="@string/app_name"
        android:theme="@style/AppTheme"
        tools:replace="android:icon,android:theme"
        tools:ignore="GoogleAppIndexingWarning">
        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />
        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="@string/google_maps_key" />

        <activity
            android:name=".activities.MapsActivity"
            android:label="@string/title_activity_maps"
            android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
            android:launchMode="singleInstance"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".service.HappyWalkService"
            android:enabled="true"
            android:exported="false" >
        </service>

        <activity
            android:name=".activities.POIDescription"
            android:label="@string/title_activity_poidescription"
            android:screenOrientation="portrait">
        </activity>
    </application>

</manifest>

Screenshot:

enter image description here

Edit: To center the map on your current location you can follow this Google guide. I.e. add this code:

/**
 * Gets the current location of the device, and positions the map's camera.
 */
private void getDeviceLocation() {
    /*
     * Get the best and most recent location of the device, which may be null in rare
     * cases when a location is not available.
     */
    try {
        if (mLocationPermissionGranted) {
            Task<Location> locationResult = mFusedLocationProviderClient.getLastLocation();
            locationResult.addOnCompleteListener(this, new OnCompleteListener<Location>() {
                @Override
                public void onComplete(@NonNull Task<Location> task) {
                    if (task.isSuccessful()) {
                        // Set the map's camera position to the current location of the device.
                        mLastKnownLocation = task.getResult();
                        if (mLastKnownLocation != null) {
                            mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
                                    new LatLng(mLastKnownLocation.getLatitude(),
                                            mLastKnownLocation.getLongitude()), DEFAULT_ZOOM));
                        }
                    } else {
                        Log.d(TAG, "Current location is null. Using defaults.");
                        Log.e(TAG, "Exception: %s", task.getException());
                        mMap.moveCamera(CameraUpdateFactory
                                .newLatLngZoom(mDefaultLocation, DEFAULT_ZOOM));
                        mMap.getUiSettings().setMyLocationButtonEnabled(false);
                    }
                }
            });
        }
    } catch (SecurityException e)  {
        Log.e("Exception: %s", e.getMessage());
    }
}


/**
 * Prompts the user for permission to use the device location.
 */
private void getLocationPermission() {
    /*
     * Request location permission, so that we can get the location of the
     * device. The result of the permission request is handled by a callback,
     * onRequestPermissionsResult.
     */
    if (ContextCompat.checkSelfPermission(this.getApplicationContext(),
            android.Manifest.permission.ACCESS_FINE_LOCATION)
            == PackageManager.PERMISSION_GRANTED) {
        mLocationPermissionGranted = true;
    } else {
        ActivityCompat.requestPermissions(this,
                new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
                PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
    }
}

/**
 * Handles the result of the request for location permissions.
 */
@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;
            }
        }
    }
}

And call the functions in onMapReady:

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

    getLocationPermission();
    getDeviceLocation();
}

Make sure you set your own defaults.

Hope this helps you! :)

answered on Stack Overflow Feb 5, 2020 by evan • edited Feb 12, 2020 by evan

User contributions licensed under CC BY-SA 3.0