Android Projeleremizde Google Maps SDK Kurulumu ve Kolay Kullanımı

Mert Melih Aytemur
Appcent
8 min readDec 18, 2023

--

Google Maps, mobil uygulamalarınıza güçlü bir konum tabanlı hizmet eklemenin harika bir yoludur. Bu makalede, Android uygulamanıza Google Maps SDK’nın nasıl kurulacağını ve kolay kullanımını nasıl gerçekleştireceğinizi adım adım öğreneceksiniz.

Bu makalemizde kullanıcının konumunu GPS üzerinden alıp google maps üzerinde göstereceğiz

Projemize Google Maps SDK ekleyelim

Gradle Scripts -> build.gradle (Module :app)

Google Maps SDK güncel sürüm için tıklayın.

plugins {
id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
}

dependencies {

//Google Maps SDK
implementation("com.google.android.gms:play-services-maps:x.y.z")
}

Gradle Scripts -> build.gradle (Project)

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.1.4" apply false
id("org.jetbrains.kotlin.android") version "1.9.0" apply false
}

buildscript {
dependencies {
classpath("com.android.tools.build:gradle:7.3.0")
classpath("com.google.gms:google-services:4.4.0")
classpath("com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1")
}
}

Layout Dosyamıza Haritamızı ve Butonumuzu Ekleyelim

Haritamız bir fragment üzerinde çalışacaktır. Butonumuz ise GPS üzerinden kullanıcının anlık konumunu alıp harita üzerinde marker ile gösterecektir.

Şimdi Google Cloud Console Hesabımız Üzerinden Bir Proje Oluşturalım

Sonrasında API & Services kısmından Maps SDK for Android Özelliğimiz Aktif Edelim

Artık Bizim İçin Oluşturulan Google Maps API Key ile uygulamamızda haritamızı kullanabiliriz. (Bizim için $200 dolarlık aylık ücretsiz bir kullanım sunmaktadır)

Manifest.xml dosyamıza Google Cloud Console üzerinden aldığımız API KEY’i meta data tag’i ile yerleştirelim.

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

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<application>

<!-- Diğer aktiviteler ve ayarlar -->

<meta-data
android:name="com.google.android.geo.API_KEY"
android:value ="YOUR_API_KEY" />

</application>

</manifest>

UYARI: Google Maps API, konum tabanlı hizmetlerin entegre edilmesi için popüler bir seçenek sunar. Ancak, API Keyi’i güvenli bir şekilde saklamak, uygulamanızın güvenliğini önemli ölçüde etkiler. Şimdi Google Maps API Keyimizi nasıl güvenli bir şekilde saklayabileceğinizi keşfedeceğiz.

Local Properties Nedir?

Local Properties, projenizin kök dizininde bulunan ve özel anahtar-değer çiftlerini içeren bir dosyadır. Bu dosya, hassas bilgilerinizi içerir ve kodunuz içinde sert kodlamak yerine bu dosyadan değerleri okuyarak kullanmanıza olanak tanır.

# local.properties

MAPS_API_KEY=my_secret_key
// app/build.gradle

android {

val key: String = gradleLocalProperties(rootDir).getProperty("MAPS_API_KEY")

buildTypes {
getByName("debug") {
buildConfigField("String", "MAPS_API_KEY", key)
}
}

}

Bu şekilde, API anahtarınızı kod içine sert kodlamak yerine, daha güvenli bir Local Properties dosyasından alarak kullanabilirsiniz.

local.properties dosyamızı .gitignore’a eklemek

Local Properties dosyasını projenizin sürüm kontrol sistemine eklememeniz önemlidir. Bu dosya, hassas bilgileri içerir ve bu bilgilerin paylaşılmasını önlemek için .gitignore dosyanıza eklenmelidir. .gitignore dosyasına eklenirse, dosyanız sürüm kontrol sistemine dahil edilmez ve projeyi paylaşırken veya depolarken güvenli kalır.

# .gitignore

# Local Properties dosyasını eklemeyin
local.properties

Bu örnek, local.properties dosyasının sürüm kontrol sistemine dahil edilmemesini sağlar, böylece bu dosyadaki hassas bilgileri güvenli bir şekilde koruruz.

Sunucu Tabanlı Güvenlik ile API Anahtarını Saklama

