Skip to content

Android SDK examples - #1

Merged
biodranik merged 4 commits into
masterfrom
sdk/part2-samples
Nov 4, 2026
Merged

biodranik merged 4 commits into
masterfrom
sdk/part2-samples

Conversation

@biodranik

@biodranik biodranik commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Adds three independently installable Android examples for embedding the Organic Maps SDK:

  1. Basic: interactive offline world map, place information, location permissions, compass forwarding, light/dark styles and system-bar insets.
  2. Offline: region search, explicit download/update/cancel/retry, progress/errors and offline POI search with request filtering and preserved native result indices.
  3. Routing: selected endpoints, walking/cycling/driving, build progress/errors, persistent missing-map Download/Retry controls and foreground navigation. Planning, endpoints, profile and errors survive recreation; backgrounding cancels navigation, and closing the activity ends the route.

Shared setup keeps asynchronous SDK readiness in the application, forwards the map lifecycle and handles unsupported graphics. Kotlin style checks include module Gradle scripts. AndroidX regressions cover compass ownership, control insets, nested result scrolling, stopped searches, routing errors, profile restoration and route cleanup.

CI runs on pull requests and master pushes. It publishes fresh SDK artifacts into an isolated Maven repository, builds all debug/minified release variants, runs lint/style checks, and exercises the samples on an API 35 emulator.

Related: organicmaps/organicmaps#5324, organicmaps/organicmaps#10922

SDK integration documentation: organicmaps/organicmaps#14024.

Screenshots

Light map Dark map
Basic map in light mode Basic map in dark mode with place information
Offline restaurant search Walking route preview Missing-map recovery controls
Offline restaurant search on a downloaded map Built walking route in Andorra Missing-map error and explicit Download missing maps action

@biodranik
biodranik force-pushed the sdk/part2-samples branch 3 times, most recently from fee745a to 1dbe6bb Compare November 3, 2026 18:48
@biodranik
biodranik requested a review from vng November 3, 2026 21:26

@vng vng left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: SDK linking, docs, and what belongs upstream

The samples cover the right scenarios: map, offline search and routing. Most of the code, docs and CI in this PR, though, work around SDK gaps or check SDK behaviour, and that should be fixed or tested in organicmaps/organicmaps. Below: (1) how the SDK is linked today, (2) defects in that flow, (3) simpler alternatives, (4) SDK fixes upstream that would remove code from here, (5) cleanup so this repo holds only client code.

1. How OM is linked today

app.organicmaps.sdk:* is not published anywhere yet (Maven Central search returns 0 artifacts). Upstream already has the release pipeline (.github/workflows/android-sdk.yaml + nmcp), but it has never been run. So to build any sample, a client has to:

  1. Clone organicmaps with all submodules recursively, then install JDK 21, NDK 29, CMake and ninja, following the upstream INSTALL.md.
  2. Check out a pinned commit.
  3. Build the whole native core: ./gradlew -Parm64 -PsdkVersion=0.0.0-sdk-check publishSdkForChecks.
  4. Pass -PsdkRepository=… -PsdkVersion=0.0.0-sdk-check to every Gradle command in this repo.

That's a heavy process for "try the SDK", and steps 2–4 have defects:

  • The pinned SDK commit is orphaned. 0357aa0a3a… was a revision of organicmaps#14025 before a force-push. No branch or tag points to it anymore (the current #14025 head is 3ecf7f4b47, with identical build scripts). The README's git -C ../organicmaps checkout 0357aa0… therefore fails in a fresh clone unless you first run an explicit git fetch origin 0357aa0…. CI only works because GitHub still serves unreferenced objects by SHA, which lasts only until it garbage-collects them.
  • publishSdkForChecks only exists in unmerged #14025. The documented flow depends on an open PR.
  • There are two contradicting instructions. The TODO in settings.gradle.kts says ./gradlew publishToMavenLocal, which publishes version version.sh android_name (e.g. 2026.11.03-N). But libs.versions.toml defaults to 0.0.0-sdk-check, so the mavenLocal() path never resolves unless you also pass -PsdkVersion to the OM build.
  • The default coordinate 0.0.0-sdk-check in libs.versions.toml doesn't resolve for anyone who copies the build file.
  • ABI trap: with -Parm64 the AAR has only arm64 libs. On an x86_64 emulator the sample crashes with UnsatisfiedLinkError. The README mentions this, but it's easy to miss.

