otp_autofill_field

A self-contained OTP input for Flutter: a customizable PIN field with Android SMS auto-read and iOS/web keyboard autofill, all in one widget.

otp_autofill_field demo

  • One hidden input behind the cells — paste, backspace and OTP autofill all behave correctly.
  • Own SMS Retriever bridgestart() is idempotent; stop()/dispose() cancel the stream and unregister the native receiver; listenTimeout + onTimeout.
  • Caller owns the controller — the field never disposes a controller you supplied.
  • onCompleted fires exactly once — de-duplicated across typing, paste, SMS and OS autofill.
  • iOS uses native oneTimeCode keyboard autofill — the only path Apple allows.

Platform support

The PIN field is pure Flutter and runs everywhere. Only the automatic SMS read is platform-specific — and where it isn't available the field degrades gracefully (no errors): it stays fully usable for manual entry, paste, onCompleted, the resend timer, and error/shake.

Platform PIN field Auto SMS read Keyboard autofill
Android ✅ SMS Retriever (no permission)
iOS ❌ Apple forbids reading SMS oneTimeCode
Web ✅ browser OTP autofill (Chrome/Safari)
macOS / Windows / Linux ⚠️ if the OS provides it

AutoOtp.isSmsRetrievalSupported is true only on Android; AutoOtp.getAppSignature() returns null elsewhere.

All 6 platforms are declared and build correctly. Only Android has real native code (SMS Retriever); every other platform ships an empty no-op plugin (pluginClass: none is invalid in Flutter and breaks builds).

iOS and macOS support both Swift Package Manager (the default from Flutter 3.44) and CocoaPods — a Package.swift ships next to the podspec, and CocoaPods apps simply ignore it.

Screenshots

Rendered from the widget itself (golden tests) — reproducible, not staged. The field is custom-drawn so it looks identical on both platforms; the platform difference is only the OS autofill mechanism.

Android (SMS Retriever)

Default Auto-filled Custom cellBuilder

iOS (one-time-code keyboard autofill)

Default Filled Error state

Install

dependencies:
  otp_autofill_field: ^1.0.0

Usage

import 'package:otp_autofill_field/otp_autofill_field.dart';

AutoOtpField(
  length: 6,
  onCompleted: (code) => verify(code),   // fires exactly once
  onAutoFill: (code) => print('from SMS: $code'),
  onTimeout: () => print('no SMS in 5 min'),
);

Clear / shake on a failed check:

final controller = AutoOtpController();

AutoOtpField(
  controller: controller,
  onCompleted: (code) {
    if (code != expected) {
      controller.triggerError(); // shakes the field, marks error
      controller.clearCode();
    }
  },
);
// You created `controller`, so you dispose it. The field never will.

Customization

Three levels, from quick to total control:

1. Theme (shape, size, colors, cursor, animation, haptics):

AutoOtpField(
  haptic: OtpHaptic.medium,            // none|light|medium|heavy|selection
  theme: AutoOtpTheme(
    shape: AutoOtpShape.circle,        // box|underline|circle
    cellWidth: 52, cellHeight: 52, spacing: 12, borderRadius: 26,
    focusedBorderColor: Colors.indigo,
    showCursor: true,
    cursorColor: Colors.indigo, cursorWidth: 2, cursorHeight: 26,
    cellAnimation: OtpCellAnimation.slide, // none|scale|fade|slide
    animationDuration: Duration(milliseconds: 180),
    animationCurve: Curves.easeOutBack,
  ),
);

2. Separator between cells (e.g. 1 2 3 - 4 5 6):

AutoOtpField(
  separatorBuilder: (context, i) => i == 3
      ? const Padding(padding: EdgeInsets.symmetric(horizontal: 8),
          child: Text('—'))
      : const SizedBox(width: 8),
);

3. cellBuilder — draw each cell entirely yourself:

AutoOtpField(
  cellBuilder: (context, s) => AnimatedContainer(
    duration: const Duration(milliseconds: 150),
    width: 46, height: 56,
    alignment: Alignment.center,
    decoration: BoxDecoration(
      gradient: s.isFilled
          ? const LinearGradient(colors: [Colors.indigo, Colors.purple])
          : null,
      border: Border.all(
        color: s.hasError
            ? Colors.red
            : s.isActive
                ? Colors.indigo
                : Colors.grey,
      ),
      borderRadius: BorderRadius.circular(12),
    ),
    child: Text(s.character,
        style: TextStyle(
          fontSize: 22,
          color: s.isFilled ? Colors.white : Colors.black,
        )),
  ),
);

OtpCellState gives you index, character, isActive, isFilled, hasError, and the theme-resolved colors.

Resend cooldown

A theme-adaptive, fully customizable 1-minute (default) resend countdown:

OtpResendTimer(
  // duration: Duration(minutes: 1),   // default
  onResend: () async => await api.requestOtp(phone), // timer auto-restarts
);

Custom UI via builders (defaults to a Material text + button):

OtpResendTimer(
  onResend: requestOtp,
  builder: (context, remaining) =>
      Text('Resend available in ${remaining.inSeconds}s'),
  resendBuilder: (context, resend) =>
      OutlinedButton(onPressed: resend, child: const Text('Send again')),
);

Drive it from elsewhere with an OtpResendController (start / restart / stop, remaining, canResend).

Android setup

The SMS Retriever API needs no permissions. The SMS just has to end with your app's 11-character signature:

final signature = await AutoOtp.getAppSignature(); // null off Android

Backend message format:

Your code is 123456

FA+9qCX9VSu

Optional phone-number hint picker:

final phone = await AutoOtp.requestPhoneHint();

Requirements: minSdkVersion >= 21 and Google Play services on the device (the plugin pulls play-services-auth-api-phone itself).

iOS setup

Nothing to do. The field is wrapped in an AutofillGroup and advertises AutofillHints.oneTimeCode, so iOS reads the code from Messages and offers it above the keyboard automatically. Apple does not permit apps to read SMS, so this keyboard suggestion is the supported "automatic" path on iOS.

API

  • AutoOtpField — the widget: length, controller, onCompleted, onAutoFill, onTimeout, autoStart, autoStopOnCompleted, reArmOnClear, listenTimeout, smsCodeRegExp, theme, obscureText, obscuringCharacter, mainAxisAlignment, enabled, haptic, cellBuilder, separatorBuilder.
  • AutoOtpTheme — shape, sizes, colors, cursor, cellAnimation, animationDuration/Curve. OtpCellState / OtpCellBuilder / OtpSeparatorBuilder / OtpHaptic / OtpCellAnimation for full custom UI.
  • AutoOtpControllercode, setCode(value, maxLength:), clearCode(), triggerError(), clearError(), requestFocus(), hasError.
  • OtpResendTimer / OtpResendController — resend cooldown (default 1 min), theme-adaptive, customizable via builder / resendBuilder.
  • OtpPinField — the standalone PIN cells, if you want them without SMS.
  • OtpRetriever — the standalone leak-free SMS Retriever bridge.
  • AutoOtp.getAppSignature(), AutoOtp.requestPhoneHint(), AutoOtp.isSmsRetrievalSupported.

License

MIT © Fayziddin2000

Libraries

otp_autofill_field
otp_autofill_field