Skip to content

Commit

Permalink
Add Place Picker Modal
Browse files Browse the repository at this point in the history
  • Loading branch information
tolu360 committed Sep 26, 2016
1 parent ac4acbd commit 0ae98a3
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 14 deletions.
56 changes: 46 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ iOS/Android Google Places Widgets (Autocomplete, Place Picker) for React Native
<img width=200 title="Modal in Search - iOS" src="./shots/modal-in-search-ios.png">
<img width=200 title="Modal Open - Android" src="./shots/modal-open-android.png">
<img width=200 title="Modal in Search - Android" src="./shots/modal-in-search-android.png">
<img width=200 title="Place Picker Open - Android" src="./shots/picker-android.png">
<img width=200 title="Place Picker Open - iOS" src="./shots/picker-ios.png">

## Install

Expand Down Expand Up @@ -38,7 +40,7 @@ react-native link react-native-google-places
##### Install CocoaPods Dependencies
- If you do not have CocoaPods already installed on your machine, run `gem install cocoapods` to set it up the first time. (Hint: Go grab a cup of coffee!)
- If you are not using Cocoapods in your project already, run `cd ios && pod init` at the root directory of your project.
- Add `pod 'GooglePlaces'` and `pod 'GoogleMaps'` to your Podfile. Otherwise just edit your Podfile to include:
- Add `pod 'GooglePlaces'`, (`pod 'GooglePlacePicker'` only if you are using the PlacePickerModal) and `pod 'GoogleMaps'` to your Podfile. Otherwise just edit your Podfile to include:

```ruby
source 'https://github.com/CocoaPods/Specs.git'
Expand All @@ -47,25 +49,33 @@ target 'YOUR_APP_TARGET_NAME' do

pod 'GooglePlaces'
pod 'GoogleMaps'
pod 'GooglePlacePicker'

end
```
- In your AppDelegate.m file, import the Google Places library by adding `@import GooglePlaces;` on top of the file.
- In your AppDelegate.m file, import the Google Places library by adding `@import GooglePlaces;` and `@import GoogleMaps;` on top of the file.
- Within the `didFinishLaunchingWithOptions` method, instantiate the library as follows:

```Objective-C
[GMSPlacesClient provideAPIKey:@"YOUR_IOS_API_KEY_HERE"];
[GMSServices provideAPIKey:@"YOUR_IOS_API_KEY_HERE"];
```
- By now, you should be all set to install the packages from your Podfile. Run `pod install` from your `ios` directory.
- Close Xcode, and then open (double-click) your project's .xcworkspace file to launch Xcode. From this time onwards, you must use the `.xcworkspace` file to open the project. Or just use the `react-native run-ios` command as usual to run your app in the simulator.
##### Android
- In your AndroidManifest.xml file add your API key in a meta-data tag (ensure you are within the <application> tag as follows:
- In your AndroidManifest.xml file, request location permissions and add your API key in a meta-data tag (ensure you are within the <application> tag as follows:
```xml
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="YOUR_ANDROID_API_KEY_HERE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application
android:name=".MainApplication"
...>
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="YOUR_ANDROID_API_KEY_HERE"/>
...
</application>
```
##### Manual Linking With Your Project (Android)
- The following additional setup steps are optional as they should have been taken care of, for you when you ran `react-native link react-native-google-places`. Otherwise, do the following or just ensure they are in place;
Expand Down Expand Up @@ -102,7 +112,7 @@ protected List<ReactPackage> getPackages() {

## Usage

### Autocomplete Modal: Allows your users to enter place names and addresses - and autocompletes your users' queries as they type.
### Allows your users to enter place names and addresses - and autocompletes your users' queries as they type.

#### Import library

Expand Down Expand Up @@ -139,8 +149,35 @@ class GPlacesDemo extends Component {
}
}
```
#### Open PlacePicker Modal
```javascript
class GPlacesDemo extends Component {
openSearchModal() {
RNGooglePlaces.openPlacePickerModal()
.then((place) => {
console.log(place);
// place represents user's selection from the
// suggestions and it is a simplified Google Place object.
})
.catch(error => console.log(error.message)); // error is a Javascript Error object
}

#### Example Response from the Autocomplete Modal
render() {
return (
<View style={styles.container}>
<TouchableOpacity
style={styles.button}
onPress={() => this.openSearchModal()}
>
<Text>Open Place Picker</Text>
</TouchableOpacity>
</View>
);
}
}
```

#### Example Response from the Autocomplete & PlacePicker Modals
```javascript
{
placeID: "ChIJZa6ezJa8j4AR1p1nTSaRtuQ",
Expand All @@ -154,8 +191,7 @@ class GPlacesDemo extends Component {
```
- Note: The keys available from the response from the resolved `Promise` from calling `RNGooglePlaces.openAutocompleteModal()` are dependent on the selected place - as `phoneNumber, website` are not set on all `Google Place` objects.

#### Open PlacePicker Modal (WIP)
- To be implemented subsequently.


### Troubleshooting

Expand Down
3 changes: 2 additions & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ android {

dependencies {
compile 'com.facebook.react:react-native:+'
compile 'com.google.android.gms:play-services-places:9.4.0';
compile 'com.google.android.gms:play-services-places:9.4.0'
compile 'com.google.android.gms:play-services-location:9.4.0'
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.common.GooglePlayServicesRepairableException;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.location.places.Place;
import com.google.android.gms.location.places.ui.PlaceAutocomplete;
import com.google.android.gms.location.places.ui.PlacePicker;

import android.Manifest;
import android.content.Intent;
Expand Down Expand Up @@ -37,6 +39,7 @@ public class RNGooglePlacesModule extends ReactContextBaseJavaModule implements
public static final String TAG = "RNGooglePlaces";

public static int AUTOCOMPLETE_REQUEST_CODE = 360;
public static int PLACE_PICKER_REQUEST_CODE = 361;
public static String REACT_CLASS = "RNGooglePlaces";

public RNGooglePlacesModule(ReactApplicationContext reactContext) {
Expand Down Expand Up @@ -97,6 +100,36 @@ public void onActivityResult(Activity activity, final int requestCode, final int
rejectPromise("E_USER_CANCELED", new Error("Search cancelled"));
}
}

if (requestCode == PLACE_PICKER_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
Place place = PlacePicker.getPlace(data, this.reactContext.getApplicationContext());

Log.i(TAG, "Place Selected: " + place.getName());

// Display attributions if required.
CharSequence attributions = place.getAttributions();

WritableMap map = Arguments.createMap();
map.putDouble("latitude", place.getLatLng().latitude);
map.putDouble("longitude", place.getLatLng().longitude);
map.putString("name", place.getName().toString());
map.putString("address", place.getAddress().toString());

if (!TextUtils.isEmpty(place.getPhoneNumber())) {
map.putString("phoneNumber", place.getPhoneNumber().toString());
}
if (null != place.getWebsiteUri()) {
map.putString("website", place.getWebsiteUri().toString());
}
map.putString("placeID", place.getId());
if (!TextUtils.isEmpty(attributions)) {
map.putString("attributions", attributions.toString());
}

resolvePromise(map);
}
}
}

/**
Expand Down Expand Up @@ -132,6 +165,26 @@ public void openAutocompleteModal(final Promise promise) {
}
}

@ReactMethod
public void openPlacePickerModal(final Promise promise) {
this.pendingPromise = promise;
Activity currentActivity = getCurrentActivity();

try {
PlacePicker.IntentBuilder intentBuilder = new PlacePicker.IntentBuilder();
Intent intent = intentBuilder.build(currentActivity);
// Start the Intent by requesting a result, identified by a request code.
currentActivity.startActivityForResult(intent, PLACE_PICKER_REQUEST_CODE);

} catch (GooglePlayServicesRepairableException e) {
GooglePlayServicesUtil
.getErrorDialog(e.getConnectionStatusCode(), currentActivity, 0);
} catch (GooglePlayServicesNotAvailableException e) {

rejectPromise("E_INTENT_ERROR", new Error("Google Play Services is not available"));
}
}

private void rejectPromise(String code, Error err) {
if (this.pendingPromise != null) {
this.pendingPromise.reject(code, err);
Expand Down
4 changes: 4 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ class RNGooglePlaces {
openAutocompleteModal() {
return RNGooglePlacesNative.openAutocompleteModal()
}

openPlacePickerModal() {
return RNGooglePlacesNative.openPlacePickerModal()
}
}

export default new RNGooglePlaces()
16 changes: 14 additions & 2 deletions ios/RNGooglePlaces.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,20 @@ - (dispatch_queue_t)methodQueue
[a openAutocompleteModal: resolve rejecter: reject];
}
@catch (NSException * e) {
reject(@"E_OPEN_FAILED", @"Could not open modal", [self errorFromException:e]);
}
reject(@"E_OPEN_FAILED", @"Could not open modal", [self errorFromException:e]);
}
}

RCT_EXPORT_METHOD(openPlacePickerModal: (RCTPromiseResolveBlock)resolve
rejecter: (RCTPromiseRejectBlock)reject)
{
@try {
RNGooglePlacesViewController* a = [[RNGooglePlacesViewController alloc] init];
[a openPlacePickerModal: resolve rejecter: reject];
}
@catch (NSException * e) {
reject(@"E_OPEN_FAILED", @"Could not open modal", [self errorFromException:e]);
}
}

- (NSError *) errorFromException: (NSException *) exception
Expand Down
2 changes: 2 additions & 0 deletions ios/RNGooglePlacesViewController.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@

- (IBAction)openAutocompleteModal: (RCTPromiseResolveBlock)resolve
rejecter: (RCTPromiseRejectBlock)reject;
- (IBAction)openPlacePickerModal: (RCTPromiseResolveBlock)resolve
rejecter: (RCTPromiseRejectBlock)reject;

@end
34 changes: 34 additions & 0 deletions ios/RNGooglePlacesViewController.m
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#import "RNGooglePlacesViewController.h"

#import <GooglePlaces/GooglePlaces.h>
#import <GooglePlacePicker/GooglePlacePicker.h>
#import "RCTUtils.h"
#import "RCTLog.h"

Expand All @@ -13,6 +14,7 @@ @implementation RNGooglePlacesViewController

RCTPromiseResolveBlock _resolve;
RCTPromiseRejectBlock _reject;
GMSPlacePicker *_placePicker;
}

- (instancetype)init
Expand All @@ -35,6 +37,38 @@ - (void)openAutocompleteModal: (RCTPromiseResolveBlock)resolve
[rootViewController presentViewController:viewController animated:YES completion:nil];
}

- (void)openPlacePickerModal: (RCTPromiseResolveBlock)resolve
rejecter: (RCTPromiseRejectBlock)reject;
{
_resolve = resolve;
_reject = reject;

GMSPlacePickerConfig *config = [[GMSPlacePickerConfig alloc] initWithViewport:nil];
_placePicker = [[GMSPlacePicker alloc] initWithConfig:config];
[_placePicker pickPlaceWithCallback:^(GMSPlace *place, NSError *error) {
if (place) {
if (_resolve) {
NSMutableDictionary *placeData =[[NSMutableDictionary alloc] init];
placeData[@"name"] = place.name;
placeData[@"address"] = place.formattedAddress;
placeData[@"attributions"] = place.attributions.string;
placeData[@"latitude"] = [NSNumber numberWithDouble:place.coordinate.latitude];
placeData[@"longitude"] = [NSNumber numberWithDouble:place.coordinate.longitude];
placeData[@"phoneNumber"] = place.phoneNumber;
placeData[@"website"] = place.website.absoluteString;
placeData[@"placeID"] = place.placeID;

_resolve(placeData);
}
} else if (error) {
_reject(@"E_PLACE_PICKER_ERROR", [error localizedDescription], nil);

} else {
_reject(@"E_USER_CANCELED", @"Search cancelled", nil);
}
}];
}


// Handle the user's selection.
- (void)viewController:(GMSAutocompleteViewController *)viewController
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"description": "iOS/Android Google Places Widgets (Autocomplete, Place Picker) for React Native Apps",
"main": "index.js",
"author": "Tolu Olowu (Arttitude 360) <[email protected]>",
"version": "0.7.5",
"version": "0.8.0",
"scripts": {
},
"repository": {
Expand Down
Binary file added shots/picker-android.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added shots/picker-ios.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 0ae98a3

Please sign in to comment.