MapLibre Native Android

MapLibre Native renders the full vector outdoor style natively on Android with OpenGL ES, including 3D terrain.

Full working example: github.com/mapriot/map-examples/MaplibreNativeAndroidKotlin — complete Android Studio project with OkHttp API key interceptor.


Installation

Add to build.gradle (app module):

dependencies {
    implementation 'org.maplibre.gl:android-sdk:11.+'
    implementation 'com.squareup.okhttp3:okhttp:4.+'
}

Add internet permission to AndroidManifest.xml:

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

Basic activity (Kotlin)

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
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.module.http.HttpRequestUtil

class MapActivity : AppCompatActivity() {

    private lateinit var mapView: MapView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val apiKey = "YOUR_API_KEY"

        MapLibre.getInstance(this)

        // Build an OkHttpClient that appends ?apiKey= to all api.mapriot.com requests.
        // This is the Android equivalent of transformRequest in MapLibre GL JS.
        val client = OkHttpClient.Builder()
            .addInterceptor(object : Interceptor {
                override fun intercept(chain: Interceptor.Chain): Response {
                    val req = chain.request()
                    val url = req.url
                    if (url.host == "api.mapriot.com" && url.queryParameter("apiKey") == null) {
                        val newUrl = url.newBuilder()
                            .addQueryParameter("apiKey", apiKey)
                            .build()
                        return chain.proceed(req.newBuilder().url(newUrl).build())
                    }
                    return chain.proceed(req)
                }
            })
            .build()

        HttpRequestUtil.setOkHttpClient(client)

        setContentView(R.layout.activity_main)
        mapView = findViewById(R.id.mapView)
        mapView.onCreate(savedInstanceState)
        mapView.getMapAsync { map ->
            map.setStyle("https://api.mapriot.com/styles/outdoor.json?apiKey=$apiKey")
            map.cameraPosition = CameraPosition.Builder()
                .target(LatLng(50.08, 14.42))
                .zoom(13.0)
                .build()
        }
    }

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

Layout XML

<org.maplibre.android.maps.MapView
    android:id="@+id/mapView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

API key injection

The OkHttpClient interceptor above appends ?apiKey= to every request to api.mapriot.com. This ensures tile, font, and sprite subrequests are authenticated — the Android equivalent of transformRequest in MapLibre GL JS.

For a production app, store the API key securely (e.g. in local.properties exposed via BuildConfig) rather than hardcoding it. See the map-examples Android project for the full setup.


Notes

  • Forward all lifecycle events to mapView (onStart, onResume, onPause, onStop, onDestroy, onSaveInstanceState, onLowMemory)
  • The OkHttp interceptor is required — without it, only the style JSON loads and all tile requests fail
  • The outdoor style URL with your API key loads the style; the interceptor handles everything else