Documentation

Microsoft's Location Insights SDK makes it easy to add location insights to your app.

Choose your platform to get started:

Getting Started - Android

Requirements:

  • Android 4.0.1+
  • Permissions - Internet, Fine Location, Course Location, and Activity Recognition

Integrate Android SDK for Analytics

Drop in a single line of code to start LOOP with analytics.

  1. Gradle Dependencies: Add the SDK to the dependencies of your gradle file (the gradle file in the module, and not app, folder)
    compile 'ms.loop:loopsdk:2.0.2'
  2. Add permissions: Request the following permissions in your AndroidManifest.xml file.
    <uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.INTERNET" />

    SDK Version: Set the targetSDKVersion to 22 or below. If you must target Android 23, you need to call requestPermissions() to get fine_location

    <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="22" />
  3. Initialize the SDK : Update the onCreate() method of your Application class with the following code. Loop will create an unique userId and deviceId for each user.
    @Override
    public void onCreate() {
        super.onCreate();
        String appId = "";
        String appToken = "";
        // iniitalize the sdk with the your appId and token. the last parameters enables location tracking
        LoopSDK.initialize(getApplicationContext(), appId, appToken, true)
    }
  4. You are done! LOOP is now ready to collect location analytics. Check your developer dashboard for stats.

Integrate Android SDK for Insights

Initialization guide for using LOOP insights. LOOP auto detects tricks and visited location history, and returns these as insights you can use your app.

  1. Gradle Dependencies: Add the SDK to the dependencies of your gradle file (the gradle file in the module, and not app, folder)
    compile 'ms.loop:loopsdk:2.0.2'
  2. Implement the ILoopSDKCallback callback in your Application Class:
    • If you have an Application class: Implement the ILoopSDKCallback interface in your class.
    • If you do not have an Application class: Create a new Java file, extend the Application class, and implement the ILoopSDKCallback. Remember to register the class in your AndroidManifest.xml.
  3. Permissions: Add these permissions to your AndroidManifest.xml file.
    <uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.INTERNET" />

    SDK Version: Set the targetSDKVersion to 22 or below. If you must target Android 23, you need to call requestPermissions() to get fine_location

    <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="22" />
  4. Initialize the SDK : Update the onCreate() de. Loop will create an unique userId and deviceId for each user.
    @Override
    public void onCreate() {
        super.onCreate();
        String appId = "";
        String appToken = "";
        // initialize the Loop SDK with your appId and token
        LoopSDK.initialize(this, appId, appToken);
    
        // Optional: initialize the Loop SDK with user id and device id
        String userId = "";
        String deviceId = "";
        LoopSDK.initialize(this, appId, appToken, userId, deviceId);
    }
  5. Override SDK Methods: Your Application class needs to override the following LOOP methods:
    @Override
    public void onInitialized() {}
    @Override
    public void onInitializeFailed(LoopError loopError) {}
    @Override
    public void onServiceStatusChanged(String provider, String status, Bundle bundle) {}
    @Override
    public void onDebug(String output) {}
    Show details
    Callbacks:
    • onInitialized()- called when the SDK has successfully initialized. You cannot interact with the SDK (no trips, no drives, no signals, etc) until LOOP is initialized.
    • onInitializeFailed(LoopError error) - called when the SDK fails to initialize. Use error.toString() to see the error message.
    • onServiceStatusChanged(String provider, String status, Bundle bundle) - called when the status of the location provider changes. For example, when the user turns on airplane mode and location signals stop, onServiceStatusChanged will be triggered with status SERVICE_STATUS_STOPPED.
      • Provider - the provider name. Currently this may be LoopLocationProvider or LoopActivityProvider.
      • Status - reports the change in the provider:
        • SERVICE_STATUS_STARTED
        • SERVICE_STATUS_FAILED
        • SERVICE_STATUS_STOPPED
        • SERVICE_STATUS_UPDATED
        • SERVICE_STATUS_SUSPENDED
      • Bundle - optional data send by the location provider (ex: whether the location coordinates were sent by the GPS or network).
    • onDebug(String output) - Logs for debugging
  6. Start sending location signals: In the onInitialized() callback, start the LocationProvider to starte sending signals to LOOP.
    public void onInitialized() {
        // Starts the locationProvider to collect signals. 
        LoopLocationProvider.start(SignalConfig.SIGNAL_SEND_MODE_BATCH);
    
         // Optional: Sends a location signal immediately  
        Location location = LoopLocationProvider.getLastLocation();
        if (location != null) {
            LoopSDK.sendTestLocationSignal();
        }
    }
  7. Start processors and turn signals into insights: LOOP offers three signal processors:
    • Locations - The KnownLocationProcessor class is used for location history and geofencing.
      KnownLocationProcessor locationProcessor = new KnownLocationProcessor();
      locationProcessor.initialize(); 
    • Trips - The TripProcessor creates paths from all activities - such as walking, biking, and driving.
      TripProcessor tripProcessor = new TripProcessor();
      tripProcessor.initialize();
    • Drives - The DriveProcessor class is a subset of the TripsProcessor class. It only includes paths from driving activities.
      DriveProcessor driveProcessor = new DriveProcessor();
      driveProcessor.initialize(); 
  8. You are done! Run the app on your phone to see it working. Running LOOP on an emulator will not send location signals. LOOP is now ready to collect signals and turn those signals into useful insights. Go for a drive or walk, and then head to your dashboard to see your data.

  9. Next step: Check out these code snippets to do more with loop:

