
Debug Mobile Autofill & Password Manager Crashes
When autofill breaks, users lose more than convenience — they lose the one flow that keeps sign-ins frictionless, and the crash that follows is usually blamed on the password manager when the real fault lives in your app. On Android, the Autofill framework and Credential Manager ship view hierarchies across process boundaries, where a single oversized AssistStructure throws TransactionTooLargeException. On iOS, Password AutoFill and ASAuthorizationController fail silently or crash when Associated Domains drift out of sync. This guide walks through the concrete crash modes in each system, with code you can drop in, and shows how to instrument the path so the next password manager crash arrives with context instead of a mystery.
Autofill is a handoff, not a widget. Your app surrenders a snapshot of its view tree and credential state to a system service — or a third-party process on Android — and that snapshot crosses a sandbox boundary, a Binder parcel limit, and a version gap that shifts every OS release.
Why autofill and password manager crashes differ from other UI bugs
Three properties make this subsystem fail differently. First, the data crosses process boundaries: on Android the entire view hierarchy is serialized into an AssistStructure and sent to an AutofillService running in another process, which means it obeys Binder transaction limits rather than your app's memory budget. Second, the counterparty is often not your code at all — it is a third-party password manager like Bitwarden or 1Password, so a crash can originate from a process you do not ship. Third, both platforms degrade silently: iOS in particular returns nothing and continues rather than throwing, so the "crash" you chase is frequently a misconfiguration that never surfaces in a stack trace.
Android: the Autofill framework
Android's Autofill framework lets a system-level or third-party AutofillService read your view hierarchy and write values back. The service contract lives in AutofillService, and the data it receives is a serialized AssistStructure. The two failure families below produce most production crashes.
TransactionTooLargeException and the Binder parcel limit
The single most common autofill crash is a TransactionTooLargeException thrown while the system serializes your view hierarchy for the autofill service. The framework packs the full AssistStructure — every ViewNode, text, hint, and autofill ID — into a Binder transaction, and Binder transactions are capped at roughly one megabyte. A deep or heavily populated layout (an infinite-scroll list, a complex form, a screen with hundreds of nodes) routinely exceeds that, and the exception surfaces not in your code but inside the framework call that starts the autofill session.
// A dense RecyclerView-driven form: every cell becomes a ViewNode in the
// AssistStructure. A few thousand nodes push the Binder parcel past ~1 MB
// and the framework throws TransactionTooLargeException.The fix is to shrink the structure the framework has to serialize. Mark containers that do not need autofill with importantForAutofill="noExcludeDescendants" so their subtrees are pruned, and keep autofill-relevant fields shallow.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:importantForAutofill="noExcludeDescendants">
<!-- A decorative, non-form subtree that was ballooning the AssistStructure -->
</LinearLayout>Android's reference for TransactionTooLargeException is explicit that the parcel size is the constraint; the only real remedy is to send less data. Prefer a few targeted android:autofillHints attributes over auto-populating the entire tree, and avoid nesting non-form views inside autofill-relevant containers.
AutofillService lifecycle and timeout crashes
Your app's own AutofillService (if you ship one) has a hard timeout on onFillRequest. The system calls onFillRequest(FillRequest, CancellationSignal, FillCallback) and expects FillCallback.onSuccess() or onFailure() promptly; do heavy work on the main thread and the session is cancelled, the UI janks, and on slower devices the framework can kill the service process mid-transaction.
class MyAutofillService : AutofillService() {
override fun onFillRequest(
request: FillRequest,
cancellationSignal: CancellationSignal,
callback: FillCallback
) {
// BUG: blocking disk/network work here stalls the fill and can
// trigger a timeout or ANR before onSuccess() is ever called.
val dataset = loadCredentialsSynchronously() // network on the binder thread
callback.onSuccess(FillResponse.Builder().addDataset(dataset).build())
}
}Move lookup work to a background executor, cache aggressively, and always answer the callback — a fill request that never completes is worse than an empty response because it leaves the UI in a pending state and can wedge the next session.
SaveInfo and onSaveRequest null handling
onSaveRequest(SaveRequest, SaveCallback) fires after a user submits a form, and SaveRequest.getSaveInfo() can be null when the framework decided there was nothing worth saving. Dereferencing it without a null check is a textbook crash, as is building a SaveInfo from a field the user left empty.
override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
val info = request.saveInfo ?: run {
callback.onSuccess() // nothing to save — respond and return
return
}
if (info.text.isNullOrBlank()) {
callback.onSuccess()
return
}
saveCredential(info.text.toString(), info.passwordIds ?: emptyArray())
callback.onSuccess()
}Treat the save path as best-effort. A SaveInfo with blank text or a missing password id is normal user behavior, not an invariant you can enforce with a force-unwrap.
Android 14+ Credential Manager
Android 14 introduced the Jetpack Credential Manager, a unified API that combines passwords, passkeys, and federated sign-in under one coroutine-based surface. It replaces the older autofill plumbing for most new apps, and it fails through typed exceptions rather than crashes — if you surface them correctly.
GetCredentialRequest and NoCredentialException
CredentialManager.getCredential() returns a coroutine that can throw several GetCredentialException subtypes. The one every app hits is NoCredentialException, thrown when the user has no saved credential — and calling it from the main thread or failing to catch it crashes the coroutine scope.
scope.launch {
val request = GetCredentialRequest(
listOf(
GetPasswordOption(),
GetCredentialOption(retrievalOption = GetSignInWithGoogleOption("serverClientId"))
)
)
try {
val result = credentialManager.getCredential(context, request)
handleCredential(result.credential)
} catch (e: NoCredentialException) {
// Normal: the user has nothing saved. Offer manual sign-in.
showManualSignIn()
} catch (e: GetCredentialCancellationException) {
// User dismissed the sheet — not an error.
} catch (e: GetCredentialException) {
reportCrash(e)
}
}A related pitfall is mixing the Credential Manager with a simultaneously active AutofillService. The two can race for the same fields, and a third-party manager intercepting the request can produce duplicate fills or a cancellation the framework did not anticipate.
CreateCredentialRequest and cross-provider races
Saving credentials through CreateCredentialRequest has the same shape. The creation flow can throw CreateCredentialException subtypes, and because it fans out to multiple credential providers (Google, passkey backends, third-party managers), one slow or misbehaving provider can stall or cancel the whole transaction.
scope.launch {
val request = CreateCredentialRequest(
CreatePasswordRequest(username, password),
CreatePublicKeyCredentialRequest(
requestJson = passkeyRequestJson,
preferImmediatelyAvailableCredentials = true
)
)
try {
val result = credentialManager.createCredential(context, request)
onSaved(result)
} catch (e: CreateCredentialCancellationException) {
// User backed out of the save sheet.
} catch (e: CreateCredentialException) {
reportCrash(e)
}
}The coroutine-based API means every call must run in a lifecycle-aware scope; a CredentialManager call fired from a destroyed activity throws into a dead scope and produces the kind of "crash with no stack trace" that users report as an autofill failure.
Third-party password managers: Bitwarden, 1Password, and friends
On Android, a large share of autofill traffic is handled by third-party AutofillService implementations. When Bitwarden or 1Password crashes, your app usually gets the blame even though the fault is in their process — but there are failures you can cause. Asking the manager to fill a field it cannot map, changing autofill IDs mid-session, or nesting editable views inside non-editable parents all produce inconsistent behavior that surfaces as a crash on the manager's side. Keep autofillHints stable, give each form field a stable resource ID, and test with at least two third-party managers installed before shipping. The same discipline applies to the secure storage your credentials ultimately land in — see our guide to debugging secure storage crashes for the Keystore and Keychain side of the story.
iOS Password AutoFill
iOS handles autofill through Password AutoFill, which reads the textContentType of your text fields and matches them against the saved credentials for your app's associated domain. It fails silently far more often than it crashes, which is exactly why it is so hard to debug.
Associated Domains misconfiguration
Password AutoFill only works when your app declares the website domain it is allowed to autofill, via the Associated Domains entitlement. A missing or mismatched webcredentials entry means autofill simply never offers credentials — no crash, no error, just an empty QuickType bar. A wrong domain, a missing apple-app-site-association file on the server, or a signing identity that does not match all produce the same silent failure.
<key>com.apple.developer.associated-domains</key>
<array>
<string>webcredentials:example.com</string>
</array>This is a configuration crash in all but name: the user reports "autofill crashed," and the real problem is that the association never resolved. Validate the association file at https://example.com/.well-known/apple-app-site-association and confirm the domain matches your webcredentials entry exactly.
ASAuthorizationController delegate lifetime
For Sign in with Apple, passkeys, and the legacy password request, iOS uses ASAuthorizationController. It holds its delegate weakly, and it requires a presentation context provider to anchor the sheet. The classic crash is letting the delegate and the presentation provider deallocate while the sheet is presented.
final class SignInCoordinator {
func start() {
let request = ASAuthorizationPasswordProvider().createRequest()
let controller = ASAuthorizationController(authorizationRequests: [request])
// BUG: a local delegate/provider dies at the end of this scope,
// and the completion handler fires into a dangling reference.
let delegate = AuthDelegate() // deallocated immediately
controller.delegate = delegate
controller.presentationContextProvider = delegate
controller.performRequests()
}
}Store the controller, delegate, and presentation context provider in properties that outlive the sheet, and implement ASAuthorizationControllerPresentationContextProviding on a retained object rather than a transient one.
textContentType and wrong field configuration
AutoFill matches on textContentType. A password field marked .username, a username field left untyped, or a secureTextEntry mismatch causes the system to fill the wrong field or refuse to fill at all.
usernameField.textContentType = .username
passwordField.textContentType = .password
newPasswordField.textContentType = .newPasswordThe mismatch rarely throws — it quietly disables autofill for that field, which your users then report as a broken or "crashed" password flow. Combine the correct textContentType with associatedDomains and an accurate apple-app-site-association file, and test the full save-then-fill loop on device rather than in the simulator, where Password AutoFill behaves differently.
Instrumenting autofill crashes
A report that says "TransactionTooLargeException in autofill" or "ASAuthorizationController crashed" tells you almost nothing on its own. Instrument the path so every failure carries context: the number of view nodes in the AssistStructure, the autofill hints in play, which AutofillService or credential provider handled the request, and on iOS whether the associated domain resolved. Attach these as breadcrumbs before the crash fires so a silent iOS failure is correlated with a missing webcredentials entry instead of a phantom stack trace.
That correlation is what a purpose-built mobile observability tool gives you. Bugspulse captures autofill and credential-manager failures with provider context intact, so you see whether a crash came from an oversized view hierarchy, a cancelled credential sheet, or a broken associated domain instead of guessing. If you are still on a generic reporter that drops autofill metadata, take a look at how Bugspulse handles mobile crash reporting and instrument your first sign-in flow today.
Ready to stop chasing anonymous password manager crashes? Create a free Bugspulse account and ship your next build with the autofill handoff fully instrumented.