├── .github └── workflows │ └── android.yml ├── .gitignore ├── .idea ├── .name ├── codeStyles │ └── Project.xml ├── gradle.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── prudhvir3ddy │ │ └── android_location │ │ └── ExampleInstrumentedTest.java │ ├── debug │ └── res │ │ └── values │ │ └── google_maps_api.xml │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── prudhvir3ddy │ │ │ └── android_location │ │ │ ├── Constants.java │ │ │ ├── FetchAddressIntentService.java │ │ │ ├── LocationMainActivity.java │ │ │ ├── MapsActivity.java │ │ │ ├── PlacesAutoCompleteAdapter.java │ │ │ └── SearchLocationDialog.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ ├── location.png │ │ ├── marker.png │ │ ├── rounded_edittext.xml │ │ └── search.png │ │ ├── font │ │ ├── open_sans.xml │ │ └── open_sans_bold.xml │ │ ├── layout │ │ ├── activity_custom_ui.xml │ │ ├── activity_main.xml │ │ ├── activity_maps.xml │ │ └── place_recycler_item_layout.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── font_certs.xml │ │ ├── preloaded_fonts.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── release │ └── res │ │ └── values │ │ └── google_maps_api.xml │ └── test │ └── java │ └── com │ └── prudhvir3ddy │ └── android_location │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshots ├── ss1.jpeg ├── ss2.jpeg ├── ss3.jpeg ├── ss4.jpeg ├── ss5.jpeg └── ss6.jpeg └── settings.gradle /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: set up JDK 1.8 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 1.8 16 | - name: Build with Gradle 17 | run: ./gradlew build 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | android-location -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # android-location 2 | 3 | handling the location permission in android in professional way with good UX 4 | 5 | [![Actions Status](https://github.com/foodbee/android-location/workflows/Android/badge.svg)](https://github.com/foodbee/android-location/actions) 6 | 7 | please add your google maps API key in app/src/debug/res/values/google_maps_api.xml 8 | 9 | #### Screenshots 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | defaultConfig { 6 | applicationId "com.prudhvir3ddy.android_location" 7 | minSdkVersion 21 8 | targetSdkVersion 29 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | compileOptions { 20 | sourceCompatibility = 1.8 21 | targetCompatibility = 1.8 22 | } 23 | buildToolsVersion = '29.0.2' 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | implementation 'androidx.appcompat:appcompat:1.1.0' 29 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 30 | testImplementation 'junit:junit:4.12' 31 | 32 | implementation 'com.google.android.gms:play-services-location:17.0.0' 33 | implementation 'com.google.android.libraries.places:places:2.0.0' 34 | implementation 'com.google.android.gms:play-services-maps:17.0.0' 35 | 36 | 37 | implementation 'com.google.android.material:material:1.0.0' 38 | 39 | androidTestImplementation 'androidx.test:runner:1.2.0' 40 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/prudhvir3ddy/android_location/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.prudhvir3ddy.android_location; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.prudhvir3ddy.android_location", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/debug/res/values/google_maps_api.xml: -------------------------------------------------------------------------------- 1 | 2 | 23 | YOUR_KEY_HERE 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | 12 | 19 | 20 | 28 | 31 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 46 | 47 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/prudhvir3ddy/android_location/Constants.java: -------------------------------------------------------------------------------- 1 | package com.prudhvir3ddy.android_location; 2 | 3 | class Constants { 4 | 5 | public static final int SUCCESS_RESULT = 0; 6 | public static final int FAILURE_RESULT = 1; 7 | private static final String PACKAGE_NAME = 8 | "com.google.android.gms.location.sample.locationaddress"; 9 | public static final String RECEIVER = PACKAGE_NAME + ".RECEIVER"; 10 | public static final String RESULT_DATA_KEY = PACKAGE_NAME + 11 | ".RESULT_DATA_KEY"; 12 | public static final String LOCATION_DATA_EXTRA = PACKAGE_NAME + 13 | ".LOCATION_DATA_EXTRA"; 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/prudhvir3ddy/android_location/FetchAddressIntentService.java: -------------------------------------------------------------------------------- 1 | package com.prudhvir3ddy.android_location; 2 | 3 | import android.app.IntentService; 4 | import android.content.Intent; 5 | import android.location.Address; 6 | import android.location.Geocoder; 7 | import android.location.Location; 8 | import android.os.Bundle; 9 | import android.os.ResultReceiver; 10 | import android.text.TextUtils; 11 | import android.util.Log; 12 | 13 | import androidx.annotation.Nullable; 14 | 15 | import java.io.IOException; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.Locale; 19 | 20 | public class FetchAddressIntentService extends IntentService { 21 | private static final String TAG = FetchAddressIntentService.class.getSimpleName(); 22 | /** 23 | * Creates an IntentService. Invoked by your subclass's constructor. 24 | * 25 | * @param name Used to name the worker thread, important only for debugging. 26 | */ 27 | private ResultReceiver receiver; 28 | 29 | public FetchAddressIntentService() { 30 | super("boom"); 31 | } 32 | 33 | private void deliverResultToReceiver(int resultCode, String message) { 34 | Bundle bundle = new Bundle(); 35 | bundle.putString(Constants.RESULT_DATA_KEY, message); 36 | receiver.send(resultCode, bundle); 37 | } 38 | 39 | @Override 40 | protected void onHandleIntent(@Nullable Intent intent) { 41 | 42 | if (intent == null) { 43 | return; 44 | } 45 | 46 | receiver = intent.getParcelableExtra(Constants.RECEIVER); 47 | String errorMessage = ""; 48 | 49 | // Get the location passed to this service through an extra. 50 | Location location = intent.getParcelableExtra( 51 | Constants.LOCATION_DATA_EXTRA); 52 | 53 | Geocoder geocoder = new Geocoder(this, Locale.getDefault()); 54 | List
addresses = null; 55 | 56 | try { 57 | addresses = geocoder.getFromLocation( 58 | location.getLatitude(), 59 | location.getLongitude(), 60 | // In this sample, get just a single address. 61 | 1); 62 | } catch (IOException ioException) { 63 | // Catch network or other I/O problems. 64 | errorMessage = getString(R.string.service_not_available); 65 | Log.e(TAG, errorMessage, ioException); 66 | } catch (IllegalArgumentException illegalArgumentException) { 67 | // Catch invalid latitude or longitude values. 68 | errorMessage = getString(R.string.invalid_lat_long_used); 69 | Log.e(TAG, errorMessage + ". " + 70 | "Latitude = " + location.getLatitude() + 71 | ", Longitude = " + 72 | location.getLongitude(), illegalArgumentException); 73 | } 74 | 75 | // Handle case where no address was found. 76 | if (addresses == null || addresses.size() == 0) { 77 | if (errorMessage.isEmpty()) { 78 | errorMessage = getString(R.string.no_address_found); 79 | Log.e(TAG, errorMessage); 80 | } 81 | deliverResultToReceiver(Constants.FAILURE_RESULT, errorMessage); 82 | } else { 83 | Address address = addresses.get(0); 84 | ArrayList addressFragments = new ArrayList<>(); 85 | 86 | // Fetch the address lines using getAddressLine, 87 | // join them, and send them to the thread. 88 | for (int i = 0; i <= address.getMaxAddressLineIndex(); i++) { 89 | addressFragments.add(address.getAddressLine(i)); 90 | } 91 | Log.i(TAG, getString(R.string.address_found)); 92 | deliverResultToReceiver(Constants.SUCCESS_RESULT, 93 | TextUtils.join(System.getProperty("line.separator"), 94 | addressFragments)); 95 | 96 | } 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/java/com/prudhvir3ddy/android_location/LocationMainActivity.java: -------------------------------------------------------------------------------- 1 | package com.prudhvir3ddy.android_location; 2 | 3 | import android.Manifest; 4 | import android.content.Intent; 5 | import android.content.IntentSender; 6 | import android.content.pm.PackageManager; 7 | import android.net.Uri; 8 | import android.os.Bundle; 9 | import android.util.Log; 10 | import android.widget.Button; 11 | import android.widget.Toast; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.annotation.Nullable; 15 | import androidx.appcompat.app.AlertDialog; 16 | import androidx.appcompat.app.AppCompatActivity; 17 | import androidx.core.app.ActivityCompat; 18 | 19 | import com.google.android.gms.common.ConnectionResult; 20 | import com.google.android.gms.common.api.GoogleApiClient; 21 | import com.google.android.gms.common.api.ResolvableApiException; 22 | import com.google.android.gms.location.FusedLocationProviderClient; 23 | import com.google.android.gms.location.LocationRequest; 24 | import com.google.android.gms.location.LocationServices; 25 | import com.google.android.gms.location.LocationSettingsRequest; 26 | import com.google.android.gms.location.LocationSettingsResponse; 27 | import com.google.android.gms.location.SettingsClient; 28 | import com.google.android.gms.tasks.Task; 29 | import com.google.android.libraries.places.api.Places; 30 | import com.prudhvir3ddy.android_location.BuildConfig; 31 | 32 | public class LocationMainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks, 33 | GoogleApiClient.OnConnectionFailedListener { 34 | 35 | //request codes 36 | private static final int REQUEST_LOCATION_PERMISSION = 1; 37 | private static final int REQUEST_CHECK_SETTINGS = 2; 38 | 39 | private static final String PLACE_EXTRA_LATITUDE = "latitude"; 40 | private static final String PLACE_EXTRA_LONGITUDE = "longitude"; 41 | 42 | //TAG for logs 43 | private static final String TAG = LocationMainActivity.class.getSimpleName(); 44 | 45 | 46 | @Override 47 | protected void onCreate(Bundle savedInstanceState) { 48 | super.onCreate(savedInstanceState); 49 | setContentView(R.layout.activity_main); 50 | 51 | //initalize places this should be on top 52 | if(getResources().getString(R.string.google_maps_key).equals("YOUR_KEY_HERE")){ 53 | Toast.makeText(getApplicationContext(),"PLEASE ADD YOUR GOOGLE MAPS API KEY IN PROJECT",Toast.LENGTH_LONG).show(); 54 | } 55 | Places.initialize(this, getResources().getString(R.string.google_maps_key)); 56 | 57 | //for asking permission for location 58 | getLocation(); 59 | 60 | Button mSelectLocationManualButton = findViewById(R.id.button); 61 | 62 | 63 | SearchLocationDialog searchLocationDialog = new SearchLocationDialog(); 64 | 65 | mSelectLocationManualButton.setOnClickListener(v -> searchLocationDialog.show(getSupportFragmentManager(), "searchDialog")); 66 | 67 | 68 | } 69 | 70 | public void getLocation() { 71 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) 72 | != PackageManager.PERMISSION_GRANTED) { 73 | 74 | if (ActivityCompat.shouldShowRequestPermissionRationale(LocationMainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION)) { 75 | showThePermissionNeedDialog(1); 76 | }else{ 77 | ActivityCompat.requestPermissions(this, 78 | new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 79 | REQUEST_LOCATION_PERMISSION); 80 | } 81 | 82 | } else { 83 | createLocationRequest(); 84 | Log.d(TAG, "getLocation: permission granted"); 85 | } 86 | } 87 | 88 | private void createLocationRequest() { 89 | 90 | LocationRequest locationRequest = LocationRequest.create(); 91 | locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); 92 | LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder() 93 | .addLocationRequest(locationRequest); 94 | 95 | SettingsClient client = LocationServices.getSettingsClient(this); 96 | Task task = client.checkLocationSettings(builder.build()); 97 | 98 | Log.d(TAG, "getLocation: permission granted- 2"); 99 | task.addOnSuccessListener(this, locationSettingsResponse -> { 100 | // All location settings are satisfied. The client can initialize 101 | // location requests here. 102 | // location requests here. 103 | // ... 104 | Log.d(TAG, "getLocation: permission granted - 3"); 105 | FusedLocationProviderClient fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this); 106 | fusedLocationProviderClient.getLastLocation() 107 | .addOnSuccessListener(location -> { 108 | if (location != null) { 109 | Log.d(TAG, "getLocation: permission granted - 4"); 110 | Log.d(TAG, location.getLatitude() + " " + location.getLongitude()); 111 | Intent intent = new Intent(LocationMainActivity.this, MapsActivity.class); 112 | intent.putExtra(PLACE_EXTRA_LATITUDE, location.getLatitude()); 113 | intent.putExtra(PLACE_EXTRA_LONGITUDE, location.getLongitude()); 114 | startActivity(intent); 115 | } 116 | }); 117 | 118 | }); 119 | 120 | task.addOnFailureListener(this, e -> { 121 | if (e instanceof ResolvableApiException) { 122 | // Location settings are not satisfied, but this can be fixed 123 | // by showing the user a dialog. 124 | try { 125 | // Show the dialog by calling startResolutionForResult(), 126 | // and check the result in onActivityResult(). 127 | ResolvableApiException resolvable = (ResolvableApiException) e; 128 | resolvable.startResolutionForResult(LocationMainActivity.this, 129 | REQUEST_CHECK_SETTINGS); 130 | } catch (IntentSender.SendIntentException sendEx) { 131 | // Ignore the error. 132 | } 133 | } 134 | }); 135 | } 136 | 137 | 138 | private void showThePermissionNeedDialog(int status) { 139 | AlertDialog.Builder locationBuilder = new AlertDialog.Builder(LocationMainActivity.this); 140 | if (status == 0) { 141 | locationBuilder.setTitle(getString(R.string.location_permission_denied)) 142 | .setMessage(R.string.location_permission_denied_message) 143 | .setNegativeButton("I'M SURE", (dialog, which) -> dialog.cancel()) 144 | .setPositiveButton("RETRY", (dialog, which) -> { 145 | dialog.cancel(); 146 | getLocation(); 147 | }); 148 | }else if(status==1){ 149 | locationBuilder.setMessage(getString(R.string.settings_permission)) 150 | .setPositiveButton("GO TO SETTINGS", (dialog, which) -> { 151 | dialog.cancel(); 152 | startActivity(new Intent( 153 | android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 154 | Uri.parse("package:" + BuildConfig.APPLICATION_ID))); 155 | }); 156 | 157 | } 158 | locationBuilder.show(); 159 | } 160 | 161 | 162 | /** 163 | * @param requestCode 164 | * @param permissions 165 | * @param grantResults 166 | */ 167 | @Override 168 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 169 | if (requestCode == REQUEST_LOCATION_PERMISSION) {// If the permission is granted, get the location, 170 | // otherwise, show a Toast 171 | if (grantResults.length > 0 172 | && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 173 | getLocation(); 174 | } else { 175 | //when user denies the location permission 176 | showThePermissionNeedDialog(0); 177 | } 178 | } 179 | } 180 | 181 | 182 | @Override 183 | public void onConnected(@Nullable Bundle bundle) { 184 | 185 | } 186 | 187 | @Override 188 | public void onConnectionSuspended(int i) { 189 | 190 | } 191 | 192 | @Override 193 | public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { 194 | 195 | } 196 | 197 | } 198 | -------------------------------------------------------------------------------- /app/src/main/java/com/prudhvir3ddy/android_location/MapsActivity.java: -------------------------------------------------------------------------------- 1 | package com.prudhvir3ddy.android_location; 2 | 3 | import android.content.Intent; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.location.Location; 7 | import android.os.Bundle; 8 | import android.os.Handler; 9 | import android.os.ResultReceiver; 10 | import android.view.View; 11 | import android.widget.Button; 12 | import android.widget.ProgressBar; 13 | import android.widget.TextView; 14 | 15 | import androidx.annotation.NonNull; 16 | import androidx.annotation.Nullable; 17 | import androidx.fragment.app.FragmentActivity; 18 | 19 | import com.google.android.gms.common.ConnectionResult; 20 | import com.google.android.gms.common.api.GoogleApiClient; 21 | import com.google.android.gms.maps.CameraUpdateFactory; 22 | import com.google.android.gms.maps.GoogleMap; 23 | import com.google.android.gms.maps.OnMapReadyCallback; 24 | import com.google.android.gms.maps.SupportMapFragment; 25 | import com.google.android.gms.maps.model.BitmapDescriptorFactory; 26 | import com.google.android.gms.maps.model.CameraPosition; 27 | import com.google.android.gms.maps.model.LatLng; 28 | import com.google.android.gms.maps.model.Marker; 29 | import com.google.android.gms.maps.model.MarkerOptions; 30 | 31 | public class MapsActivity extends FragmentActivity implements OnMapReadyCallback, 32 | GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { 33 | 34 | private static final String PLACE_EXTRA_LATITUDE = "latitude"; 35 | private static final String PLACE_EXTRA_LONGITUDE = "longitude"; 36 | private Location lastLocation; 37 | private Marker marker; 38 | private TextView locAddressTv; 39 | private ProgressBar locPb; 40 | private GoogleMap mMap; 41 | 42 | @Override 43 | protected void onCreate(Bundle savedInstanceState) { 44 | super.onCreate(savedInstanceState); 45 | setContentView(R.layout.activity_maps); 46 | // Obtain the SupportMapFragment and get notified when the map is ready to be used. 47 | 48 | Button confirmLocationBtn = findViewById(R.id.confirm_loc_btn); 49 | locAddressTv = findViewById(R.id.location_address_tv); 50 | locPb = findViewById(R.id.loc_pb); 51 | 52 | Intent intent = getIntent(); 53 | double latitude = intent.getDoubleExtra(PLACE_EXTRA_LATITUDE, 0); 54 | double longitude = intent.getDoubleExtra(PLACE_EXTRA_LONGITUDE, 0); 55 | 56 | lastLocation = new Location(""); 57 | lastLocation.setLatitude(latitude); 58 | lastLocation.setLongitude(longitude); 59 | 60 | 61 | startIntentService(); 62 | 63 | SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() 64 | .findFragmentById(R.id.map); 65 | mapFragment.getMapAsync(this); 66 | 67 | confirmLocationBtn.setOnClickListener(v -> { 68 | 69 | }); 70 | } 71 | 72 | 73 | /** 74 | * Manipulates the map once available. 75 | * This callback is triggered when the map is ready to be used. 76 | * This is where we can add markers or lines, add listeners or move the camera. In this case, 77 | * we just add a marker near Sydney, Australia. 78 | * If Google Play services is not installed on the device, the user will be prompted to install 79 | * it inside the SupportMapFragment. This method will only be triggered once the user has 80 | * installed Google Play services and returned to the app. 81 | */ 82 | @Override 83 | public void onMapReady(GoogleMap googleMap) { 84 | mMap = googleMap; 85 | 86 | // Add a marker in Sydney and move the camera 87 | LatLng current = new LatLng(lastLocation.getLatitude(), lastLocation.getLongitude()); 88 | BitmapFactory.Options options = new BitmapFactory.Options(); 89 | options.inScaled = false; 90 | Bitmap source = BitmapFactory.decodeResource(this.getResources(), 91 | R.drawable.marker, options); 92 | 93 | mMap.setMinZoomPreference(10f); 94 | 95 | MarkerOptions markerOptions = new MarkerOptions() 96 | .position(current) 97 | .title(getString(R.string.fooddeliver)) 98 | .icon(BitmapDescriptorFactory.fromBitmap(source)); 99 | 100 | if (marker == null) 101 | marker = mMap.addMarker(markerOptions); 102 | 103 | 104 | CameraPosition cameraPosition = new CameraPosition.Builder() 105 | .target(current).zoom(18f).build(); 106 | mMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition)); 107 | mMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition)); 108 | 109 | 110 | mMap.setOnCameraMoveListener(() -> { 111 | LatLng center = mMap.getCameraPosition().target; 112 | lastLocation.setLongitude(center.longitude); 113 | lastLocation.setLatitude(center.latitude); 114 | marker.setPosition(center); 115 | 116 | 117 | }); 118 | 119 | mMap.setOnCameraIdleListener(this::startIntentService); 120 | } 121 | 122 | @Override 123 | public void onConnected(@Nullable Bundle bundle) { 124 | 125 | } 126 | 127 | @Override 128 | public void onConnectionSuspended(int i) { 129 | 130 | } 131 | 132 | @Override 133 | public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { 134 | 135 | } 136 | 137 | private void startIntentService() { 138 | locPb.setVisibility(View.VISIBLE); 139 | Handler handler = new Handler(); 140 | AddressResultReceiver resultReceiver = new AddressResultReceiver(handler); 141 | Intent intent = new Intent(this, FetchAddressIntentService.class); 142 | intent.putExtra(Constants.RECEIVER, resultReceiver); 143 | intent.putExtra(Constants.LOCATION_DATA_EXTRA, lastLocation); 144 | startService(intent); 145 | } 146 | 147 | 148 | class AddressResultReceiver extends ResultReceiver { 149 | AddressResultReceiver(Handler handler) { 150 | super(handler); 151 | } 152 | 153 | @Override 154 | protected void onReceiveResult(int resultCode, Bundle resultData) { 155 | 156 | if (resultData == null) { 157 | return; 158 | } 159 | 160 | String addressOutput = resultData.getString(Constants.RESULT_DATA_KEY); 161 | if (addressOutput == null) { 162 | addressOutput = ""; 163 | } 164 | //displayAddressOutput(); 165 | 166 | // Show a toast message if an address was found. 167 | if (resultCode == Constants.SUCCESS_RESULT) { 168 | locPb.setVisibility(View.GONE); 169 | locAddressTv.setText(addressOutput); 170 | } 171 | 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /app/src/main/java/com/prudhvir3ddy/android_location/PlacesAutoCompleteAdapter.java: -------------------------------------------------------------------------------- 1 | package com.prudhvir3ddy.android_location; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | import android.widget.Toast; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.cardview.widget.CardView; 12 | import androidx.recyclerview.widget.RecyclerView; 13 | 14 | import com.google.android.gms.common.api.ApiException; 15 | import com.google.android.libraries.places.api.model.Place; 16 | import com.google.android.libraries.places.api.net.FetchPlaceRequest; 17 | import com.google.android.libraries.places.api.net.PlacesClient; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Arrays; 21 | import java.util.List; 22 | 23 | 24 | public class PlacesAutoCompleteAdapter extends RecyclerView.Adapter { 25 | 26 | private final ArrayList mResultList; 27 | 28 | private final Context mContext; 29 | private final PlacesClient placesClient; 30 | private final ClickListener clickListener; 31 | 32 | public PlacesAutoCompleteAdapter(Context context,ArrayList mResultList,ClickListener clickListener) { 33 | mContext = context; 34 | this.mResultList = mResultList; 35 | this.clickListener = clickListener; 36 | placesClient = com.google.android.libraries.places.api.Places.createClient(context); 37 | } 38 | 39 | 40 | @NonNull 41 | @Override 42 | public PlacesAutoCompleteHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { 43 | LayoutInflater layoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 44 | View convertView = layoutInflater.inflate(R.layout.place_recycler_item_layout, viewGroup, false); 45 | return new PlacesAutoCompleteHolder(convertView); 46 | } 47 | 48 | @Override 49 | public void onBindViewHolder(@NonNull PlacesAutoCompleteHolder mPredictionHolder, final int i) { 50 | mPredictionHolder.address.setText(mResultList.get(i).address); 51 | mPredictionHolder.area.setText(mResultList.get(i).area); 52 | 53 | } 54 | 55 | @Override 56 | public int getItemCount() { 57 | return mResultList.size(); 58 | } 59 | 60 | /** 61 | * Holder for Places Geo Data Autocomplete API results. 62 | */ 63 | public static class PlaceAutocomplete { 64 | 65 | final String placeId; 66 | final String address; 67 | final String area; 68 | 69 | PlaceAutocomplete(String placeId, String area, String address) { 70 | this.placeId = placeId; 71 | this.area = area; 72 | this.address = address; 73 | } 74 | 75 | @Override 76 | public String toString() { 77 | return area.toString(); 78 | } 79 | } 80 | 81 | public interface ClickListener { 82 | void click(Place place); 83 | } 84 | 85 | public class PlacesAutoCompleteHolder extends RecyclerView.ViewHolder implements View.OnClickListener { 86 | private final TextView address; 87 | private final TextView area; 88 | 89 | PlacesAutoCompleteHolder(View itemView) { 90 | super(itemView); 91 | area = itemView.findViewById(R.id.place_area); 92 | address = itemView.findViewById(R.id.place_address); 93 | CardView mRow = itemView.findViewById(R.id.place_item_view); 94 | 95 | itemView.setOnClickListener(this); 96 | } 97 | 98 | @Override 99 | public void onClick(View v) { 100 | PlaceAutocomplete item = mResultList.get(getAdapterPosition()); 101 | if (v.getId() == R.id.place_item_view) { 102 | 103 | String placeId = String.valueOf(item.placeId); 104 | 105 | List placeFields = Arrays.asList(Place.Field.ID, Place.Field.NAME, Place.Field.LAT_LNG, Place.Field.ADDRESS); 106 | FetchPlaceRequest request = FetchPlaceRequest.builder(placeId, placeFields).build(); 107 | placesClient.fetchPlace(request).addOnSuccessListener(response -> { 108 | Place place = response.getPlace(); 109 | clickListener.click(place); 110 | 111 | }).addOnFailureListener(exception -> { 112 | if (exception instanceof ApiException) { 113 | Toast.makeText(mContext, exception.getMessage() + "", Toast.LENGTH_SHORT).show(); 114 | } 115 | }); 116 | } 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /app/src/main/java/com/prudhvir3ddy/android_location/SearchLocationDialog.java: -------------------------------------------------------------------------------- 1 | package com.prudhvir3ddy.android_location; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.text.Editable; 7 | import android.text.TextWatcher; 8 | import android.util.Log; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.EditText; 13 | import android.widget.Filter; 14 | import android.widget.Filterable; 15 | import android.widget.ProgressBar; 16 | import android.widget.TextView; 17 | 18 | import androidx.annotation.NonNull; 19 | import androidx.annotation.Nullable; 20 | import androidx.recyclerview.widget.LinearLayoutManager; 21 | import androidx.recyclerview.widget.RecyclerView; 22 | 23 | import com.google.android.gms.tasks.Task; 24 | import com.google.android.gms.tasks.Tasks; 25 | import com.google.android.libraries.places.api.model.AutocompletePrediction; 26 | import com.google.android.libraries.places.api.model.AutocompleteSessionToken; 27 | import com.google.android.libraries.places.api.model.Place; 28 | import com.google.android.libraries.places.api.model.TypeFilter; 29 | import com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest; 30 | import com.google.android.libraries.places.api.net.FindAutocompletePredictionsResponse; 31 | import com.google.android.libraries.places.api.net.PlacesClient; 32 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment; 33 | 34 | import java.util.ArrayList; 35 | import java.util.concurrent.ExecutionException; 36 | import java.util.concurrent.TimeUnit; 37 | import java.util.concurrent.TimeoutException; 38 | 39 | public class SearchLocationDialog extends BottomSheetDialogFragment implements PlacesAutoCompleteAdapter.ClickListener, Filterable { 40 | 41 | 42 | private static final String PLACE_EXTRA_LATITUDE = "latitude"; 43 | private static final String PLACE_EXTRA_LONGITUDE = "longitude"; 44 | private static final String PLACE_EXTRA_ADDRESS = "address"; 45 | private ArrayList mResultList; 46 | private static final String TAG = SearchLocationDialog.class.getSimpleName(); 47 | 48 | private PlacesClient placesClient; 49 | private PlacesAutoCompleteAdapter placesAutoCompleteAdapter; 50 | private ProgressBar locBar; 51 | private RecyclerView recyclerView; 52 | private TextView mGetLocationTextView; 53 | 54 | @Nullable 55 | @Override 56 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 57 | View view = inflater.inflate(R.layout.activity_custom_ui, container, false); 58 | 59 | placesClient = com.google.android.libraries.places.api.Places.createClient(getContext()); 60 | mGetLocationTextView = view.findViewById(R.id.get_user_location); 61 | EditText mSearchLocationEditText = view.findViewById(R.id.search); 62 | locBar = view.findViewById(R.id.pb_location); 63 | 64 | recyclerView = view.findViewById(R.id.recyclerview); 65 | recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 66 | placesAutoCompleteAdapter = new PlacesAutoCompleteAdapter(getContext(),mResultList,this); 67 | 68 | 69 | mGetLocationTextView.setOnClickListener(v -> ((LocationMainActivity) getActivity()).getLocation()); 70 | 71 | mSearchLocationEditText.addTextChangedListener(new TextWatcher() { 72 | 73 | @Override 74 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 75 | } 76 | 77 | @Override 78 | public void onTextChanged(CharSequence s, int start, int before, int count) { 79 | 80 | } 81 | 82 | @Override 83 | public void afterTextChanged(Editable s) { 84 | 85 | mGetLocationTextView.setVisibility(View.VISIBLE); 86 | if (s.toString().length() > 2) { 87 | locBar.setVisibility(View.VISIBLE); 88 | getFilter().filter(s.toString()); 89 | } 90 | } 91 | }); 92 | return view; 93 | } 94 | 95 | 96 | 97 | @Override 98 | public void onAttach(Context context) { 99 | super.onAttach(context); 100 | } 101 | 102 | @Override 103 | public void click(Place place) { 104 | Intent intent = new Intent(getActivity(), MapsActivity.class); 105 | intent.putExtra(PLACE_EXTRA_LATITUDE, place.getLatLng().latitude); 106 | intent.putExtra(PLACE_EXTRA_LONGITUDE, place.getLatLng().longitude); 107 | intent.putExtra(PLACE_EXTRA_ADDRESS, place.getAddress()); 108 | startActivity(intent); 109 | } 110 | 111 | @Override 112 | public Filter getFilter() { 113 | return new Filter() { 114 | @Override 115 | protected FilterResults performFiltering(CharSequence constraint) { 116 | FilterResults results = new FilterResults(); 117 | // Skip the autocomplete query if no constraints are given. 118 | if (constraint != null) { 119 | // Query the autocomplete API for the (constraint) search string. 120 | mResultList = new ArrayList<>(); 121 | mResultList = getPredictions(constraint); 122 | 123 | if (mResultList != null) { 124 | // The API successfully returned results. 125 | results.values = mResultList; 126 | results.count = mResultList.size(); 127 | } 128 | } 129 | return results; 130 | } 131 | 132 | @Override 133 | protected void publishResults(CharSequence constraint, FilterResults results) { 134 | if (results != null && results.count > 0) { 135 | // The API returned at least one result, update the data. 136 | placesAutoCompleteAdapter = new PlacesAutoCompleteAdapter(getContext(), mResultList, SearchLocationDialog.this); 137 | 138 | recyclerView.setAdapter(placesAutoCompleteAdapter); 139 | locBar.setVisibility(View.GONE); 140 | mGetLocationTextView.setVisibility(View.GONE); 141 | 142 | } else { 143 | // The API did not return any results, invalidate the data set. 144 | //notifyDataSetInvalidated(); 145 | } 146 | } 147 | }; 148 | } 149 | 150 | private ArrayList getPredictions(CharSequence constraint) { 151 | 152 | final ArrayList resultList = new ArrayList<>(); 153 | 154 | // Create a new token for the autocomplete session. Pass this to FindAutocompletePredictionsRequest, 155 | // and once again when the user makes a selection (for example when calling fetchPlace()). 156 | AutocompleteSessionToken token = AutocompleteSessionToken.newInstance(); 157 | 158 | FindAutocompletePredictionsRequest request = FindAutocompletePredictionsRequest.builder() 159 | // Call either setLocationBias() OR setLocationRestriction(). 160 | //.setLocationBias(bounds) 161 | //.setCountry("BD") 162 | .setTypeFilter(TypeFilter.ADDRESS) 163 | .setSessionToken(token) 164 | .setQuery(constraint.toString()) 165 | .build(); 166 | 167 | Task autocompletePredictions = placesClient.findAutocompletePredictions(request); 168 | 169 | // This method should have been called off the main UI thread. Block and wait for at most 170 | // 60s for a result from the API. 171 | try { 172 | 173 | Tasks.await(autocompletePredictions, 60, TimeUnit.SECONDS); 174 | } catch (ExecutionException | InterruptedException | TimeoutException e) { 175 | e.printStackTrace(); 176 | } 177 | 178 | if (autocompletePredictions.isSuccessful()) { 179 | FindAutocompletePredictionsResponse findAutocompletePredictionsResponse = autocompletePredictions.getResult(); 180 | if (findAutocompletePredictionsResponse != null) 181 | for (AutocompletePrediction prediction : findAutocompletePredictionsResponse.getAutocompletePredictions()) { 182 | Log.i(TAG, prediction.getPlaceId()); 183 | resultList.add(new PlacesAutoCompleteAdapter.PlaceAutocomplete(prediction.getPlaceId(), prediction.getPrimaryText(null).toString(), prediction.getFullText(null).toString())); 184 | } 185 | 186 | return resultList; 187 | } else { 188 | return resultList; 189 | } 190 | 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FoodBee/Android-Location/e32ac68060d4e6beda970c018dcb5e713acd5f44/app/src/main/res/drawable/location.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FoodBee/Android-Location/e32ac68060d4e6beda970c018dcb5e713acd5f44/app/src/main/res/drawable/marker.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_edittext.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FoodBee/Android-Location/e32ac68060d4e6beda970c018dcb5e713acd5f44/app/src/main/res/drawable/search.png -------------------------------------------------------------------------------- /app/src/main/res/font/open_sans.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/font/open_sans_bold.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_custom_ui.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 17 | 18 | 30 | 31 | 39 | 40 | 41 | 42 | 43 | 44 | 55 | 56 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 |