Sunucu tabanlı bir yaklaşım benimsemek, API anahtarınızı uygulama içinde değil, bir güvenli sunucuda saklamayı içerir. Bu şekilde, uygulama içinde anahtara erişim daha zor hale gelir ve daha güvenli olur.

  1. Sunucu API Hizmeti Oluşturma: Bir sunucu API hizmeti oluşturun. Bu hizmet, Google Haritalar API ile iletişim kuracak ve uygulamanızdan gelen istekleri işleyecektir.
  2. API Key’i Sunucuda Saklama: API anahtarınızı sunucuda saklayın ve sadece bu sunucu üzerinden Haritalar API’ye erişim sağlayın.
  3. Uygulamada Sunucu API’yi Kullanma: Uygulamanızdaki harita istekleri, sunucu API üzerinden yapılarak güvenli bir şekilde gerçekleştirilir.

Bu yöntem, API Keyimizi uygulama içinde değil, güvenli bir sunucuda saklayarak daha yüksek bir güvenlik seviyesi sağlar.

Bu projemizde Api Keyimizi locale.properties dosyamızda saklayarak örneklerimize devam edeceğiz.

Artık meta-data tag’e android:value=”${MAPS_API_KEY}” değerini verebiliriz.

Şimdi kullanıcının konumunu almadan haritamızda statik bir lokasyon gösterelim.

class MainActivity : AppCompatActivity(){

private lateinit var binding: ActivityMainBinding

private val callback = OnMapReadyCallback { googleMap ->
/**
* Manipulates the map once available.
* This callback is triggered when the map is ready to be used.
* This is where we can add markers or lines, add listeners or move the camera.
* In this case, we just add a marker near Sydney, Australia.
* If Google Play services is not installed on the device, the user will be prompted to
* install it inside the SupportMapFragment. This method will only be triggered once the
* user has installed Google Play services and returned to the app.
*/
val sydney = LatLng(-34.0, 151.0)
googleMap.addMarker(MarkerOptions().position(sydney).title("Marker in Sydney"))
googleMap.moveCamera(CameraUpdateFactory.newLatLng(sydney))
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

val mapFragment = supportFragmentManager.findFragmentById(R.id.googleMapsFragment) as SupportMapFragment
mapFragment.getMapAsync(callback)
}
}

Uygulamamızı çalıştırdığımızda verdiğimiz statik enlem boylam bilgilerinin olduğu loksayonda bir adet markerımızı göreceğiz.

Kullanıcı Konumunu Alma

Şimdi, GPS aracılığyla kullanıcının anlık konumu alarak haritamız üzerinde göstereceğiz. Bunun için öncelikle kullanıcıdan konum izni almamız, sonrasında ise kullanıcının gps’ini açmasını isteyeceğiz.

İzinler için dexter permission kütüphanesini kullanacağız.

PermissionManager adlı objemizi yaratalım

object PermissionManager {

fun requestRuntimeLocationPermission(
context: Context,
onPermissionGranted: () -> Unit,
onPermissionDenied: () -> Unit
) {
Dexter.withContext(context)
.withPermission(
Manifest.permission.ACCESS_FINE_LOCATION
)
.withListener(
object : PermissionListener {
override fun onPermissionGranted(p0: PermissionGrantedResponse?) {
onPermissionGranted()
}

override fun onPermissionDenied(p0: PermissionDeniedResponse?) {
onPermissionDenied()
}

override fun onPermissionRationaleShouldBeShown(
p0: com.karumi.dexter.listener.PermissionRequest?,
token: PermissionToken?
) {
token?.continuePermissionRequest()
}
}
)
.withErrorListener {
onPermissionDenied()
}
.onSameThread()
.check()
}

fun hasAccessFineLocationPermission(activity: Activity) : Boolean{
val permissionRequestAccessFineLocation = ContextCompat.checkSelfPermission(activity,
Manifest.permission.ACCESS_FINE_LOCATION)
return permissionRequestAccessFineLocation == PackageManager.PERMISSION_GRANTED
}
}

Sonrasında ise activity üzerinde ilgili methodlarımızı yazıp çağıralım.

class MainActivity : AppCompatActivity() {

private lateinit var fusedLocationClient: FusedLocationProviderClient

private lateinit var googleMap: GoogleMap

private lateinit var binding: ActivityMainBinding

private val callback = OnMapReadyCallback { map ->
googleMap = map
// Harita hazır olduğunda yapılacak işlemler
handleLocationPermission()
}

@SuppressLint("MissingPermission")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)

val mapFragment =
supportFragmentManager.findFragmentById(R.id.googleMapsFragment) as SupportMapFragment
mapFragment.getMapAsync(callback)

binding.btnUserLocation.setOnClickListener {
setUserLocation(this)
}
}

private fun setUserLocation(context: Context) {
getUserLocation(onLocationSaved = {
addMarkerToUserLocation(it)
}, onResponseFailed = {
getUserLocationWithLocationUtils(context, onLocationSaved = {
addMarkerToUserLocation(it)
})
})
}

@SuppressLint("MissingPermission")
@SuppressWarnings("OptionalWhenBraces")
private fun getUserLocation(
onLocationSaved: (LatLng) -> Unit,
onResponseFailed: () -> Unit
) {
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
location?.let {
val userLocation = LatLng(location.latitude, location.longitude)
userLocation.let {
moveCameraToLocation(it)
onLocationSaved(it)
}
} ?: run(onResponseFailed)

}.addOnFailureListener {
onResponseFailed()
}
}

private fun getUserLocationWithLocationUtils(
context: Context,
onLocationSaved: (LatLng) -> Unit
) {
val locationResult = object : GetLocationUtils.LocationResult() {
override fun gotLocation(location: Location?) {
location?.let {
val userLocation = LatLng(it.latitude, it.longitude)

userLocation.let { latlng ->
moveCameraToLocation(latlng)
onLocationSaved(latlng)
}
}
}

override fun onLocationUpdateFailed() {}
}
val userLocationProvider = GetLocationUtils()
userLocationProvider.getLocation(context, locationResult)
}

private fun moveCameraToLocation(userPosition: LatLng) {
addMarkerToUserLocation(userPosition)
val zoomLevel = 12F
googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(userPosition, zoomLevel))
}

/**
* Add markers to [userLocation]
*/
private fun addMarkerToUserLocation(latLng: LatLng) {
googleMap.clear()
googleMap.addMarker(
MarkerOptions().position(latLng).title("Konumunuz")
)
}

private fun handleLocationPermission() {
if (!PermissionManager.hasAccessFineLocationPermission(this)) {
PermissionManager.requestRuntimeLocationPermission(this,
onPermissionGranted = {
setUserLocation(this)
},
onPermissionDenied = {
//show location needed info dialog or etc.
})
}
}
}

1. setUserLocation Metodu

Bu metod, kullanıcının konumunu belirleme işlevini yerine getirir. Kullanıcının konumunu almak için iki farklı yöntemi kullanır

  • İlk olarak, getUserLocation metodunu kullanarak kullanıcının konumunu almaya çalışır.
  • Eğer konum alımı başarısız olursa (null dönerse), getUserLocationWithLocationUtils metodunu kullanarak alternatif bir yönteme geçer.

2. getUserLocation Metodu

Bu metod, kullanıcının konumunu fused location client kullanarak almaya çalışır.

  • fusedLocationClient kullanarak son bilinen konumu alır.
  • Eğer konum null değilse, bu bilgiyi LatLng objesine dönüştürür, kamerayı bu konuma hareket ettirir ve onLocationSaved callback'ini çağırır.
  • Eğer konum null ise, onResponseFailed callback'ini çağırır.

3. getUserLocationWithLocationUtils Metodu

Bu metod, kullanıcının konumunu almak için özel bir yardımcı sınıf olan GetLocationUtils kullanır.

  • Konum callback’lerini işlemek üzere bir LocationResult objesi oluşturur.
  • Daha sonra GetLocationUtils sınıfının bir örneğini oluşturur ve getLocation metodunu çağırarak LocationResult objesini ileterek konumu alır.

4. GetLocationUtils Sınıfı

class GetLocationUtils {
private lateinit var timer1: Timer
private lateinit var locationManager: LocationManager
private lateinit var locationResult: LocationResult
private var gpsEnabled = false
private var networkEnabled = false
private lateinit var context: Context

/**
* Get user location from gps or network location provider services
*/
@SuppressLint("MissingPermission")
@SuppressWarnings("ComplexMethod")
fun getLocation(context: Context, result: LocationResult): Boolean {
// I use LocationResult callback class to pass location value from MyLocation to user code
this.context = context
locationResult = result
if (!this::locationManager.isInitialized) locationManager =
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
// exceptions will be thrown if provider is not permitted.
try {
gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
} catch (ex: Exception) {
ex.printStackTrace()
}
try {
networkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
} catch (ex: Exception) {
ex.printStackTrace()
}
// don't start listeners if no provider is enabled
if (!gpsEnabled && !networkEnabled) return false
if (isFineLocationPermissionGranted() &&
isCoarseLocationPermissionGranted()
) {
if (gpsEnabled) {
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
30L,
0F,
locationListenerGps
)
}
if (networkEnabled) {
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
30L,
0F,
locationListenerNetwork
)
}
}

timer1 = Timer()
timer1.schedule(GetLastLocation(), 15000)
return true
}

var locationListenerGps: LocationListener = object : LocationListener {
/**
* get user location from gps
*/
override fun onLocationChanged(location: Location) {
timer1.cancel()
locationResult.gotLocation(location)
this@GetLocationUtils.locationManager.removeUpdates(this)
this@GetLocationUtils.locationManager.removeUpdates(locationListenerNetwork)
}
}
var locationListenerNetwork: LocationListener = object : LocationListener {
/**
* get user location from network
*/
override fun onLocationChanged(location: Location) {
timer1.cancel()
locationResult.gotLocation(location)
this@GetLocationUtils.locationManager.removeUpdates(this)
this@GetLocationUtils.locationManager.removeUpdates(locationListenerGps)
}
}

internal inner class GetLastLocation : TimerTask() {
@SuppressLint("MissingPermission")
@SuppressWarnings("ComplexMethod", "ReturnCount", "MandatoryBracesIfStatements")
override fun run() {
locationManager.removeUpdates(locationListenerGps)
locationManager.removeUpdates(locationListenerNetwork)
var netLoc: Location? = null
var gpsLoc: Location? = null
if (isFineLocationPermissionGranted() &&
isCoarseLocationPermissionGranted()
) {
if (gpsEnabled) {
gpsLoc = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
}
if (networkEnabled) {
netLoc = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
}
} else {
locationResult.onLocationUpdateFailed()
//Timber.e("Permission grant error Permission not granted")
}
// if there are both values use the latest one
if (gpsLoc != null && netLoc != null) {
if (gpsLoc.time > netLoc.time) {
locationResult.gotLocation(gpsLoc)
} else locationResult.gotLocation(
netLoc
)
return
}
if (gpsLoc != null) {
locationResult.gotLocation(gpsLoc)
return
}
if (netLoc != null) {
locationResult.gotLocation(netLoc)
return
}
locationResult.gotLocation(null)
}
}

private fun isFineLocationPermissionGranted():
Boolean =
ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED

private fun isCoarseLocationPermissionGranted():
Boolean =
ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) ==
PackageManager.PERMISSION_GRANTED

/**
* Result class invokes on location received or failed
*/
@SuppressWarnings("UnnecessaryAbstractClass")
abstract class LocationResult {
/**
* return user location
*/
abstract fun gotLocation(location: Location?)

/**
* invokes on user location finding failure
*/
abstract fun onLocationUpdateFailed()
}
}

Bu, kullanıcının konumunu hem GPS hem de ağ sağlayıcıları kullanarak almak için bir yardımcı sınıftır.

  • GPS ve ağ sağlayıcılarının etkin olup olmadığını kontrol etmek için LocationManager kullanır.
  • Her iki sağlayıcı için de konum güncellemelerini almak için konum dinleyicilerini kaydeder.
  • Belirli bir süre sonra konum güncellemelerini iptal etmek için bir zamanlayıcı kullanır.
  • GetLastLocation iç içe sınıfı, her iki sağlayıcıdan son bilinen konumu almak ve en güncel olanı belirlemekten sorumludur.

Tebrikler! artık Android uygulamalarınızda Google Maps SDK kullanımını ve Kullanıcının Konumunu alıp harita üzerinde gösterme işlemlerini yapabilirsiniz.

Herkese iyi çalışmalar dilerim :)

--

--