Android SDK
Integrate Graffity AR Cloud 5.0.2 with GraffityARCloudConfiguration.Builder and the public GraffityARCloudSDK session class.
Requirements
- A project and access token from Graffity Console.
- Android 9.0 / API 28 or later on an ARCore-supported device , with Google Play Services for AR available.
- Android Studio and JDK 17. The SDK is built with Kotlin 2.3.20; use a compatible Kotlin toolchain.
- The examples use
compileSdk = 36. Your app also needs the AndroidX Activity, Fragment, Lifecycle, and coroutines libraries used by its host code. - Network access for localization, content, venue lookup, and map overlays.
Install the SDK
Use Google Maven and Maven Central in your project’s repository configuration:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}Add the SDK to your app module:
android {
compileSdk = 36
defaultConfig {
minSdk = 28
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
kotlin {
jvmToolchain(17)
}
dependencies {
implementation("tech.graffity.android:arcloud:5.0.2")
}Public configuration, state, and listener types are in tech.graffity.android.arcloud.publicapi. Use the SDK’s factories to create its view or fragment.
The published AAR includes consumer R8 rules. Keep those rules intact when building a minified app; additional SDK keep rules are not required.
Add permissions and AR metadata
For an app whose AR screen requires ARCore, declare the following in your app manifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.camera.ar" android:required="true" />
<application>
<meta-data android:name="com.google.ar.core" android:value="required" />
<activity android:name=".GraffityARActivity" android:exported="false" />
</application>
</manifest>If AR is optional in your app, use android:value="optional" and avoid requiring the AR feature for the whole app. Check support before offering the AR experience. See Google’s AR Required and AR Optional setup .
Your app requests camera and foreground precise location permissions before attaching the SDK surface. Request fine and coarse location together on Android 12+; a coarse-only grant is insufficient for this SDK. See Android’s runtime location guidance .
A seed location does not replace permission. The SDK does not request background location.
Configure and initialize
import android.content.Context
import tech.graffity.android.arcloud.publicapi.GraffityARCloudConfiguration
import tech.graffity.android.arcloud.publicapi.GraffityARCloudSDK
import tech.graffity.android.arcloud.publicapi.GraffityARMode
fun makeARSession(context: Context, accessToken: String): GraffityARCloudSDK {
val configuration = GraffityARCloudConfiguration.Builder(accessToken)
.mode(GraffityARMode.ARContent)
.build()
return GraffityARCloudSDK(context, configuration)
}ARContent localizes when the hosted view starts and displays content without destination UI. The default, Navigation, opens a destination map first; selecting a destination starts AR localization and route guidance. Enable a docked minimap separately with .minimap(true).
Both build() and the SDK constructor reject a blank access token with GraffityARCloudError.InvalidAccessToken. Catch these failures in your host. A nonblank token still needs backend authorization later. See configuration options.
Present the AR experience
Retain one SDK per logical session and keep one active SDK surface. Create and attach Android UI on the main thread.
The following host owns configuration, requests runtime permissions, checks ARCore installation, and creates a fragment. It includes a Close button and shuts down in an independent cleanup scope, so Activity lifecycle cancellation does not prevent teardown.
package com.example.app
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import com.google.ar.core.ArCoreApk
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import tech.graffity.android.arcloud.presentation.view.GraffityARCloudHost
import tech.graffity.android.arcloud.publicapi.*
open class GraffityARActivity : FragmentActivity(),
GraffityARCloudHost, GraffityARCloudListener {
private var sdk: GraffityARCloudSDK? = null
private var mounted = false
private var installRequested = false
private val cleanupScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
override val graffityARCloudSDK: GraffityARCloudSDK
get() = checkNotNull(sdk)
private val permissionRequest = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) {
if (hasPermissions()) {
mountWhenReady()
} else {
Toast.makeText(this, "Camera and precise location are required.", Toast.LENGTH_LONG).show()
finish()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_graffity_ar)
findViewById<Button>(R.id.close_ar).setOnClickListener { finish() }
try {
val token = intent.getStringExtra(EXTRA_ACCESS_TOKEN).orEmpty()
val configuration = GraffityARCloudConfiguration.Builder(token)
.mode(GraffityARMode.ARContent)
.build()
sdk = GraffityARCloudSDK(this, configuration).also { it.listener = this }
} catch (error: GraffityARCloudError) {
Toast.makeText(this, error.localizedMessage(this), Toast.LENGTH_LONG).show()
finish()
return
}
// This host starts a fresh logical session when the Activity is recreated.
supportFragmentManager.findFragmentByTag(AR_FRAGMENT_TAG)?.let {
supportFragmentManager.beginTransaction().remove(it).commitNow()
}
if (!hasPermissions()) {
permissionRequest.launch(arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
))
}
}
override fun onResume() {
super.onResume()
if (hasPermissions()) mountWhenReady()
}
private fun hasPermissions(): Boolean =
listOf(Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION).all {
ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
}
private fun mountWhenReady() {
val session = sdk ?: return
if (mounted) return
try {
when (ArCoreApk.getInstance().requestInstall(this, !installRequested)) {
ArCoreApk.InstallStatus.INSTALL_REQUESTED -> {
installRequested = true
return
}
ArCoreApk.InstallStatus.INSTALLED -> Unit
}
mounted = true
hostAR(session)
} catch (error: Exception) {
Toast.makeText(this, error.message ?: "AR is unavailable.", Toast.LENGTH_LONG).show()
finish()
}
}
protected open fun hostAR(session: GraffityARCloudSDK) {
supportFragmentManager.beginTransaction()
.replace(R.id.ar_host, session.createFragment(), AR_FRAGMENT_TAG)
.commitNow()
}
override fun onError(sdk: GraffityARCloudSDK, error: GraffityARCloudError) {
Toast.makeText(this, error.localizedMessage(this), Toast.LENGTH_LONG).show()
}
override fun onDestroy() {
val session = sdk
cleanupScope.launch {
try {
session?.shutdown()
} finally {
cleanupScope.cancel()
}
}
super.onDestroy()
}
companion object {
const val EXTRA_ACCESS_TOKEN = "graffity.accessToken"
private const val AR_FRAGMENT_TAG = "graffity.ar"
}
}Replace com.example.app with your app’s package. Add the following layout; Compose apps can override hostAR using the second tab.
XML + Fragment
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/ar_host"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:id="@+id/close_ar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|end"
android:text="Close" />
</FrameLayout>Launch your chosen Activity with an access token:
import android.app.Activity
import android.content.Intent
fun openGraffityAR(activity: Activity, accessToken: String) {
activity.startActivity(
Intent(activity, com.example.app.GraffityARActivity::class.java)
.putExtra(com.example.app.GraffityARActivity.EXTRA_ACCESS_TOKEN, accessToken)
)
}Run on a device
Run on a supported physical device, grant camera and precise location access, and complete any Google Play Services for AR installation prompt. Follow the guidance in the mapped venue. Navigation mode requires a destination selection before scanning.
Observe vpsState or onLocalized to recognize a successful localization. See Android options, the API reference, and the issue center.