Skip to content

Commit 84e86f8

Browse files
authored
feat: add Autocomplete Address Form sample to Java GMS demo (#167)
* feat: add Autocomplete Address Form sample to Java GMS demo * fix: implement address form fill and add confirmation map
1 parent 7386046 commit 84e86f8

14 files changed

+796
-31
lines changed

Diff for: demo-java/app/build.gradle

+20-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import org.apache.tools.ant.filters.ConcatFilter
22

3-
apply plugin: 'com.android.application'
3+
plugins {
4+
id 'com.android.application'
5+
id 'com.google.secrets_gradle_plugin' version '0.6'
6+
}
47

58
android {
69
compileSdkVersion 29
@@ -11,18 +14,6 @@ android {
1114
multiDexEnabled true
1215
versionCode 1
1316
versionName "1.0"
14-
15-
// Read the API key from ./secure.properties into R.string.maps_api_key
16-
def secureProps = new Properties()
17-
if (file("../../secure.properties").exists()) {
18-
file("../../secure.properties")?.withInputStream { secureProps.load(it) }
19-
}
20-
buildConfigField("String", "PLACES_API_KEY", (secureProps.getProperty("PLACES_API_KEY") ?: "\"\""))
21-
22-
// To add your Maps API key to this project:
23-
// 1. Create a file ./secure.properties
24-
// 2. Add this line, where YOUR_API_KEY is your API key:
25-
// PLACES_API_KEY=YOUR_API_KEY
2617
}
2718
buildTypes {
2819
release {
@@ -63,11 +54,14 @@ task generateV3(type: Copy) {
6354

6455
dependencies {
6556
implementation 'androidx.appcompat:appcompat:1.2.0'
66-
implementation 'com.android.support:multidex:1.0.3'
6757
implementation 'com.google.android.material:material:1.3.0'
58+
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
59+
implementation 'androidx.navigation:navigation-fragment:2.3.5'
60+
implementation 'androidx.navigation:navigation-ui:2.3.5'
6861

6962
// GMS
7063
gmsImplementation 'com.google.android.libraries.places:places:2.4.0'
64+
gmsImplementation 'com.google.android.gms:play-services-maps:17.0.0'
7165

7266
// V3
7367
v3Implementation name:'places-maps-sdk-3.1.0-beta', ext:'aar'
@@ -81,3 +75,15 @@ dependencies {
8175
v3Implementation 'com.google.auto.value:auto-value-annotations:1.8.1'
8276
v3Implementation 'com.google.code.gson:gson:2.8.6'
8377
}
78+
79+
secrets {
80+
// To add your Google Maps Platform API key to this project:
81+
// 1. Create or open file local.properties in this folder, which will be ready by default
82+
// by secrets_gradle_plugin
83+
// 2. Add this line, replacing YOUR_API_KEY with a key from a project with Places API enabled:
84+
// PLACES_API_KEY=YOUR_API_KEY
85+
// 3. Add this line, replacing YOUR_API_KEY with a key from a project with Maps SDK for Android
86+
// enabled (can be the same project and key as in Step 2):
87+
// MAPS_API_KEY=YOUR_API_KEY
88+
defaultPropertiesFileName 'local.defaults.properties'
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
package com.example.placesdemo;
2+
3+
import android.content.Intent;
4+
import android.content.res.Resources;
5+
import android.os.Bundle;
6+
import android.util.Log;
7+
import android.view.View;
8+
import android.view.ViewStub;
9+
import android.widget.Button;
10+
import android.widget.EditText;
11+
12+
import androidx.annotation.Nullable;
13+
import androidx.appcompat.app.AppCompatActivity;
14+
15+
import com.example.placesdemo.model.AutocompleteEditText;
16+
import com.google.android.gms.common.api.Status;
17+
import com.google.android.gms.maps.CameraUpdateFactory;
18+
import com.google.android.gms.maps.GoogleMap;
19+
import com.google.android.gms.maps.GoogleMapOptions;
20+
import com.google.android.gms.maps.OnMapReadyCallback;
21+
import com.google.android.gms.maps.SupportMapFragment;
22+
import com.google.android.gms.maps.model.LatLng;
23+
import com.google.android.gms.maps.model.MapStyleOptions;
24+
import com.google.android.gms.maps.model.Marker;
25+
import com.google.android.gms.maps.model.MarkerOptions;
26+
import com.google.android.libraries.places.api.Places;
27+
import com.google.android.libraries.places.api.model.AddressComponent;
28+
import com.google.android.libraries.places.api.model.AddressComponents;
29+
import com.google.android.libraries.places.api.model.Place;
30+
import com.google.android.libraries.places.api.net.PlacesClient;
31+
import com.google.android.libraries.places.widget.Autocomplete;
32+
import com.google.android.libraries.places.widget.AutocompleteActivity;
33+
import com.google.android.libraries.places.widget.model.AutocompleteActivityMode;
34+
35+
import java.util.Arrays;
36+
import java.util.List;
37+
38+
/**
39+
* Activity for using Place Autocomplete to assist filling out an address form.
40+
*/
41+
@SuppressWarnings("FieldCanBeLocal")
42+
public class AutocompleteAddressActivity extends AppCompatActivity implements OnMapReadyCallback {
43+
44+
private static final String TAG = "ADDRESS_AUTOCOMPLETE";
45+
private static final String MAP_FRAGMENT_TAG = "MAP";
46+
private static final int AUTOCOMPLETE_REQUEST_CODE = 23487;
47+
private AutocompleteEditText address1Field;
48+
private EditText address2Field;
49+
private EditText cityField;
50+
private EditText stateField;
51+
private EditText postalField;
52+
private EditText countryField;
53+
private LatLng coordinates;
54+
private SupportMapFragment mapFragment;
55+
private GoogleMap map;
56+
private Marker marker;
57+
private PlacesClient placesClient;
58+
private View mapPanel;
59+
60+
@Override
61+
protected void onCreate(Bundle savedInstanceState) {
62+
super.onCreate(savedInstanceState);
63+
64+
// Use whatever theme was set from the MainActivity - some of these colors (e.g primary color)
65+
// will get picked up by the AutocompleteActivity.
66+
int theme = getIntent().getIntExtra(MainActivity.THEME_RES_ID_EXTRA, 0);
67+
if (theme != 0) {
68+
setTheme(theme);
69+
}
70+
71+
setContentView(R.layout.autocomplete_address_activity);
72+
73+
// Retrieve a PlacesClient (previously initialized - see MainActivity)
74+
placesClient = Places.createClient(this);
75+
76+
address1Field = findViewById(R.id.autocomplete_address1);
77+
address2Field = findViewById(R.id.autocomplete_address2);
78+
cityField = findViewById(R.id.autocomplete_city);
79+
stateField = findViewById(R.id.autocomplete_state);
80+
postalField = findViewById(R.id.autocomplete_postal);
81+
countryField = findViewById(R.id.autocomplete_country);
82+
83+
// Attach an Autocomplete intent to the Address 1 EditText field
84+
address1Field.setOnClickListener(v -> startAutocompleteIntent());
85+
86+
// Reset the form
87+
Button resetButton = findViewById(R.id.autocomplete_reset_button);
88+
resetButton.setOnClickListener(v -> clearForm());
89+
}
90+
91+
private void startAutocompleteIntent() {
92+
// Set the fields to specify which types of place data to
93+
// return after the user has made a selection.
94+
List<Place.Field> fields = Arrays.asList(Place.Field.ADDRESS_COMPONENTS,
95+
Place.Field.LAT_LNG, Place.Field.VIEWPORT);
96+
97+
// Start the autocomplete intent.
98+
Intent intent = new Autocomplete.IntentBuilder(AutocompleteActivityMode.OVERLAY, fields)
99+
.build(this);
100+
startActivityForResult(intent, AUTOCOMPLETE_REQUEST_CODE);
101+
}
102+
103+
@Override
104+
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
105+
if (requestCode == AUTOCOMPLETE_REQUEST_CODE) {
106+
if (resultCode == RESULT_OK) {
107+
Place place = Autocomplete.getPlaceFromIntent(data);
108+
Log.i(TAG, "Place: " + place.getAddressComponents());
109+
110+
fillInAddress(place);
111+
112+
} else if (resultCode == AutocompleteActivity.RESULT_ERROR) {
113+
// TODO: Handle the error.
114+
Status status = Autocomplete.getStatusFromIntent(data);
115+
Log.i(TAG, status.getStatusMessage());
116+
} else if (resultCode == RESULT_CANCELED) {
117+
// The user canceled the operation.
118+
}
119+
return;
120+
}
121+
super.onActivityResult(requestCode, resultCode, data);
122+
}
123+
124+
@Override
125+
public void onMapReady(GoogleMap googleMap) {
126+
map = googleMap;
127+
try {
128+
// Customise the styling of the base map using a JSON object defined
129+
// in a string resource.
130+
boolean success = map.setMapStyle(
131+
MapStyleOptions.loadRawResourceStyle(this, R.raw.style_json));
132+
133+
if (!success) {
134+
Log.e(TAG, "Style parsing failed.");
135+
}
136+
} catch (Resources.NotFoundException e) {
137+
Log.e(TAG, "Can't find style. Error: ", e);
138+
}
139+
map.moveCamera(CameraUpdateFactory.newLatLngZoom(coordinates, 15f));
140+
marker = map.addMarker(new MarkerOptions().position(coordinates));
141+
}
142+
143+
private void fillInAddress(Place place) {
144+
AddressComponents components = place.getAddressComponents();
145+
StringBuilder address1 = new StringBuilder();
146+
StringBuilder postcode = new StringBuilder();
147+
148+
// Get each component of the address from the place details,
149+
// and then fill-in the corresponding field on the form.
150+
// Possible AddressComponent types are documented at https://goo.gle/32SJPM1
151+
if (components != null) {
152+
for(AddressComponent component : components.asList()) {
153+
String type = component.getTypes().get(0);
154+
switch(type) {
155+
case "street_number": {
156+
address1.insert(0, component.getName());
157+
break;
158+
}
159+
160+
case "route": {
161+
address1.append(" ");
162+
address1.append(component.getShortName());
163+
break;
164+
}
165+
166+
case "postal_code": {
167+
postcode.insert(0, component.getName());
168+
break;
169+
}
170+
171+
case "postal_code_suffix": {
172+
postcode.append("-").append(component.getName());
173+
break;
174+
}
175+
176+
case "locality":
177+
cityField.setText(component.getName());
178+
break;
179+
180+
case "administrative_area_level_1": {
181+
stateField.setText(component.getShortName());
182+
break;
183+
}
184+
185+
case "country":
186+
countryField.setText(component.getName());
187+
break;
188+
}
189+
}
190+
}
191+
192+
address1Field.setText(address1.toString());
193+
postalField.setText(postcode.toString());
194+
195+
// After filling the form with address components from the Autocomplete
196+
// prediction, set cursor focus on the second address line to encourage
197+
// entry of sub-premise information such as apartment, unit, or floor number.
198+
address2Field.requestFocus();
199+
200+
// Add a map for visual confirmation of the address
201+
showMap(place);
202+
}
203+
204+
private void showMap(Place place) {
205+
coordinates = place.getLatLng();
206+
207+
// It isn't possible to set a fragment's id programmatically so we set a tag instead and
208+
// search for it using that.
209+
mapFragment = (SupportMapFragment)
210+
getSupportFragmentManager().findFragmentByTag(MAP_FRAGMENT_TAG);
211+
212+
// We only create a fragment if it doesn't already exist.
213+
if (mapFragment == null) {
214+
mapPanel = ((ViewStub) findViewById(R.id.stub_map)).inflate();
215+
GoogleMapOptions mapOptions = new GoogleMapOptions();
216+
mapOptions.mapToolbarEnabled(false);
217+
218+
// To programmatically add the map, we first create a SupportMapFragment.
219+
mapFragment = SupportMapFragment.newInstance(mapOptions);
220+
221+
// Then we add it using a FragmentTransaction.
222+
getSupportFragmentManager()
223+
.beginTransaction()
224+
.add(R.id.confirmation_map, mapFragment, MAP_FRAGMENT_TAG)
225+
.commit();
226+
mapFragment.getMapAsync(this);
227+
} else {
228+
updateMap(coordinates);
229+
}
230+
}
231+
232+
private void updateMap(LatLng latLng) {
233+
marker.setPosition(latLng);
234+
map.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15f));
235+
if (mapPanel.getVisibility() == View.GONE) {
236+
mapPanel.setVisibility(View.VISIBLE);
237+
}
238+
}
239+
240+
private void clearForm() {
241+
address1Field.setText("");
242+
address2Field.getText().clear();
243+
cityField.getText().clear();
244+
stateField.getText().clear();
245+
postalField.getText().clear();
246+
countryField.getText().clear();
247+
if (mapPanel != null) {
248+
mapPanel.setVisibility(View.GONE);
249+
}
250+
address1Field.requestFocus();
251+
}
252+
}

Diff for: demo-java/app/src/gms/java/com/example/placesdemo/MainActivity.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,9 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
5353
Places.initialize(getApplicationContext(), apiKey);
5454
}
5555

56-
setLaunchActivityClickListener(R.id.programmatic_autocomplete_button, ProgrammaticAutocompleteToolbarActivity.class);
5756
setLaunchActivityClickListener(R.id.autocomplete_button, AutocompleteTestActivity.class);
57+
setLaunchActivityClickListener(R.id.autocomplete_address_button, AutocompleteAddressActivity.class);
58+
setLaunchActivityClickListener(R.id.programmatic_autocomplete_button, ProgrammaticAutocompleteToolbarActivity.class);
5859
setLaunchActivityClickListener(R.id.place_and_photo_button, PlaceAndPhotoTestActivity.class);
5960
setLaunchActivityClickListener(R.id.current_place_button, CurrentPlaceTestActivity.class);
6061

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.example.placesdemo.model;
2+
3+
import android.content.Context;
4+
import android.util.AttributeSet;
5+
import android.view.MotionEvent;
6+
7+
public class AutocompleteEditText extends androidx.appcompat.widget.AppCompatEditText {
8+
public AutocompleteEditText(Context context) {
9+
super(context);
10+
}
11+
12+
public AutocompleteEditText(Context context, AttributeSet attrs) {
13+
super(context, attrs);
14+
}
15+
16+
@Override
17+
public boolean onTouchEvent(MotionEvent event) {
18+
super.onTouchEvent(event);
19+
20+
switch (event.getAction()) {
21+
case MotionEvent.ACTION_DOWN:
22+
return true;
23+
24+
case MotionEvent.ACTION_UP:
25+
performClick();
26+
return true;
27+
}
28+
return false;
29+
}
30+
31+
// Because we call this from onTouchEvent, this code will be executed for both
32+
// normal touch events and for when the system calls this using Accessibility
33+
@Override
34+
public boolean performClick() {
35+
super.performClick();
36+
return true;
37+
}
38+
}

Diff for: demo-java/app/src/main/AndroidManifest.xml

+9
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@
2828
android:supportsRtl="true"
2929
android:theme="@style/Theme.AppCompat.Light">
3030

31+
<meta-data
32+
android:name="com.google.android.gms.version"
33+
android:value="@integer/google_play_services_version" />
34+
35+
<meta-data
36+
android:name="com.google.android.geo.API_KEY"
37+
android:value="${MAPS_API_KEY}" />
38+
3139
<activity android:name=".MainActivity">
3240
<intent-filter>
3341
<action android:name="android.intent.action.MAIN" />
@@ -37,6 +45,7 @@
3745
</activity>
3846

3947
<activity android:name=".AutocompleteTestActivity" />
48+
<activity android:name=".AutocompleteAddressActivity" />
4049
<activity android:name=".PlaceAndPhotoTestActivity" />
4150
<activity android:name=".CurrentPlaceTestActivity" />
4251
<activity

0 commit comments

Comments
 (0)