Docs

  • The README (154 lines) is mostly CI, QA and SDK-internals notes, and it duplicates android/sdk/README.md: the init()-returns-false semantics, MapStyle.mark/set, R8, resource shrinking. Two copies will drift. Upstream should be the single source.
  • The README embeds docs/screenshot-*.png, but docs/ isn't in this PR, so all images are broken. Not committing PNGs is good; either drop the references or use GitHub user-attachments URLs. The PR description's images point to 1901fb6b…, a force-pushed commit that doesn't exist on any ref here either.
  • There's already drift: upstream README noCompress includes png, but this sample omits it.
  • The upstream README sends readers to gradle.properties and a workflow file to learn the compileSdk/JDK requirements. It should state them directly.

2. Suggested linking alternatives (in order)

  1. Do a first Maven Central release now (0.x / preview). The README disclaimer already says the API is unstable, so there's nothing to wait for. After that, the client setup is just:
    repositories { google(); mavenCentral() }
    dependencies { implementation("app.organicmaps.sdk:sdk:<ver>") }
    and the sdkRepository / exclusiveContent / mavenLocal() logic in settings.gradle.kts goes away.
  2. Publish snapshots from OM master (Central Portal snapshots) so this repo can follow master without building native code.
  3. For people changing the SDK and the samples together: use a Gradle composite build instead of publish + version juggling.
    // settings.gradle.kts
    providers.gradleProperty("omSource").orNull?.let {
        includeBuild(it) {
            dependencySubstitution {
                substitute(module("app.organicmaps.sdk:sdk")).using(project(":sdk"))
                substitute(module("app.organicmaps.sdk:maps-world")).using(project(":sdk:maps:world"))
            }
        }
    }
    ./gradlew -PomSource=../organicmaps/android installDebug would then build the core as part of the sample build. Not verified yet: it needs the same AGP version in both builds and configures OM's :app/:wear too, so it's worth a quick try.
  4. Keep SDK-correctness CI in one place. #14025 already adds android/sdk/consumer: a minified consumer, three location flavors and an emulator smoke test. This repo's CI rebuilding the native core and running an emulator duplicates it, and every core PR can break this repo without upstream CI noticing. Pick one: either upstream CI builds this repo as its consumer (and android/sdk/consumer is dropped), or this repo's CI becomes assembleDebug assembleRelease lint against a released or snapshot artifact. That takes minutes and needs no NDK, ccache or emulator.

3. SDK fixes for the main repo (each removes code or docs from here)

