Skip to content

Kotlin Quickstart

Android native maps via MapLibre Native Android.

Install

kotlin
// app/build.gradle.kts
dependencies {
    implementation("org.maplibre.gl:android-sdk:11.0.0")
}

AndroidManifest.xml

Add Internet permission. Register your package name in the dashboard against the public key.

xml
<uses-permission android:name="android.permission.INTERNET" />

Render a map

kotlin
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import org.maplibre.android.MapLibre
import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.geometry.LatLng
import org.maplibre.android.maps.MapView
import org.maplibre.android.maps.Style

class MapActivity : AppCompatActivity() {
    private lateinit var mapView: MapView

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

        val key = BuildConfig.MFD_PUBLIC_KEY
        val styleUrl = "https://api.mapsfordevs.com/styles/standard.json?key=$key"

        mapView = findViewById(R.id.mapView)
        mapView.onCreate(savedInstanceState)
        mapView.getMapAsync { map ->
            map.setStyle(styleUrl) { /* style loaded */ }
            map.cameraPosition = CameraPosition.Builder()
                .target(LatLng(-26.20, 28.04))
                .zoom(11.0)
                .build()
        }
    }

    override fun onStart()  { super.onStart(); mapView.onStart() }
    override fun onResume() { super.onResume(); mapView.onResume() }
    override fun onPause()  { super.onPause(); mapView.onPause() }
    override fun onStop()   { super.onStop(); mapView.onStop() }
    override fun onDestroy(){ super.onDestroy(); mapView.onDestroy() }
    override fun onLowMemory() { super.onLowMemory(); mapView.onLowMemory() }
}

Geocoding (kotlinx.serialization + ktor)

kotlin
// build.gradle.kts
// implementation("io.ktor:ktor-client-core:2.3.10")
// implementation("io.ktor:ktor-client-cio:2.3.10")
// implementation("io.ktor:ktor-client-content-negotiation:2.3.10")
// implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.10")

import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.Serializable

@Serializable data class GeocodeItem(
    val lat: Double, val lng: Double, val label: String,
    val confidence: Double, val type: String
)

@Serializable data class GeocodeResponse(
    val ok: Boolean, val items: List<GeocodeItem>? = null, val error: String? = null
)

class MapsForDevsClient(private val apiKey: String) {
    private val http = HttpClient(CIO) {
        install(ContentNegotiation) { json() }
    }

    suspend fun geocode(query: String, country: String? = null, limit: Int = 5): List<GeocodeItem> {
        val resp: GeocodeResponse = http.get("https://api.mapsfordevs.com/geocode/forward") {
            header("Authorization", "Bearer $apiKey")
            url.parameters.append("q", query)
            url.parameters.append("limit", limit.toString())
            country?.let { url.parameters.append("country", it) }
        }.body()

        if (!resp.ok) throw RuntimeException(resp.error ?: "mfd error")
        return resp.items ?: emptyList()
    }
}

Compose helper

kotlin
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView

@Composable
fun MapLibreMap(styleUrl: String) {
    val context = LocalContext.current
    AndroidView(factory = {
        MapLibre.getInstance(it)
        MapView(it).apply {
            onCreate(null)
            getMapAsync { map -> map.setStyle(styleUrl) }
        }
    })
}