Service Scenario

Many ride-hailing, fitness, and navigation apps are capable of tracking the user's real time location, and presenting it on a map display, offering a new realm of convenience for users.

In this codelab, we will take a closer look into how this is made possible, with user running used as an example, for the motion tracking capabilities in Location Kit and Map Kit, which you can integrate into your app with ease.

What You Will Create

In this codelab, you will:

What You Will Learn

In this codelab, you will learn how to:

What You Will Need

Hardware Requirements

Software Requirements

To integrate HMS Core kits, you need to complete the following preparations:

For details, please refer to Preparations for Integrating HUAWEI HMS Core.

You can enable Location Kit and Map Kit in either of the following methods:

Method 1:

Sign in to AppGallery Connect, click My projects, select the created project, and go to Project settings > Manage APIs.

Toggle on the Location Kit and Map Kit switches.

Method 2:

Sign in to HUAWEI Developers and click Console. Then, go to HMS API Services > API Library, and select your project.

Find the Location Kit and Map Kit cards on the API Library page.

Click the cards, and then click Enable to enable the kits.

Obtaining the Configuration File

Sign in to AppGallery Connect, click My projects, select your project, then go to Project settings > General information, and click Set located next to Data storage location.

Download the agconnect-services.json file in the App information area.

Save the agconnect-services.json file to the app directory in your project.

Configuring Dependencies for the HMS Core SDK

Open the build.gradle file in the root directory for your project.

The code in the project-level build.gradle file is as follows:

