You join the mobile AI repo at 9:40 and the first ticket looks almost friendly. You add a multiline prompt field so testers can type a question into the on-device assistant. The app is still in the foreground on a development phone, and you have not touched Settings yet. Then you background the app, open Messages, and the keyboard suggests the private canary sentence you typed two minutes ago.
That leak is not a model bug, and it will not show up in your unit tests. iOS Keyboard Learning and Android Autofill treat a prompt box like any other text field. Your first-hour PR can ship on-device inference and still hand the user's last utterance to the IME. Treat every procedure below as a proposed single-device experiment until you record your own outcome.
What you are actually protecting
You should treat the prompt field as sensitive even when inference never leaves the device. The OS mechanism that learns those characters sits outside your process and your unit tests. Your first PR should name the strings you refuse to feed to QuickType, Gboard, and Autofill.
Keep these out of keyboard learning on the first-hour build:
- system or developer prompt templates pasted during debugging sessions
- user utterances that mention health, workplace, or account identifiers
- few-shot examples that still contain customer names from staging
- canary phrases you will later use to prove whether a leak happened
You are not hiding model weights in this pass, and you should not pretend otherwise. You are keeping first-hour text out of keyboard learning, Autofill stores, and OEM dictionaries. Those stores survive process death, and they can surface inside Messages after a single background.
Proposed test: one device, one lifecycle transition
Label this work as a proposed test until you record device, OS, keyboard, and outcome. Do not copy another person's pass or fail and treat it as your release evidence. Open with one lifecycle transition: prompt focused, then Home gesture, then a different app. If you skip the Messages check, you will ship a green build that still leaks the canary.
Record this environment before you start:
- device model and OS version, plus whether this is a simulator, emulator, or hardware
- application state: cold start, prompt focused, draft not yet written to disk
- keyboard vendor and Autofill provider, including Gboard, Samsung Keyboard, or stock iOS
- permission state: microphone is optional here, because the leak is text-only
- framework versions: UIKit/SwiftUI, AndroidX, React Native, or Flutter, with exact numbers
- network and power: note them so you do not mix this failure with radio or thermal bugs
Exact steps
- Install a clean build and skip restoring a keyboard backup if the OS offers one.
- Focus the prompt field and type a unique canary such as
ZXQ-ONDEVICE-PROMPT-9182. - Submit once so the field blurs and the IME has a chance to commit the phrase.
- Background the app with the Home gesture; do not force-quit before the first check.
- Open Messages or Notes and type
ZXQto see whether the canary is suggested. - On Android, open Settings and inspect Autofill plus the keyboard personalization screen.
- Return to the app and confirm whether the field still shows the canary after resume.
- Kill the process, relaunch, and check the field, the IME bar, and any Autofill save prompt.
Expected observations on a naive first PR
- iOS may offer the canary as a QuickType prediction after a single commit
- Android Autofill or Gboard may store the phrase against your application package
- process death may clear your ViewModel while the IME memory still keeps the canary
- reinstalling the app may not clear a cloud-backed keyboard dictionary on the same account
Recovery outcome you want
- the canary never appears in another app's keyboard suggestion bar
- clearing the field and killing the process leaves no local prompt draft
- testers do not need a keyboard dictionary reset after every internal build
If the canary silently appears in another app, the first PR failed the privacy bar. On-device inference quality does not excuse an IME side channel on a first-hour build. Write the failed observation into the PR, including keyboard vendor and OS point release.
First-hour code you should put in the PR
Treat the snippets below as starting points you still have to execute on a real device. These flags are not a guarantee against OEM keyboards, work profiles, or accessibility services. Check the framework version in the PR body so reviewers can reproduce the same traits. If a flag is missing on your min OS, document the gap instead of commenting it out quietly.
iOS UIKit
final class PromptViewController: UIViewController {
let promptView = UITextView()
override func viewDidLoad() {
super.viewDidLoad()
promptView.autocorrectionType = .no
promptView.spellCheckingType = .no
promptView.smartQuotesType = .no
promptView.smartDashesType = .no
promptView.smartInsertDeleteType = .no
promptView.autocapitalizationType = .none
promptView.textContentType = .none
if #available(iOS 17.0, *) {
promptView.inlinePredictionType = .no
}
if #available(iOS 18.0, *) {
promptView.writingToolsBehavior = .none
}
}
}
Read Apple's UITextInputTraits notes before you assume one property disables every learning path. Writing Tools and inline predictions are newer OS mechanisms than the classic autocorrect toggle. Gate the newer properties with availability checks so the first PR still compiles on older targets. Re-run the canary after each OS bump, because keyboard subsystems change in point releases.
SwiftUI
TextEditor(text: $prompt)
.textInputAutocapitalization(.never)
.autocorrectionDisabled(true)
.textContentType(.none)
.privacySensitive(true)
The privacySensitive modifier can hide text in system screen captures and some app switcher thumbnails. It does not replace autocorrect, text content type, or inline prediction controls on the field. Keep both layers in the first PR if your testers use TestFlight screenshots during onboarding.
Android XML and view code
<EditText
android:id="@+id/prompt"
android:inputType="textMultiLine|textNoSuggestions"
android:importantForAutofill="noExcludeDescendants"
android:importantForContentCapture="noExcludeDescendants"
android:autofillHints=""
android:imeOptions="flagNoPersonalizedLearning" />
promptEditText.setImportantForAutofill(
View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
promptEditText.setImportantForContentCapture(
View.IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS
)
}
promptEditText.setAutofillHints(null as String?)
flagNoPersonalizedLearning is an IME option, and a vendor keyboard may ignore it completely. You need importantForAutofill and importantForContentCapture because they close two different capture channels. Empty autofill hints are not enough if the view still reports itself as important for Autofill. After the XML change, still set the same flags in code so later Compose wrappers cannot drift.
Call the Autofill APIs after setContentView so the view tree does not restore a naive default. Content capture arrived later than Autofill, so wrap it with an SDK_INT check in the first PR. Null autofill hints plus IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS is the conservative pairing for prompts. Log the sdkInt and the flag values once in debug, then delete that log before you ship.
Jetpack Compose
OutlinedTextField(
value = prompt,
onValueChange = { prompt = it },
keyboardOptions = KeyboardOptions(
autoCorrectEnabled = false,
capitalization = KeyboardCapitalization.None,
keyboardType = KeyboardType.Text
)
)
Compose keyboard options do not automatically disable Autofill on every BOM version you might pin. Check the AndroidX release notes for your BOM and add Autofill semantics if the canary still appears. Keep the XML flags in mind when this field is hosted inside a hybrid View/Compose screen. Hybrid trees are a common first-PR miss when a junior only patches the Compose widget.
React Native
<TextInput
multiline
value={prompt}
onChangeText={setPrompt}
autoCorrect={false}
autoCapitalize="none"
spellCheck={false}
textContentType="none"
autoComplete="off"
importantForAutofill="no"
/>
Confirm the React Native version in your first-hour notes, because autoComplete mapping changed across releases. textContentType none is an iOS prop, and importantForAutofill is the Android counterpart on TextInput. spellCheck false does not disable Gboard personalization by itself on every Android device. If you wrap TextInput, forward these props or the first PR will look correct and still leak.
Flutter
TextField(
controller: promptController,
autocorrect: false,
enableSuggestions: false,
enableIMEPersonalizedLearning: false,
spellCheckConfiguration: const SpellCheckConfiguration.disabled(),
keyboardType: TextInputType.multiline,
textCapitalization: TextCapitalization.none,
)
enableIMEPersonalizedLearning false is the Android Flutter flag that juniors miss during onboarding. Put it on the checklist beside model packaging, because it is unrelated to inference quality. SpellCheckConfiguration.disabled stops the OS spell service from taking a second copy of the buffer. Run the canary on a physical Pixel or Samsung phone, not only on the desktop emulator keyboard.
Commands that help you prove the leak
You cannot dump another vendor's keyboard database on a stock phone without extra privileges. You can still collect evidence around your process, your view flags, and accidental log copies. If the canary appears in logcat, fix logging in the same PR as the IME flags. Keyboard learning is not the only first-hour side channel on a debug mobile AI build.
# Android: inspect Autofill around your prompt view after typing the canary
adb shell dumpsys autofill | grep -A 24 "prompt"
# Android: fail the PR if debug logging echoed the canary
adb logcat -d | grep -F "ZXQ-ONDEVICE-PROMPT-9182"
# Android: confirm the focused view after you tap the field
adb shell dumpsys window windows | grep -A 12 "mCurrentFocus"
# iOS Simulator: identify the booted device before you reset test state
xcrun simctl list devices booted
On a physical iPhone, Reset Keyboard Dictionary is the recovery control after a failed canary. Document that reset because it is destructive for the tester's other learned words and shortcuts. On Android, a keyboard app reset in Settings is similarly destructive and should be a last resort. Prefer preventing the learn step over asking testers to wipe dictionaries after every build.
Decision table for the first PR
Use this table during the first PR review, not as a substitute for the canary run. If the product needs a draft after process death, store it in the app container only. File protection and encrypted storage are a different change from IME flags, so split the diffs. Do not copy prompt flags onto a public help search box or you will harm everyday typing.
| Field purpose | Disable IME learning | Disable Autofill | Keep a local draft | First-PR note |
|---|---|---|---|---|
| On-device user prompt | Yes | Yes | Memory only unless policy requires a draft | Default for this article |
| Public help search | No | Usually no | Disk cache is fine | Do not reuse prompt flags |
| API key or session paste | Yes | Yes | Never | Use a secure field, not a prompt |
| Internal few-shot editor | Yes | Yes | Encrypted app storage only | Still run the canary |
If reviewers ask for a surviving draft, that is a storage ticket, not an IME ticket. Mixing those concerns is how first-hour PRs grow into unreviewable diffs. Keep the canary, the flags, and the logging redaction in one privacy-sized change. Leave model packaging and rollback rehearsal to the PRs that already own those failures.
Where a coding assistant actually helps
A junior engineer can waste the first hour hunting flag names across UIKit, Jetpack, React Native, and Flutter. Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode is an open-source project with free model access and a free server option you can use to generate platform wrappers from the table above. You still owe the device run, because generated flags are not evidence of keyboard behavior.
Paste your real prompt widget and ask only for the platform-specific props that match this table. Then run the canary steps on one phone and record whether the suggestion recovered, restarted, or silently appeared. Skip any generated claim about battery, quotas, or model quality, because this leak is an OS input path.
Limitations, and who should not use this
This approach does not stop a user from pasting the prompt into Messages themselves on purpose. It does not control a work profile keyboard, a Bluetooth hardware keyboard, or an accessibility service. It also does not replace screenshot policy, backup exclusion, or redaction of prompt text in crash logs. Treat IME flags as a product hygiene control, not as a compliance certification for regulated data.
You should not disable learning on every text box, because navigation search becomes slower and more error prone. You should not skip the canary test because a simulator keyboard looked clean during the first hour. You should not ship FLAG_SECURE on every screen only to hide this leak; that is a different tradeoff. Health and finance clients still need legal review beyond a TextField configuration in the onboarding PR.
Inline predictions, Writing Tools, content capture, and OEM clipboard suggestions change by OS point release. Re-run the steps when you bump minSdk or the iOS deployment target. Flutter's IME personalization flag is an Android control and will not save you on iOS. Cross-platform wrappers still need the native props on each side of the bridge.
What you should send back
Please do not reply with a generic claim that the field looked fine on your machine. Send comparable evidence: device, OS, keyboard vendor, Autofill provider, and the lifecycle transition. Include whether the canary recovered after process death, restarted after reinstall, or silently appeared elsewhere. If the canary stayed inside your process, the first PR is ready for a reviewer to reproduce.
If it escaped into the keyboard bar, fix the field before you debate on-device model quality. First-hour mobile AI work fails in OS input paths more often than it fails in the interpreter. Your onboarding checklist should include this canary beside permission revoke tests and backup exclusion checks.
Top comments (0)