otp_autofill_field 1.2.0
otp_autofill_field: ^1.2.0 copied to clipboard
Self-contained Flutter OTP field: PIN UI with automatic Android SMS Retriever and iOS one-time-code keyboard autofill, plus a resend timer. No external OTP/PIN dependency.
import 'package:flutter/material.dart';
import 'package:otp_autofill_field/otp_autofill_field.dart';
void main() => runApp(const ExampleApp());
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'otp_autofill_field demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
),
home: const OtpPage(),
);
}
}
class OtpPage extends StatefulWidget {
const OtpPage({super.key});
@override
State<OtpPage> createState() => _OtpPageState();
}
class _OtpPageState extends State<OtpPage> {
// 1) Controller — lets us read/clear the code and flag errors from
// outside the widget. We created it, so we dispose it.
final _otp = AutoOtpController();
// The "correct" code in this demo. A real app verifies on the server.
static const _expected = '123456';
String _status = 'Waiting for the 6-digit code…';
@override
void dispose() {
_otp.dispose();
super.dispose();
}
// 2) Fired once when 6 digits are present (typed OR auto-filled).
void _verify(String code) {
if (code == _expected) {
setState(() => _status = '✅ Verified: $code');
} else {
// Wrong → shake + clear. Because reArmOnClear is true (default), the
// SMS retriever re-arms automatically, so a *resent* OTP autofills.
_otp.triggerError();
_otp.clearCode();
setState(() => _status = '❌ Wrong code — try again');
}
}
Future<void> _resend() async {
// Call your backend to send a new OTP here.
setState(() => _status = '🔄 New code requested');
}
Future<void> _showSignature() async {
final sig = await AutoOtp.getAppSignature(); // null off Android
if (!mounted) return;
setState(() => _status = 'App signature: ${sig ?? "n/a (not Android)"}');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('otp_autofill_field')),
body: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Enter the 6-digit code',
style: TextStyle(fontSize: 18)),
const SizedBox(height: 24),
// 3) The field. Auto-fills from Android SMS / iOS keyboard.
AutoOtpField(
length: 6,
controller: _otp,
haptic: OtpHaptic.selection, // tap feedback per digit
theme: const AutoOtpTheme(
shape: AutoOtpShape.box,
borderRadius: 12,
focusedBorderColor: Colors.indigo,
cellAnimation: OtpCellAnimation.scale,
),
// Group as 3 — 3 with a wider gap in the middle.
separatorBuilder: (_, i) => SizedBox(width: i == 3 ? 18 : 6),
onChanged: (code) => debugPrint('onChanged: $code'),
onAutoFill: (code) => debugPrint('onAutoFill: $code'),
onCompleted: _verify,
onTimeout: () =>
setState(() => _status = '⌛ SMS wait timed out'),
),
const SizedBox(height: 16),
// 4) Resend cooldown — 1 minute by default, then a button.
OtpResendTimer(
duration: const Duration(minutes: 1),
onResend: _resend, // timer auto-restarts afterwards
),
const SizedBox(height: 20),
Text(_status, textAlign: TextAlign.center),
const SizedBox(height: 24),
Wrap(
spacing: 12,
alignment: WrapAlignment.center,
children: [
OutlinedButton(
onPressed: () => _otp.clearCode(),
child: const Text('Clear'),
),
OutlinedButton(
onPressed: () => _otp.triggerError(),
child: const Text('Show error'),
),
FilledButton(
onPressed: _showSignature,
child: const Text('App signature'),
),
],
),
],
),
),
),
);
}
}