buildscript { ext.kotlin_version = "1.4.10" repositories { google() jcenter() maven {url 'http://developer.huawei.com/repo/'} } dependencies { classpath "com.android.tools.build:gradle:4.1.1" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.huawei.agconnect:agcp:1.4.0.300' // NOTE: Do not place your app dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { google() jcenter() maven {url 'http://developer.huawei.com/repo/'} } }

Open the build.gradle file in the app directory for your project.

Add the dependencies on Location Kit and Map Kit in the dependencies section.

dependencies { implementation 'com.huawei.hms:location:{version}' implementation 'com.huawei.hms:maps:{version}' }

Click Sync Now to synchronize the project.

Configuring Obfuscation Scripts

  1. Open the obfuscation configuration file proguard-rules.pro for your Android Studio project.
  2. Add configurations to exclude the enabled kits from obfuscation.
    -ignorewarnings -keepattributes *Annotation* -keepattributes Exceptions -keepattributes InnerClasses -keepattributes Signature -keepattributes SourceFile,LineNumberTable -keep class com.huawei.hianalytics.**{*;} -keep class com.huawei.updatesdk.**{*;} -keep class com.huawei.hms.**{*;}
  3. If you are using AndResGuard, add its trustlist to the obfuscation configuration file.
    "R.string.hms*", "R.string.connect_server_fail_prompt_toast", "R.string.getting_message_fail_prompt_toast", "R.string.no_available_network_prompt_toast", "R.string.third_app_*", "R.string.upsdk_*", "R.layout.hms*", "R.layout.upsdk_*", "R.drawable.upsdk*", "R.color.upsdk*", "R.dimen.upsdk*", "R.style.upsdk*", "R.string.agc*"

Map Kit Overview

Map Kit is an SDK for map development that covers map data in more than 200 countries and regions, and offers support in over 100 different languages. It is designed to help you integrate map-based functions into your app with ease.

We will now use Map Kit to draw a basic map.

Map Container

The HMS Core Map SDK currently supports two types of map containers: SupportMapFragment and MapView.

MapView is a subclass of the Android View class, and can be used to place a map within an Android view. Similar to SupportMapFragment, MapView serves as a map container, and displays core map functions via a HuaweiMap object. To use the MapView class when calling APIs in normal interaction mode, you need to call the following methods in the corresponding methods for your Activity/Fragment: onCreate(), onStart(), onResume(), onPause(), onStop(), onDestroy(), onSaveInstanceState(Bundle outState), and onLowMemory().

In this codelab, we will use MapView to create a map sample.

1. Create a MapView layout.

Create a MapActivity class, add a MapView object in the layout file, and set the map attributes in the file.

Layout sample code:

<com.huawei.hms.maps.MapView android:id="@+id/hw_mapview" android:layout_width="match_parent" android:layout_height="match_parent" map:mapType="normal" map:uiCompass="true" map:uiZoomControls="true"/>

2. Call the callback API and declare the required object.

In MapActivity, you need to call the OnMapReadyCallback API, and declare the MapView, HuaweiMap, as well as PolylineOptions (for drawing a polyline on the map) objects.

Java:

public class MapActivity extends AppCompatActivity implements OnMapReadyCallback { private MapView mMapView; private HuaweiMap mHwMap; private PolylineOptions mPolylineOptions; private LocationSource.OnLocationChangedListener mListener; }

Kotlin:

class MapActivity : AppCompatActivity(), OnMapReadyCallback { private lateinit var mMapView: MapView private var mHwMap: HuaweiMap? = null private var mPolylineOptions: PolylineOptions? = null private var mListener: LocationSource.OnLocationChangedListener? = null }

3. Create a MapView instance.

In MapActivity, load MapView in the onCreate() method, and call getMapAsync() to register the callback.

Java:

// init MapView mMapView = findViewById(R.id.hw_mapview); Bundle mapViewBundle = null; if (savedInstanceState != null) { mapViewBundle = savedInstanceState.getBundle(MAPVIEW_BUNDLE_KEY); } mMapView.onCreate(mapViewBundle); mMapView.getMapAsync(this);

Kotlin:

// init MapView mMapView = findViewById(R.id.hw_mapview) var mapViewBundle: Bundle? = null if (savedInstanceState != null) { mapViewBundle = savedInstanceState.getBundle(MAPVIEW_BUNDLE_KEY) } mMapView.onCreate(mapViewBundle) mMapView.getMapAsync(this)

4. Call the onMapReady callback to obtain the HuaweiMap object.

Java:

@Override public void onMapReady(HuaweiMap huaweiMap) { mHwMap = huaweiMap; }

Kotlin:

override fun onMapReady(huaweiMap: HuaweiMap?) { mHwMap = huaweiMap }

5. Add methods of the MapView object.

Call the MapView's methods corresponding to onStart(), onResume(), onPause(), onStop(), onDestroy(), and onLowMemory().

Java:

@Override protected void onStart() { super.onStart(); mMapView.onStart(); } @Override protected void onResume() { super.onResume(); mMapView.onResume(); } @Override protected void onPause() { super.onPause(); mMapView.onPause(); } @Override protected void onStop() { super.onStop(); mMapView.onStop(); } @Override protected void onDestroy() { super.onDestroy(); mMapView.onDestroy(); } @Override public void onLowMemory() { super.onLowMemory(); mMapView.onLowMemory(); }

Kotlin:

companion object { private const val MAPVIEW_BUNDLE_KEY = "MapViewBundleKey" } override fun onStart() { super.onStart() mMapView.onStart() } override fun onResume() { super.onResume() mMapView.onResume() } override fun onPause() { super.onPause() mMapView.onPause() } override fun onStop() { super.onStop() mMapView.onStop() } override fun onDestroy() { super.onDestroy() mMapView.onDestroy() } override fun onLowMemory() { super.onLowMemory() mMapView.onLowMemory() }

Now that we have created a basic map, you can get a sense of how it looks, as seen below:

Location Kit incorporates the GNSS, Wi-Fi, and base station location functionalities into your app to bolster global positioning capabilities, allowing you to offer flexible location-based services that meet the needs of a global user base. The kit currently provides three main capabilities: fused location, activity identification, and geofence.
In this codelab, we'll be using the fused location function.

1. Assign app permissions.

<!-- Location kit needs location permission--> <uses-permission android:name="android.permission.ACCESS_COARES_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <!-- Use the mock location data--> <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>

In this codelab, the mock location function is used. Therefore, the ACCESS_MOCK_LOCATION permission is assigned.

2. Apply for the permissions dynamically in the code.

The following is in accordance with the requirements for dangerous permissions in Android 6.0.

Java:

// Dynamically apply for required permissions if the API level is 28 or smaller. if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { Log.i(TAG, "sdk <= 28 Q"); if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { String[] strings = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}; ActivityCompat.requestPermissions(this, strings, 1); } } else { // Dynamically apply for required permissions if the API level is greater than 28. The android.permission.ACCESS_BACKGROUND_LOCATION permission is required. if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, "android.permission.ACCESS_BACKGROUND_LOCATION") != PackageManager.PERMISSION_GRANTED) { String[] strings = {android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_COARSE_LOCATION, "android.permission.ACCESS_BACKGROUND_LOCATION"}; ActivityCompat.requestPermissions(this, strings, 2); } }

Kotlin:

// Dynamically apply for required permissions if the API level is 28 or smaller. if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { Log.i(TAG, "sdk <= 28 Q") if (ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { val strings = arrayOf( Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION ) ActivityCompat.requestPermissions(this, strings, 1) } } else { // // Dynamically apply for required permissions if the API level is greater than 28. The android.permission.ACCESS_BACKGROUND_LOCATION permission is required. if (ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_BACKGROUND_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { val strings = arrayOf( Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION ) ActivityCompat.requestPermissions(this, strings, 2) } }

3. Create a location instance in MapActivity.

Java:

protected void onCreate(@Nullable Bundle savedInstanceState) { fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this); }

Kotlin:

override fun onCreate(savedInstanceState: Bundle?) { fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this) }

