Wire up the real 2GIS MapKit SDK (was a placeholder)
Added the actual ru.dgis.sdk:sdk-map + compose-map dependencies (from https://artifactory.2gis.dev/sdk-maven-release) instead of the Canvas placeholder guess. Verified the exact API surface by downloading the AARs directly and inspecting classes with javap (DGis.initialize, MapOptions, MapComposableState/MapComposable, CameraPosition/GeoPoint) rather than relying on possibly-stale docs. DgisSdkProvider lazily calls DGis.initialize() once per process, catching failure so a missing key doesn't crash the app; DgisMapView renders the real MapComposable when that succeeds, falling back to the old placeholder canvas otherwise. Important finding: the existing DGIS_API_KEY (2GIS's public REST/JS API key format) does NOT work with this native SDK. It requires a separate dgissdk.key file issued per-app-package from dev.2gis.com, placed in app/src/main/assets/ (gitignored). Documented in README. Without it the app still runs fine on the placeholder map. Also restricted ndk.abiFilters to arm64-v8a — the SDK's native libs otherwise balloon the APK from ~19MB to ~190MB. Verified: testDebugUnitTest passes, assembleDebug produces a working (arm64-only, ~69MB) APK. Not runtime-verified against a real map key or device — no emulator/device available in this environment. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
7f1a6d51fc
commit
7c4d5727e4
9 changed files with 134 additions and 20 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -27,6 +27,7 @@ android/captures/
|
|||
android/.externalNativeBuild/
|
||||
*.keystore
|
||||
*.jks
|
||||
android/app/src/main/assets/dgissdk.key
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
|
|
|
|||
32
README.md
32
README.md
|
|
@ -68,11 +68,39 @@ Wi-Fi/LAN.
|
|||
Run unit tests before building — `./gradlew testDebugUnitTest`, then
|
||||
`./gradlew :app:assembleDebug`.
|
||||
|
||||
## 2GIS map SDK
|
||||
|
||||
The app depends on the real 2GIS MapKit SDK (`ru.dgis.sdk:sdk-map` +
|
||||
`compose-map`, from `https://artifactory.2gis.dev/sdk-maven-release`, wired
|
||||
up in `settings.gradle.kts`/`app/build.gradle.kts`). It needs a **separate**
|
||||
key from the `DGIS_API_KEY` above:
|
||||
|
||||
- `DGIS_API_KEY` (the `b4df01a...` value) is a 2GIS **public REST/JS API**
|
||||
key — not used by the native SDK at all currently.
|
||||
- The native MapKit SDK instead needs a **`dgissdk.key` file**, issued
|
||||
per-app (tied to the package name `com.guidecity.app`) from
|
||||
https://dev.2gis.com/. Place it at `android/app/src/main/assets/dgissdk.key`
|
||||
(gitignored — don't commit it).
|
||||
|
||||
Without that key file, `DgisSdkProvider` fails to initialize (logged, not
|
||||
crashed) and `DgisMapView` falls back to a placeholder canvas rendering
|
||||
(user location + nearby place dots, no real map tiles). Once a real key is
|
||||
in place, the real map should render automatically — no code changes needed.
|
||||
|
||||
Also note: the SDK bundles native libraries per CPU architecture, which
|
||||
balloons APK size a lot (~19MB → ~190MB unfiltered). `app/build.gradle.kts`
|
||||
restricts `ndk.abiFilters` to `arm64-v8a` only (~69MB) since that covers
|
||||
virtually all real devices today — remove that filter if you need to test
|
||||
on an x86 emulator or 32-bit device.
|
||||
|
||||
## Notes / current scope
|
||||
|
||||
This is iteration 1: Moscow only (Gorky Park, Red Square area, Moscow-City),
|
||||
no user accounts (favorites are local/Room-only), no speed/heading-aware
|
||||
auto content-length selection yet (reserved API params exist, unused).
|
||||
Nearby-search radius caps at 10km. Place markers aren't yet plotted on the
|
||||
real 2GIS map (only the placeholder does that) — follow-up work.
|
||||
|
||||
The 2GIS API key above is used directly via `local.properties`/`BuildConfig`
|
||||
for development convenience — don't ship it as-is in a public repo.
|
||||
The 2GIS REST/JS API key above is used directly via
|
||||
`local.properties`/`BuildConfig` for development convenience — don't ship it
|
||||
as-is in a public repo. Same goes for `dgissdk.key` once you have one.
|
||||
|
|
|
|||
|
|
@ -42,6 +42,14 @@ android {
|
|||
"API_BASE_URL",
|
||||
"\"${localProp("API_BASE_URL", "http://10.0.2.2:8000/")}\"",
|
||||
)
|
||||
|
||||
// The 2GIS SDK ships native libs for every ABI, which balloons the APK
|
||||
// (~19MB -> ~190MB unfiltered). Almost all real devices are arm64
|
||||
// today, and this keeps debug builds small enough to send anywhere
|
||||
// (e.g. under Telegram bot's 50MB upload limit).
|
||||
ndk {
|
||||
abiFilters += "arm64-v8a"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
|
@ -110,9 +118,12 @@ dependencies {
|
|||
implementation(libs.play.services.location)
|
||||
implementation(libs.kotlinx.coroutines.play.services)
|
||||
|
||||
// 2GIS MapKit SDK: add once the exact Maven coordinates/repository are
|
||||
// confirmed from https://docs.2gis.com/ — see map/DgisMapView.kt for the
|
||||
// isolated integration point in the meantime.
|
||||
// 2GIS MapKit SDK. Requires a real dgissdk.key in app/src/main/assets/,
|
||||
// obtained per-app from dev.2gis.com — see README for details. Without
|
||||
// it, DgisSdkProvider's init fails gracefully and DgisMapView falls back
|
||||
// to its placeholder rendering.
|
||||
implementation(libs.dgis.sdk.map)
|
||||
implementation(libs.dgis.compose.map)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
testImplementation(libs.mockk)
|
||||
|
|
|
|||
|
|
@ -7,37 +7,64 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.guidecity.app.data.remote.dto.NearbyPlaceDto
|
||||
import com.guidecity.app.location.LatLon
|
||||
import com.guidecity.app.theme.Mocha
|
||||
import ru.dgis.sdk.Context as DgisContext
|
||||
import ru.dgis.sdk.coordinates.Bearing
|
||||
import ru.dgis.sdk.coordinates.GeoPoint
|
||||
import ru.dgis.sdk.coordinates.Latitude
|
||||
import ru.dgis.sdk.coordinates.Longitude
|
||||
import ru.dgis.sdk.compose.map.MapComposable
|
||||
import ru.dgis.sdk.compose.map.MapComposableState
|
||||
import ru.dgis.sdk.map.CameraPosition
|
||||
import ru.dgis.sdk.map.MapOptions
|
||||
import ru.dgis.sdk.map.Tilt
|
||||
import ru.dgis.sdk.map.Zoom
|
||||
import kotlin.math.cos
|
||||
|
||||
/**
|
||||
* Isolated integration point for the 2GIS MapKit SDK.
|
||||
* Renders the real 2GIS MapKit map when the SDK initialized successfully
|
||||
* (i.e. a valid `dgissdk.key` is present — see [DgisSdkProvider]); otherwise
|
||||
* falls back to a lightweight placeholder (user location centered, nearby
|
||||
* places plotted by relative lat/lon offset) so the rest of the app —
|
||||
* permission flow, nearby fetch, navigation to place detail — stays fully
|
||||
* functional and demoable without a real key.
|
||||
*
|
||||
* This currently renders a lightweight placeholder (user location centered,
|
||||
* nearby places plotted by relative lat/lon offset) so the rest of the app
|
||||
* — permission flow, nearby fetch, navigation to place detail — is fully
|
||||
* functional and demoable before the real SDK is wired in.
|
||||
*
|
||||
* To integrate the real map: replace the Canvas placeholder below with the
|
||||
* 2GIS MapKit view (see android/build.gradle.kts and settings.gradle.kts for
|
||||
* the pending dependency/repository TODOs — the exact Maven coordinates need
|
||||
* confirming from https://docs.2gis.com/), keeping this function's signature
|
||||
* so callers (MapScreen, GuideScreen) don't need to change.
|
||||
* Follow-up not yet implemented: plotting [places] as markers on the real
|
||||
* map (needs the SDK's MapObjectManager/Marker API) — for now the real map
|
||||
* only centers on [userLocation]; the placeholder still shows place dots.
|
||||
*/
|
||||
@Composable
|
||||
fun DgisMapView(
|
||||
sdkContext: DgisContext?,
|
||||
userLocation: LatLon?,
|
||||
places: List<NearbyPlaceDto>,
|
||||
onPlaceClick: (placeId: Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (sdkContext != null && userLocation != null) {
|
||||
val mapState = remember(userLocation) {
|
||||
MapComposableState(
|
||||
MapOptions().apply {
|
||||
position = CameraPosition(
|
||||
point = GeoPoint(Latitude(userLocation.lat), Longitude(userLocation.lon)),
|
||||
zoom = Zoom(15f),
|
||||
tilt = Tilt(),
|
||||
bearing = Bearing(),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
MapComposable(modifier = modifier.fillMaxSize(), state = mapState)
|
||||
return
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
package com.guidecity.app.map
|
||||
|
||||
import android.util.Log
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import ru.dgis.sdk.Context as DgisContext
|
||||
import ru.dgis.sdk.DGis
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
private const val TAG = "DgisSdkProvider"
|
||||
|
||||
/**
|
||||
* Lazily initializes the 2GIS MapKit SDK once per process.
|
||||
*
|
||||
* Requires a real `dgissdk.key` file in `app/src/main/assets/`, issued
|
||||
* per-app (tied to the package name) from https://dev.2gis.com/ — the
|
||||
* `DGIS_API_KEY` used elsewhere in this app (2GIS's public REST/JS API key
|
||||
* format) is a *different* product and will not work here. Without a valid
|
||||
* key file, [sdkContext] is null and [DgisMapView] falls back to its
|
||||
* placeholder rendering instead of crashing.
|
||||
*/
|
||||
@Singleton
|
||||
class DgisSdkProvider @Inject constructor(
|
||||
@ApplicationContext private val androidContext: android.content.Context,
|
||||
) {
|
||||
val sdkContext: DgisContext? by lazy {
|
||||
runCatching { DGis.initialize(androidContext) }
|
||||
.onSuccess { Log.i(TAG, "2GIS SDK initialized") }
|
||||
.onFailure {
|
||||
Log.e(
|
||||
TAG,
|
||||
"2GIS SDK initialization failed — is a valid dgissdk.key present in " +
|
||||
"app/src/main/assets/? (see README)",
|
||||
it,
|
||||
)
|
||||
}
|
||||
.getOrNull()
|
||||
}
|
||||
}
|
||||
|
|
@ -85,6 +85,7 @@ fun MapScreen(
|
|||
.padding(padding),
|
||||
) {
|
||||
DgisMapView(
|
||||
sdkContext = viewModel.dgisSdkContext,
|
||||
userLocation = resolvedLocation,
|
||||
places = nearbyPlaces,
|
||||
onPlaceClick = {},
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.guidecity.app.data.repository.PlacesRepository
|
|||
import com.guidecity.app.location.LatLon
|
||||
import com.guidecity.app.location.LocationProvider
|
||||
import com.guidecity.app.location.SelectedLocationHolder
|
||||
import com.guidecity.app.map.DgisSdkProvider
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
@ -34,8 +35,12 @@ class MapViewModel @Inject constructor(
|
|||
private val locationProvider: LocationProvider,
|
||||
private val selectedLocationHolder: SelectedLocationHolder,
|
||||
private val placesRepository: PlacesRepository,
|
||||
dgisSdkProvider: DgisSdkProvider,
|
||||
) : ViewModel() {
|
||||
|
||||
/** Null until a valid `dgissdk.key` is present — see [DgisSdkProvider]. */
|
||||
val dgisSdkContext = dgisSdkProvider.sdkContext
|
||||
|
||||
private val _uiState = MutableStateFlow<MapUiState>(MapUiState.CheckingPermission)
|
||||
val uiState: StateFlow<MapUiState> = _uiState.asStateFlow()
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ androidxTestExtJunit = "1.2.1"
|
|||
espressoCore = "3.6.1"
|
||||
mockk = "1.13.12"
|
||||
kotlinxCoroutinesTest = "1.8.1"
|
||||
dgisSdk = "13.5.0"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
|
|
@ -62,6 +63,9 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man
|
|||
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
|
||||
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutinesTest" }
|
||||
|
||||
dgis-sdk-map = { group = "ru.dgis.sdk", name = "sdk-map", version.ref = "dgisSdk" }
|
||||
dgis-compose-map = { group = "ru.dgis.sdk", name = "compose-map", version.ref = "dgisSdk" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
|
|
|
|||
|
|
@ -11,9 +11,7 @@ dependencyResolutionManagement {
|
|||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
// TODO: add the 2GIS MapKit maven repository here once its exact URL is
|
||||
// confirmed from the 2GIS developer portal (see android/README section
|
||||
// on the map SDK integration point).
|
||||
maven { url = uri("https://artifactory.2gis.dev/sdk-maven-release") }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue