otp_autofill_field
A self-contained OTP input for Flutter. It does not depend on
pin_code_fields or sms_autofill — both are reimplemented here, fixing
their long-standing bugs:
| Old packages | otp_autofill_field |
|---|---|
pin_code_fields: one TextField per cell → broken paste, soft-keyboard backspace skipping cells, autofill filling only cell 1 |
One hidden input behind the cells → paste, backspace and OTP autofill all correct |
sms_autofill: native receiver + Dart stream leak; double listenForCode() registers two receivers; ~5 min timeout is silent |
Own SMS Retriever bridge: stop()/dispose() cancel the stream and unregister the receiver; start() is idempotent; listenTimeout + onTimeout |
| Controller auto-disposed by the widget → "used after disposed" | Field never disposes a caller-supplied controller |
SMS + last keystroke both fire onCompleted |
De-duplicated: fires exactly once |
| iOS often wired so the retriever is armed pointlessly | 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.
Android, iOS and Web are declared plugin platforms. On macOS / Windows / Linux the package is a plain Flutter widget — it works there with no plugin declaration (declaring them would require needless native stubs).
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/OtpCellAnimationfor full custom UI.AutoOtpController—code,setCode(value, maxLength:),clearCode(),triggerError(),clearError(),requestFocus(),hasError.OtpResendTimer/OtpResendController— resend cooldown (default 1 min), theme-adaptive, customizable viabuilder/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