Sample Android Integration

This is a sample Application Class with the DriveProcessor initialized. You can create a new Application Class with this code into your app to quickly get started. We have open sourced sample apps on Github that highlight LOOP features.

public class LoopApplication extends Application implements ILoopSDKCallback {
    @Override
    public void onCreate() {
        super.onCreate();

        // replace appId and device id below
        String appId = "";
        String appToken = "";
        
        // Initialize with appId and appToken
        LoopSDK.initialize(this, appId, appToken);
    }

    @Override
    public void onInitialized() {
        // Starts provider to listen for signals
        LoopLocationProvider.start(SignalConfig.SIGNAL_SEND_MODE_BATCH);

        // Starts drive processor
        DriveProcessor driveProcessor = new DriveProcessor();
        driveProcessor.initialize();
        
        // You will NOT receive insights from LOOP until the SDK is initalized
        // You can send an intent to your MainActivity when LOOP initializes successfully
        // Intent i = new Intent("android.intent.action.onInitialized").putExtra("status", "initialized");
        // this.sendBroadcast(i);
    }
    @Override
    public void onInitializeFailed(LoopError loopError) {
        Log.d("LoopLog", "Error: " + loopError.toString());
    }

    @Override
    public void onServiceStatusChanged(String provider, String status, Bundle bundle) {}

    @Override
    public void onDebug(String output) {}
}

Getting Started - iOS

iOS Requirements

Integrate iOS SDK

The easiest way to integrate the iOS SDK is with Carthage. Carthage is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.

You can install Carthage with Homebrew using the following command:

$ brew update
$ brew install carthage 

Step 1 - Adding LOOP framework

  1. Create a new project, and go to the project's root's directory
  2. In your project directory, create a file called Cartfile (no extension). Edit the file to add:
    github "Alamofire/Alamofire" ~> 3.0
    github "LpDevBuilder/Loop-iOS-SDK-Framework" ~>2.0
  3. Run carthage update --platform iOS to build the framework at the root directory of your project, the two frameworks are then built in your project root directory Carthage/Build/iOS
  4. Drag the built LoopSDK.framework and Alamofire.framework into the "embedded binaries" of your project. The frameworks should be under Carthage -> Build -> iOS. The Alamofire.framework may take a minute to appear.

Step 2 - Enable Location Tracking

  1. In Xcode, click your Project in the site bar. Navigate to Project -> Targets (your target app) -> Capabilities -> Background Modes. Turn on Background mode. Find "Location Updates", and select it as well.

  2. In the Info.plist of your main project folder (there are also Info.plist for each test project folders that XCode creates), add NSLocationAlwaysUsageDescription (String) "Location Tracking (or any string explaining why you need this permission)"

Step 3 - Initializing the SDK in your code

  1. In your AppDelegate.swift, add import LoopSDK
  2. In func application, initialize Loop with
     LoopSDK.initialize(appID: , token: );
  3. (Optional) If you would like to get the status of LoopSDK initialization, you can implement LoopSDKListener and initialize Loop with:
    LoopSDK.initialize(self, appID: , token: );
  4. (Optional) If you would like to initialize the SDK with a user, you can set the following before initializing LOOP.
    LoopSDK.setUserID(userID:String)
    LoopSDK.setDeviceID(deviceID:String)
  5. Congrats! You are done with integration and ready to start using LOOP insights. Check out these code samples to see what else you can do with LOOP:

Sample AppDelegate Class with LOOP

This is a sample AppDelegate.swift LOOP integration