Sample workaround Upstream fix
Client must enable isCoreLibraryDesugaringEnabled + desugar_jdk_libs (AGP's checkAarMetadata enforces it) Only three spots need it: PredefinedColors (IntStream), HoursMinutes (LocalTime/DateTimeFormatter), ResourceIdCache (IntSupplier). Rewrite them in plain Java and the requirement disappears.
Client is forced to compileSdk 37 publishing.gradle sets aarMetadata.minCompileSdk = propCompileSdkVersion. AndroidX deps already declare their own minimums, so drop this or set it to the real minimum.
ExampleApplication keeps a LiveData readiness flag; README explains that init() returns false and never calls later callbacks Make init(callback) idempotent: queue the callback while loading and run it immediately if already loaded, or expose isReady() / an observable.
OrganicMaps(ctx, "example", APPLICATION_ID, VERSION_CODE, VERSION_NAME) → buildConfig = true Read these from PackageManager; keep the flavor as optional.
if (OMMap.isEngineCreated()) MapStyle.set() else MapStyle.mark() MapStyle.set() should mark by itself when the engine doesn't exist yet.
import app.organicmaps.sdk.Map as OMMap Map clashes with kotlin.collections.Map. Rename it before the first release.
noCompress += mwm, ttf, packed_polygons.bin (AAR can't propagate it) Fail fast: in init(), call assets.openFd("World.mwm") and a font. It throws on compressed entries, so turn that into an IllegalStateException that names the required noCompress line, instead of an obscure native failure.
"Resource shrinking remains disabled" Ship res/raw/organicmaps_sdk_keep.xml with tools:keep for resources looked up via getIdentifier(): Icon.java:106 (ic_bookmark_*), Utils.java:167 (string keys such as feature types), Utils.java:285 (brand.*). Consumers can then use shrinkResources.
About half of MainActivity (295 lines) is plumbing: forwarding compass headings with display rotation, replaying savedNorth on rendering created/restored, the nativeOnLocationError(ERROR_DENIED) → request permissions → nativeSwitchToNextMode() sequence, stopping location in onStop unless changing configurations MapController is already a LifecycleObserver, so it can own all of this. The host would supply only a "request location permission" callback. Every client will otherwise copy these ~150 lines, with their bugs.
downloadRegion() in SampleControls.kt (status switch, storage preflight, update vs. download vs. retry) Add MapManager.download(id) that returns a result enum (STARTED, NO_SPACE, ALREADY_DONE, …).
Comment: "download them … with DownloadResourcesLegacyActivity natives (see how the Organic Maps app does it)" Either add a public API to fetch World maps or remove the hint. Clients shouldn't reverse-engineer the app.
Sample calls 15 different native* methods directly (Framework.nativeDeactivatePopup, LocationState.nativeSwitchToNextMode, MapManager.nativeSubscribe, …). The SDK has 263 public static native methods, 132 of them in Framework Before the first release, wrap the ones clients need in named Java methods and hide the rest. Otherwise the JNI surface becomes the public API. While there, replace positional-boolean calls like search(ctx, q, false, ts, false, 0.0, 0.0) with a params object, and replace the process-wide single listeners (place page, location mode) with add/remove listener lists.

4. Keep this repo client-only

  • Use one APK instead of three flavors. The flavors only change the launcher activity (all code is in main). Three <activity> entries with LAUNCHER filters, or a small chooser screen, remove productFlavors, manifestPlaceholders, resValues, the per-flavor androidTest* source sets and the per-flavor lint aggregation. As a bonus, downloaded maps are shared between the samples (the README currently has to warn that they aren't).
  • Drop androidTest* and the emulator job. These tests either check the sample's own UI state machine or SDK internals: MapLifecycleTest reads SensorHelper.mListeners, a private field, via reflection. SDK behaviour tests belong upstream next to the code.
  • Drop the ktlint wiring and the checkSamples / connectedSamples aggregate tasks from build.gradle.kts. .editorconfig alone is enough for a sample.
  • gradle.properties: android.nonTransitiveRClass and android.nonFinalResIds are already defaults since AGP 8.
  • Cut the README to about 30 lines: what each sample shows, requirements, the dependency snippet and how to run it, with a link to android/sdk/README.md for behaviour. Drop "Suggested device checks", the native-stripping paragraph, the CI description and the broken image references.
  • gradle-wrapper.jar is the only binary. I'd keep it: it's what makes ./gradlew work, and CI validates its checksum.

Suggested order

  1. Merge #14025, or at least stop pinning an orphaned SHA.
  2. Make the desugaring, minCompileSdk, init() and Map-rename fixes upstream.
  3. Cut a first 0.x Central release.
  4. Shrink this PR to a single client-only app that depends on app.organicmaps.sdk:sdk:0.x from mavenCentral(), with a build-only CI.

Provide independently installable map, offline-search and routing apps.
Keep asynchronous SDK readiness in the application, forward map, compass
and foreground location lifecycle, and handle permission/provider results.

Preserve search scrolling and native indexes across filtered snapshots,
keep download feedback separate, reject stale callbacks, and retain route
planning across recreation. Complete foreground navigation on arrival.

Add focused lifecycle, provider-resolution, gesture and search regressions.
Document a fetchable SDK revision and isolated local publication workflow.

Related: organicmaps/organicmaps#5324, organicmaps/organicmaps#14102
Signed-off-by: Alexander Borsuk <me@alex.bio>
Publish the pinned SDK into an isolated Maven repository, then build and
lint every sample's debug and minified release APKs. Exercise lifecycle,
provider-resolution and search regressions on an API 35 emulator.

Run on master pushes and pull requests, cancel superseded PR runs, and use
shallow submodules and a native compilation cache.

Related: organicmaps/organicmaps#14025
Signed-off-by: Alexander Borsuk <me@alex.bio>
Resolve a supplied SDK release exclusively from Maven Central and keep source integration checks optional. Run the sample regressions on a Pixel 2 profile and continue the remaining tests after failures.

Document that SDK implementation is authoritative and consumer compatibility checks belong in this repository after publication.

Signed-off-by: Alexander Borsuk <me@alex.bio>
Pin source integration and guide links to the SDK publication support merged into master. Remove resource flags already enabled by AGP defaults.

Keep compatibility checks for different SDK versions or source revisions in independent concurrency groups.

Signed-off-by: Alexander Borsuk <me@alex.bio>
@biodranik

Copy link
Copy Markdown
Member Author

@vng thanks, fixed and created follow-up issue #2 , PTAL

@biodranik
biodranik merged commit bcfdf05 into master Nov 4, 2026
1 check passed
@biodranik
biodranik deleted the sdk/part2-samples branch November 4, 2026 16:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants