Android Example
A Kotlin Android application that renders a MapLibre GL Native vector tile map and adds places search. Uses a custom OkHttp interceptor for tile authentication.
Prerequisites
- Android Studio Ladybug (2024.2+)
- Android API 24+ (Android 7.0) device or emulator
- A Geog API key (create one in the console)
Quick Start
git clone https://github.com/geogdev/examples.git
Open examples/mobile/android-maplibre in Android Studio. Add your API key to local.properties:
GEOG_API_KEY=your-api-key-here
Click Sync Now when prompted for Gradle sync, then run the app.
Project Structure
| File | Description |
|---|---|
local.properties | API key storage (git-ignored) — injected via Gradle buildConfigField |
app/build.gradle.kts | Module config — reads API key from local.properties into BuildConfig |
app/src/main/java/.../api/GeogService.kt | API client — OkHttp-based token exchange, caching, and places search |
app/src/main/java/.../MapFragment.kt | Fragment — MapLibre map with programmatic style and OkHttp auth interceptor |
app/src/main/java/.../PlacesSearchFragment.kt | Fragment — search UI, results list, and marker management |
app/src/main/java/.../api/Place.kt | @Serializable data model for search results |
How It Works
API Key Configuration
The API key is stored in local.properties (git-ignored) and injected into the app at build time through Gradle:
// app/build.gradle.kts
android {
defaultConfig {
buildConfigField(
"String",
"GEOG_API_KEY",
"\"${localProperties["GEOG_API_KEY"]}\""
)
}
}
Access it in code as BuildConfig.GEOG_API_KEY.
Token Exchange
GeogService exchanges the API key for a short-lived access token using OkHttp:
⚠️ Production note: This example embeds the API key via
BuildConfigfor simplicity. In a production app distributed to end users, the key can be extracted from the APK. Use a backend token-proxy instead — your server holds the long-lived key and returns short-lived tokens to the app. See Token Exchange API for details.
object GeogService {
private var cachedToken: String? = null
private var tokenExpiresAt: Long = 0
suspend fun getAccessToken(): String {
val now = System.currentTimeMillis()
cachedToken?.let { token ->
if (now < tokenExpiresAt) return token
}
val response = client.newCall(
Request.Builder()
.url("https://api.geog.dev/v1/auth/token")
.header("Authorization", "Bearer ${BuildConfig.GEOG_API_KEY}")
.post("""{"ttl":3600,"scope":"tiles:read places:read"}""".toRequestBody("application/json".toMediaType()))
.build()
).await()
val data = Json.decodeFromString<TokenResponse>(response.body!!.string())
cachedToken = data.accessToken
tokenExpiresAt = now + (data.expiresIn * 1000 * 0.8).toLong()
return data.accessToken
}
}
Custom OkHttp Interceptor
MapLibre GL Native for Android uses OkHttp under the hood. A custom interceptor attaches the Bearer token to all tile requests:
val client = OkHttpClient.Builder()
.addInterceptor { chain ->
val request = chain.request()
if (request.url.host == "api.geog.dev") {
chain.proceed(
request.newBuilder()
.header("Authorization", "Bearer $accessToken")
.build()
)
} else {
chain.proceed(request)
}
}
.build()
HttpRequestUtil.setOkHttpClient(client)
Coroutines
All API calls are suspend functions and run within lifecycleScope for structured concurrency.
Full Source
github.com/geogdev/examples/tree/main/mobile/android-maplibre
See Also
- Examples Overview — All examples and the integration pattern
- Token Exchange API — Endpoint specification
- Vector Tiles API — Tile endpoint reference
- Places API — Search endpoint reference
- Styles & Sprites — Hosted themes, sprites, and npm packages