import LoopSDK
@UIApplicationMain
// Implement the LoopSDKListener
class AppDelegate: UIResponder, UIApplicationDelegate, LoopSDKListener {
        var window: UIWindow?

	func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
            // Get your app id and token from the LOOP developer dashboard
            let appID = ""
            let appToken = ""
            
            // Initialize LOOP. Designate callback for self
            LoopSDK.initialize(self, appID: appID, token: appToken);

            // (Optional) initialization logging
            LoopSDK.logManager.logEvent("Launch option \(launchOptions)")
            return true
	}
    
    // (Optional) Add the LoopSDKListener to receive a callback when Loop initializes
    func onLoopInitialized() {
        print("on initialized")            }
    
    // (Optional) Callback for initialization errors
    func onLoopInitializeError(error: String) {
        print("initialize error \(error)")
    }
}

Test users

Plugin a test user ID and device token to get sample trips and visited locations for your app.

  • Creating test users:

    Go to the Users tab of your app in your dashboard (you need to be logged in for this link to work). If you do not have any test users, click create test users.

  • Initialize the SDK:
     LoopSDK.initialize(getApplicationContext(), appId, appToken, testUserID, testDeviceId, true);
    
  • If I continue to use the test userId, will LOOP generate additional trips and home/work profiles?
    Yes. The test user is unique to your app. If you continue to use your app with a test userId and deviceToken, LOOP will generate new location insights. It will take longer for LOOP to generate your actual home and work profile because the home and work scores for your test users are high.
  • How can I see the test user trips and locations in my app?

    Create a test user through the developer dashboard. This test user will have pre-generated trips and locations. In your app, initialize LOOP with the test userId and deviceToken. After the SDK initializes, download the user's locations and trips.

Sample Code

public class MainActivity extends AppCompatActivity {
    private Drives testDrives;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Initialize LOOP in your application class. 
        // Register a receiver to listen for Loop Initialize intent
        IntentFilter intentFilter = new IntentFilter("android.intent.action.onInitialized");
        BroadcastReceiver mReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (LoopSDK.isInitialized()) {
                    // After LOOP initialize, you can download drives from server
                    downloadDrives();
                }
            }
        };
        registerReceiver(mReceiver, intentFilter);
    }

    private void downloadDrives() {
        testDrives.download(true, new IProfileDownloadCallback() {
            @Override
            public void onProfileDownloadComplete(int itemCount) {
                List sortedDrives = testDrives.sortedByStartedAt(); 
                for (int i = 0; i < sortedDrives.size(); i++){
                    // you can get properties from each drive. ex: the minutes of each drive
                    int minutesDriven = sortedDrives.get(i).getDurationMinutes();
                }
       
            }
            @Override
            public void onProfileDownloadFailed(LoopError error) {
                Log.d("LoopLog", "Profile download failed: " + error.toString());
            }
        });
    }
}
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // Initialize the SDK
    LoopSDK.initialize(nil, appID: "YOUR APP ID", token: "YOUR APP TOKEN");

    // Set the test userID and deviceID 
    // You can create test users unique to your app in the developer dashboard's users tab
    LoopSDK.setUserID("USER ID");
    LoopSDK.setDeviceID("DEVICE ID");
    
    // Drives are processed on the server. Sync with the server, and get the drives
    // If the user is offline, we store their signals locally. The signals are uploaded
    // once they are online to process the signals into trips.
    LoopSDK.syncManager.getDrives { (drives:[LoopTrip]) i
        // do something with drives
        print("number of drives %d", drives.count)
    }
    return true
}

SDK Reference

To learn how to view trip details, trigger geofencings, callbacks based on alerts, and other features supported by the LOOP SDK, please checkout our SDK references for iOS and Android.

SDK Reference

Code Snippets

The following are examples of common scenarios you can accomplish with LOOP:

Calculate distance driven

private void calculateDistanceDriven() {
	double distanceDrive = 0;
	// Loads list of drives stored on disk
	List drives = Drives.createAndLoad(Drives.class, Drive.class);
	for (int i = 0; i < drives.size(); i++) {
		distanceDrive += drives.get(0).getRouteDistanceInMiles();
	}
}
// Async queries are queued, so you can get drives even regardless of whether the LoopSDK // initialization has finished or not
LoopSDK.syncManager.getDrives {
    (drives:[LoopTrip]) in
    
    //drives contains a list of your drives
    var totalDistanceKM = 0.0
    if drives.isEmpty {
        print("no user drives");
    } else {
        drives.forEach { drive in
            totalDistanceKM = totalDistanceKM + drive.distanceTraveledInKilometers;
        }
    }
}

Show your trip on a map

public void drawPath(int entityId) {
    // Load all trips on disk
    Trips trips = Trips.createAndLoad(Trips.class, Trip.class);
    
    // Get the trip by entityId
    Trip myTrip = trips.byEntityId(entityId);

    // If the trip is not found, look in drives for the same entityId
    if (myTrip == null) {
        Drives drives = Drives.createAndLoad(Drives.class, Drive.class);
        myTrip = drives.byEntityId(entityId);
        if (myTrip == null) return;
    }

    // Get the latitude and longitude of the trip path
    GeospatialPoint firstPoint = trip.path.points.get(0);
    PolylineOptions options = new PolylineOptions()
            .add(new LatLng(firstPoint.latDegrees,firstPoint.longDegrees))
            .width(10)
            .color(Color.BLUE)
            .geodesic(true).clickable(true);
    mMap.addMarker(new MarkerOptions().position(new LatLng(firstPoint.latDegrees,firstPoint.longDegrees)).title("Trip starts"));
    LatLng latLng = new LatLng(firstPoint.latDegrees, firstPoint.longDegrees);

    // Cycle through trip path and plot each point
    for (GeospatialPoint point: trip.path.points) {
        latLng = new LatLng(point.latDegrees,point.longDegrees);
        mMap.addCircle(new CircleOptions()
                .center(latLng)
                .radius(20)
                .strokeColor(Color.RED)
                .fillColor(Color.RED));
        options.add(latLng);
    }

    mMap.addPolyline(options);
    mMap.addMarker(new MarkerOptions().position(latLng).title("Trip ends"));
    mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 12));
}
import UIKit
import MapKit
import LoopSDK

// Class for showing annotations on drive signals
class LoopPointAnnotation: MKPointAnnotation {
}

// You can initialize the MapViewController with: 
// let vc = storyboard.instantiateViewControllerWithIdentifier("MapViewController") as! MapViewController
// vc.driveData

class MapViewController: UIViewController, MKMapViewDelegate {
	
	@IBOutlet weak var mapView: MKMapView!
	var driveData:LoopTrip? = nil;
	
	override func viewDidLoad() {
		super.viewDidLoad()
		
		if driveData != nil {
			mapView.showAnnotations(driveData!.path.enumerate().map { index, element in
				return createAnnotationFromLocation(index, location: element)}, animated: false)
			var points = driveData!.path.map { return $0.coordinate }
			let polyline = MKPolyline(coordinates: &points, count: points.count)
			mapView.addOverlay(polyline)
			mapView.delegate = self;
		}
	}
    
    func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
        // Maps the drive path
        let polylineRenderer = MKPolylineRenderer(overlay: overlay)
        polylineRenderer.strokeColor = UIColor.blueColor()
        polylineRenderer.lineWidth = 2
        return polylineRenderer
    }
	
	func createAnnotationFromLocation(index: Int, location: LoopTripPoint) -> MKPointAnnotation {
		// Add annotation to each location point 
        // When you tap on the location point, a label with the a trip time pops up
		let annotation = LoopPointAnnotation()
		let dateFormatter = NSDateFormatter();
		dateFormatter.dateFormat = "MM-dd HH:mm";
		
		annotation.coordinate = location.coordinate
		annotation.title = "Point #\(index)"
		annotation.subtitle = "\(dateFormatter.stringFromDate(location.timeAt))"
		
		return annotation;
	}
}

Trigger an alert when arriving home

// You can pass in a label to identify the callback
LoopLocationProvider.registerCallback("homeTrigger", new LoopLocationProvider.ILocationProviderCallback() {
    @Override
    // User has entered their home or work
    public void onLocationChanged(Location location) {}
        if (knownLocation.hasLabels()){
            Label label = knownLocation.labels.getLabels().get(0);
            if (label.name.equals("home")){
                // Trigger action when user is home
            }
        }
    }

    @Override
    // User has exited their home or work location 
    public void onKnownLocationExited(KnownLocation location) {}
    
    @Override
    // User has entered or exited a location other than their home or work
    public void onLocationChanged(Location location) {}

    @Override
    // User has moved from still to motino or motion to still
    public void onModeChanged(int modeFrom, int modeTo, Location location) {}
User has entered or exited a location});
import LoopSDK
import CoreLocation

// Add LoopLocationProviderListener to listen to location changes
class AppDelegate: UIResponder, UIApplicationDelegate, LoopLocationProviderListener {

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Initialize the LOOP SDK
        LoopSDK.initialize(self, appID: "YOUR APP ID", token: "YOUR APP TOKEN");
        // Add your class as the location listener
        LoopSDK.loopLocationProvider.addListener(self)
        return true
    }

    // triggers enters when a user enters a location
    func onLocationEnter(visit: CLVisit, location: LoopLocation) {
        location.labels.forEach { label in
            if (label.name == "home") {
                print("Home score %i", label.score)
            }
            if (label.name == "work") {
                print("Work score %i", label.score)
            }
        }
        // array of all previous visits. most recently visited is last.
        location.visits 
        print("Latitude %f, Longitude %f", location.latitude, location.longitude);
    }
}

// Additional geofencing functions 
optional public func onNewLocationUpdate(newLocation: CLLocation, activity: CMMotionActivity?)
optional public func onLocationEnter(newVisit: CLVisit, location: LoopSDK.LoopLocation)
optional public func onLocationExit(newVisit: CLVisit, location: LoopSDK.LoopLocation)
optional public func onNewVisitUpdate(newVisit: CLVisit)
optional public func onLocationUpdateError(error: NSError)
optional public func onLocationUpdatePause()
optional public func onLocationUpdateResume()
optional public func onKnownLocationUpdate(locations: [LoopSDK.LoopLocation])

Trigger an alert when the user starts driving

private void drivingTrigger() {
    // Get user drives
    final Drives myDrives = Drives.createAndLoad(Drives.class, Drive.class);
    
    // Register callbacks when drives are added and updated
    myDrives.registerItemChangedCallback("Drives", new IProfileItemChangedCallback() {
        @Override
        public void onItemChanged(String entityId) {
            // This callback triggers when a new point is added to a drive
            // Use the entity ID to look up the drive, and get the user's activity
            Drive drive = myDrives.byEntityId(entityId);
            if (drive.receivedDrivingMotion) {
                // Send an alert
            }
        }
        @Override
        public void onItemAdded(String entityId) {
        }

        @Override
        public void onItemRemoved(String entityId) {}
    });
}

Autoreply to a text if the user is driving

private void autoReplySms() {
    // when you receive an SMS, use the LoopActivityProvider to get the current activity
    if (LoopActivityProvider.getCurrentActivity().equals(LoopActivity.DRIVING)) {
        // driving - send an autoreply
    } else if (LoopActivityProvider.getCurrentActivity().equals(LoopActivity.STILL)) {
        // user is still - no autoreply needed
    }
}

Sample Apps

Check out our github repos for sample LOOP apps. We encourage you to customize these apps to get started with the SDK.

Android Apps

  1. Trips and Drives Sample App
  2. Home / Work Sample App
  3. Hello World App - initializing Loop

iOS Apps

  1. Trips and Drives Sample App
  2. Home / Work Sample App
  3. Hello World App - initializing Loop

FAQ

  1. What is the Location and Observation Platform?

    LOOP enables mobile developers to easily make their apps location aware.

  2. What is a location insight?

    LOOP turns raw signals (GPS and network latitude and longitude points) into location insights that helps you create interest apps for your users. LOOP currently offers three insights: known locations (home and work), drives, and trips.

  3. Do my users need to be online to generate inferences with LOOP?

    LOOP can generate trips and location inferences locally. You can sync signals with the server when the user is online.

  4. What’s the memory profile of LOOP on Android?

    Small. We have extensively tested LOOP on a range of Android devices, and the memory usage was negligible. If you run into memory issues, please post an issue on UserVoice with your device and OS.

  5. Is there an API to get signals and location inferences?

    We currently do not offer a Javascript API. If you would like this feature, please let us know on our UserVoice page.

  6. How much battery does LOOP use?

    Because we poll the user's location, LOOP will have an effect on battery. However, we have optimized our algorithm to reduce location polling whenever possible so LOOP has a minimal effect on battery. For Android, LOOP uses a minimal of 1% battery.

    If battery is an issue for you, please post an issue on UserVoice with the device and OS info.

  7. Does the SDK require additional permissions?

    The LOOP SDK requests three permissions to generate location insights:

    <uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.INTERNET" />

  8. What is the difference between the development and enterprise tiers?

    Development tier will be capped in the number of signals you can send. The enterprise tier is unlimited in the number of users, signals, and insights.

    To thank you for being an early adopter of LOOP, usage is unlimited in BETA.

  9. How much does LOOP cost?

    LOOP is free during beta. See our pricing page for more info.

Contact Us & Feedback

We want to hear from you! If you have feature suggestions or run into issues, join our UserVoice community and let us know.

Join our UserVoice community