Get Started

🚧

PREREQUISITES

In order to implement the Movement SDK in your app, you must first complete the following:

1. Register Your App's Key Hashes

Important: In order for Movement SDK to authenticate with our server, you'll need to add your app's Android Key Hash to your app's Foursquare configuration settings in the Foursquare Developer Console.

  1. Generate a key hash of your developer certificate using this command:
keytool -list -v -keystore [your keystore file path]

For example, your debug key for development might look like:

keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
  1. In your Foursquare Developer Console on the Movement SDK Settings page, paste your Android SHA1 Key Hash in the Android Key Hashes field:

NOTE: you can add multiple key hashes delimited by commas.

  1. Save your changes.

2. Install the Movement SDK

a. Add the Dependency

Once you have been given a username and password from Foursquare, you can access the repository that has the Movement SDK. Add the following snippet to your root build.gradle file and you will be able to resolve the library.

allprojects {
    repositories {
        maven {
            url 'https://foursquare.jfrog.io/foursquare/libs-release/'
        }
    }
}

Then, add the latest version of the Movement SDK in the build.gradle file for the project in which you would like to use the Movement SDK:

implementation 'com.foursquare:movementsdk:4.0.0'

b. View Permissions

The following permissions are automatically added to your manifest file when you import the Movement SDK library:

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

📘

Removing Permissions

Depending on the type of app you're building, not all of the above-mentioned permissions are required. To remove any permission not required for your type of app, please include tools:node="remove"in the manifest file.

Removing a specific permission may be necessary to pass Google Play Store app submission if features of your app do not meet applicable requirements.

For example, only apps with health features that meet health app privacy requirements may use the ACTIVITY_RECOGNITION permission. If you're not building a health app, you can remove this permission by including tools:node="remove" in the manifest file as seen below:

<uses-permission-sdk-23 android:name="android.permission.ACTIVITY_RECOGNITION" 
    tools:node="remove"/>

Please let us know if you have any questions about this list. An example AndroidManifest.xml file can be found here.

3. Configure the Movement SDK

Use the following code to configure the Movement SDK notification handler in your Application's onCreate method. Also, you can optionally set the log level to DEBUG to receive more detailed logs while you're developing and persist them on disk.

MovementSdk.Builder builder = new MovementSdk.Builder(this)
        .consumer("CLIENT_ID", "CLIENT_SECRET")
        .notificationHandler(NotificationHandler)
        .logLevel(LogLevel.DEBUG);
MovementSdk.with(builder);
MovementSdk.with(
    MovementSdk.Builder(this)
        .consumer("CLIENT_ID", "CLIENT_SECRET")
        .notificationHandler(NotificationHandler)
        .logLevel(LogLevel.DEBUG)
)

If you are leveraging the Movement SDK in conjunction with our Personalization APIs, make sure to also include the oauthtoken parameter in the above code. Be sure to replace OAUTH_TOKEN with the real oauth token.

MovementSdk.Builder builder = new MovementSdk.Builder(this)
        .consumer("CLIENT_ID", "CLIENT_SECRET")
        .oauthtoken("OAUTH_TOKEN")
        .notificationHandler(NotificationHandler)
        .logLevel(LogLevel.DEBUG);
MovementSdk.with(builder);

Common Mistake: Why must the Movement SDK's configuration live in the Application's onCreate and not in an activity?

It relates to how the SDK functions within Android's app life cycle. The SDK works in the background and the Application's onCreate is the ONLY part of the app's life cycle that is guaranteed to be called, even when the app is in the background. When you put the SDK's configuration and notification handlers in an activity, it only gets called when that app is active, meaning you will miss user activity when the app is relaunched from the background.

a. Disable AdId transmission

If you're using the free tier or do not have a data sharing agreement, you don't need to transmit the phone's mobile AdId to Foursquare. While we currently just disregard the AdId in these cases as soon as it is received, you can disable the device from even sending it to Foursquare by calling the .disableAdIdentitySharing() method during the Movement SDK initialization:

MovementSdk.Builder builder = new MovementSdk.Builder(this)
        .consumer("CLIENT_ID", "CLIENT_SECRET")
        .notificationHandler(NotificationHandler)
        .logLevel(LogLevel.DEBUG);
        .disableAdIdentitySharing()
MovementSdk.with(builder);

b. Set up the Notification Handler

In order to be notified of visits, add the Movement SDK notification handlers to your Application's onCreate method.

