Dieses Tutorial zeigt Ihnen, wie Sie eine Kotlin-App erstellen können, die Ihren Standort über die "Location API" ermittelt und in einer "Google Maps"-Ansicht anzeigt.
Wir werden die neuesten stabilen Versionen verwenden, die während der Erstellung dieses Tutorials zur Verfügung stehen.
Anforderungen
Wir werden die "Fused Location API" für die GPS-Standorterkennung und "Google Maps" zur Anzeige unseres Standortes auf einer Karte verwenden.
Fügen Sie diese Abhängigkeiten in Ihre "app/build.gradle"-Datei ein:
implementation 'com.google.android.gms:play-services-location:17.0.0'
implementation 'com.google.android.gms:play-services-maps:17.0.0'
Fügen Sie diese Berechtigungen in Ihre "AndroidManifest.xml"-Datei ein:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<permission
android:name="my.packagename.permission.MAPS_RECEIVE"
android:protectionLevel="signature" />
<uses-permission android:name="my.packagename.permission.MAPS_RECEIVE" />
Sie müssen "my.packagename" in den Paketnamen Ihres "Android"-Projekts ändern. Sie finden den Paketnamen in der ersten Zeile Ihrer "AndroidManifest"-Datei.
Sie müssen einen API-Schlüssel erstellen, um "Google Maps" in Ihrer App verwenden zu können. Bitte melden Sie sich mit Ihrem Google-Konto in der "Google Cloud Platform Cloud" an. Erstellen Sie ein neues Projekt, falls Sie dies noch nicht getan haben, und gehen Sie dann auf den Menüpunkt "APIs & Services > Zugangsdaten".
Link zu "Google Cloud Platform Cloud":
https://cloud.google.com/console/google/maps-apis/overview
Gehen Sie auf die Seite ("Zugangsdaten") und klicken Sie auf "Zugangsdaten erstellen" und "API-Schlüssel". Ihr neu erstellter API-Schlüssel wird danach angezeigt. Sie müssen diesen Schlüssel in einem "Meta"-Tag im Abschnitt "<Anwendung>" Ihrer Datei "AndroidManifest.xml" hinzufügen.
Das sieht folgendermaßen aus:
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="XXXXXXXXXXXXXXXXXXXX" />
Bitte gehen Sie erneut zu "Google Cloud Platform Cloud" und zum Menüpunkt "Übersicht". Suchen Sie dann nach "Maps SDK for Android" und klicken Sie auf "aktivieren":
https://console.cloud.google.com/apis/library/maps-android-backend.googleapis.com
Programm-Code
Erstellen der Standorterkennungsfunktion unserer App
Der Anwendungscode wird Schritt für Schritt erstellt und der Code wird in Teilen angezeigt. Der vollständige Code ist auf Github verfügbar.
Jetzt können wir unseren Anwendungscode schreiben. Wir werden für dieses Beispiel eine Aktivitätsklasse verwenden, bei der der Standort automatisch erkannt und dann in einem "Google Maps"-Fragment angezeigt wird.
Bitte erstellen Sie eine leere "Activity", die die Klassen "GoogleApiClient.ConnectionCallbacks" und "GoogleApiClient.OnConnectionFailedListener" implementiert.
Wir erstellen auch die folgenden Instanzvariablen und eine statische Variable, die später in unseren Funktionen verwendet wird:
private const val PERMISSION_REQUEST_CODE = 10
class MainActivity: AppCompatActivity(), GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
val API_CONNECTION_LOG_TAG = "GoogleAPIConnection"
val GPS_LOG_TAG = "GPSDection"
private lateinit var googleApiClient: GoogleApiClient
private lateinit var gpsLocation: Location
private var locationPermissions = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
lateinit var gpsLocationManager: LocationManager
private lateinit var gpsGoogleMap: GoogleMap
override fun onConnected(p0: Bundle?) {
Log.i(API_CONNECTION_LOG_TAG, "Location services connection enabled")
//Save GPS detection in variable
//Display location in Google Maps Marker
}
override fun onConnectionSuspended(p0: Int) {
Log.i(API_CONNECTION_LOG_TAG, "Location services disconnect. Please reconnect")
}
override fun onConnectionFailed(p0: ConnectionResult) {
Log.i(API_CONNECTION_LOG_TAG, "Location services connection failed. Please reconnect")
val connectionFailedDialog = AlertDialog.Builder(this)
connectionFailedDialog.setTitle(getString(R.string.googleApiConnectionFailedErrorTitle))
.setMessage(getString(R.string.googleApiConnectionFailedErrorMessage))
.setPositiveButton(getString(R.string.googleApiConnectionFailedErrorOKButton)) { paramDialogInterface, paramInt ->
}
connectionFailedDialog.show()
}
Erstellen Sie eine Funktion, die den "Google API-Client" lädt, der zur Ermittlung der GPS-Position verwendet wird.
private fun loadGoogleApiClient() {
googleApiClient =
GoogleApiClient.Builder(this).addConnectionCallbacks(this)
.addOnConnectionFailedListener(this).addApi(LocationServices.API).build()
googleApiClient!!.connect()
}
Diese erzeugte Funktion wird in der Methode "onCreate()" aufgerufen:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loadGoogleApiClient()
setContentView(R.layout.activity_main)
}
Diese Funktion sollte dort direkt nach dem "Super"-Befehl aufgerufen werden.
Ebenso wie in den Methoden "onResume()". Der erstellte "Google API-Client" wird jedoch in der Methode "onPause()" getrennt.
override fun onResume() {
super.onResume()
loadGoogleApiClient()
requestAndLoadGpsLocation()
}
override fun onPause() {
super.onPause()
if (googleApiClient != null && googleApiClient.isConnected()) {
googleApiClient.disconnect()
}
}
Die Funktion "requestAndLoadGpsLocation()" wird später implementiert.
Aber jetzt müssen wir eine Funktion erstellen, die prüft, ob der Benutzer dieser Anwendung die Erlaubnis gegeben hat, auf die aktuelle GPS-Position zuzugreifen.
Dies geschieht über diese Funktion:
private fun checkLocationDetectionPermission(permissionArray: Array<String>): Boolean {
var permissionAllSuccess = true
for (i in permissionArray.indices) {
if (checkCallingOrSelfPermission(permissionArray[i]) == PackageManager.PERMISSION_DENIED)
permissionAllSuccess = false
}
return permissionAllSuccess
}
Gehen Sie zu Ihrer "onCreate()"-Funktion und fügen Sie den folgenden Code hinzu:
override fun onCreate(savedInstanceState: Bundle?) {
[ ... ]
loadGoogleApiClient()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkLocationDetectionPermission(locationPermissions)) {
requestAndLoadGpsLocation()
} else {
requestPermissions(locationPermissions, PERMISSION_REQUEST_CODE)
}
} else {
requestAndLoadGpsLocation()
}
[ ... ]
}
Die Erlaubnis, den Standort zu ermitteln, wird geprüft und bei Bedarf angefordert. Wenn die Anwendung über die Berechtigung zur Überprüfung des Standorts verfügt, dann rufen wir die Funktion "requestAndLoadGpsLocation()" auf.
Nun müssen wir die Funktion "requestAndLoadGpsLocation()" erstellen:
@SuppressLint("MissingPermission")
fun requestAndLoadGpsLocation() {
//If user did activate GPS, then create the locationManager that will be used to automatically detect changes in the GPS location and display the new location
if (checkGpsActivated()) {
try {
gpsLocationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
0,
0F,
object : LocationListener {
override fun onLocationChanged(location: Location?) {
if (location != null) {
setNewLocation(location)
Log.d(
GPS_LOG_TAG,
"New GPS Latitude : " + location!!.latitude
)
Log.d(
GPS_LOG_TAG,
"New GPS Longitude : " + location!!.longitude
)
}
}
override fun onStatusChanged(
provider: String?,
status: Int,
extras: Bundle?
) {
}
override fun onProviderEnabled(provider: String?) {
}
override fun onProviderDisabled(provider: String?) {
}
})
//Load the GPS location
gpsLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
val gpsFusedLocationClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(this)
gpsFusedLocationClient.getLastLocation()
.addOnSuccessListener(object : OnSuccessListener<Location?> {
override fun onSuccess(location: Location?) {
if (location != null) {
Log.d(GPS_LOG_TAG, "Location was detected")
Log.d(
GPS_LOG_TAG,
"Lat " + location.latitude + " Long " + location.longitude
)
setNewLocation(location)
} else {
Log.d(GPS_LOG_TAG, "Location was not detected")
}
}
})
.addOnFailureListener(object : OnFailureListener {
override fun onFailure(e: Exception) {
Log.d(GPS_LOG_TAG, "Error: Trying to get last GPS location")
e.printStackTrace()
}
})
} catch (e: SecurityException) {
requestPermissions(locationPermissions, PERMISSION_REQUEST_CODE)
}
} else {
errorLocationDetectionDeactivated(this)
}
}
Wenn der Benutzer GPS aktiviert hat, dann erstellen Sie den "LocationManager", der verwendet wird, um Änderungen der GPS-Position automatisch zu erkennen und den neuen Standort anzuzeigen. Der Standort wird über den "LocationManager" angefordert.
Nun müssen wir die Funktionen erstellen, die wir oben in unserer neu erstellten Funktion "requestAndLoadGpsLocation()" aufgerufen haben:
Funktion "checkGpsActivated()": Funktion "checkGpsActivated()":
fun checkGpsActivated(): Boolean {
gpsLocationManager =
getSystemService(Context.LOCATION_SERVICE) as LocationManager
return gpsLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
}
Funktion "errorLocationDetectionDeactivated(mainActivity)":
fun errorLocationDetectionDeactivated(activity: MainActivity) {
val locationDetectionDeactivatedDialog = AlertDialog.Builder(activity)
locationDetectionDeactivatedDialog.setTitle(getString(R.string.errorLocationDetectionDeactivatedTitle))
.setMessage(getString(R.string.errorLocationDetectionDeactivatedMesssage))
.setPositiveButton(getString(R.string.errorLocationDetectionDeactivatedOKButton)) { paramDialogInterface, paramInt ->
startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
}
locationDetectionDeactivatedDialog.show()
}
Nun werden wir die Funktionen erstellen, die für unser "Google Maps"-Fragment benötigt werden.
Funktion "errorMapCouldNotBeLoaded(mainActivity)":
fun errorMapCouldNotBeLoaded(activity: MainActivity) {
val errorMapCouldNotBeLoadedDialog = AlertDialog.Builder(activity)
errorMapCouldNotBeLoadedDialog.setTitle(getString(R.string.errorMapCouldNotBeLoadedTitle))
.setMessage(getString(R.string.errorMapCouldNotBeLoadedMessage))
.setPositiveButton(getString(R.string.errorMapCouldNotBeLoadedOKButton)) { paramDialogInterface, paramInt ->
startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
}
errorMapCouldNotBeLoadedDialog.show()
}
Die Funktion "setNewLocation(location)", die unseren neuen Standort speichert und im "Google Maps"-Fragment aktualisiert:
fun setNewLocation(
location: Location
) {
if (::gpsGoogleMap.isInitialized && gpsGoogleMap != null) {
val gpsPositionLatLng = LatLng(location.latitude, location.longitude)
latitudeValue.setText(String.format("%.5f", location.latitude))
longitudeValue.setText(String.format("%.5f", location.longitude))
gpsGoogleMap.clear()
gpsGoogleMap.addMarker(
MarkerOptions().position(gpsPositionLatLng)
.title(getString(R.string.my_position_text) + gpsPositionLatLng.latitude + " - " + gpsPositionLatLng.longitude)
)
gpsGoogleMap.moveCamera(
CameraUpdateFactory.newLatLngZoom(
gpsPositionLatLng,
20F
)
)
} else {
Log.d("MapDisplay", "Map could not be loaded")
errorMapCouldNotBeLoaded(this)
}
}
Die Funktion "moveCamera()" verschiebt die "Google Maps"-Ansicht auf den Längen- und Breitengrad, der in der erforderlichen "LatLng"-Variablen "gpsPositionLatLng" definiert ist. Die "Google Maps"-Ansicht wird auf die Größe "20F" gezoomt, die ein beliebiger "float"-Wert zwischen 1 und 21 sein kann.
Wenn Sie eine Zoom-Animation in Ihre Position haben möchten, dann können Sie die Funktion "animateCamera()" mit den gleichen Werten verwenden.
Der Befehl "isInitialized" prüft, ob das Objekt erstellt wurde.
Wir haben eine Funktion hinzugefügt, die nach der Erlaubnis fragt, den Standort zu ermitteln, indem sie den Dialog "Permission Request" aus dem Android-Betriebssystem aufruft.
Aber jetzt brauchen wir eine Funktion, die definiert, was die Anwendung nach der Anzeige des Dialoges zur Berechtigungsanfrage tun wird.
Funktion "onRequestPermissionsResult()":
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == PERMISSION_REQUEST_CODE && grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
requestAndLoadGpsLocation()
return
}
//Display error if permission was not granted or the gps is deactivated
fun errorLocationDetectionDeactivated(activity: MainActivity) {
val locationDetectionDeactivatedDialog = AlertDialog.Builder(activity)
locationDetectionDeactivatedDialog.setTitle(getString(R.string.errorLocationDetectionDeactivatedTitle))
.setMessage(getString(R.string.errorLocationDetectionDeactivatedMesssage))
.setPositiveButton(getString(R.string.errorLocationDetectionDeactivatedOKButton)) { paramDialogInterface, paramInt ->
startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
}
locationDetectionDeactivatedDialog.show()
}
}
Wir rufen unsere Standorterkennungsfunktion nur auf, wenn der Benutzer die erforderliche Erlaubnis erteilt hat. Wenn dies nicht der Fall ist, dann werden wir stattdessen einen Fehlerdialog anzeigen.
Einrichten der Karte, die unseren Standort anzeigt
Nun kommen wir zum letzten Teil dieses Tutorials.
Sie müssen die Schnittstelle "OnMapReadyCallback" in der MainActivity implementieren und die Methode "OnMapReady()" mit dem folgenden Code hinzufügen:
class MainActivity : AppCompatActivity(),
GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener,
OnMapReadyCallback {
[...]
override fun onMapReady(map: GoogleMap?) {
if (map != null) {
loadMap(map)
} else {
errorMapCouldNotBeLoaded(this)
}
}
fun loadMap(map: GoogleMap) {
gpsGoogleMap = map
gpsGoogleMap.mapType = GoogleMap.MAP_TYPE_NORMAL
}
}
Wenn Sie die Satellitenversion von "Google Maps" verwenden möchten, dann verwenden Sie den Kartentyp "GoogleMap.MAP_TYPE_SATELLITE". Der Kartentyp "GoogleMap.MAP_TYPE_HYBRID" zeigt sowohl die Satellitenversion als auch alle Beschriftungen (Straßen, Ortsnamen usw.) aus der Kartenversion an.
Jetzt können wir das Layout unserer "Aktivität" erstellen. Bitte fügen Sie das Folgende in Ihrer Datei "layout_main.xml" hinzu:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:id="@+id/latlngInfoLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:padding="15dp"
android:weightSum="10">
<TextView
android:id="@+id/latitudeDesc"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="@string/latDesc"
android:textColor="@android:color/black" />
<TextView
android:id="@+id/latitudeValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="8"
android:textColor="@android:color/black" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="10"
android:orientation="horizontal"
android:padding="15dp"
android:weightSum="10"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/longitudeDesc"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="@string/longDesc"
android:textColor="@android:color/black" />
<TextView
android:id="@+id/longitudeValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="8"
android:textColor="@android:color/black" />
</LinearLayout>
</LinearLayout>
<fragment
android:id="@+id/gpsMapView"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="409dp"
android:layout_height="694dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/latlngInfoLayout"
tools:context=".MainActivity" />
</androidx.constraintlayout.widget.ConstraintLayout>
Sie müssen ein "Fragment" verwenden, um die "Google Maps"-Ansicht anzuzeigen.
Schließlich müssen wir das neu erstellte "Fragment" laden. Sie müssen diesen Code zu Ihrem "onCreate()" in Ihrer "Aktivität" hinzufügen:
val gpsMapFragment = supportFragmentManager.findFragmentById(R.id.gpsMapView) as SupportMapFragment
gpsMapFragment.getMapAsync(this)
Dies war ein Beispiel für die Verwendung der Location API zur Ermittlung Ihrer GPS-Position. Sie können nun Ihre Anwendung kompilieren.
Die Ermittlung Ihres Standortes kann einige Zeit dauern, wenn Sie die Anwendung zum ersten Mal starten. Diese Anwendung mit dem vollständigen Code ist auch auf "Github" verfügbar.
Diese Anwendung auf "Github":
https://github.com/a-dridi/LocationDetection
Mehr über "Google Maps API":
https://cloud.google.com/console/google/maps-apis/overview
Mehr über "Location API":
https://developer.android.com/training/location