The app still shipped under the identity of the DJI MSDK Flutter sample it was started from, so every install, log line and crash report named a DJI sample rather than PilotVault. Rename both namespaces: Android com.dji.flutter.dji_msdk_sample -> com.pilotvault.flyapp Dart dji_msdk_sample -> pilotvault_fly The Kotlin sources move under com/pilotvault/flyapp to match. The manifest's meta-data name stays com.dji.sdk.API_KEY — that string is fixed by the SDK's own lookup and is not ours to rename. A DJI App Key is bound to the application id, so the old key died with the old id and a new one was registered against com.pilotvault.flyapp. That forced the key to be touched anyway, so stop committing it: the Gradle property becomes PILOTVAULT_FLY_API_KEY and now lives in the developer's ~/.gradle/gradle.properties, which Gradle merges into every build. android/gradle.properties keeps the build flags — gitignoring it wholesale would have taken useAndroidX and the Flutter migrator flags out of version control with it — and documents where the key belongs. A missing key prints a banner rather than silently baking the placeholder into an APK that cannot register, which otherwise only surfaces as a registration failure once the tablet is out at the aircraft. That warning uses println because `flutter build` filters Gradle's warn-level output. The rename means Android treats this as a new app: uninstall com.dji.flutter.dji_msdk_sample before installing, or both will sit on the tablet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
182 lines
7.4 KiB
Groovy
182 lines
7.4 KiB
Groovy
plugins {
|
|
id "com.android.application"
|
|
id "kotlin-android"
|
|
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
|
id "dev.flutter.flutter-gradle-plugin"
|
|
}
|
|
|
|
android {
|
|
namespace = "com.pilotvault.flyapp"
|
|
compileSdk = 35
|
|
// DJI MSDK V4 ships prebuilt native (.so) libraries; pin a known-good NDK.
|
|
ndkVersion = flutter.ndkVersion
|
|
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
targetCompatibility = JavaVersion.VERSION_1_8
|
|
}
|
|
|
|
kotlinOptions {
|
|
jvmTarget = JavaVersion.VERSION_1_8
|
|
}
|
|
|
|
defaultConfig {
|
|
applicationId = "com.pilotvault.flyapp"
|
|
// DJI MSDK V4 requires Android 5.0+ (API 21).
|
|
minSdkVersion = flutter.minSdkVersion
|
|
targetSdk = 34
|
|
versionCode = flutter.versionCode
|
|
versionName = flutter.versionName
|
|
|
|
// DJI's SDK + Secneo Helper class loading require MultiDex.
|
|
multiDexEnabled = true
|
|
|
|
// DJI provides prebuilt .so files only for these ABIs.
|
|
ndk {
|
|
abiFilters "armeabi-v7a", "arm64-v8a"
|
|
}
|
|
|
|
// The DJI App Key is injected into AndroidManifest.xml at build time.
|
|
// It is deliberately not stored in the repo — see android/gradle.properties
|
|
// for where to put it (~/.gradle/gradle.properties, or -P on the command
|
|
// line). Warn loudly when it is missing: the build still succeeds, but the
|
|
// resulting APK fails DJI SDK registration once it is on the aircraft.
|
|
// println, not logger.warn: `flutter build` filters Gradle's warn-level
|
|
// output, so a logger.warn here is never seen by the person building.
|
|
def djiAppKey = project.findProperty("PILOTVAULT_FLY_API_KEY")
|
|
if (!djiAppKey) {
|
|
println(
|
|
"\n**************************************************************\n" +
|
|
"WARNING: PILOTVAULT_FLY_API_KEY is not set.\n" +
|
|
"The APK will build, but DJI SDK registration will FAIL at\n" +
|
|
"runtime. Set the key in ~/.gradle/gradle.properties - see\n" +
|
|
"android/gradle.properties for details.\n" +
|
|
"**************************************************************\n")
|
|
}
|
|
manifestPlaceholders["PILOTVAULT_FLY_API_KEY"] =
|
|
(djiAppKey ?: "PASTE_YOUR_DJI_APP_KEY_HERE")
|
|
}
|
|
|
|
buildTypes {
|
|
release {
|
|
// TODO: Add your own signing config for the release build.
|
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
|
signingConfig = signingConfigs.debug
|
|
// DJI requires its ProGuard rules when minify is enabled.
|
|
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
|
}
|
|
}
|
|
|
|
// DJI native libraries must not be stripped, and several bundled assets/
|
|
// resources collide across the SDK modules and must be de-duplicated.
|
|
packaging {
|
|
jniLibs {
|
|
keepDebugSymbols += [
|
|
"**/libdjivideo.so",
|
|
"**/libSDKRelativeJNI.so",
|
|
"**/libFlyForbid.so",
|
|
"**/libduml_vision_bokeh.so",
|
|
"**/libyuv2.so",
|
|
"**/libGroudStation.so",
|
|
"**/libFRCorkscrew.so",
|
|
"**/libUpgradeVerify.so",
|
|
"**/libFR.so",
|
|
"**/libDJIFlySafeCore.so",
|
|
"**/libdjifs_jni.so",
|
|
"**/libsfjni.so",
|
|
"**/libDJICommonJNI.so",
|
|
"**/libDJICSDKCommon.so",
|
|
"**/libDJIUpgradeCore.so",
|
|
"**/libDJIUpgradeJNI.so",
|
|
"**/libDJIWaypointV2Core.so",
|
|
"**/libdjiwaypointv2.so",
|
|
"**/libDJIMOP.so",
|
|
"**/libDJISDKLOGJNI.so",
|
|
]
|
|
}
|
|
resources {
|
|
excludes += [
|
|
"META-INF/rxjava.properties",
|
|
"META-INF/proguard/*",
|
|
"META-INF/INDEX.LIST",
|
|
"META-INF/DEPENDENCIES",
|
|
"assets/location_map_gps_locked.png",
|
|
"assets/location_map_gps_3d.png",
|
|
]
|
|
pickFirsts += [
|
|
"lib/**/libstlport_shared.so",
|
|
"lib/**/libRoadLineRebuildAPI.so",
|
|
"lib/**/libGNaviUtils.so",
|
|
"lib/**/libGNaviMapex.so",
|
|
"lib/**/libGNaviMap.so",
|
|
"lib/**/libGNaviSearch.so",
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
flutter {
|
|
source = "../.."
|
|
}
|
|
|
|
dependencies {
|
|
// DJI Mobile SDK V4 (core). 'provided' contains compile-time-only stubs.
|
|
implementation "com.dji:dji-sdk:4.18"
|
|
compileOnly "com.dji:dji-sdk-provided:4.18"
|
|
|
|
implementation "androidx.multidex:multidex:2.0.1"
|
|
implementation "androidx.core:core-ktx:1.13.1"
|
|
|
|
// The DJI SDK bundles layouts that reference AppCompat (srcCompat) and
|
|
// ConstraintLayout attributes, so these must be on the resource classpath.
|
|
implementation "androidx.appcompat:appcompat:1.6.1"
|
|
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
|
|
|
|
// DJI's SDK publishes an event bus dependency used internally by some callbacks.
|
|
implementation "com.squareup:otto:1.3.8"
|
|
|
|
// Required by the FlySafe / GEO modules pulled in by the SDK.
|
|
implementation "androidx.recyclerview:recyclerview:1.3.2"
|
|
}
|
|
|
|
// --- Keep transitive AndroidX on the AGP-8.6 / compileSdk-35 track -----------
|
|
// The newer Flutter plugins (url_launcher / path_provider / flutter_svg) pull
|
|
// androidx.core 1.17 and androidx.browser 1.9, whose aar metadata demands AGP
|
|
// 8.9.1 + compileSdk 36. The DJI MSDK V4 toolchain is pinned to AGP 8.6 / SDK 35
|
|
// (bumping it risks the fragile native build), so force these artifacts back to
|
|
// versions that build against SDK 35. Their APIs used by the plugins are stable
|
|
// across this range.
|
|
configurations.all {
|
|
resolutionStrategy {
|
|
force "androidx.core:core:1.13.1"
|
|
force "androidx.core:core-ktx:1.13.1"
|
|
force "androidx.browser:browser:1.8.0"
|
|
}
|
|
}
|
|
|
|
// --- Gradle 8 task-validation workaround ------------------------------------
|
|
// Flutter's `compileFlutterBuild<Variant>` task declares an output directory
|
|
// that overlaps the Android source sets, so Gradle 8's execution-time
|
|
// validation flags several AGP tasks (mergeShaders, checkAarMetadata, …) as
|
|
// consuming that output without a declared dependency, failing the build.
|
|
// Wire the dependency in explicitly. Safe: none of these tasks are inputs to
|
|
// the Flutter compile task, so no dependency cycle is introduced.
|
|
afterEvaluate {
|
|
def flutterTaskFor = { String name ->
|
|
for (v in ["Debug", "Release", "Profile"]) {
|
|
if (name.contains(v)) return tasks.findByName("compileFlutterBuild${v}")
|
|
}
|
|
return null
|
|
}
|
|
// Merge* source-set tasks (shaders / assets / jniLibs) — public AGP type.
|
|
tasks.withType(com.android.build.gradle.tasks.MergeSourceSetFolders).configureEach { t ->
|
|
def ft = flutterTaskFor(t.name)
|
|
if (ft != null) t.dependsOn(ft)
|
|
}
|
|
// Other AGP tasks that read the merged inputs.
|
|
tasks.matching { it.name ==~ /^(check|process|package|bundle|lintVitalAnalyze|lintAnalyze)(Debug|Release|Profile).*/ }.configureEach { t ->
|
|
def ft = flutterTaskFor(t.name)
|
|
if (ft != null) t.dependsOn(ft)
|
|
}
|
|
}
|