4. Check the location settings on the device.

Location Kit also provides a function for checking the device location settings. First, create a checkLocationSettings() method. Your app can obtain the SettingsClient instance using getSettingsClient(Activity activity) for LocationServices, and call the checkLocationSettings(LocationSettingsRequest locationSettingsRequest) API to obtain the device location settings.

Java:

private void checkLocationSettings() { LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder(); LocationSettingsRequest locationSettingsRequest = builder.build(); SettingsClient settingsClient = LocationServices.getSettingsClient(this); settingsClient.checkLocationSettings(locationSettingsRequest) .addOnSuccessListener(new OnSuccessListener<LocationSettingsResponse>() { @Override public void onSuccess(LocationSettingsResponse locationSettingsResponse) { requestLocationUpdate(); } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { int statusCode = ((ApiException) e).getStatusCode(); switch (statusCode) { case LocationSettingsStatusCodes.RESOLUTION_REQUIRED: try { ResolvableApiException rae = (ResolvableApiException) e; rae.startResolutionForResult(MapActivity.this, 0); } catch (IntentSender.SendIntentException sie) { } break; } } }); }

Kotlin:

private fun checkLocationSettings() { val builder: LocationSettingsRequest.Builder = LocationSettingsRequest.Builder() val locationSettingsRequest: LocationSettingsRequest = builder.build() val settingsClient: SettingsClient = LocationServices.getSettingsClient(this) settingsClient.checkLocationSettings(locationSettingsRequest) .addOnSuccessListener { requestLocationUpdate() }.addOnFailureListener { e -> when ((e as ApiException).statusCode) { LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> try { val rae: ResolvableApiException = e as ResolvableApiException rae.startResolutionForResult(this@MapActivity, 0) } catch (sie: SendIntentException) { } } } }

5. Continually obtain the device location updates.

If you would like to continually locate the device, you can use the requestLocationUpdates() API provided by Location Kit to do so. This API returns a LocationResult object containing the location information by calling the onLocationResult() method in the defined LocationCallback class.

Java:

private void requestLocationUpdate() { mLocationRequest = new LocationRequest(); mLocationRequest.setInterval(5000); mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); mLocationCallback = new LocationCallback() { @Override public void onLocationResult(LocationResult locationResult) { super.onLocationResult(locationResult); writeGpsData2Sdcard(locationResult.getLastLocation()); if (mIsRunning) { updateLocation(locationResult.getLastLocation()); } } @Override public void onLocationAvailability(LocationAvailability locationAvailability) { super.onLocationAvailability(locationAvailability); } }; fusedLocationProviderClient .requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.getMainLooper()) .addOnSuccessListener(new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { Log.i(TAG, "request location updates success"); } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { Log.e(TAG, "request location updates failed, error: " + e.getMessage()); } }); }

Kotlin:

private fun requestLocationUpdate() { mLocationRequest = LocationRequest() mLocationRequest!!.interval = 5000 mLocationRequest!!.priority = LocationRequest.PRIORITY_HIGH_ACCURACY mLocationCallback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult) { super.onLocationResult(locationResult) writeGpsData2Sdcard(locationResult.lastLocation) if (mIsRunning) { processLocationChange(locationResult.lastLocation) } } override fun onLocationAvailability(locationAvailability: LocationAvailability?) { super.onLocationAvailability(locationAvailability) } } fusedLocationProviderClient ?.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.getMainLooper()) ?.addOnSuccessListener { Log.i(TAG, "request location updates success") } ?.addOnFailureListener { e -> Log.e( TAG, "request location updates failed, error: " + e.message ) } }

Stop requesting location updates when it is no longer required, to reduce power consumption. To do this, call removeLocationUpdates() and pass LocationCallback that you have passed to the requestLocationUpdates() API.

Java:

private void removeLocationUpdatesWithCallback() { try { Task<Void> voidTask = fusedLocationProviderClient.removeLocationUpdates(mLocationCallback); voidTask.addOnSuccessListener(new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { } }); } catch (Exception e) { } }

Kotlin:

private fun removeLocationUpdatesWithCallback() { try { val voidTask: Task<Void> = fusedLocationProviderClient!!.removeLocationUpdates(mLocationCallback) voidTask.addOnSuccessListener { }.addOnFailureListener { } } catch (e: Exception) { } }

In the steps above, we have implemented the basic functions in Map Kit and Location Kit. This section will detail how to use Map Kit and Location Kit to record the user running track, as well as calculate the running distance and speed.

1. Set the location source for the my-location layer.

Add the location source to the OnMapReady callback.

Java:

// Add the location source. mHwMap.setLocationSource(new LocationSource() { @Override public void activate(OnLocationChangedListener onLocationChangedListener) { mListener = onLocationChangedListener; } @Override public void deactivate() { } });

Kotlin:

// Add the location source. mHwMap?.setLocationSource(object : LocationSource { override fun activate(onLocationChangedListener: LocationSource.OnLocationChangedListener?) { mListener = onLocationChangedListener } override fun deactivate() {} })

2. Obtain the current location and update the map camera.

In the OnMapReady callback, add the code for obtaining the location, and update the map camera.

Java:

// Obtain the current location and update the map camera. try { Task<Location> lastLocation = fusedLocationProviderClient.getLastLocation(); lastLocation.addOnSuccessListener(new OnSuccessListener<Location>() { @Override public void onSuccess(Location location) { mListener.onLocationChanged(location); if (mListener != null) { mListener.onLocationChanged(location); CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom( new LatLng(location.getLatitude(), location.getLongitude()), 15f); mHwMap.animateCamera(cameraUpdate); } } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { } }); } catch (Exception e) { }

Kotlin:

// Obtain the current location and update the map camera. try { val lastLocation: Task<Location> = fusedLocationProviderClient!!.lastLocation lastLocation.addOnSuccessListener { location -> mListener!!.onLocationChanged(location) if (mListener != null) { mListener!!.onLocationChanged(location) val cameraUpdate: CameraUpdate = CameraUpdateFactory.newLatLngZoom( LatLng(location.latitude, location.longitude), 15f ) mHwMap!!.animateCamera(cameraUpdate) } }.addOnFailureListener { Log.d(TAG, "onMapReady: Obtains the current position failure") } } catch (e: Exception) { }

After the location source is set, the current location can be displayed on the map, as shown in the figure below.

3. Add a button for starting and stopping the running.

First, add a layout for displaying motion data.

<LinearLayout android:layout_width="match_parent" android:layout_height="100dp" android:layout_marginTop="60dp" android:layout_marginStart="15dp" android:layout_marginEnd="15dp" android:background="@color/bgWhite" android:orientation="horizontal"> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginStart="20dp" android:layout_marginTop="20dp" android:orientation="vertical"> <TextView android:id="@+id/tv_distance" android:layout_width="match_parent" android:layout_height="wrap_content" android:ellipsize="end" android:gravity="center_horizontal" android:maxLength="8" android:text="0.00" android:textColor="#000000" android:textSize="25sp" android:textStyle="bold" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:layout_marginStart="6dp" android:layout_marginBottom="2.5dp" android:text="km" android:textColor="#88000000" android:textSize="18sp" /> </LinearLayout>

Add the motion data display UI in MapActivity.
Java:

// Add the motion data display UI. mTvSpeed = findViewById(R.id.tv_speed); mTvDistance = findViewById(R.id.tv_distance); mTime = findViewById(R.id.cm_time); mTvStart = findViewById(R.id.tv_start); mTvStart.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { processStartClick(); } });

Kotlin:

