Location Based Service
& Google
Maps SDK πΊοΈ
Mari buat aplikasimu mengenali dunia nyata! Belajar cara mendaftarkan aplikasi ke Google Cloud Console, menyematkan Peta (Map), dan melacak koordinat GPS pengguna.
1. Kunci Utama: Google API Key
Berbeda dengan fitur bawaan seperti Camera atau SQLite, Google Maps adalah layanan Cloud milik Google yang memerlukan autentikasi. Tanpa tiket masuk (API Key), petamu tidak akan memuat gambar dan hanya menampilkan layar blank / abu-abu dengan tulisan "Google".
Langkah Mendapatkan API Key:
- Buka Google Cloud Console.
- Buat Project baru.
- Buka menu APIs & Services -> Library. Cari dan aktifkan (Enable) "Maps SDK for Android".
- Buka menu Credentials, klik Create Credentials -> API Key.
- Copy kode panjang yang dihasilkan (contoh:
AIzaSyB...). Kode ini akan kita tempelkan di fileAndroidManifest.xmlnanti!
2. Simulator: Peta Interaktif (Ultra HD)
Uji coba aplikasi pencari Cafe terdekat! Coba geser-geser (drag/pan) dan Zoom (Scroll Wheel/Tombol) petanya. Lalu klik "Dapatkan Lokasiku" (atau icon target) untuk terbang otomatis ke koordinat kawasan Jl. Soka, Bandung. Terakhir, klik "Tambah Pin Cafe" untuk menjatuhkan Marker yang sangat tajam!
Panel Kontrol Peta
D/Maps: onMapReady callback diterima.
D/Maps: Peta HD siap di-zoom dan digeser!
3. Bedah Kode Lengkap
Ini adalah struktur kode lengkap untuk menampilkan peta, meminta izin lokasi, dan meletakkan Marker. Pastikan koneksi internet komputermu lancar saat melakukan Sync Gradle!
build.gradle.kts (Module :app)
Kita wajib mengunduh library resmi dari Google untuk Maps (untuk menampilkan peta) dan Location (untuk mendapatkan GPS akurat).
dependencies {
// Library standar Android...
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.10.0")
// π¨ TAMBAHKAN 2 BARIS INI UNTUK GOOGLE MAPS & LOKASI
implementation("com.google.android.gms:play-services-maps:18.2.0")
implementation("com.google.android.gms:play-services-location:21.0.1")
}
AndroidManifest.xml
Tempat menaruh izin `ACCESS_FINE_LOCATION` dan meletakkan `API KEY` Google Maps di dalam tag `<meta-data>`.
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.mapsapp">
<!-- 1. Izin Akses GPS (Tingkat Akurat & Kasar) -->
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission
android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.MyApp">
<!-- 2. Daftarkan API KEY Google Maps milikmu di sini! -->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyBxxxx_PASTE_API_KEY_KAMU_DISINI_xxxx" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
activity_main.xml
Sama seperti NavGraph, untuk menampilkan
Google Maps kita membutuhkan sebuah "Panggung Peta" menggunakan
SupportMapFragment.
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Kontainer Peta (Wajib Fragment) -->
<fragment
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- UI Overlay (Tombol yang mengambang di atas peta) -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:layout_alignParentBottom="true">
<Button
android:id="@+id/btnLokasi"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="LOKASI SAYA"
android:layout_marginEnd="8dp" />
<Button
android:id="@+id/btnMarker"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="TAMBAH MARKER" />
</LinearLayout>
</RelativeLayout>
MainActivity.java (Pusat Komando)
Wajib implementasi antarmuka
OnMapReadyCallback. Di sini kita mengurus Izin Lokasi, menarik sinyal GPS,
dan memanipulasi kamera peta dengan koordinat Makmur Jaya Coffee, Jalan Soka Bandung!
package com.example.mapsapp;
import android.Manifest;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationServices;
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.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
public class MainActivity extends AppCompatActivity implements OnMapReadyCallback {
private GoogleMap mMap;
private FusedLocationProviderClient fusedLocationClient;
private static final int KODE_IZIN_LOKASI = 99;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btnLokasi = findViewById(R.id.btnLokasi);
Button btnMarker = findViewById(R.id.btnMarker);
// 1. Hubungkan Map Fragment di XML dengan Java
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
if (mapFragment != null) {
// Panggil map secara async. Jika sudah siap, fungsi onMapReady akan tereksekusi otomatis.
mapFragment.getMapAsync(this);
}
// Inisialisasi radar pelacak lokasi bawaan Google
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
// Aksi Tombol Minta Lokasi
btnLokasi.setOnClickListener(v -> cekIzinLokasi());
// Aksi Tombol Tambah Marker
btnMarker.setOnClickListener(v -> {
if (mMap != null) {
// Objek LatLng membutuhkan koordinat Latitude dan Longitude (Format desimal)
LatLng posisiCafe = new LatLng(-6.916002, 107.627578); // Makmur Jaya Coffee - Soka Bandung
// Jatuhkan Marker ke Peta
mMap.addMarker(new MarkerOptions()
.position(posisiCafe)
.title("β Makmur Jaya Coffee")
.snippet("Jl. Soka No.15, Bandung"));
// Pindahkan/terbangkan kamera peta ke marker tersebut dengan Zoom level 15
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(posisiCafe, 15f));
}
});
}
// Fungsi ini dipanggil otomatis ketika Peta sudah berhasil dimuat (di-download)
@Override
public void onMapReady(@NonNull GoogleMap googleMap) {
mMap = googleMap;
// Arahkan kamera awal ke Indonesia saat pertama kali buka
LatLng indo = new LatLng(-2.5489, 118.0149);
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(indo, 4f));
}
// --- MANAJEMEN IZIN (PERMISSIONS) ---
private void cekIzinLokasi() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
// Munculkan Pop-up Minta Izin ke User
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
KODE_IZIN_LOKASI);
} else {
// Jika sudah diizinkan sebelumnya
dapatkanLokasiTerkini();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == KODE_IZIN_LOKASI) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
dapatkanLokasiTerkini();
} else {
Toast.makeText(this, "Aplikasi butuh izin lokasi untuk fitur ini!", Toast.LENGTH_SHORT).show();
}
}
}
// --- TARIK SINYAL GPS SUNGGUHAN ---
private void dapatkanLokasiTerkini() {
// (Peringatan IDE akan merah jika kita tidak pasang SuppressLint, tapi aman kok karena kita sudah cek di atas)
@SuppressWarnings("MissingPermission")
// Aktifkan titik biru bawaan Google Maps
mMap.setMyLocationEnabled(true);
fusedLocationClient.getLastLocation().addOnSuccessListener(this, location -> {
if (location != null) {
double lat = location.getLatitude();
double lng = location.getLongitude();
LatLng lokasiSaya = new LatLng(lat, lng);
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(lokasiSaya, 16f));
Toast.makeText(this, "Ketemu! Lat: " + lat + " Lng: " + lng, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Gagal melacak GPS (Hidupkan Lokasi HP-mu)", Toast.LENGTH_SHORT).show();
}
});
}
}
5. Tugas Super: Cafe Finder!
Mari padukan materi Modul 12 ini dengan Modul 9 (Room Database) untuk membuat proyek yang keren layaknya pencarian kuliner profesional!
Misi Project Utama (Cafe Finder Edition):
- Dapatkan API Key Google Maps resmi dari Cloud Console.
- Buat `Entity` Room Database bernama
TokoCafeyang memiliki kolom: `id`, `nama_cafe`, `latitude`, dan `longitude`. (Tipe data untuk Lat/Lng adalah `Double`). - Buat fitur Input sederhana untuk menyimpan data Cafe baru ke database. (Isi manual angka Lat/Lng nya).
- Saat aplikasi pertama kali dibuka (di `onMapReady`), ambil semua data `TokoCafe` dari Database. Lakukan proses Looping (For) untuk merubah seluruh data tersebut menjadi tumpukan `Marker` di atas peta!
Kunci Jawaban Lengkap (Room + Maps):
// π¨ TAMBAHKAN LIBRARY INI di dalam dependencies { ... }
// Library Room DB
def room_version = "2.6.1"
implementation("androidx.room:room-runtime:$room_version")
annotationProcessor("androidx.room:room-compiler:$room_version")
// Library Google Maps
implementation("com.google.android.gms:play-services-maps:18.2.0")
<manifest ...>
<!-- Izin Lokasi -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<application ...>
<!-- API KEY GOOGLE MAPS SANGAT WAJIB! -->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyBxxxx_API_KEY_KAMU_xxxx" />
<activity ...> ... </activity>
</application>
</manifest>
package com.example.cafefinder;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "tabel_cafe")
public class TokoCafe {
@PrimaryKey(autoGenerate = true)
public int id;
public String namaCafe;
public double latitude;
public double longitude;
public TokoCafe(String namaCafe, double latitude, double longitude) {
this.namaCafe = namaCafe;
this.latitude = latitude;
this.longitude = longitude;
}
}
package com.example.cafefinder;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
@Dao
public interface CafeDao {
@Insert
void insertCafe(TokoCafe cafe);
@Query("SELECT * FROM tabel_cafe")
List<TokoCafe> getAllCafe();
}
package com.example.cafefinder;
import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
@Database(entities = {TokoCafe.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract CafeDao cafeDao();
private static volatile AppDatabase INSTANCE;
public static AppDatabase getInstance(Context context) {
if (INSTANCE == null) {
synchronized (AppDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, "cafe_db").build();
}
}
}
return INSTANCE;
}
}
<!-- activity_main.xml -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 1. Area Peta (Gunakan FragmentContainerView versi modern) -->
<androidx.fragment.app.FragmentContainerView
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<!-- 2. Area Form Input (Bagian Bawah) -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
android:elevation="8dp"
android:background="#FFFFFF">
<EditText
android:id="@+id/etNama"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Nama Cafe" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/etLat"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:inputType="numberDecimal|numberSigned"
android:hint="Latitude" />
<EditText
android:id="@+id/etLng"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:inputType="numberDecimal|numberSigned"
android:hint="Longitude" />
</LinearLayout>
<Button
android:id="@+id/btnSimpan"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="SIMPAN CAFE KE MAPS" />
</LinearLayout>
</LinearLayout>
package com.example.cafefinder;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.NonNull;
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.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MainActivity extends AppCompatActivity implements OnMapReadyCallback {
private GoogleMap mMap;
private AppDatabase db;
private ExecutorService executor = Executors.newSingleThreadExecutor();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1. Siapkan Room DB
db = AppDatabase.getInstance(this);
// 2. Setup Peta
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
if (mapFragment != null) {
mapFragment.getMapAsync(this);
}
// 3. UI Input
EditText etNama = findViewById(R.id.etNama);
EditText etLat = findViewById(R.id.etLat);
EditText etLng = findViewById(R.id.etLng);
Button btnSimpan = findViewById(R.id.btnSimpan);
// 4. Aksi Simpan Data ke Room
btnSimpan.setOnClickListener(v -> {
String nama = etNama.getText().toString();
String strLat = etLat.getText().toString();
String strLng = etLng.getText().toString();
if (nama.isEmpty() || strLat.isEmpty() || strLng.isEmpty()) {
Toast.makeText(this, "Isi semua kolom!", Toast.LENGTH_SHORT).show();
return;
}
// Jaring Pengaman Anti-Crash (Try-Catch) jika user mengetik koma/teks aneh
try {
double lat = Double.parseDouble(strLat);
double lng = Double.parseDouble(strLng);
// WAJIB simpan data di Background Thread (Pegawai Gudang)
executor.execute(() -> {
db.cafeDao().insertCafe(new TokoCafe(nama, lat, lng));
// Setelah tersimpan, suruh Kasir (UI Thread) merubah layar
runOnUiThread(() -> {
Toast.makeText(MainActivity.this, "Tersimpan!", Toast.LENGTH_SHORT).show();
etNama.setText(""); etLat.setText(""); etLng.setText("");
// Refresh semua pin di peta!
refreshMarkers();
});
});
} catch (NumberFormatException e) {
Toast.makeText(this, "Format angka Lat/Lng salah! Gunakan titik (.)", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onMapReady(@NonNull GoogleMap googleMap) {
mMap = googleMap;
// Arahkan kamera awal ke Bandung
LatLng bandung = new LatLng(-6.914744, 107.609810);
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(bandung, 12f));
// Panggil fungsi tarik data dari database untuk memasang pin
refreshMarkers();
}
// FUNGSI UTAMA: Menarik data Room DB dan meloopingnya menjadi Marker
private void refreshMarkers() {
if (mMap == null) return;
// 1. Tarik data dari SQLite (Room) lewat Background
executor.execute(() -> {
List<TokoCafe> listCafe = db.cafeDao().getAllCafe();
// 2. Gambar ke Layar Peta lewat UI Thread
runOnUiThread(() -> {
mMap.clear(); // Bersihkan semua marker lama agar tidak menumpuk dobel
// 3. LOOPING! Pasang marker satu persatu
for (TokoCafe cafe : listCafe) {
LatLng posisi = new LatLng(cafe.latitude, cafe.longitude);
mMap.addMarker(new MarkerOptions()
.position(posisi)
.title(cafe.namaCafe));
}
});
});
}
}