private final NotificationHandler NotificationHandler = new NotificationHandler() {
    // Primary visit handler
    @Override
    public void handleVisit(Context context, VisitNotification notification) {
        // Process the visit however you'd like:
        Visit visit = notification.getVisit();
        Venue venue = visit.getVenue();
        Log.d("MovementSdk", visit.toString());
    }

    // Optional: If visit occurred while in Doze mode or without network connectivity
    @Override
    public void handleBackfillVisit(Context context, BackfillNotification notification) {
        // Process the visit however you'd like:
        super.handleBackfillVisit(context, notification);
        Visit visit = notification.getVisit();
        Venue venue = visit.getVenue();
        Log.d("MovementSdk", visit.toString());

    }

    // Optional: If visit occurred by triggering a geofence
    @Override
    public void handleGeofenceEventNotification(Context context, GeofenceEventNotification notification) {
        // Process the geofence events however you'd like:
        List<GeofenceEvent> geofenceEvents = notification.getGeofenceEvents();
        for (GeofenceEvent geofenceEvent : geofenceEvents) {
          Log.d("MovementSdk", geofenceEvent.toString());
        }
    }

    // Optional: If any changes in user state occured
    @Override
    public void handleUserStateChange(@NonNull Context context, @NonNull UserStateNotification notification) {
        // Process the new user state however you'd like:
        UserState userState = notification.getUserState()
        for (UserState.Component component : notification.getChangedComponents()) {
            Log.d("Changed Component", component.name)
        }
    }
};
private val NotificationHandler = object : NotificationHandler() {
// Primary visit handler
    override fun handleVisit(context: Context, notification: VisitNotification) {
        val visit = notification.visit
        val venue = visit.venue
        Log.d("MovementSdk", visit.toString())
    }

    // Optional: If visit occurred while in Doze mode or without network connectivity
    override fun handleBackfillVisit(context: Context, notification: BackfillNotification) {
        val visit = notification.visit
        val venue = visit.venue
        Log.d("MovementSdk", visit.toString())
    }

    // Optional: If visit occurred by triggering a geofence
    override fun handleGeofenceEventNotification(context: Context, notification: GeofenceEventNotification) {
        super.handleGeofenceEventNotification(context, notification)
        // Process the geofence events however you'd like. Here we loop through the potentially multiple geofence events and handle them individually:
        notification.geofenceEvents.forEach { geofenceEvent ->
            Log.d("MovementSdk", geofenceEvent.toString())
        }
    }


    override fun handleUserStateChange(context: Context, notification: UserStateNotification){
      // Process the new user state however you'd like:
      val userState = notification.UserState
      notification.changedComponents.forEach {
          Log.d("Changed state", it.name)
      }
}

}

Note the types of notifications you may receive:

  • handleVisit: The primary visit handler that receives arrival and departure events.
  • handleBackfillVisit: This handler receives visits that occurred when there was no network connectivity or for failed visits that have been retried. For arrivals, departureTime will be null.
  • handleGeofenceEventNotification: This handler receives visits for geofences.
  • handleUserStateNotification: This handler receives any updates to the user state.

4. Initialize Movement SDK

Once the SDK is configured and set up to handle events, you just need to request location permissions from your user and tell the SDK to start running by calling start when ACCESS_FINE_LOCATION has been granted:

MovementSdk.start(this);

Note: You must make sure the user has provided access to location permissions before starting the SDK. It is your responsibility as a developer to inform the user of how you are using these permissions and how it benefits them.

To help you maximize the number of users that opt into location sharing and your Movement SDK powered features, here are some recommendations for how you should handle requesting permissions:

  • For newer versions of Android, ask for location permission only when a user accesses a feature that uses location.
  • Make sure it's clear, in the app, why the user should provide access to their location and how it benefits their app experience.
  • If the user declines to give permission, consider places in your app that you can promote features that make use of background location.

a. Request Foreground Location

The SDK's traditional visit detection works effortlessly in the background, allowing continued interaction with your users regardless of if they are in your app or not. But what about when you want to manually find a user's current location while they are interacting within your app? The SDK allows you to actively request the current location manually when the app is in use by calling the MovementSdk.get().getCurrentLocation() method:

if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            Result<CurrentLocation, Exception> currentLocationResult = MovementSdk.get().getCurrentLocation();
            if (currentLocationResult.isOk()) {
                CurrentLocation currentLocation = currentLocationResult.getResult();
                Log.d("MovementSdk", "Currently at " + currentLocation.getCurrentPlace().toString() + " and inside " + currentLocation.getMatchedGeofences().size() + " geofence(s)");
            } else {
                Log.e("MovementSdk", currentLocationResult.getErr().getMessage(), currentLocationResult.getErr());
            }
        }
    }).start();
}
if (
    ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) ==
        PackageManager.PERMISSION_GRANTED
) {
    Thread(
            Runnable {
                val currentLocationResult:
                    com.foursquare.pilgrim.Result<CurrentLocation, Exception> =
                    PilgrimSdk.get().currentLocation
                if (currentLocationResult.isOk) {
                    val currentLocation: CurrentLocation = currentLocationResult.result
                    Log.d(
                        "PilgrimSdk",
                        "Currently at ${currentLocation?.currentPlace} and inside ${currentLocation?.matchedGeofences?.size} geofence(s)"
                    )
                } else {
                    Log.e(
                        "PilgrimSdk",
                        currentLocationResult.err!!.message,
                        currentLocationResult.err
                    )
                }
            }
        )
        .start()
}

This will return the current venue the device is most likely at (in the currentLocation object), as well as any geofences that the device is in (if configured). Note that this will forgo any "stop detection" so it shouldn't be used as a proxy for visits. For example, this method could return a high confidence result for Starbucks if called while someone is walking by the entrance.