Android

Getting Started

The steps below will guide you through installing the Factual Location Engine SDK into your app via Android Studio and Java.

🚧

After completing the Getting Started guide please refer to the rest of our developer docs for examples of how to:


1. Ensure the SDK requirements are met

  • The Factual Location Engine SDK is compatible with API level 21 or higher.
  • After compilation, the SDK should add approximately 1.5 MB to your app's download size.
  • The SDK requires ACCESS_FINE_LOCATION and INTERNET permissions.
  • Androidx support is enabled.

2. Get an API key

Sign up for access to Engine here.


3. Add Repositories and Dependencies

Provided below are examples of how to install the SDK via Maven (3a) or to install manually (3b)


3a. Install via Maven

Add the Factual and Maven Central repository to your app/build.gradle:

repositories {
  mavenCentral()
  maven { url "https://factual.bintray.com/maven" }
}

Add the Engine dependencies to app/build.gradle:

📘

Make sure to use the latest engine version by replacing [ENGINE_VERSION] in the code below with the latest version available in Bintray

dependencies {
  // Existing Dependencies
  ...

  // Third party libraries used by the Engine SDK:
  implementation 'com.android.volley:volley:1.1.0'
  implementation('com.google.android.gms:play-services-location:16.0.0', {
    exclude group: 'com.android.support', module: 'support-v4'
  })
  implementation 'org.apache.thrift:libthrift:0.11.0'

  def work_version = "2.1.0"
  implementation "androidx.work:work-runtime:$work_version"
  implementation "androidx.lifecycle:lifecycle-extensions:$work_version"
  annotationProcessor "androidx.lifecycle:lifecycle-compiler:$work_version"
 
  // The Engine Library:
  implementation 'com.factual:engine-sdk:[ENGINE_VERSION]'
}

You may need to exclude META-INF/DEPENDENCIES by adding a packagingOptions block under android in your app/build.gradle:

android {
  ...
  packagingOptions {
    exclude 'META-INF/DEPENDENCIES'
  }
}

3b. Install manually (alternative to Maven install)

Add Maven and the path to the AAR as repositories in your app/build.gradle:

repositories {
  // Volley and Thrift are found here
  mavenCentral()

  flatDir {
    // Path to the AAR, relative to your project's "app" dir.
    dirs "libs"
  }
}

dependencies {
  ...

Add the AAR and dependencies to app/build.gradle:

dependencies {
  // Existing Dependencies
  ...

  // Third party libraries used by the Engine SDK:
  implementation 'com.android.volley:volley:1.1.0'
  implementation('com.google.android.gms:play-services-location:16.0.0', {
    exclude group: 'com.android.support', module: 'support-v4'
  })
  implementation 'org.apache.thrift:libthrift:0.11.0'

  def work_version = "2.1.0"
  implementation "androidx.work:work-runtime:$work_version"
  implementation "androidx.lifecycle:lifecycle-extensions:$work_version"
  annotationProcessor "androidx.lifecycle:lifecycle-compiler:$work_version"
 
  // The Engine AAR:
  implementation(name: 'factual-engine-sdk-android', ext: 'aar')
}

You may need to exclude META-INF/DEPENDENCIES by adding a packagingOptions block under android in your app/build.gradle:

android {
  ...
  packagingOptions {
    exclude 'META-INF/DEPENDENCIES'
  }
}

4. Location Permissions and Service Registration

In AndroidManifest.xml under the application label, add your receiver that extends FactualClientReceiver. For example:

...
    <receiver android:name=".ConsoleLoggingFactualClientReceiver"/>
  </application>
...

Additionally, AndroidManifest.xml should include the following permissions:

...
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  <uses-feature android:name="android.hardware.location.gps" />
  <uses-feature android:name="android.hardware.location.network" />
  
  <application
  ...
</manifest>

The SDK does NOT prompt for these permissions on its own. We've deliberately left that to you, so that you can lump in any other permissions you may require, as well as choose the method and timing of the prompt as you require.


5. Follow the sample implementation

The example Android code below will initialize and start Engine, in conjunction with the required permission checks. Once complete, the ConsoleLoggingFactualClientReceiver will print out basic information like when the SDK has started among other things.

A few things to note:

  • The SDK detects which location permissions your app has requested. It will not request any location authorizations on its own. This example contains boilerplate code for seeking the permissions.
  • The receiver allows your app to receive info from the SDK in the foreground and background.
package com.factual.androidwalkthrough;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;

import com.factual.engine.FactualEngine;

public class MainActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    /*
     *   We leave checking/obtaining location permissions to you, so that you can lump in
     *   any other permissions you may require, as well as choose the method and timing of the
     *   prompt.
     *
     *   Therefore, the following is for illustrative purposes:
     */
    if (isRequiredPermissionAvailable()) {
      initializeEngine();
    } else {
      requestLocationPermissions();
    }
  }

  public void initializeEngine() {
    // ConsoleLoggingFactualClientReceiver extends FactualClientReceiver
    FactualEngine.initialize(
      this,
      "your api-key goes here",
      ConsoleLoggingFactualClientReceiver.class); // TODO: Put your API key here
  }

  // example permissions boilerplate
  @Override
  public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
    if (isRequiredPermissionAvailable()) {
      initializeEngine();
    } else {
      Log.e("engine", "Necessary permissions were never provided.");
    }
  }

  public boolean isRequiredPermissionAvailable(){
    return ContextCompat.checkSelfPermission(this,
        Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
        ContextCompat.checkSelfPermission(this,
            Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
        ContextCompat.checkSelfPermission(this,
            Manifest.permission.INTERNET) == PackageManager.PERMISSION_GRANTED;
  }

  public void requestLocationPermissions() {
    ActivityCompat.requestPermissions(
        this,
        new String[]{
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION,
            Manifest.permission.INTERNET
        },
        0);
  }
}
package com.factual.androidwalkthrough;

import android.content.Context;
import android.util.Log;
import java.util.List;

import com.factual.engine.FactualEngine;
import com.factual.engine.api.FactualConfigMetadata;
import com.factual.engine.api.FactualError;
import com.factual.engine.api.FactualException;
import com.factual.engine.api.FactualInfo;
import com.factual.engine.FactualClientReceiver;
import com.factual.engine.api.CircumstanceResponse;

public class ConsoleLoggingFactualClientReceiver extends FactualClientReceiver {
  @Override
  public void onContext(Context context) {
    // If you need fields from the context
  }

  @Override
  public void onInitialized() {
    try {
      FactualEngine.start();
    } catch (FactualException e) {
      Log.e("engine", e.getMessage());
    }
  }
  
  @Override
  public void onStarted() {
    Log.i("engine", "Engine has started.");
  }

  @Override
  public void onStopped() {
    Log.i("engine", "Engine has stopped.");
  }

  @Override
  public void onError(FactualError e) {
    Log.i("engine", e.getMessage());
  }

  @Override
  public void onInfo(FactualInfo i) {
    Log.i("engine", i.getInfo());
  }

  @Override
  public void onSyncWithGarageComplete() {
    Log.i("engine", "Garage synced.");
  }

  @Override
  public void onConfigLoad(FactualConfigMetadata data) {
    Log.i("engine", "Garage config loaded at: " + data.getVersion());
  }

  @Override
  public void onDiagnosticMessage(String diagnosticMessage) {
    // Delivers structured diagnostic data that is helpful for Factual when evaluating performance
    // and diagnosing issues.
  }
  
  @Override
  public void onCircumstancesMet(List<CircumstanceResponse> responses) {
    for (CircumstanceResponse response : responses) {
      List<String> tags = response.getCircumstance().getTags();
      String circumstanceId = response.getCircumstance().getCircumstanceId();
      String message = "Circumstance " + circumstanceId + " with tags: " + tags + " met.";
      Log.i("engine", message);
    }
  }
}

6. Insert your API Key

In MainActivity.java in the code example above, you'll need to provide your API key:

FactualEngine.initialize(this, "your API Key Goes Here", ConsoleLoggingFactualClientReceiver.class);

Further Code Examples

🚧

Look through the rest of our developer docs for examples on how to: