# Bearound Flutter — AI agent setup prompt Hover the block below and click the **copy icon** in its top-right corner to copy the prompt, then paste it into your AI coding agent (Claude Code, Cursor, Copilot, …) with your app's repo open. The agent reads the [SDK README](./README.md) and wires the full iOS/Android background integration. ```text Integrate bearound_flutter_sdk into this Flutter app (iOS + Android). First READ the SDK's README end to end — especially "Platform Setup" (Android + iOS), "Disable UIScene (Flutter 3.41+) — required", "Background tasks (BGTaskScheduler) — required", "Push notifications & background wakeup", "Silent-push wake-up (Android)", and "Quick Start" — then do ALL of the following. SCOPE: You do NOT have the SDK's example/ dir in your app repo, so never try to open it. Wire the complete iOS AppDelegate from the SDK README (step 4); apply every change to YOUR app's repo. The example is a demo, NOT a template: its Info.plist is a build-time stub, its build.gradle.kts has a dev-only mavenLocal(), its Runner.entitlements is `development`, and its main.dart hardcodes a demo businessToken. Do NOT copy those. 1. Install: add `bearound_flutter_sdk: ^3.5.0` under dependencies in pubspec.yaml and run `flutter pub get`. In android/app/build.gradle.kts set `minSdk = 23` (replace the `flutter.minSdkVersion` reference). iOS Podfile: UNCOMMENT and set `platform :ios, '13.0'` (fresh Podfiles ship it commented), and INSIDE `target 'Runner' do` add `use_frameworks!` — the AppDelegate in step 4 does `import BearoundSDK`, a transitive Swift pod that is only visible with dynamic- framework linkage, which a fresh Podfile omits (without it, step 8's iOS build fails with "no such module BearoundSDK"). Then `cd ios && pod install` (run `flutter pub get` FIRST so `.symlinks/plugins` exists). Also ensure the Runner APP target's IPHONEOS_DEPLOYMENT_TARGET >= 13.0. Add JitPack for the native Android SDK: if the app's android/settings.gradle(.kts) has a `dependencyResolutionManagement { repositories { … } }` block (newer Flutter template), add `maven { url = uri("https://jitpack.io") }` INSIDE that repositories block — do NOT use allprojects, it fails under RepositoriesMode.FAIL_ON_PROJECT_REPOS. Otherwise (older template, like example/) add it in the root android/build.gradle.kts under `allprojects { repositories { … } }`. Kotlin DSL uses `maven { url = uri("https://jitpack.io") }`; only in a Groovy build.gradle use `maven { url "https://jitpack.io" }` — never paste Groovy string-call syntax into a .kts file (compile error → the native SDK never resolves). 2. iOS Info.plist (ios/Runner/Info.plist): add the five UIBackgroundModes (fetch, location, processing, bluetooth-central, remote-notification), the two BGTaskSchedulerPermittedIdentifiers (io.bearound.sdk.sync, io.bearound.sdk.processing), and the NS…UsageDescription strings (NSBluetoothAlwaysUsageDescription, NSLocationWhenInUseUsageDescription, NSLocationAlwaysAndWhenInUseUsageDescription) — user-facing rationale, no jargon. Then run `plutil -lint ios/Runner/Info.plist`. 3. iOS UIScene — REQUIRED (README "Disable UIScene"): find `UIApplicationSceneManifest` in ios/Runner/Info.plist and rename it to `_UIApplicationSceneManifest` — keep the `_`, do NOT delete the block. If the key is NOT present, this is a fresh Flutter 3.41+ project where the migrator injects it only at BUILD time: run `flutter build ios --no-codesign` ONCE so the key appears, THEN rename it; if it is still absent, add this top-level inert block manually: `_UIApplicationSceneManifestUIApplicationSupportsMultipleScenes`. A literal "rename" when no key exists is a no-op — step 8's build then injects the UNPREFIXED key, UIScene stays ENABLED, and state restoration, region relaunch and silent-push wakeup all silently die. Keep UIMainStoryboardFile / UILaunchStoryboardName as-is (removing them causes a black screen). If a SceneDelegate.swift exists it is now dead code — delete it and its project.pbxproj references. 4. iOS AppDelegate — wire the COMPLETE AppDelegate. The SDK README's iOS section ("Background tasks (BGTaskScheduler) — required") shows it IN FULL — it is example/ios/Runner/AppDelegate.swift verbatim, also fetchable at https://raw.githubusercontent.com/Bearound/bearound-flutter-sdk/main/example/ios/Runner/AppDelegate.swift — use it as-is, including `import BearoundSDK` and the patchFlutterProMotionCrash() call (invoked FIRST in didFinishLaunching — REQUIRED, because disabling UIScene in step 3 re-enters an iOS-26 ProMotion crash path that this workaround fixes; omitting it crashes the host on iPhone 15 Pro and newer). The file uses `class AppDelegate: FlutterAppDelegate`, calls GeneratedPluginRegistrant.register(with: self), BeAroundSDK.shared.registerBackgroundTasks() before super, and application.registerForRemoteNotifications(); overrides performFetchWithCompletionHandler and handleEventsForBackgroundURLSession; and overrides the DEPRECATED application(_:didReceiveRemoteNotification:) WITHOUT fetchCompletionHandler (the modern variant is not delivered on Flutter, flutter#155479), guarded on userInfo["bearound"] != nil -> performBackgroundBLERefreshAndSync. The bullet list describes that file — it is NOT a license to hand-write a subset. ONE conditional edit to the verbatim copy: keep registerBackgroundTasks, registerForRemoteNotifications, performFetch, handleEventsForBackgroundURLSession and the bearound-guarded silent-push override UNCONDITIONAL; BUT if YOUR app already registers a UNUserNotificationCenter delegate (firebase_messaging / flutter_local_notifications), do NOT set `center.delegate = self` or call `requestAuthorization` — leave the existing owner and add only the bearound-guarded silent-push override. Otherwise copy as-is. 5. iOS Push entitlement: write ios/Runner/Runner.entitlements with `aps-environment` = `production`, then set `CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements` in ONLY the three XCBuildConfiguration blocks (Debug/Profile/Release) that carry `PRODUCT_BUNDLE_IDENTIFIER` for the Runner APP target — NOT the project-level blocks and NOT the RunnerTests blocks (their bundle ids end in `.RunnerTests`). Prefer editing via Xcode or the ruby `xcodeproj` gem over a blind text replace of project.pbxproj. Do NOT copy example/ios/Runner/Runner.entitlements (it is `development`). Match the backend APNs credential environment to the build under test (dev/sandbox ⇄ store/production). 6. Dart (Quick Start): in `main()` call `WidgetsFlutterBinding.ensureInitialized()` FIRST, then run the async setup (await before runApp, or from the root widget's initState) — never invoke SDK methods before the binding is ready. Call `await BearoundFlutterSdk.requestPermissions()`, then `await BearoundFlutterSdk.configure(businessToken: , scanPrecision: ScanPrecision.high, maxQueuedPayloads: MaxQueuedPayloads.medium)`, then `await BearoundFlutterSdk.startScanning()` with NO foregroundScanConfig — OPPORTUNISTIC is the default and wakes even app-killed via the PendingIntent, with no Play Store review video. Only pass `foregroundScanConfig: const ForegroundScanConfig()` if I confirm we will submit the Play Console foreground- service declaration + demo video; STOP and ask which mode before wiring it. Wire the OEM-reliability flow: if !isIgnoringBatteryOptimizations() show a rationale then openBatteryOptimizationSettings(); if isAutostartManageable() show a rationale then openManufacturerAutostartSettings(). businessToken: read from `const String.fromEnvironment('BEAROUND_TOKEN')` (passed as `--dart-define=BEAROUND_TOKEN=…`). NOTE this returns '' (compiles green) when the define is absent, and configure() only throws at RUNTIME — which step 8's build never reaches — so ASSERT the token is non-empty before calling configure(). If unset, STOP and get the real token; never hardcode a placeholder and never fall back to the example token `ee2ec9c46d2b2ad99bddcdd0afe224e6` (example/lib/main.dart hardcodes it; it authenticates as the wrong tenant). Push token: the SDK's AppDelegate swizzle already captures the RAW APNs token on iOS, so setPushToken is OPTIONAL — on a fresh Firebase-less app do NOT add Firebase. Only if Firebase is present (it intercepts the swizzle): add `firebase_messaging` to pubspec.yaml and run `flutter pub get`, then forward `FirebaseMessaging.instance.getToken()` on Android / `getAPNSToken()` (RAW APNs, NOT getToken()) on iOS via setPushToken. Do NOT invent a token. 7. Silent-push wake-up on Android — OPTIONAL: wire this ONLY if the backend wakes the device by silent push; skip the whole step otherwise. It applies solely when the app uses `firebase_messaging` — Firebase delivers the data push to YOUR background handler, not to the SDK, so you MUST forward it. In the handler registered via `FirebaseMessaging.onBackgroundMessage` (annotate it `@pragma('vm:entry-point')`), call `DartPluginRegistrant.ensureInitialized()` FIRST — the handler runs in a SEPARATE isolate where the plugin's method channel isn't registered, so without it the next call silently no-ops — THEN `await BearoundFlutterSdk.handleRemoteMessage( Map.from(message.data))`. It returns `true` and fires the on-demand scan + sync for Bearound wake-ups (the payload carries the `bearound` marker) and `false` for any third-party push — pass those through to your own handling. iOS needs NOTHING here: the AppDelegate from step 4 already forwards the silent push down the same path. If the app does NOT use `firebase_messaging` there is nothing to wire — a backend push wake-up needs an FCM receiver, so the step is inapplicable. See README → "Silent-push wake-up (Android)". 8. Verify: run `flutter analyze` (0 errors), `flutter build apk --debug` (proves JitPack + native Android SDK resolve), and `flutter build ios --no-codesign`. Run the builds WITH `--dart-define=BEAROUND_TOKEN=` so the binary is actually authenticated — a tokenless build compiles green but fails at configure() on-device, and IS a FAILED verification. `flutter build ios --no-codesign` proves ONLY that pods resolve and the AppDelegate COMPILES; `--no-codesign` SKIPS signing, so it does NOT validate the entitlement, the Push capability, the provisioning profile, or any background/terminated wake — do NOT claim success from a green build. Then `plutil -lint` / `plutil -p` ios/Runner/Info.plist shows all five modes AND both BGTask ids. Assert the UIScene rename survived the FINAL build: `plutil -p ios/Runner/Info.plist | grep -q _UIApplicationSceneManifest` AND the unprefixed key is ABSENT (`! plutil -p ios/Runner/Info.plist | grep -qE '"UIApplicationSceneManifest"'`). If the unprefixed key reappeared, the migrator re-injected it — re-apply step 3's rename and rebuild. Treat a failed assert as a build FAILURE. A green build is NOT success: terminated wake still requires the human Xcode Push capability + on-device Always-location & Background App Refresh, then a PASSING 3-state field test with a real beacon. Only after the builds pass, hand me the 3-state field-test checklist. Report any build failure instead of claiming success. Guardrails — follow strictly: - iOS: NEVER rely on the SDK's APNs push swizzle alone if Firebase is present — Firebase intercepts it and the token ends up NULL. Forward the RAW APNs token via setPushToken (NOT the FCM token on iOS). - Android: default to the OPPORTUNISTIC scan (no foregroundScanConfig) — it wakes even app-killed via the PendingIntent and needs no Play review video. The connectedDevice foreground service is more resilient to aggressive OEM kills but REQUIRES a Google Play foreground-service declaration + demonstration video at review — do NOT wire it without my confirmation (STOP and ask which mode). - The SDK must NEVER crash the host app. - businessToken is REQUIRED and must be supplied by ME. If you do not have it, STOP and ask — do NOT reuse the example app's demo token (example/lib/main.dart hardcodes one; it authenticates as the wrong tenant and must never ship). Read it from `--dart-define=BEAROUND_TOKEN` or a config I provide, and assert it is non-empty before configure(). - STOP and hand me click-by-click steps for anything only a human can do: the Xcode Push Notifications capability signed with my provisioning profile (aps-environment development for Debug, production for Release), on-device grants (Always location + Background App Refresh), and the Google Play foreground-service declaration + demonstration video. Do not attempt those yourself. ``` Web-capable agents can fetch this prompt directly from its raw URL: `https://raw.githubusercontent.com/Bearound/bearound-flutter-sdk/main/AI-AGENT-SETUP.md`