8

React Native: Using Native Modules for Location Services

 2 years ago
source link: https://iwritecodesometimes.net/2019/07/19/react-native-using-native-modules-for-location-services/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

React Native: Using Native Modules for Location Services

About Location Services

Location-based services are a common utility in mobile apps. Typical use cases range anywhere from serving ads relevant to a certain location, to presenting special content at specific places, to out-right navigational aid. If you should find that your own react native app would benefit from location services, there are a few approaches we can take.

The easiest, of course, is pure react native! The documentation leaves a bit to be desired, but if you only need location info while your app is open and does not require any advanced features, you can get location info in your app using an extension to the GeoLocation Web Spec.

In some instances, you may need more customizable location services, or periodic updates in the background. In these instances, there is no avoiding it – we’ve gotta dust off our objective-c skills and go with a native module. Fortunately, Apple provides the means for us to approach Location Services in a few different ways. In this example, we will make a Native Module that uses Significant-Change Location Service, which provides a good balance between power-saving and accuracy.

Updating info.plist

Let’s first take care of some housekeeping before we start writing code. Open up Xcode or an inline editor and add the following entitlements:

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>This is needed to get location updates in the background</string> <key>NSLocationWhenInUseUsageDescription</key> <string>This is needed to get location updates when in use</string>

N.B – these keys changed slightly starting with iOS 11, so if you support older versions, the NSLocationAlwaysUsageDescription key is also required. 

You’ll also need to open up the Project Editor in Xcode (by double-clicking the top-most node of the Project Navigator). Under the “Targets” section, toggle Background Modes to be ON and check both Location Updates and Background Fetch from the list.

location

With that done, we are ready to start bridging our javascript to native code!

Adding the Native Module

If you’ve written iOS apps in Swift or Objective C, the process of adding a native module should be very familiar, since a Native Module is just an Objective-C class that implements whatever native API we can’t get to in React Native via an existing module. We start by creating a header file that will define the module, like so:

#import <React/RCTBridgeModule.h>

@interface MyLocationDataManager : NSObject <RCTBridgeModule> @end

Now, let’s create the module:

#import "MyLocationDataManager.h" #import <React/RCTLog.h> #import <CoreLocation/CLError.h> #import <CoreLocation/CLLocationManager.h> #import <CoreLocation/CLLocationManagerDelegate.h>

@implementation MyLocationDataManager { CLLocationManager * locationManager; NSDictionary<NSString *, id> * lastLocationEvent; }

– (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); }

RCT_EXPORT_MODULE(MyLocationDataManager);

– (NSDictionary *)constantsToExport { return @{ @"listOfPermissions": @[@"significantLocationChange"] }; }

+ (BOOL)requiresMainQueueSetup { return YES; // only do this if your module exports constants or calls UIKit }

//all methods currently async RCT_EXPORT_METHOD(initialize:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { RCTLogInfo(@"Pretending to do something natively: initialize");

resolve(@(true)); }

RCT_EXPORT_METHOD(hasPermissions:(NSString *)permissionType hasPermissionsWithResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { RCTLogInfo(@"Pretending to do something natively: hasPermissions %@", permissionType); BOOL locationAllowed = [CLLocationManager locationServicesEnabled]; resolve(@(locationAllowed)); }

RCT_EXPORT_METHOD(requestPermissions:(NSString *)permissionType requestPermissionsWithResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { NSArray *arbitraryReturnVal = @[@"testing…"]; RCTLogInfo(@"Pretending to do something natively: requestPermissions %@", permissionType); // location if (!locationManager) { RCTLogInfo(@"init locationManager…"); locationManager = [[CLLocationManager alloc] init]; } locationManager.delegate = self; locationManager.allowsBackgroundLocationUpdates = true; locationManager.pausesLocationUpdatesAutomatically = true;

if ([locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) { [locationManager requestAlwaysAuthorization]; } else if ([locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) { [locationManager requestWhenInUseAuthorization]; }

[locationManager startUpdatingLocation]; [locationManager startMonitoringSignificantLocationChanges];

resolve(arbitraryReturnVal); }

– (NSArray<NSString *> *)supportedEvents { return @[@"significantLocationChange"]; }

– (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { CLLocation* location = [locations lastObject]; lastLocationEvent = @{ @"coords": @{ @"latitude": @(location.coordinate.latitude), @"longitude": @(location.coordinate.longitude), @"altitude": @(location.altitude), @"accuracy": @(location.horizontalAccuracy), @"altitudeAccuracy": @(location.verticalAccuracy), @"heading": @(location.course), @"speed": @(location.speed), }, @"timestamp": @([location.timestamp timeIntervalSince1970] * 1000) // in ms };

RCTLogInfo(@"significantLocationChange : %@", lastLocationEvent); // TODO: do something meaningful with our location event. We can do that here, or emit back to React Native // https://facebook.github.io/react-native/docs/native-modules-ios.html#sending-events-to-javascript }

@end

It’s important that before we attempt to start getting location info, we request the users permission to use that capability. Notice also, that we request the more intense permission () and fall back to the less demanding option if possible. As is always the case in The Land of Apple, we only have one chance to ask for these permissions in-app (though a user can always modify them from settings), so carefully consider when and where in the app this should occur. Generally speaking, it’s best to ask for permissions only at the time the app starts needing them, so that the user is saturated with requests up front.

Once we’ve identified an appropriate place to request location data, we can import our native module and make the request:

// @flow import { NativeModules } from 'react-native';

var location = NativeModules.MyLocationDataManager;

async requestPermissions() { const result = await location.requestPermissions('');

return result; }

We’re now ready to get location data! From here, we have a few options for using info from our native module. We can subscribe to events, or call custom methods directly as above, depending on our needs. Be sure to review the documentation for the implementation that most suits your needs.

For now though, let’s test our native module using basic logging , which we added to MyLocationDataManager.m on line 98. The easiest way to test significant location change is via the simulator, which allows us the control of mocking a location without leaving the comfort of our chairs 🙃

Once the simulator is running and the app has started (via normal react-native run-ios, or deployed directly from XCode), we can launch the debugging bridge and open an inspector so we can see the console output as it is emitted from the app. Once our connected, we can navigate in-app to where we request location data. We should see our permission request:

Screen Shot 2019-07-19 at 11.08.50 AM.png

From the Simulator menu, we can update the simulator location of our device from the Debug dropdown:

Screen Shot 2019-07-19 at 11.13.08 AM.png

…and the change is emitted into the console via RCTLogInfo. 

Screen Shot 2019-07-19 at 11.14.09 AM.png

We did it! With location data verified, we’re now ready to write up whatever more sophisticated implementation(s) suit our needs. But, that’s an exercise for another day 🤔

Troubleshooting Tips

If you’re having trouble getting the location data permissions to show, here are some common questions to ask:

  • is info.plist updated with the required entitlements? Are the entitlement keys free of typos?
  • is info.plist using the correct keys? NSLocationAlwaysAndWhenInUseUsageDescription for iOS 11 and beyond and NSLocationAlwaysUsageDescription for anything prior? Are both keys included if older and contemporary iSO versions are supported?
  • have Background Modes been enabled from the Project Editor?
  • have we requested the users’ permission prior to subscribing to location change events or querying for location data explicitly?
  • has permission for location data previously been denied to the app?

Hopefully this brief look at Native Modules in React Native was illuminating! Stay tuned for a follow up post about getting location data in Android, and check out the official documentation in the meantime

Shout out to npomfret, whose source repo here heavily influenced the content for my example location manager above.  

Photo Credit: Capturing the Human Heart on UnSplash

Tagged iOS, location, react-native

Full-time developer, part-time hobby-jogger, Tsar of awful check-in comments. I like cooking, exploring Chicago, and a good story. I write code sometimes. View all posts by Joe Meyer


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK