Repository navigation
Android SDK examples - #1
Conversation
fee745a to
1dbe6bb
Compare
1dbe6bb to
3175088
Compare
vng
left a comment
There was a problem hiding this comment.
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:
- Clone
organicmapswith all submodules recursively, then install JDK 21, NDK 29, CMake and ninja, following the upstream INSTALL.md. - Check out a pinned commit.
- Build the whole native core:
./gradlew -Parm64 -PsdkVersion=0.0.0-sdk-check publishSdkForChecks. - Pass
-PsdkRepository=… -PsdkVersion=0.0.0-sdk-checkto 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 is3ecf7f4b47, with identical build scripts). The README'sgit -C ../organicmaps checkout 0357aa0…therefore fails in a fresh clone unless you first run an explicitgit fetch origin 0357aa0…. CI only works because GitHub still serves unreferenced objects by SHA, which lasts only until it garbage-collects them. publishSdkForChecksonly exists in unmerged #14025. The documented flow depends on an open PR.- There are two contradicting instructions. The TODO in
settings.gradle.ktssays./gradlew publishToMavenLocal, which publishes versionversion.sh android_name(e.g.2026.11.03-N). Butlibs.versions.tomldefaults to0.0.0-sdk-check, so themavenLocal()path never resolves unless you also pass-PsdkVersionto the OM build. - The default coordinate
0.0.0-sdk-checkinlibs.versions.tomldoesn't resolve for anyone who copies the build file. - ABI trap: with
-Parm64the AAR has only arm64 libs. On an x86_64 emulator the sample crashes withUnsatisfiedLinkError. 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: theinit()-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, butdocs/isn't in this PR, so all images are broken. Not committing PNGs is good; either drop the references or use GitHubuser-attachmentsURLs. The PR description's images point to1901fb6b…, a force-pushed commit that doesn't exist on any ref here either. - There's already drift: upstream README
noCompressincludespng, but this sample omits it. - The upstream README sends readers to
gradle.propertiesand a workflow file to learn the compileSdk/JDK requirements. It should state them directly.
2. Suggested linking alternatives (in order)
- 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:and therepositories { google(); mavenCentral() } dependencies { implementation("app.organicmaps.sdk:sdk:<ver>") }sdkRepository/exclusiveContent/mavenLocal()logic insettings.gradle.ktsgoes away. - Publish snapshots from OM master (Central Portal snapshots) so this repo can follow master without building native code.
- 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 installDebugwould 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/:weartoo, so it's worth a quick try. - 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 (andandroid/sdk/consumeris dropped), or this repo's CI becomesassembleDebug assembleRelease lintagainst 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 withLAUNCHERfilters, or a small chooser screen, removeproductFlavors,manifestPlaceholders,resValues, the per-flavorandroidTest*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:MapLifecycleTestreadsSensorHelper.mListeners, a private field, via reflection. SDK behaviour tests belong upstream next to the code. - Drop the ktlint wiring and the
checkSamples/connectedSamplesaggregate tasks frombuild.gradle.kts..editorconfigalone is enough for a sample. gradle.properties:android.nonTransitiveRClassandandroid.nonFinalResIdsare 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.mdfor behaviour. Drop "Suggested device checks", the native-stripping paragraph, the CI description and the broken image references. gradle-wrapper.jaris the only binary. I'd keep it: it's what makes./gradlewwork, and CI validates its checksum.
Suggested order
- Merge #14025, or at least stop pinning an orphaned SHA.
- Make the desugaring,
minCompileSdk,init()andMap-rename fixes upstream. - Cut a first
0.xCentral release. - Shrink this PR to a single client-only app that depends on
app.organicmaps.sdk:sdk:0.xfrommavenCentral(), 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>
862ab4c to
043afb3
Compare
Adds three independently installable Android examples for embedding the Organic Maps SDK:
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
masterpushes. 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