SDK

Location Services

In this page you'll dive deeper into functionality like using GPS signals to get the user's location or monitor their visits to regions and proximity to BTLE devices.

These services will bring a new level of contextuality to your app, allowing you to create geo-triggers to send notifications or categorize users based on their location behaviour.

Tracking a user location in iOS will require the user's authorization. This means that your app must declare usage description texts explaining why it needs to track location. It is mandatory the use of the key NSLocationAlwaysUsageDescription. In iOS 11, a new permission was introduced, and you are required to include both NSLocationAlwaysAndWhenInUseUsageDescription and NSLocationWhenInUseUsageDescription in your Info.plist file. In short, if you want to support all versions, you need to include the following:

<plist version="1.0">
<dict>
    <key>NSLocationWhenInUseUsageDescription</key>
    <string>We will need to make use of your location to present relevant information about offers around you.</string>
    <key>NSLocationAlwaysUsageDescription</key>
    <string>We will need to make use of your location to present relevant information about offers around you.</string>
    <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
    <string>We will need to make use of your location to present relevant information about offers around you.</string>
</dict>
</plist>

Make sure you provide a text that best describes why and how you are going to use the user’s location. This text will be displayed to the user the first time you request the use of location services. Users are more likely to trust your app with location data if they understand why you need it.

As mentioned in the Implementation page, if you are going to use location services, you must include the following dependency in your package.json:

{
  "dependencies": {
    "react-native-notificare-geo": "^3.0.0"
  }
}

Requesting Permissions

Additional to the manifest permissions and plist entries, applications will need to request the user permission to use location.

These permissions need to be requested by your app, after which you can safely enable location updates.

In order to fully leverage the geo functionality, the users needs to allow access to background location. To allow for an easier handling of permissions between versions, your app needs to make a distinction between foreground and background location updates permissions.

It is important to note the Notificare library is not notified of authorization status changes, therefore you have to call NotificareGeo.enableLocationUpdates() on every authorization status change. This allows us to check the latest authorization and act accordingly by enabling location tracking, geofencing or clearing up location data based on the user's decision.

In order to achieve what was mentioned above, here is a rough implementation to serve as a guide. You'll have to adjust this sample to match your application.

async function onEnableLocationUpdatesClicked() {
  try {
    const hasFullCapabilities =
      (await ensureForegroundLocationPermission()) &&
      (await ensureBackgroundLocationPermission()) &&
      (await ensureBluetoothScanPermission());

    // Calling enableLocationUpdates() will evaluate the given permissions, if any, and enable the available capabilities.
    await NotificareGeo.enableLocationUpdates();
  } catch (e) {
    // Handle the platform error.
  }
}

async function ensureForegroundLocationPermission(): Promise<boolean> {
  const permission: Permission = Platform.select({
    android: PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION,
    ios: PERMISSIONS.IOS.LOCATION_WHEN_IN_USE,
  })!;

  let status = await check(permission);
  if (status === 'granted') return true;

  if (status === 'blocked') {
    // TODO: Show some informational UI, educating the user to change the permission via the Settings app.
    await openSettings();
    return false;
  }

  status = await request(permission, {
    title: 'Sample',
    message: 'We need access to foreground location in order to show relevant content.',
    buttonPositive: 'OK',
  });

  return status === 'granted';
}

async function ensureBackgroundLocationPermission(): Promise<boolean> {
  const permission: Permission = Platform.select({
    android:
      Platform.Version >= 29 // Android Q+
        ? PERMISSIONS.ANDROID.ACCESS_BACKGROUND_LOCATION
        : PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION,
    ios: PERMISSIONS.IOS.LOCATION_ALWAYS,
  })!;

  let status = await check(permission);
  if (status === 'granted') return true;

  if (status === 'blocked') {
    // TODO: Show some informational UI, educating the user to change the permission via the Settings app.
    await openSettings();
    return false;
  }

  status = await request(permission, {
    title: 'Sample',
    message: 'We need access to background location in order to show relevant content.',
    buttonPositive: 'OK',
  });

  return status === 'granted';
}

async function ensureBluetoothScanPermission(): Promise<boolean> {
  if (Platform.OS === 'android') {
    if (Platform.Version < 31) return true;

    let status = await check(PERMISSIONS.ANDROID.BLUETOOTH_SCAN);
    if (status === 'granted') return true;

    if (status === 'blocked') {
      // TODO: Show some informational UI, educating the user to change the permission via the Settings app.
      await openSettings();
      return false;
    }

    status = await request(PERMISSIONS.ANDROID.BLUETOOTH_SCAN, {
      title: 'Sample',
      message: 'We need access to bluetooth scan in order to show relevant content.',
      buttonPositive: 'OK',
    });

    return status === 'granted';
  }

  return true;
}

The snippet above uses the react-native-permissions plugin to handle the bridge into native permissions. You can use any other plugin or implement the bridge yourself. The focus of the snippet is guiding you on the necessary permissions and recommended upgrade path.

To make sure the location updates aren't started before the library is ready to be used, you should wait until it is safe to do so, by listening to the ready event. Once you have requested the appropriate permissions, and they have been granted, you can call NotificareGeo.enableLocationUpdates() to start receiving location updates. Our library will automatically collect the user location and start monitoring for regions you've created via the dashboard or API.

You can also check whether the user enrolled on location updates.

await NotificareGeo.hasLocationServicesEnabled();

Receiving geo events

Once you start monitoring location updates in your app, you can subscribe to certain events and perform additional operations as necessary. However, it's worth mentioning you may not receive these events when they occur on the background while your application is in a dead state due to how the hybrid engine is created.

NotificareGeo.onLocationUpdated((location) => {

});

NotificareGeo.onRegionEntered((region) => {

});

NotificareGeo.onRegionExited((region) => {

});

NotificareGeo.onBeaconEntered((beacon) => {

});

NotificareGeo.onBeaconExited((beacon) => {

});

Using Bluetooth Low-Energy Beacons

Once you've implemented the location functionality, we automatically monitor BTLE beacons in the user's vicinity.

Since Android 12, your app needs explicit permission for Bluetooth scanning in order to detect beacons. On devices running older versions of Android, the permission will be granted automatically.

However, you might want to retrieve the proximity level of any beacons around you. This is only possible while the app is in foreground. To get this information you will need to implement the following listener:

NotificareGeo.onBeaconsRanged(({ region, beacons }) => {

});

This is not a mandatory step in order to use beacons, as geo-triggers created in the dashboard will trigger notifications whenever you are in the vicinity of a beacon, even when your app is not being used.

Beacon scanning with a foreground service

In Oreo and up, background scans are more limited. First scans of a beacon in a region will come in very quickly, but detection of changes or leaving the beacon's range will take up to 15 minutes when in background. This limitation is posed by Android itself and there is no workaround to do this in background mode.

The only way to have your app scan for beacons more often on Android version Oreo and up, is by starting the scan as a foreground service. Foreground services will be shown to the user as an ongoing notification. You can opt in to this feature by adding the following to your AndroidManifest.xml:

<application>
  <meta-data
      android:name="re.notifica.geo.beacons.foreground_service_enabled"
      android:value="true" />
</application>

By default, the notification will be shown in the default channel from the Push module. If you are not using it, or would like to customise how the notification looks like, you can use the following meta-data properties.

<application>
    <meta-data
        android:name="re.notifica.geo.beacons.service_notification_channel"
        android:value="custom_beacon_notification_channel" />

    <meta-data
        android:name="re.notifica.geo.beacons.service_notification_small_icon"
        android:resource="@drawable/ic_baseline_bluetooth_searching_24" />

    <meta-data
        android:name="re.notifica.geo.beacons.service_notification_content_title"
        android:value="Scanning for beacons" />

    <meta-data
        android:name="re.notifica.geo.beacons.service_notification_content_text"
        android:value="A relevant piece of text informing the user why the app is scanning for beacons." />

    <meta-data
        android:name="re.notifica.geo.beacons.service_notification_progress"
        android:value="true" />
</application>

Using Visits API (iOS-only)

As part of our location services feature, you will be able to use Apple's Visits API. This API will allow you to get events whenever users visit a location. This might be a great way to discover which areas a user visits. To use this feature you will need to turn the VISITS_API_ENABLED property to YES in your app's NotificareOptions.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>VISITS_API_ENABLED</key>
	<true/>
</dict>
</plist>

When enabled, as soon as you start location updates you will be receiving visits events too. To handle those visits make sure you implement the following delegate method:

NotificareGeo.onVisit((visit) => {

});

Using Heading API (iOS-only)

Also part of our location services feature, Apple's Heading API will allow you to obtain the user's current heading. To use this feature you will need to turn the HEADING_API_ENABLED property to YES in your app's NotificareOptions.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>HEADING_API_ENABLED</key>
	<true/>
</dict>
</plist>

When enabled, as soon as you start location updates you will be receiving heading events too. To handle those events make sure you implement the following delegate method:

NotificareGeo.onHeadingUpdated((heading) => {

});

Checking the monitoring state

There are some use cases when it's useful to know what is the current state of the monitoring service. This can be particularly helpful during development. You can check, on-demand, which regions are being monitored like the following.

await NotificareGeo.getMonitoredRegions();

Similarly, you can also check which regions the device is currently inside of.

await NotificareGeo.getEnteredRegions();

Disable Location

The same way you enable location, you can stop tracking the user location by invoking the following method:

await NotificareGeo.disableLocationUpdates();