Skip to content

Android SDK

Embed interactive Layota maps into your Android app with a native Kotlin API.

Requirements

  • Android API 23+ (Android 6.0)
  • Kotlin 1.9+
  • Jetpack Compose (optional)

Installation

Source distribution for now

The Android SDK is not published to Maven Central yet — contact us to get the module source. A Maven release is planned.

The SDK ships as a standard Gradle library module. Once you have the source, include it in your project's settings.gradle.kts:

kotlin
include(":layota-map-sdk")
project(":layota-map-sdk").projectDir = file("../layota-map-sdk")

Then add the dependency to your module's build.gradle.kts:

kotlin
dependencies {
    implementation(project(":layota-map-sdk"))
}

Quick Start

XML Layout

kotlin
import app.layota.sdk.*

class MapActivity : AppCompatActivity() {

    private lateinit var mapView: LayotaMapView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_map)

        mapView = findViewById(R.id.map_view)
        mapView.initialize(LayotaMapOptions(
            projectId = "your-project-id",
            apiKey = "sk_..."
        ))

        val map = mapView.map!!

        map.on(SdkEventType.AREA_CLICK) { event ->
            if (event is LayotaEvent.AreaClick) {
                Log.d("Map", "Tapped area: ${event.area.title}")
            }
        }

        lifecycleScope.launch {
            map.ready()
            map.selectArea("coffee-shop").focusOn("coffee-shop")
        }
    }
}
xml
<app.layota.sdk.LayotaMapView
    android:id="@+id/map_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Jetpack Compose

kotlin
import app.layota.sdk.*

@Composable
fun MapScreen() {
    var map by remember { mutableStateOf<LayotaMap?>(null) }

    LayotaMapCompose(
        options = LayotaMapOptions(
            projectId = "your-project-id",
            apiKey = "sk_...",
            language = "en"
        ),
        modifier = Modifier.fillMaxSize(),
        onMapReady = { map = it }
    )

    LaunchedEffect(map) {
        map?.let {
            it.on(SdkEventType.AREA_CLICK) { event ->
                if (event is LayotaEvent.AreaClick) {
                    Log.d("Map", "Tapped: ${event.area.title}")
                }
            }
            it.ready()
            it.selectArea("entrance")
        }
    }
}

Configuration

kotlin
val options = LayotaMapOptions(
    projectId = "proj-uuid",         // Required
    apiKey = "sk_...",               // Required if project has allowed domains
    theme = MapTheme.DARK,           // LIGHT, DARK, BLUE, AUTO
    language = "ru",                 // Language code
    levelId = "floor-2",            // Start on this level (id, key, or name)
    areaId = "cafe",                // Focus this area on load
    markerId = "exit-north",        // Focus this marker on load
    baseUrl = "https://layota.app", // Override for self-hosted
)

Methods

WARNING

All SDK methods must be called from the main thread. The SDK enforces this with an internal assertMainThread() check.

Lifecycle

kotlin
// Wait for the map to finish loading (suspending function)
map.ready()

// Clean up WebView and all event listeners
map.destroy()

Level Control

kotlin
// Switch level by id, key (slug), or name
map.selectLevel("floor-2")

// Fetch all visible levels (suspending function)
val levels: List<Level> = map.getLevels()
// Level(id, name)

Area & Marker Selection

kotlin
// Select by id, key (slug), or title — opens the info card
map.selectArea("cafe")
map.selectArea("cafe", levelId = "floor-1")

map.selectMarker("exit-north")
map.selectMarker("exit-north", levelId = "floor-1")

// Close the info card and deselect
map.clearSelection()
kotlin
// Pan and zoom to area or marker — switches level if needed
map.focusOn("cafe")

// Reset zoom and pan to default
map.resetView()

// Set zoom (throws LayotaInvalidScaleException if scale <= 0)
map.setZoom(1.5)
kotlin
// Open the search panel and filter results by query
map.search("coffee")

// Clear the search
map.search("")

Routing

kotlin
val origin = RouteEntity(id = "entrance", type = RouteEntity.EntityType.AREA, levelId = "floor-1")
val destination = RouteEntity(id = "cafe", type = RouteEntity.EntityType.AREA, levelId = "floor-2")

map.buildRoute(from = origin, to = destination)
map.clearRoute()

Language

kotlin
// Get available languages
val (languages, current) = map.getLanguages()
// Language(code, name)
// current: currently active language code

// Switch language at runtime
map.setLanguage("de")

Areas & Markers Data

kotlin
// All areas across all levels
val allAreas: List<Area> = map.getAreas()

// Areas for a specific level (by id, key, or name)
val floorAreas: List<Area> = map.getAreas("floor-1")

// Same for markers
val allMarkers: List<Marker> = map.getMarkers()
val floorMarkers: List<Marker> = map.getMarkers("floor-1")

Project Data

kotlin
val project: ProjectInfo = map.getProject()
// ProjectInfo(id, levels: List<ProjectLevel>)
// ProjectLevel(id, name, areas: List<Area>, markers: List<Marker>)

Method Chaining

kotlin
map.selectLevel("floor-1")
   .selectArea("cafe")
   .focusOn("cafe")
   .setZoom(1.5)

Events

Subscribe

kotlin
val subscription = map.on(SdkEventType.AREA_CLICK) { event ->
    if (event is LayotaEvent.AreaClick) {
        println("Clicked: ${event.area.title}")
    }
}

map.on(SdkEventType.LEVEL_CHANGE) { event ->
    if (event is LayotaEvent.LevelChange) {
        println("Switched to: ${event.level.name}")
    }
}

map.on(SdkEventType.ROUTE_BUILT) { event ->
    if (event is LayotaEvent.RouteBuilt) {
        println("Distance: ${event.route.totalDistance}")
    }
}

Subscribe Once

kotlin
map.once(SdkEventType.READY) { println("Map is ready") }

Unsubscribe

kotlin
// Cancel a specific subscription
subscription.cancel()

// Remove ALL handlers for an event type
map.off(SdkEventType.AREA_CLICK)

Event Reference

EventPayloadDescription
READYMap fully loaded
LEVEL_CHANGELayotaEvent.LevelChange(level)Level switched
AREA_CLICKLayotaEvent.AreaClick(area)User tapped an area
MARKER_CLICKLayotaEvent.MarkerClick(marker)User tapped a marker
AREA_SELECTLayotaEvent.AreaSelect(area)Area selected programmatically
MARKER_SELECTLayotaEvent.MarkerSelect(marker)Marker selected programmatically
ROUTE_BUILTLayotaEvent.RouteBuilt(route, levelId)Route calculated
ROUTE_NOT_FOUNDLayotaEvent.RouteNotFoundNo route found
LANGUAGE_CHANGELayotaEvent.LanguageChange(language)Language switched via setLanguage()
ERRORLayotaEvent.Error(message)SDK error

Exceptions

ExceptionWhen
LayotaTimeoutExceptionready() or getLevels() timed out
LayotaDestroyedExceptionMethod called after destroy()
LayotaInvalidScaleExceptionsetZoom() called with scale <= 0
LayotaDecodingExceptionResponse from map could not be parsed
LayotaLoadExceptionWebView failed to load the map URL

Architecture

The SDK loads https://layota.app/embed/{projectId}?native=1 in a WebView. The ?native=1 parameter enables the native bridge — events are sent via window.layotaAndroid.postMessage() using @JavascriptInterface.

Commands are sent to the web page via WebView.evaluateJavascript(). Commands issued before the map finishes loading are queued and flushed once READY is received. All messages from the JavaScript bridge are dispatched to the main thread via Handler(Looper.getMainLooper()).

Layota Documentation