Skip to content

[Android] No autofill session is ever started for TextInputs on a pushed screen #4612

Description

@tomKFM

Description

On Android, a TextInput that lives on a screen pushed onto a native stack never gets an autofill session. The platform does not merely fail to show a suggestion: it never sends a FillRequest to the autofill service at all, so no password manager can offer credentials, and no "save password" prompt appears after a successful sign-in.

The same TextInput, with the same props, works normally on the stack's initial route. That is the whole difference.

This is what makes it hard to spot from the app side: the fields are correctly annotated, the accessibility tree is correct, the keyboard opens, and nothing is logged. The framework simply never asks.

Verified with two different autofill services (Bitwarden and Google's), so this is not service-specific. Measured with adb shell dumpsys autofill, section Requests history:

Screen FillRequest recorded
initial route of the stack yes, immediately on focus
route pushed onto the same stack none, ever

A native Android app on the same emulator, with the same service, records a request instantly, so the device and the service are working.

Expected: focusing an autofillable TextInput starts an autofill session, wherever the screen sits in the navigation stack.

Things that do not change the outcome

What does change it

Replacing <Stack> with expo-router's <Slot>, which builds its navigation with the same StackRouter but renders only the focused route, without the native screen container. Autofill then works on every screen. That is the workaround we shipped, and it is only viable because those screens needed no stack animation.

Hypothesis

Offered as a hypothesis, not a claim, since I have not instrumented the native side.

View.notifyEnterOrExitForAutoFillIfNeeded defers the autofill notification to the next View.layout() when isLaidOut() is false, setting PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT instead of calling AutofillManager.notifyViewEntered. For a pushed screen, the subtree appears to be mounted and laid out by the Fabric mount batch before the fragment transaction attaches it, and View.onAttachedToWindow clears that laid-out flag. React Native never lays out a view again when its frame has not changed, so the deferred notification never fires.

That would explain why the initial route is unaffected: its fragment is attached before the first layout, not after it.

Steps to reproduce

  1. Create a blank Expo SDK 54 app and add expo-router.
  2. Use the three files below.
  3. npx expo prebuild --platform android and build a release APK.
  4. Install a password manager (Bitwarden works) and set it as the system autofill service, or use Google's.
  5. Launch the app. On the initial screen, focus a field. adb shell dumpsys autofill shows a new line under Requests history.
  6. Tap "Go to sign in" to push the second screen. Focus the username field.
  7. adb shell dumpsys autofill shows no new line. No suggestion appears above the keyboard.

To see the contrast in one run, put a TextInput on both screens.

app/_layout.tsx

import { Stack } from "expo-router";

export default function RootLayout() {
  return <Stack />;
}

app/index.tsx

import { Link } from "expo-router";
import { Text, View } from "react-native";

export default function Index() {
  return (
    <View style={{ flex: 1, justifyContent: "center", alignItems: "center", gap: 24 }}>
      <Text style={{ fontSize: 20 }}>Autofill repro</Text>
      <Link href="/login" style={{ fontSize: 18, color: "#06c", padding: 16 }}>
        Go to sign in
      </Link>
    </View>
  );
}

app/login.tsx

import { useState } from "react";
import { StyleSheet, Text, TextInput, View } from "react-native";

export default function Login() {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");

  return (
    <View style={styles.page}>
      <Text style={styles.h1}>Sign in</Text>
      <TextInput
        style={styles.input}
        value={username}
        onChangeText={setUsername}
        placeholder="username"
        autoComplete="username"
        autoCapitalize="none"
        autoCorrect={false}
      />
      <TextInput
        style={styles.input}
        value={password}
        onChangeText={setPassword}
        placeholder="password"
        secureTextEntry
        autoComplete="password"
        autoCapitalize="none"
        autoCorrect={false}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  page: { flex: 1, paddingTop: 120, paddingHorizontal: 20, gap: 16, backgroundColor: "#fff" },
  h1: { fontSize: 22, fontWeight: "700", marginBottom: 12 },
  input: { borderWidth: 1, borderColor: "#bbb", borderRadius: 8, paddingHorizontal: 12, height: 48, fontSize: 16 },
});

Snack or a link to a repository

https://github.com/tomKFM/rn-screens-android-autofill-repro

Screens version

4.16.0

React Native version

0.81.5

Platforms

Android

JavaScript runtime

Hermes

Workflow

Expo bare workflow

Build type

Release mode

Device

Android emulator

Device model

Pixel emulator, Android 17 (API 37), also seen on a production build on physical devices

Acknowledgements

Yes

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:stack-v4Issue related to Native Stack v4 implementationarea:stack-v5Issues related to Native Stack v5 implementationarea:tabsIssue related to bottom tabscr:platform:androidcr:repro-providedplatform:androidIssue related to Android part of the libraryrepro-providedA reproduction with a snack or repo is provided

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions