Hello world with Places (Android)

Welcome to this tutorial. Here we are going through all necessary steps to create a native app for Android that makes use of Onirix’s Places technology for Android platform. The goal of this tutorial is to create an application that runs on your AR compatible device and allows us to place points of interest on a map to create outdoor experiences with location based Augmented Reality.

Set-up and tools

Let's go over the tools we are going to need:

  1. Onirix Studio: the web platform that is the base/starting point for all our AR projects.
  2. Android Studio: is the official IDE for Android development, and includes everything you need to build Android apps.
  3. Places Android Library: this library allows us to create Places-type apps in Android.

Step 1: creating a new Places project with Onirix Studio web

We will start by creating a new project in the Onirix Studio. In this video you can see how to create a new project of type Place and how to use the tools to add place points of interest in every place nearby, using our map creation tool in Onirix.

Step 2: creating a new Android project with Android Studio platform

The next step is to download and install Android Studio. To create a new project, run Android Studio and click on the first option “Start a new Android Studio project”.

Create project in Android_Studio_S1

Choose the type of project “Phone and Tablet” with “Empty Activity”.

Create project in Android_Studio_S2

Configure we project give it a name, package name, save location and set minimum API level (we recommend level to 19 or above). Is very important that we ckeck option “User AndroidX artifacts”, this gives us the functionality of the AndroidX library. When click on the “Finish” button the project will be created.

Create project in Android_Studio_S3

Step 3: include Onirix Places Android library in project

The next step is import the Onirix Places Android library. For this we have to modify the following files:

Configuration_gradle_files

build.gradle (Project: name_project)

We have to add a custom maven repository under "_build.gradle (Project: nameproject)" file. Repositories tag should contain the following (order is important for dependency resolution).

allprojects {
   repositories {
       google()
       jcenter()
       maven {
           url 'http://archiva.neosentec.com/repository/internal/'
       }
   }
}

build.gradle (Module: app)

In the "build.gradle (Module: app)" file is neccesary that we will to include:

  • Data Binding Library: is a support library that allows we to bind UI components in your layouts to data sources in your app using a declarative format rather than programmatically.
  • Onirix-places-android-sdk: is a library that provides us with the tools that allow us to create applications that show places and routes in maps.
apply plugin: 'com.android.application'

android {
   compileSdkVersion 28
   defaultConfig {
       applicationId "com.onirix.myfirstplaces"
       minSdkVersion 19
       targetSdkVersion 28
       versionCode 1
       versionName "1.0"
   }
   buildTypes {
       release {
           minifyEnabled false
           proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
       }
   }
   dataBinding {
       enabled = true
   }
   compileOptions {
       targetCompatibility 1.8
       sourceCompatibility 1.8
   }
}

dependencies {
   implementation fileTree(dir: 'libs', include: ['*.jar'])
   implementation 'androidx.appcompat:appcompat:1.0.2'
   implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
   implementation 'com.onirix:places:1.1.0'
}

Step 4: creating the Places Android application

Now we have everything we need to create our first Places Android application. For this we will have to modify and add the following files:

MainActivity.java

In the file "MainActivity" we will implement the functionality to manage the permissions of the camera and the location (this is necessary for the application to work).

private List<String> permissionsToRequest;

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

       boolean hasPermissions = arePermissionsGranted(android.Manifest.permission.CAMERA,
               Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION);

       if (hasPermissions) {
           startActivity(new Intent(this, ARMapActivity.class));
       } else {
           requestPermissions();
       }
   }

   public boolean arePermissionsGranted(String... permissions) {

       permissionsToRequest = new ArrayList<>();

       for (String permission : permissions) {
           if (!(ActivityCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED)) {
               permissionsToRequest.add(permission);
           }
       }

       return permissionsToRequest.size() == 0;
   }

   public void requestPermissions() {
       ActivityCompat.requestPermissions(this, permissionsToRequest.toArray(new String[] {}), 1);
   }

When permits are granted, we can access the Activity where the Augmented Reality (AR) screen is implemented.

@Override
   public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
       if (grantResults.length > 0) {
           int deniedPermissions = 0;
           for (int grantResult : grantResults) {
               if (grantResult == PackageManager.PERMISSION_DENIED) {
                   deniedPermissions++;
               }
           }

           if (deniedPermissions > 0) {
               requestPermissions();
           } else {
               startActivity(new Intent(this, ARMapActivity.class));
           }
       } else {
           requestPermissions();
       }
   }

ARMapActivity.java

We should configure any Activity as the Augmented Reality (AR) screen. For the example we will create “ARMapActivity” that it must extend from "com.onirix.places.ui.MapActivity".

This will result in the following methods of listening and callbacks being implemented:

  • onMapLoaded: this method is called when the map is loaded. If there is any problem, map will be null and an error message will be populated.
  • onLocationUpdate: this method is called when the a new location arrives from GPS or Network providers.
  • onPlaceTouched: this method is called whenever a Place is touched.
  • onFocusChanged: this method is called whenever a Place enters the crosshair on screen's center.
@Override
public void onMapLoaded(Map map, String error) {}

@Override
public void onLocationUpdate(Location location) {}

@Override
public void onPlaceTouched(@NonNull PlaceARWrapper place) {}

@Override
public void onFocusChanged(@Nullable PlaceARWrapper newFocus) {}

Once the Activity is extends from MapActivity, we will have to initialize the configure of the screen, map and places, as well, load the map calling the method "loadOnirixMap" inside “onCreate” lifecycle function:

private static final String TOKEN = "<VALUTE_TOKEN>";
private static final String MAP_OID = "<VALUTE_MAP_OID>";

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);

   getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

   MapConfig mapConfig = new MapConfig(this)
           .withVisionDistance(100)
           .withShowRadar(true)
           .withRadarDistance(100)
           .withUseUpdateRadius(true)
           .withUpdateRatio(0.5f)
           .withUpdateRadius(2 * 100);

   PlaceConfig placeConfig = new PlaceConfig(this)
           .withShowDistance(true)
           .withShowMarker(true)
           .withShowName(true);

   mapConfig.setConfigForCategory("default", placeConfig);
   loadOnirixMap(MAP_OID, TOKEN, mapConfig);
}

We will explain a little more in detail that is done in each point:

Screen

This application must keep the screen on to be able to view the loaded map.

getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

Map configuration

This class allows we to configure different display options on the map. In this example we indicate that we would like the radar to be shown, different distances… For more information on this point we recommend to consult the section Map Configuration within the Places Android SDK.

MapConfig mapConfig = new MapConfig(this)
       .withVisionDistance(100)
       .withShowRadar(true)
       .withRadarDistance(100)
       .withUseUpdateRadius(true)
       .withUpdateRatio(0.5f)
       .withUpdateRadius(2 * 100);

Place configuration

This class allows we to configure different display options of the places. In this example we indicate that we would like the radar to be shown the distance, marker,… For more information on this point we recommend to consult the section Place Configuration within the Places Android SDK.

PlaceConfig placeConfig = new PlaceConfig(this)
           .withShowDistance(true)
           .withShowMarker(true)
           .withShowName(true);

Load Onirix Map

The call to the "loadOnirixMap" method allows us to load the map we want to show in our application.

private static final String TOKEN = "<VALUE_TOKEN>";
private static final String MAP_OID = "<VALUE_MAP_OID>";
……
loadOnirixMap(MAP_OID, AUTH_TOKEN, mapConfig);

We need to set some parameters:

  • Map Oid
  • Token
  • Map Config

To obtain the MAP_OID we can do this:

And for the TOKEN we can copy it from the project card menu:

AndroidManifest.xml

As a last step we should change in the "AndroidManifest.xml" the default theme of your application to something like “Theme.AppCompat.Light.DarkActionBar”, that extends from “Theme.AppCompat”.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.onirix.my_first_places">

   <application
       android:allowBackup="true"
       android:icon="@mipmap/ic_launcher"
       android:label="@string/app_name"
       android:roundIcon="@mipmap/ic_launcher_round"
       android:supportsRtl="true"
       android:theme="@style/Theme.AppCompat.Light.DarkActionBar">
       <activity android:name=".MainActivity" android:theme="@style/Theme.AppCompat.Light.NoActionBar">
           <intent-filter>
               <action android:name="android.intent.action.MAIN" />

               <category android:name="android.intent.category.LAUNCHER" />
           </intent-filter>
       </activity>
       <activity android:name=".ARMapActivity"></activity>
   </application>

</manifest>

Customization

Add your own logo to the view

We've added an empty ImageView inside the activity layout with ID: R.id.logoIcon. Therefore you are able to locate it within your activity and put an icon on it (Logo should have square dimensions). Here it is an example (this should be called inside onCreate() lifecycle activity method):

ImageView logoIcon = (ImageView) findViewById(R.id.logoIcon);
logoIcon.setImageDrawable(getResources().getDrawable(R.drawable.your_icon))

Proguard rules

If you want to enable proguard in your app, you will need the following rules

-keep class com.google.**
-dontwarn com.google.**
-dontwarn okio.**
-dontwarn okhttp3.**
-keep class okhttp3.** { *; }
-dontwarn java.awt.**
-dontwarn android.graphics.**

# Keep native funcitons
-keepclasseswithmembers class * {
    native <methods>;
}

-keep class com.onirix.places_core.model.** { *; }
-keep class com.onirix.places.model.** { *; }

##---------------Begin: proguard configuration for Gson  ----------
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature

# For using GSON @Expose annotation
-keepattributes *Annotation*

# Gson specific classes
-dontwarn sun.misc.**
#-keep class com.google.gson.stream.** { *; }

# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { *; }

# Prevent proguard from stripping interface information from TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
##---------------End: proguard configuration for Gson  ----------

##---------------Begin: proguard configuration for Retrofit  ----------
# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
# EnclosingMethod is required to use InnerClasses.
-keepattributes Signature, InnerClasses, EnclosingMethod

# Retain service method parameters when optimizing.
-keepclassmembers,allowshrinking,allowobfuscation interface * {
    @retrofit2.http.* <methods>;
}

# Ignore annotation used for build tooling.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement

# Ignore JSR 305 annotations for embedding nullability information.
-dontwarn javax.annotation.**

# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
-dontwarn kotlin.Unit

# Top-level functions that can only be used by Kotlin.
-dontwarn retrofit2.-KotlinExtensions
-dontwarn retrofit2.Platform$Java8
##---------------End: proguard configuration for Retrofit  ----------

Video Demo HWPlaces Android

An here you can see how our example looks like.