Menampilkan alamat lokasi

Tutorial Mendapatkan lokasi terakhir yang diketahui dan Menerima update lokasi menjelaskan cara mendapatkan lokasi pengguna dalam bentuk objek Location yang berisi koordinat lintang dan bujur. Meskipun lintang dan bujur berguna untuk menghitung jarak atau menampilkan posisi peta, alamat lokasi sering kali lebih berguna. Misalnya, jika Anda ingin memberi tahu pengguna tempat mereka berada atau apa yang dekat, alamat jalan lebih bermakna daripada koordinat geografis (lintang/bujur) lokasi.

Dengan class Geocoder di API lokasi framework Android, Anda bisa mengonversi alamat ke koordinat geografis yang sesuai. Proses ini disebut geocoding. Selain itu, Anda bisa mengonversi lokasi geografis ke suatu alamat. Fitur pencarian alamat juga disebut sebagai geocoding balik.

Tutorial ini menunjukkan pada Anda cara menggunakan metode getFromLocation() untuk mengonversi lokasi geografis ke alamat. Metode tersebut akan menampilkan perkiraan alamat jalan yang sesuai dengan lintang dan bujur yang ditetapkan.

Mendapatkan lokasi geografis

Lokasi terakhir yang diketahui dari perangkat merupakan titik awal yang berguna untuk fitur pencarian alamat. Tutorial tentang cara Mendapatkan lokasi yang terakhir diketahui menunjukkan kepada Anda cara menggunakan metode getLastLocation() yang disediakan oleh penyedia lokasi fusi untuk menemukan lokasi terbaru perangkat.

Untuk mengakses penyedia lokasi fusi, buat instance FusedLocationProviderClient. Untuk mempelajari cara membuat klien Anda, lihat Membuat klien layanan lokasi.

Untuk mengaktifkan penyedia lokasi fusi guna mengambil alamat jalan yang tepat, setel izin lokasi di manifes aplikasi Anda ke ACCESS_FINE_LOCATION, seperti yang ditunjukkan dalam sampel berikut:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.google.android.gms.location.sample.locationupdates" >

      <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    </manifest>
    

Menetapkan layanan intent untuk mengambil alamat

Metode getFromLocation() yang disediakan oleh class Geocoder akan menerima garis lintang dan bujur dan mengembalikan daftar alamat. Metode ini sinkron dan mungkin membutuhkan waktu lama untuk melakukan tugasnya, jadi sebaiknya Anda tidak memanggilnya dari thread antarmuka pengguna (UI) yang utama dari aplikasi Anda.

Class IntentService menyediakan struktur untuk menjalankan tugas di thread latar belakang. Dengan class ini, Anda bisa menangani operasi yang berjalan lama tanpa memengaruhi responsivitas UI Anda.

Tentukan class FetchAddressIntentService yang memperluas IntentService. Class ini adalah layanan pencarian alamat Anda. Layanan intent menangani intent secara asinkron pada thread pekerja dan berhenti sendiri jika kehabisan pekerjaan. Tambahan intent menyediakan data yang dibutuhkan oleh layanan, termasuk objek Location untuk dikonversi ke alamat dan objek ResultReceiver guna menangani hasil pencarian alamat. Layanan tersebut menggunakan Geocoder untuk mengambil alamat lokasi dan mengirimkan hasilnya ke ResultReceiver.

Menentukan layanan intent di manifes aplikasi Anda

Tambahkan entri ke manifes aplikasi Anda yang menentukan layanan intent, seperti yang ditunjukkan di sini:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.google.android.gms.location.sample.locationaddress" >
        <application
            ...
            <service
                android:name=".FetchAddressIntentService"
                android:exported="false"/>
        </application>
        ...
    </manifest>
    

Catatan: Elemen <service> dalam manifes tidak perlu menyertakan filter intent karena aktivitas utama Anda membuat intent eksplisit dengan menentukan nama class yang akan digunakan untuk intent tersebut.

Membuat Geocoder

Proses konversi lokasi geografis ke alamat disebut geocoding balik. Untuk melakukan pekerjaan utama dari layanan intent (permintaan geocoding balik Anda), terapkan onHandleIntent() dalam class FetchAddressIntentService. Buat objek Geocoder untuk menangani geocoding balik.

Lokal akan mewakili wilayah geografis atau linguistik tertentu. Objek lokal menyesuaikan presentasi informasi, seperti angka atau tanggal, agar sesuai dengan konvensi di wilayah yang diwakili oleh lokal tersebut. Teruskan objek Locale ke objek Geocoder untuk memastikan bahwa alamat yang dihasilkan dilokalkan ke wilayah geografis pengguna. Berikut contohnya:

Kotlin

    override fun onHandleIntent(intent: Intent?) {
        val geocoder = Geocoder(this, Locale.getDefault())
        // ...
    }
    

Java

    @Override
    protected void onHandleIntent(Intent intent) {
        Geocoder geocoder = new Geocoder(this, Locale.getDefault());
        // ...
    }
    

Mengambil alamat jalan

Anda sekarang bisa mengambil alamat jalan dari geocoder, menangani error yang mungkin terjadi, dan mengirimkan hasilnya kembali ke aktivitas yang meminta alamat. Untuk melaporkan hasil proses geocoding, Anda perlu dua konstanta numerik yang menunjukkan keberhasilan atau kegagalan. Tentukan konstanta yang berisi nilai-nilai, seperti yang ditunjukkan dalam cuplikan kode ini:

Kotlin

    object Constants {
        const val SUCCESS_RESULT = 0
        const val FAILURE_RESULT = 1
        const val PACKAGE_NAME = "com.google.android.gms.location.sample.locationaddress"
        const val RECEIVER = "$PACKAGE_NAME.RECEIVER"
        const val RESULT_DATA_KEY = "${PACKAGE_NAME}.RESULT_DATA_KEY"
        const val LOCATION_DATA_EXTRA = "${PACKAGE_NAME}.LOCATION_DATA_EXTRA"
    }
    

Java

    public final class Constants {
        public static final int SUCCESS_RESULT = 0;
        public static final int FAILURE_RESULT = 1;
        public static final String PACKAGE_NAME =
            "com.google.android.gms.location.sample.locationaddress";
        public static final String RECEIVER = PACKAGE_NAME + ".RECEIVER";
        public static final String RESULT_DATA_KEY = PACKAGE_NAME +
            ".RESULT_DATA_KEY";
        public static final String LOCATION_DATA_EXTRA = PACKAGE_NAME +
            ".LOCATION_DATA_EXTRA";
    }
    

Untuk mendapatkan alamat jalan yang sesuai dengan lokasi geografis, panggil getFromLocation() yang meneruskan garis lintang dan bujur dari objek lokasi dan jumlah maksimum alamat yang ingin Anda tampilkan. Dalam hal ini, Anda hanya menginginkan satu alamat. Geocoder akan menampilkan array alamat. Jika tidak ada alamat yang cocok dengan lokasi yang diberikan, daftar akan ditampilkan dalam keadaan kosong. Jika tidak ada layanan geocoding backend yang tersedia, geocoder akan menampilkan null.

Periksa error berikut, seperti yang ditunjukkan pada sampel kode di bawah ini:

  • Tidak ada data lokasi yang disediakan - Tambahan intent tidak termasuk objek Location yang diperlukan untuk geocoding balik.
  • Lintang atau bujur yang digunakan tidak valid - Nilai lintang dan/atau bujur yang disediakan di objek Location tidak valid.
  • Geocoder tidak tersedia - Layanan geocoding latar belakang tidak tersedia karena kesalahan jaringan atau pengecualian IO.
  • Maaf, alamat tidak ditemukan - Geocoder tidak dapat menemukan alamat untuk lintang/bujur yang diberikan.

Jika terjadi error, letakkan pesan error yang sesuai dalam variabel errorMessage sehingga Anda dapat mengirimkannya kembali ke aktivitas yang meminta.

Untuk mendapatkan baris individual dari objek alamat, gunakan metode getAddressLine() yang disediakan oleh class Address. Gabungkan baris-baris tersebut ke dalam daftar fragmen alamat yang siap untuk kembali ke aktivitas yang meminta alamat tersebut.

Untuk mengirim hasilnya kembali ke aktivitas yang meminta, panggil metode deliverResultToReceiver() (ditentukan dalam Mengembalikan alamat ke pemohon). Hasilnya terdiri dari kode keberhasilan/kegagalan numerik yang telah disebutkan sebelumnya dan sebuah string. Dalam kasus geocoding balik yang berhasil, string tersebut berisi alamat. Jika terjadi kegagalan, string berisi pesan error, seperti yang ditunjukkan dalam sampel kode ini:

Kotlin

    protected fun onHandleIntent(intent: Intent?) {
        intent ?: return

        var errorMessage = ""

        // Get the location passed to this service through an extra.
        val location = intent.getParcelableExtra(
                Constants.LOCATION_DATA_EXTRA)

        // ...

        var addresses: List<Address> = emptyList()

        try {
            addresses = geocoder.getFromLocation(
                    location.latitude,
                    location.longitude,
                    // In this sample, we get just a single address.
                    1)
        } catch (ioException: IOException) {
            // Catch network or other I/O problems.
            errorMessage = getString(R.string.service_not_available)
            Log.e(TAG, errorMessage, ioException)
        } catch (illegalArgumentException: IllegalArgumentException) {
            // Catch invalid latitude or longitude values.
            errorMessage = getString(R.string.invalid_lat_long_used)
            Log.e(TAG, "$errorMessage. Latitude = $location.latitude , " +
                    "Longitude =  $location.longitude", illegalArgumentException)
        }

        // Handle case where no address was found.
        if (addresses.isEmpty()) {
            if (errorMessage.isEmpty()) {
                errorMessage = getString(R.string.no_address_found)
                Log.e(TAG, errorMessage)
            }
            deliverResultToReceiver(Constants.FAILURE_RESULT, errorMessage)
        } else {
            val address = addresses[0]
            // Fetch the address lines using getAddressLine,
            // join them, and send them to the thread.
            val addressFragments = with(address) {
                (0..maxAddressLineIndex).map { getAddressLine(it) }
            }
            Log.i(TAG, getString(R.string.address_found))
            deliverResultToReceiver(Constants.SUCCESS_RESULT,
                    addressFragments.joinToString(separator = "\n"))
        }
    }
    

Java

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent == null) {
            return;
        }
        String errorMessage = "";

        // Get the location passed to this service through an extra.
        Location location = intent.getParcelableExtra(
                Constants.LOCATION_DATA_EXTRA);

        // ...

        List<Address> addresses = null;

        try {
            addresses = geocoder.getFromLocation(
                    location.getLatitude(),
                    location.getLongitude(),
                    // In this sample, get just a single address.
                    1);
        } catch (IOException ioException) {
            // Catch network or other I/O problems.
            errorMessage = getString(R.string.service_not_available);
            Log.e(TAG, errorMessage, ioException);
        } catch (IllegalArgumentException illegalArgumentException) {
            // Catch invalid latitude or longitude values.
            errorMessage = getString(R.string.invalid_lat_long_used);
            Log.e(TAG, errorMessage + ". " +
                    "Latitude = " + location.getLatitude() +
                    ", Longitude = " +
                    location.getLongitude(), illegalArgumentException);
        }

        // Handle case where no address was found.
        if (addresses == null || addresses.size()  == 0) {
            if (errorMessage.isEmpty()) {
                errorMessage = getString(R.string.no_address_found);
                Log.e(TAG, errorMessage);
            }
            deliverResultToReceiver(Constants.FAILURE_RESULT, errorMessage);
        } else {
            Address address = addresses.get(0);
            ArrayList<String> addressFragments = new ArrayList<String>();

            // Fetch the address lines using getAddressLine,
            // join them, and send them to the thread.
            for(int i = 0; i <= address.getMaxAddressLineIndex(); i++) {
                addressFragments.add(address.getAddressLine(i));
            }
            Log.i(TAG, getString(R.string.address_found));
            deliverResultToReceiver(Constants.SUCCESS_RESULT,
                    TextUtils.join(System.getProperty("line.separator"),
                            addressFragments));
        }
    }
    

Mengembalikan alamat ke pemohon

Tindakan terakhir yang harus diselesaikan oleh layanan intent adalah mengirimkan alamat kembali ke ResultReceiver dalam aktivitas yang memulai layanan. Class ResultReceiver memungkinkan Anda untuk mengirim kode hasil numerik serta pesan yang berisi data hasil. Kode numerik berguna untuk melaporkan keberhasilan atau kegagalan permintaan geocoding. Dalam kasus geocoding balik yang berhasil, pesan tersebut berisi alamat. Jika terjadi kegagalan, pesan berisi beberapa teks yang menjelaskan alasan kegagalan tersebut.

Anda telah mengambil alamat dari geocoder, memerangkap error yang mungkin terjadi, dan memanggil metode deliverResultToReceiver(), jadi sekarang Anda harus menentukan metode deliverResultToReceiver() yang mengirimkan kode hasil dan paket pesan ke penerima hasil.

Untuk kode hasil, gunakan nilai yang Anda teruskan ke metode deliverResultToReceiver() dalam parameter resultCode. Untuk membuat paket pesan, gabungkan konstanta RESULT_DATA_KEY dari class Constants Anda (dijelaskan dalam Mengambil data alamat jalan ) dan nilai dalam parameter message yang diteruskan ke metode deliverResultToReceiver() , seperti yang ditunjukkan dalam sampel berikut:

Kotlin

    class FetchAddressIntentService : IntentService() {
        private var receiver: ResultReceiver? = null

        // ...

        private fun deliverResultToReceiver(resultCode: Int, message: String) {
            val bundle = Bundle().apply { putString(Constants.RESULT_DATA_KEY, message) }
            receiver?.send(resultCode, bundle)
        }

    }
    

Java

    public class FetchAddressIntentService extends IntentService {
        protected ResultReceiver receiver;
        // ...
        private void deliverResultToReceiver(int resultCode, String message) {
            Bundle bundle = new Bundle();
            bundle.putString(Constants.RESULT_DATA_KEY, message);
            receiver.send(resultCode, bundle);
        }
    }
    

Memulai layanan intent

Layanan intent, sebagaimana yang dijelaskan di bagian sebelumnya, berjalan di latar belakang dan mengambil alamat yang sesuai dengan lokasi geografis tertentu. Saat Anda memulai layanan, framework Android akan membuat instance dan memulai layanan jika belum berjalan, dan framework akan membuat suatu proses jika diperlukan. Jika layanan sudah berjalan, layanan akan tetap berjalan. Karena layanan memperluas IntentService, layanan akan dimatikan secara otomatis setelah semua intent diproses.

Mulai layanan dari aktivitas utama aplikasi Anda dan buat Intent untuk meneruskan data ke layanan. Anda memerlukan intent eksplisit karena Anda ingin layanan Anda saja yang merespons intent tersebut. Untuk informasi selengkapnya, lihat Jenis Intent.

Untuk membuat intent eksplisit, tentukan nama class yang akan digunakan untuk layanan: FetchAddressIntentService.class. Sampaikan informasi ini dalam tambahan intent:

  • ResultReceiver untuk menangani hasil pencarian alamat.
  • Objek Location yang berisi garis lintang dan bujur yang ingin Anda konversi ke suatu alamat.

Sampel kode berikut menunjukkan kepada Anda cara memulai layanan intent:

Kotlin

    class MainActivity : AppCompatActivity(), ConnectionCallbacks, OnConnectionFailedListener {

        private var lastLocation: Location? = null
        private lateinit var resultReceiver: AddressResultReceiver

        // ...

        private fun startIntentService() {

            val intent = Intent(this, FetchAddressIntentService::class.java).apply {
                putExtra(Constants.RECEIVER, resultReceiver)
                putExtra(Constants.LOCATION_DATA_EXTRA, lastLocation)
            }
            startService(intent)
        }
    }
    

Java

    public class MainActivity extends AppCompatActivity implements
            ConnectionCallbacks, OnConnectionFailedListener {

        protected Location lastLocation;
        private AddressResultReceiver resultReceiver;

        // ...

        protected void startIntentService() {
            Intent intent = new Intent(this, FetchAddressIntentService.class);
            intent.putExtra(Constants.RECEIVER, resultReceiver);
            intent.putExtra(Constants.LOCATION_DATA_EXTRA, lastLocation);
            startService(intent);
        }
    }
    

Perhatian: Untuk memastikan bahwa aplikasi Anda aman, selalu gunakan intent eksplisit ketika memulai Service dan jangan menyatakan filter intent untuk layanan Anda. Menggunakan intent implisit untuk memulai layanan akan menimbulkan bahaya keamanan karena Anda tidak bisa memastikan layanan yang akan merespons intent tersebut, dan pengguna tidak bisa melihat layanan mana yang dimulai.

Panggil metode startIntentService() di atas saat pengguna mengambil tindakan yang memerlukan pencarian alamat geocoding. Misalnya, pengguna bisa menekan tombol Ambil alamat di UI aplikasi Anda. Cuplikan kode berikut ini menunjukkan panggilan ke metode startIntentService() di pengendali tombol:

Kotlin

    fun fetchAddressButtonHander(view: View) {
        fusedLocationClient?.lastLocation?.addOnSuccessListener { location: Location? ->
            lastKnownLocation = location

            if (lastKnownLocation == null) return@addOnSuccessListener

            if (!Geocoder.isPresent()) {
                Toast.makeText(this@MainActivity,
                        R.string.no_geocoder_available,
                        Toast.LENGTH_LONG).show()
                return@addOnSuccessListener
            }

            // Start service and update UI to reflect new location
            startIntentService()
            updateUI()
        }
    }
    

Java

    private void fetchAddressButtonHander(View view) {
        fusedLocationClient.getLastLocation()
                .addOnSuccessListener(this, new OnSuccessListener<Location>() {
                    @Override
                    public void onSuccess(Location location) {
                        lastKnownLocation = location;

                        // In some rare cases the location returned can be null
                        if (lastKnownLocation == null) {
                            return;
                        }

                        if (!Geocoder.isPresent()) {
                            Toast.makeText(MainActivity.this,
                                    R.string.no_geocoder_available,
                                    Toast.LENGTH_LONG).show();
                            return;
                        }

                        // Start service and update UI to reflect new location
                        startIntentService();
                        updateUI();
                    }
                });
        }
    

Menerima hasil geocoding

Setelah layanan intent menangani permintaan geocoding, layanan tersebut akan menggunakan ResultReceiver untuk mengembalikan hasil ke aktivitas yang melakukan permintaan. Dalam aktivitas yang melakukan permintaan, tentukan AddressResultReceiver yang memperluas ResultReceiver untuk menangani respons dari FetchAddressIntentService.

Hasilnya meliputi kode hasil numerik (resultCode) serta pesan yang berisi data hasil (resultData). Jika proses geocoding balik berhasil, resultData berisi alamat. Jika terjadi kegagalan, resultData berisi teks yang menjelaskan alasan kegagalan tersebut. Untuk detail kemungkinan error, lihat Mengembalikan alamat ke pemohon.

Ganti metode onReceiveResult() untuk menangani hasil yang dikirim ke penerima hasil, seperti yang ditunjukkan dalam sampel kode berikut:

Kotlin

    class MainActivity : AppCompatActivity() {
        // ...
        internal inner class AddressResultReceiver(handler: Handler) : ResultReceiver(handler) {

            override fun onReceiveResult(resultCode: Int, resultData: Bundle?) {

                // Display the address string
                // or an error message sent from the intent service.
                addressOutput = resultData?.getString(Constants.RESULT_DATA_KEY) ?: ""
                displayAddressOutput()

                // Show a toast message if an address was found.
                if (resultCode == Constants.SUCCESS_RESULT) {
                    showToast(getString(R.string.address_found))
                }

            }
        }
    }
    

Java

    public class MainActivity extends AppCompatActivity {

        // ...

        class AddressResultReceiver extends ResultReceiver {
            public AddressResultReceiver(Handler handler) {
                super(handler);
            }

            @Override
            protected void onReceiveResult(int resultCode, Bundle resultData) {

                if (resultData == null) {
                    return;
                }

                // Display the address string
                // or an error message sent from the intent service.
                addressOutput = resultData.getString(Constants.RESULT_DATA_KEY);
                if (addressOutput == null) {
                    addressOutput = "";
                }
                displayAddressOutput();

                // Show a toast message if an address was found.
                if (resultCode == Constants.SUCCESS_RESULT) {
                    showToast(getString(R.string.address_found));
                }

            }
        }
    }