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.
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:
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! :)
User contributions licensed under CC BY-SA 3.0