Skip to Content
Quick StartAndroid SDK

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:

settings.gradle.kts
dependencyResolutionManagement { repositories { google() mavenCentral() } }

Add the SDK to your app module:

app/build.gradle.kts
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:

AndroidManifest.xml
<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.

GraffityARActivity.kt
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.

res/layout/activity_graffity_ar.xml
<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.