// Add the motion data display UI. mTvSpeed = findViewById(R.id.tv_speed) mTvDistance = findViewById(R.id.tv_distance) mTime = findViewById(R.id.cm_time) mTvStart = findViewById(R.id.tv_start) mTvStart!!.setOnClickListener(View.OnClickListener { processStartClick() })

Add the following code to the processStartClick() method, which will be called when the user starts running:

Java:

mIsRunning = true; mPath.reset(); mPath.setStartTime(System.currentTimeMillis()); mHandler.post(mTimeRunnable); mTvStart.setText("Stop");

Kotlin:

mIsRunning = true mPath.reset() mPath.startTime = System.currentTimeMillis() mHandler.post(mTimeRunnable) mTvStart!!.text = "Stop"

Add the following code to the processStartClick() method, which will be called when the user stops running:

Java:

mIsRunning = false; mPath.setEndTime(System.currentTimeMillis()); mTvStart.setText("Start"); mHandler.removeCallbacks(mTimeRunnable); if (mPath.getPathLine().size() > 0) { mPath.setEndPoint(mPath.getPathLine().get(mPath.getPathLine().size() - 1)); if (null != mMarkerStart && null != mMarkerEnd) { mMarkerStart.remove(); mMarkerEnd.remove(); } MarkerOptions StartPointOptions = new MarkerOptions() .icon(BitmapDescriptorFactory.fromResource(R.mipmap.map_marker_bubble_azure_small)) .position(mPath.getStartPoint()); StartPointOptions.title("Start Point"); StartPointOptions.snippet("Start Point"); mMarkerStart = mHwMap.addMarker(StartPointOptions); MarkerOptions EndPointOptions = new MarkerOptions() .icon(BitmapDescriptorFactory.fromResource(R.mipmap.map_marker_bubble_azure_small)) .position(mPath.getEndPoint()); EndPointOptions.title("End Point"); EndPointOptions.snippet("End Point"); mMarkerEnd = mHwMap.addMarker(EndPointOptions); }

Kotlin:

mIsRunning = false mPath.endTime = (System.currentTimeMillis()) mTvStart!!.text = "Start" mHandler.removeCallbacks(mTimeRunnable) if (mPath.mPathLinePoints!!.size > 0) { mPath.endPoint = (mPath.mPathLinePoints!!.get(mPath.mPathLinePoints!!.size - 1)) if (null != mMarkerStart && null != mMarkerEnd) { mMarkerStart!!.remove() mMarkerEnd!!.remove() } val startPointOptions: MarkerOptions = MarkerOptions() .icon(BitmapDescriptorFactory.fromResource(R.mipmap.map_marker_bubble_azure_small)) .position(mPath.mStartPoint) startPointOptions.title("Start Point") startPointOptions.snippet("Start Point") mMarkerStart = mHwMap!!.addMarker(startPointOptions) val endPointOptions: MarkerOptions = MarkerOptions() .icon(BitmapDescriptorFactory.fromResource(R.mipmap.map_marker_bubble_azure_small)) .position(mPath.mEndPoint) endPointOptions.title("End Point") endPointOptions.snippet("End Point") mMarkerEnd = mHwMap!!.addMarker(endPointOptions) }

Bind the processStartClick() method to the Start button.

4. Draw a motion track based on location updates.

Create the processLocationChange(Location location) method, which will draw a polyline on the map based on location updates.
Java:

private void processLocationChange(Location location) { LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude()); // Draw a motion track on a map. mPolylineOptions.add(latLng); mHwMap.addPolyline(mPolylineOptions); // Update the Map Kit camera. if (mListener != null) { mListener.onLocationChanged(location); CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom( new LatLng(location.getLatitude(), location.getLongitude()), 15f); mHwMap.animateCamera(cameraUpdate); } }

Kotlin:

private fun processLocationChange(location: Location) { val latLng = LatLng(location.latitude, location.longitude) if (mPath.mStartPoint == null) { mPath.mStartPoint = latLng } mPath.addPoint(latLng) val distance: Float = mPath.updateDistance() val sportMile = distance / 1000.0 if (mSeconds > 0) { val distribution = mSeconds.toDouble() / 60.0 / sportMile mPath.setDistribution(distribution) mTvSpeed!!.text = mDecimalFormat.format(distribution) mTvDistance!!.text = mDecimalFormat.format(sportMile) } else { mPath.setDistribution(0.0) mTvSpeed!!.text = "0.00" mTvDistance!!.text = "0.00" } // Draw a motion track on a map. mPolylineOptions!!.add(latLng) mHwMap!!.addPolyline(mPolylineOptions) // Update the Map Kit camera. if (mListener != null) { mListener!!.onLocationChanged(location) val cameraUpdate: CameraUpdate = CameraUpdateFactory.newLatLngZoom( LatLng(location.latitude, location.longitude), 15f ) mHwMap!!.animateCamera(cameraUpdate) } }

Call the processLocationChange() method in the requestLocationUpdate() method.
Java:

private void requestLocationUpdate() { mLocationCallback = new LocationCallback() { @Override public void onLocationResult(LocationResult locationResult) { super.onLocationResult(locationResult); if (mIsRunning) { processLocationChange(locationResult.getLastLocation()); } } };

Kotlin:

mLocationCallback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult) { super.onLocationResult(locationResult) writeGpsData2Sdcard(locationResult.lastLocation) if (mIsRunning) { processLocationChange(locationResult.lastLocation) } } override fun onLocationAvailability(locationAvailability: LocationAvailability?) { super.onLocationAvailability(locationAvailability) } }

Finally, the app can draw the motion track based on location updates that are obtained continuously.

Location Kit provides the mock location function, which can be used for app debugging purposes.

1. Enable the mock location function.

Go to Developer options > Select mock location app on the test device and select a specific app.

2. Load mock GPS data.

Obtain a set of mock GPS data. In this codelab, the Java GPS data being used, is stored in MockGpsData.java, and the Kotlin GPS data is stored in MockGpsData.kt.
Before creating a map and obtaining the location, add the following code for loading the mock GPS data.
Java:

Task<Void> voidTask = fusedLocationProviderClient.setMockMode(true); voidTask.addOnSuccessListener(new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { Toast.makeText(MainActivity.this, "setMockMode onSuccess", Toast.LENGTH_SHORT).show(); mHandler.post(new Runnable() { int point = 0; @Override public void run() { if (point + 1 >= MockGpsData.POINTS.length) { return; } double latitude = MockGpsData.POINTS[point++]; double longitude = MockGpsData.POINTS[point++]; final Location mockLocation = new Location(LocationManager.GPS_PROVIDER); mockLocation.setLongitude(longitude); mockLocation.setLatitude(latitude); Task<Void> voidTask = fusedLocationProviderClient.setMockLocation(mockLocation); voidTask.addOnSuccessListener(new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { } }); mHandler.postDelayed(this, 100); } }); } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { Toast.makeText(MainActivity.this, "setMockMode onFailure:" + e.getMessage(), Toast.LENGTH_SHORT).show(); } });

Kotlin:

val voidTask: Task<Void> = fusedLocationProviderClient!!.setMockMode(true) voidTask.addOnSuccessListener(object : OnSuccessListener<Void?> { override fun onSuccess(aVoid: Void?) { Toast.makeText(this@MainActivity, "setMockMode onSuccess", Toast.LENGTH_SHORT) .show() mHandler.post(object : Runnable { var point = 0 override fun run() { if (point + 1 >= MockGpsData.POINTS.size) { return } val latitude: Double = MockGpsData.POINTS.get(point++) val longitude: Double = MockGpsData.POINTS.get(point++) val mockLocation = Location(LocationManager.GPS_PROVIDER) mockLocation.longitude = longitude mockLocation.latitude = latitude val voidTask: Task<Void> = fusedLocationProviderClient!!.setMockLocation(mockLocation) voidTask.addOnSuccessListener(object : OnSuccessListener<Void?> { override fun onSuccess(aVoid: Void?) {} }).addOnFailureListener(object : OnFailureListener { override fun onFailure(e: Exception?) {} }) mHandler.postDelayed(this, 100) } }) } }).addOnFailureListener { e -> if (e != null) { Toast.makeText( this@MainActivity, "setMockMode onFailure:" + e.message, Toast.LENGTH_SHORT ).show() } }

After the mock GPS data is loaded, you can use it for debugging.

Well done. You have successfully completed this codelab, and learned how to:

Reference

For more information, please visit the following:

You can also click the button below to download the source code.

Download source code

Code copied