# Platform Setup: iOS, Android, Tokens Deeper reference for getting `mapbox_maps_flutter` building and running cleanly on both platforms. The SKILL.md covers the 90% path — come here for details, gotchas, and CI/release notes. --- ## iOS ### Deployment target: 14.0 Mapbox requires iOS 14.0. Flutter's default is lower, which is why a fresh `flutter create` + `flutter pub add mapbox_maps_flutter` build fails. Set the minimum deployment on the Runner target in Xcode (General → Minimum Deployments → iOS = 14.0). If the project has an `ios/Podfile`, update the platform line to match: ```ruby # ios/Podfile platform :ios, '14.0' ``` After editing the Podfile, run: ```bash cd ios && pod install && cd .. ``` If you get a Podfile.lock conflict, delete `ios/Pods/`, `ios/Podfile.lock`, and re-run `pod install`. ### CocoaPods vs Swift Package Manager The plugin ships both: `ios/mapbox_maps_flutter.podspec` (CocoaPods) and `ios/mapbox_maps_flutter/Package.swift` (SPM). Flutter decides which to use based on your app configuration — new projects on Flutter 3.29+ default to SPM, older projects stay on CocoaPods. Either works; you don't need to choose or vendor dependencies yourself. To force a mode machine-wide: ```bash flutter config --enable-swift-package-manager # SPM flutter config --no-enable-swift-package-manager # CocoaPods only ``` ### Location permission `ios/Runner/Info.plist`: ```xml NSLocationWhenInUseUsageDescription Show your location on the map. ``` If you ever call any "Always" authorization API, you also need `NSLocationAlwaysAndWhenInUseUsageDescription`. Without the correct key, iOS will reject the permission prompt and `CLLocationManager` will stay in `.notDetermined`. ### Background modes The stock plugin does not require any background mode entitlement. Only enable Location updates under **Signing & Capabilities → Background Modes** if your app genuinely needs background location — each capability triggers additional App Store review scrutiny. ### Apple Silicon simulator No extra setup needed on Flutter 3.16+. If you see `building for iOS Simulator, but linking in object file built for iOS`, clean and re-run: `flutter clean && cd ios && pod deintegrate && pod install`. --- ## Android ### minSdk and compileSdk ```kotlin // android/app/build.gradle.kts android { defaultConfig { minSdk = 21 targetSdk = 34 } compileSdk = 34 } ``` The Mapbox Maven repository is configured automatically by the plugin; you do not need to add it yourself. ### Permissions `android/app/src/main/AndroidManifest.xml`: ```xml ``` Android 10+ treats location as a runtime permission — use `permission_handler` or your own flow to request it before enabling the puck. ### R8 / ProGuard The SDK ships consumer ProGuard rules. No app-side configuration is required unless you've disabled consumer rules with `android.proguardFiles.disableConsumer`. ### Platform view modes Android has multiple platform-view hosting modes (TLHC_VD, TLHC_HC, HC, VD). The default is fine for most apps. If you see black frames during scroll, try switching `MapWidget`'s `androidHostingMode` — this is an advanced knob, see the plugin's `MapWidget` docs. --- ## Access tokens ### Where it goes ```dart const accessToken = String.fromEnvironment('ACCESS_TOKEN'); void main() { MapboxOptions.setAccessToken(accessToken); runApp(const MyApp()); } ``` Must run **before** any `MapWidget` is constructed — a token set later won't retroactively authenticate an already-created map. ### Passing the token at build time ```bash flutter run --dart-define=ACCESS_TOKEN=pk.your_public_token flutter build ios --dart-define=ACCESS_TOKEN=$MAPBOX_TOKEN flutter build apk --dart-define=ACCESS_TOKEN=$MAPBOX_TOKEN ``` For a team of developers, a `--dart-define-from-file=env.json` file (gitignored) is convenient: ```json // env.json { "ACCESS_TOKEN": "pk.your_public_token" } ``` ```bash flutter run --dart-define-from-file=env.json ``` Add `env.json` to `.gitignore`. ### VS Code / Android Studio launch configs `.vscode/launch.json`: ```json { "configurations": [ { "name": "Flutter (dev)", "request": "launch", "type": "dart", "args": ["--dart-define-from-file=env.json"] } ] } ``` ### Public vs secret tokens Use a **public** token (`pk.…`) in the app — it's shipped in the binary and can be extracted. Rotate and scope it with URL restrictions in the Mapbox dashboard. Secret tokens (`sk.…`) must never ship to the client; they are for server-side tile preprocessing, offline downloads in CI, etc. ### Download token for installs Some release workflows require a **secret download token** with the `DOWNLOADS:READ` scope. This is separate from the runtime public token and is not needed at app runtime — only by CI to fetch the native SDK binaries. If the Android build fails with "401 Unauthorized" fetching Mapbox artifacts, configure the download token in `~/.gradle/gradle.properties`: ```properties MAPBOX_DOWNLOADS_TOKEN=sk.your_downloads_token ``` --- ## CI notes - Cache the `~/.pub-cache`, `ios/Pods`, and Gradle caches across runs — Flutter + Mapbox builds are slow from cold. - For iOS device builds, provision profiles must include the bundle ID; nothing Mapbox-specific. - Pass tokens via CI secrets, not committed files. Use `--dart-define=ACCESS_TOKEN=${{ secrets.MAPBOX_PUBLIC_TOKEN }}`. --- ## Release checklist - ✅ iOS Runner Minimum Deployment = 14.0 - ✅ Android minSdk = 21 - ✅ Public token passed via `--dart-define`, not hard-coded - ✅ Location permission purpose strings in `Info.plist` - ✅ Location permissions declared in `AndroidManifest.xml` - ✅ `MapboxOptions.setAccessToken(...)` called in `main()` before `runApp`