Penerapan Haversine Formula Pada Aplikasi Android
Haversine Formula adalah persamaan penting dalam navigasi, memberikan jarak yang jauh lingkaran antara dua titik pada bola dari garis bujur (longitude) dan garis lintang (latitude). Haversine formula merupakan kasus khusus dari rumus yang lebih umum di trigonometri bola, hukum haversines, yang berkaitan dengan sisi dan sudut segitiga bola. Tabel pertama haversines dalam bahasa Inggris diterbitkan oleh James Andrew pada tahun 1805. Baca selengkapnya pada Wikipedia atau Movable Type’s site.
Dalam tutorial ini perhitungan harversine formula berada pada sintak SQL di web service. Alur jalannya aplikasi yaitu ketika perangkat Android meluncurkan aplikasi ini akan mengirim parameter koordinat bumi (Latitude, Longitude) berdasarkan lokasi perangkat Android ke web service kemudian melakukan perhitungan dan menampilkan data lokasi-lokasi dari lokasi terdekat sampai lokasi terjauh dari perangkat Android pengguna. berikut ini adalah sintak SQL haversine formula pada web service.
SELECT id, nama, gambar, (6371 * ACOS(SIN(RADIANS(lat)) * SIN(RADIANS($lat)) + COS(RADIANS(lng - $lng)) * COS(RADIANS(lat)) * COS(RADIANS($lat)))) AS jarak FROM wisata HAVING jarak < 6371 ORDER BY jarak ASC

Langkah pertama yaitu membuat database dengan nama kuncoro_haversine kemudian membuat tabel dengan nama wisata dan struktur tabelnya seperti berikut ini :
Column Name | Data Type | Lenght | Primary Key | Not null | Auto Increment |
---|---|---|---|---|---|
id | int | 5 | v | v | v |
nama | varchar | 30 | |||
gambar | varchar | 100 | |||
lat | double | ||||
lng | double |
Membuat web service untuk parsing data aplikasi android.
koneksi.php
Sebagai koneksi aplikasi ke database. Coding-nya disini.
haversine.php
Untuk menampilkan nama lokasi-lokasi terdekat dari perangkat Andorid setelah menerima parameter koordinat bumi lokasi perangkat Android. Coding-nya disini.
Buat project baru di Android Studio File ⇒ New Project. Kemudian pilih Empty Activity dan melanjutkannya hingga selesai.
activity_main.xml
Layout untuk menampilkan data-data lokasi terdekat dari posisi perangkat Andorid pengguna.
<?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/swipe" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@+id/list" android:layout_width="match_parent" android:layout_height="wrap_content" android:divider="@color/list_divider" android:dividerHeight="2dp" android:listSelector="@drawable/list_row_selector" /> </android.support.v4.widget.SwipeRefreshLayout>
list_row.xml
Sebagai tampilan custom listview yang berisi gambar, nama dan jarak terdekat dari posisi pengguna.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="@drawable/list_row_selector" android:padding="8dp" > <com.android.volley.toolbox.NetworkImageView android:id="@+id/gambar" android:layout_width="80dp" android:layout_height="80dp" android:layout_alignParentLeft="true" android:layout_marginRight="8dp" /> <TextView android:id="@+id/nama" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignTop="@+id/gambar" android:layout_toRightOf="@+id/gambar" android:textStyle="bold" /> <TextView android:id="@+id/jarak" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="5dp" android:layout_toRightOf="@+id/gambar" android:layout_below="@+id/nama" /> </RelativeLayout>
Buat folder drawable didalam res dan isi file baru dengan nama list_row_bg.xml, list_row_bg_hover.xml, dan list_row_selector.xml sebagai style listview.
list_row_bg.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <gradient android:startColor="@color/list_row_start_color" android:endColor="@color/list_row_end_color" android:angle="270" /> </shape>
list_row_bg_hover.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <gradient android:angle="270" android:endColor="@color/list_row_hover_end_color" android:startColor="@color/list_row_hover_start_color" /> </shape>
list_row_selector.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/list_row_bg" android:state_pressed="false" android:state_selected="false"/> <item android:drawable="@drawable/list_row_bg_hover" android:state_pressed="true"/> <item android:drawable="@drawable/list_row_bg_hover" android:state_pressed="false" android:state_selected="true"/> </selector>
Masuk folder res=>values=>color.xml dan tambahkan code seperti berikut :
color.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimary">#3F51B5</color> <color name="colorPrimaryDark">#303F9F</color> <color name="colorAccent">#FF4081</color> <color name="list_divider">#d9d9d9</color> <color name="list_row_start_color">#ffffff</color> <color name="list_row_end_color">#ffffff</color> <color name="list_row_hover_start_color">#ebeef0</color> <color name="list_row_hover_end_color">#ebeef0</color> </resources>
Masuk folder res=>values=>strings.xml dan tambahkan code seperti berikut :
color.xml
<resources> <string name="app_name">Kuncoro Haversine</string> <string name="permission_dialog">Accessing Location Permissions required</string> <string name="go_to_permissions_settings">Go to App settings and enable permissions</string> </resources>
Buka build.gradle dan tambahkan volley library didalamnya.
compile 'com.android.volley:volley:1.0.0'
build.gradle
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.2.1' compile 'com.android.volley:volley:1.0.0' /*tambahan*/ }
Agar project terstruktur dan terorganisir, buat 5 paket dengan nama adapter, app, helper, module, dan util. Untuk membuat paket baru , klik kanan pada src=>New=>Peckage dan memberikan nama paket . Contoh : com.dedykuncoro.kuncorohaversine.

Buat class dengan nama LruBitmapCache.java didalam package util dan tambah coding seperti dibawah ini. Class ini berfingsi untuk mengatur caching network image dalam penyimpanan.
LruBitmapCache.java
package com.dedykuncoro.kuncorohaversine.util; import android.graphics.Bitmap; import android.support.v4.util.LruCache; import com.android.volley.toolbox.ImageLoader; /** * Created by Kuncoro on 03/29/2016. */ public class LruBitmapCache extends LruCache<String, Bitmap> implements ImageLoader.ImageCache { public static int getDefaultLruCacheSize() { final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); final int cacheSize = maxMemory / 8; return cacheSize; } public LruBitmapCache() { this(getDefaultLruCacheSize()); } public LruBitmapCache(int sizeInKiloBytes) { super(sizeInKiloBytes); } @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight() / 1024; } @Override public Bitmap getBitmap(String url) { return get(url); } @Override public void putBitmap(String url, Bitmap bitmap) { put(url, bitmap); } }
Buat class AppController.java didalam package app dan tambah coding seperti dibawah ini. Class tunggal yang menginisialisasi class global yang diperlukan. Semua objek yang berhubungan dengan volley diinisialisasi di sini.
AppController.java
package com.dedykuncoro.kuncorohaversine.app; import android.app.Application; import android.text.TextUtils; import com.android.volley.Request; import com.android.volley.RequestQueue; import com.android.volley.toolbox.ImageLoader; import com.android.volley.toolbox.Volley; import com.dedykuncoro.kuncorohaversine.util.LruBitmapCache; /** * Created by Kuncoro on 03/29/2016. */ public class AppController extends Application { public static final String TAG = AppController.class.getSimpleName(); private RequestQueue mRequestQueue; private ImageLoader mImageLoader; private static AppController mInstance; @Override public void onCreate() { super.onCreate(); mInstance = this; } public static synchronized AppController getInstance() { return mInstance; } public RequestQueue getRequestQueue() { if (mRequestQueue == null) { mRequestQueue = Volley.newRequestQueue(getApplicationContext()); } return mRequestQueue; } public ImageLoader getImageLoader() { getRequestQueue(); if (mImageLoader == null) { mImageLoader = new ImageLoader(this.mRequestQueue, new LruBitmapCache()); } return this.mImageLoader; } public <T> void addToRequestQueue(Request<T> req, String tag) { // set the default tag if tag is empty req.setTag(TextUtils.isEmpty(tag) ? TAG : tag); getRequestQueue().add(req); } public <T> void addToRequestQueue(Request<T> req) { req.setTag(TAG); getRequestQueue().add(req); } public void cancelPendingRequests(Object tag) { if (mRequestQueue != null) { mRequestQueue.cancelAll(tag); } } }
Buat class dengan nama PermissionHelper.java didalam package helper dan tambah coding seperti dibawah ini. Class ini berfingsi untuk permission location pada Perangkat Android.
PermissionHelper.java
<pre> package com.dedykuncoro.haversine.helper; import android.Manifest; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.StrictMode; import android.provider.Settings; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.util.Log; import android.widget.Toast; import com.dedykuncoro.haversine.R; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class PermissionHelper { private Activity mActivity; private final int REQUEST_PERMISSION = 99; private String TAG = "PermissionHelper"; private PermissionListener listener; public PermissionHelper(Activity activity) { mActivity = activity; } public void permissionListener(PermissionListener permissionListener) { listener = permissionListener; } public boolean checkAndRequestPermissions() {//1. Call this to check permission. (Call this affected loop for check permission until user Approved it) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { int locationPermission = ContextCompat.checkSelfPermission(mActivity, Manifest.permission.ACCESS_FINE_LOCATION); int coarseLocationPermission = ContextCompat.checkSelfPermission(mActivity, Manifest.permission.ACCESS_COARSE_LOCATION); List<String> listPermissionsNeeded = new ArrayList<>(); if (locationPermission != PackageManager.PERMISSION_GRANTED) { listPermissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION); } if (coarseLocationPermission != PackageManager.PERMISSION_GRANTED) { listPermissionsNeeded.add(Manifest.permission.ACCESS_COARSE_LOCATION); } if (!listPermissionsNeeded.isEmpty()) { ActivityCompat.requestPermissions(mActivity, listPermissionsNeeded.toArray(new String[listPermissionsNeeded.size()]), REQUEST_PERMISSION); return false; } } if (Build.VERSION.SDK_INT > 9) { StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); } listener.onPermissionCheckDone(); return true; } public void onRequestCallBack(int RequestCode, String[] permissions, int[] grantResults) {//2. call this inside onRequestPermissionsResult switch (RequestCode) { case REQUEST_PERMISSION: { Map<String, Integer> perms = new HashMap<>(); // Initialize the map with both permissions perms.put(Manifest.permission.ACCESS_FINE_LOCATION, PackageManager.PERMISSION_GRANTED); perms.put(Manifest.permission.ACCESS_COARSE_LOCATION, PackageManager.PERMISSION_GRANTED); // Fill with actual results from user if (grantResults.length > 0) { for (int i = 0; i < permissions.length; i++) perms.put(permissions[i], grantResults[i]); // Check for both permissions if (perms.get(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED && perms.get(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) { Log.e(TAG, "permission granted"); checkAndRequestPermissions(); } else { Log.e(TAG, "Some permissions are not granted ask again "); // permission is denied (this is the first time, when "never ask again" is not checked) so ask again explaining the usage of permission // shouldShowRequestPermissionRationale will return true // show the dialog or snackbar saying its necessary and try again otherwise proceed with setup. if (ActivityCompat.shouldShowRequestPermissionRationale(mActivity, Manifest.permission.ACCESS_FINE_LOCATION) || ActivityCompat.shouldShowRequestPermissionRationale(mActivity, Manifest.permission.ACCESS_COARSE_LOCATION)) { showDialogOK(mActivity.getString(R.string.permission_dialog), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { switch (which) { case DialogInterface.BUTTON_POSITIVE: checkAndRequestPermissions(); break; // case DialogInterface.BUTTON_NEGATIVE: // // proceed with logic by disabling the related features or quit the app. // break; } } }); } // permission is denied (and never ask again is checked) // shouldShowRequestPermissionRationale will return false else { Toast.makeText(mActivity, R.string.go_to_permissions_settings, Toast.LENGTH_LONG).show(); Intent intent = new Intent(); intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", mActivity.getPackageName(), null); intent.setData(uri); mActivity.startActivity(intent); } } } } } } private void showDialogOK(String message, DialogInterface.OnClickListener okListener) { new AlertDialog.Builder(mActivity) .setMessage(message) .setPositiveButton("OK", okListener) // .setNegativeButton("Cancel", okListener) .create() .show(); } public interface PermissionListener { void onPermissionCheckDone(); } } </pre>
Buat class Jarak.java didalam package module dan tambahkan coding seperti dibawah ini. Class ini berfungsi sebagai membuat objek untuk setiap item yang diparsing JSON. Objek ini berisi informasi seperti id, nama, jarak, dan url gambar.
Jarak.java
package com.dedykuncoro.kuncorohaversine.module; /** * Created by Kuncoro on 03/29/2016. */ public class Jarak { private String nama, jarak, gambar; public Jarak() { } public Jarak(String nama, String jarak, String gambar) { this.nama = nama; this.jarak = jarak; this.gambar = gambar; } public String getNama() { return nama; } public void setNama(String nama) { this.nama = nama; } public String getJarak() { return jarak; } public void setJarak(String jarak) { this.jarak = jarak; } public String getGambar() { return gambar; } public void setGambar(String gambar) { this.gambar = gambar; } }
Buat class CustomListAdapter.java didalam package adapter dan tambahkan coding seperti dibawah ini. Class ini berfungsi sebagai menampilkan data seperti id, nama, jarak, data url gambar kemudian ditampilkan ke dalam listview.
CustomListAdapter.java
package com.dedykuncoro.kuncorohaversine.adapter; import android.app.Activity; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; import com.android.volley.toolbox.ImageLoader; import com.android.volley.toolbox.NetworkImageView; import com.dedykuncoro.kuncorohaversine.R; import com.dedykuncoro.kuncorohaversine.app.AppController; import com.dedykuncoro.kuncorohaversine.module.Jarak; import java.util.List; /** * Created by Kuncoro on 03/29/2016. */ public class CustomListAdapter extends BaseAdapter { private Activity activity; private LayoutInflater inflater; private List<Jarak> jarakItems; ImageLoader imageLoader; public CustomListAdapter(Activity activity, List<Jarak> jarakItems) { this.activity = activity; this.jarakItems = jarakItems; } @Override public int getCount() { return jarakItems.size(); } @Override public Object getItem(int location) { return jarakItems.get(location); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (inflater == null) inflater = (LayoutInflater) activity .getSystemService(Context.LAYOUT_INFLATER_SERVICE); if (convertView == null) convertView = inflater.inflate(R.layout.list_row, null); if (imageLoader == null) imageLoader = AppController.getInstance().getImageLoader(); NetworkImageView thumbNail = (NetworkImageView) convertView .findViewById(R.id.gambar); TextView nama = (TextView) convertView.findViewById(R.id.nama); TextView jarak = (TextView) convertView.findViewById(R.id.jarak); Jarak j = jarakItems.get(position); thumbNail.setImageUrl(j.getGambar(), imageLoader); nama.setText(j.getNama()); jarak.setText(j.getJarak()+" Km"); return convertView; } }
Buat class dengan nama SplashScreen.java dan tambah coding seperti dibawah ini. Class ini berfingsi untuk check/request permission location pada Perangkat Android.
SplashScreen.java
package com.dedykuncoro.haversine; import android.content.Intent; import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import com.dedykuncoro.haversine.helper.PermissionHelper; public class SplashScreen extends AppCompatActivity { private PermissionHelper permissionHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.splash_screen); permissionHelper = new PermissionHelper(this); checkAndRequestPermissions(); } private boolean checkAndRequestPermissions() { permissionHelper.permissionListener(new PermissionHelper.PermissionListener() { @Override public void onPermissionCheckDone() { Intent intent = new Intent(SplashScreen.this, MainActivity.class); startActivity(intent); finish(); } }); permissionHelper.checkAndRequestPermissions(); return true; } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); permissionHelper.onRequestCallBack(requestCode, permissions, grantResults); } }
Buka MainActivity.java dan tambahkan coding seperti dibawah ini. Class ini digunakan untuk mengirim parameter koordinat bumi perangkat android ke web service, kemudian web service merespon dan menghitung menggunakan haversine formula jarak antara lokasi perangkat Android pengguna dengan lokasi-lokasi yang berada dalam database. Setelah dilakukan perhitungan, maka web service akan menampilkan JSON data-data lokasi berdasarkan lokasi terdekat dari perangkat pengguna dan data tersebut akan ditampilkan pada listview aplikasi Android.
MainActivity.java
<pre> package com.dedykuncoro.haversine; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.location.Criteria; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.support.v4.app.ActivityCompat; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.widget.ListView; import android.widget.Toast; import com.android.volley.Response; import com.android.volley.VolleyError; import com.android.volley.VolleyLog; import com.android.volley.toolbox.JsonArrayRequest; import com.dedykuncoro.haversine.adapter.CustomListAdapter; import com.dedykuncoro.haversine.app.AppController; import com.dedykuncoro.haversine.module.Jarak; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.List; /** * Created by Kuncoro on 03/29/2016. */ public class MainActivity extends AppCompatActivity implements LocationListener, SwipeRefreshLayout.OnRefreshListener { SwipeRefreshLayout swipe; ListView list; CustomListAdapter adapter; List<Jarak> itemList = new ArrayList<>(); Double latitude, longitude; Criteria criteria; Location location; LocationManager locationManager; String provider; private static final String TAG = MainActivity.class.getSimpleName(); /* 10.0.2.2 adalah IP Address localhost EMULATOR ANDROID STUDIO, Ganti IP Address tersebut dengan IP Laptop Apabila di RUN di HP. HP dan Laptop harus 1 jaringan */ private static final String url = "http://10.0.2.2/android/haversine/haversine.php?lat="; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // menyamakan variabel pada layout dan java list = (ListView) findViewById(R.id.list); swipe = (SwipeRefreshLayout) findViewById(R.id.swipe); // mengisi data dari adapter ke listview adapter = new CustomListAdapter(this, itemList); list.setAdapter(adapter); locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); criteria = new Criteria(); provider = locationManager.getBestProvider(criteria, false); swipe.setOnRefreshListener(this); swipe.post(new Runnable() { @Override public void run() { lokasi(); } } ); } @Override public void onRefresh() { lokasi(); } // fungsi ngecek lokasi GPS device pengguna private void lokasi() { location = locationManager.getLastKnownLocation(provider); if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { // TODO: Consider calling // ActivityCompat#requestPermissions // here to request the missing permissions, and then overriding // public void onRequestPermissionsResult(int requestCode, String[] permissions, // int[] grantResults) // to handle the case where the user grants the permission. See the documentation // for ActivityCompat#requestPermissions for more details. return; } // permintaan update lokasi device dalam waktu per detik locationManager.requestLocationUpdates(provider, 1000, 1, this); if (location != null) { onLocationChanged(location); } else { /* latitude longitude Alun-alun Demak sebagai default jika tidak ditemukan lokasi dari device pengguna */ callListVolley(-6.894796, 110.638413); } } // untuk menampilkan lokasi wisata terdekat dari device pengguna private void callListVolley(double lat, double lng) { itemList.clear(); adapter.notifyDataSetChanged(); swipe.setRefreshing(true); JsonArrayRequest jArr = new JsonArrayRequest(url + lat + "&lng=" + lng, new Response.Listener<JSONArray>() { @Override public void onResponse(JSONArray response) { Log.e(TAG, response.toString()); // Parsing json for (int i = 0; i < response.length(); i++) { try { JSONObject obj = response.getJSONObject(i); Jarak j = new Jarak(); j.setNama(obj.getString("nama")); j.setGambar(obj.getString("gambar")); double jarak = Double.parseDouble(obj.getString("jarak")); j.setJarak("" + round(jarak, 2)); itemList.add(j); } catch (JSONException e) { e.printStackTrace(); } } // memberitahu adapter jika ada perubahan data adapter.notifyDataSetChanged(); swipe.setRefreshing(false); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { VolleyLog.e(TAG, "Error: " + error.getMessage()); Toast.makeText(getApplicationContext(), error.getMessage(), Toast.LENGTH_LONG).show(); swipe.setRefreshing(false); } }); // menambah permintaan ke queue AppController.getInstance().addToRequestQueue(jArr); } @Override public void onBackPressed() { finish(); System.exit(0); } // untuk menyederhanakan angka dibelakan koma jarak public static double round(double value, int places) { if (places < 0) throw new IllegalArgumentException(); long factor = (long) Math.pow(10, places); value = value * factor; long tmp = Math.round(value); return (double) tmp / factor; } // untuk menentukan lokasi gps dari device pengguna @Override public void onLocationChanged(Location location) { latitude = location.getLatitude(); longitude = location.getLongitude(); // untuk melihat latitude longitude posisi device pengguna pada logcat ditemukan atau tidak Log.e(TAG, "User location latitude:" + latitude + ", longitude:" + longitude); callListVolley(latitude, longitude); } @Override public void onStatusChanged(String provider, int status, Bundle extras) { } @Override public void onProviderEnabled(String provider) { } @Override public void onProviderDisabled(String provider) { } } </pre>
Tambahkan beberapa perijinan pada AndroidManifest.xml seperti dibawah ini :
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.dedykuncoro.kuncorohaversine"> <!-- Tambahan --> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <!-- The following two permissions are not required to use Google Maps Android API v2, but are recommended. --> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" /> <!-- Tambahan --> <application android:name=".app.AppController" <!-- Tambahan --> android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <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>
Run Aplikasinya.