Skip to content

Commit 0db4572

Browse files
committed
feat(authenticator): add AuthenticatorTextFieldController and TextEditingController support for form fields
- Export AuthenticatorTextFieldController from package API. - Integrate optional controller parameter into prebuilt form fields (sign-in, sign-up, confirm, reset password, verify user, email setup, etc.) with proper diagnostics. - Ensure bidirectional sync between controllers and internal authenticator state. - Add example demonstrating controller usage and pre-population (examples/authenticator_with_controllers.dart). - Add comprehensive widget tests for controller <-> state synchronization, standard TextEditingController compatibility, and rapid updates. - Update CHANGELOG with 2.4.0 notes.
1 parent 2a801d5 commit 0db4572

14 files changed

+672
-41
lines changed

packages/authenticator/amplify_authenticator/CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
## 2.4.0
2+
3+
### Features
4+
5+
- feat(authenticator): Add TextEditingController support to form fields
6+
- Added `AuthenticatorTextFieldController` class for programmatic control of form fields
7+
- All text-based form fields now accept an optional `controller` parameter
8+
- Enables pre-populating fields (e.g., from GPS/API data) and auto-filling verification codes
9+
- Bidirectional sync between controller and internal state
10+
- Compatible with standard `TextEditingController` for flexibility
11+
112
## 2.3.8
213

314
### Chores
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import 'package:amplify_auth_cognito/amplify_auth_cognito.dart';
5+
import 'package:amplify_authenticator/amplify_authenticator.dart';
6+
import 'package:amplify_flutter/amplify_flutter.dart';
7+
import 'package:flutter/material.dart';
8+
9+
import 'amplifyconfiguration.dart';
10+
11+
/// Example demonstrating the use of TextEditingController with
12+
/// Amplify Authenticator form fields.
13+
///
14+
/// This allows programmatic control over form field values, enabling
15+
/// use cases such as:
16+
/// - Pre-populating fields with data from APIs (e.g., GPS location)
17+
/// - Auto-filling verification codes from SMS
18+
/// - Dynamic form validation and manipulation
19+
class AuthenticatorWithControllers extends StatefulWidget {
20+
const AuthenticatorWithControllers({super.key});
21+
22+
@override
23+
State<AuthenticatorWithControllers> createState() =>
24+
_AuthenticatorWithControllersState();
25+
}
26+
27+
class _AuthenticatorWithControllersState
28+
extends State<AuthenticatorWithControllers> {
29+
// Controllers for programmatic access to form fields
30+
final _usernameController = AuthenticatorTextFieldController();
31+
final _emailController = AuthenticatorTextFieldController();
32+
final _addressController = AuthenticatorTextFieldController();
33+
final _phoneController = AuthenticatorTextFieldController();
34+
35+
@override
36+
void initState() {
37+
super.initState();
38+
_configureAmplify();
39+
40+
// Example: Pre-populate fields with default/fetched data
41+
_usernameController.text = 'amplify_user';
42+
_emailController.text = 'user@amplify.example.com';
43+
}
44+
45+
@override
46+
void dispose() {
47+
// Clean up controllers when the widget is disposed
48+
_usernameController.dispose();
49+
_emailController.dispose();
50+
_addressController.dispose();
51+
_phoneController.dispose();
52+
super.dispose();
53+
}
54+
55+
void _configureAmplify() async {
56+
final authPlugin = AmplifyAuthCognito(
57+
// FIXME: In your app, make sure to remove this line and set up
58+
/// Keychain Sharing in Xcode as described in the docs:
59+
/// https://docs.amplify.aws/lib/project-setup/platform-setup/q/platform/flutter/#enable-keychain
60+
secureStorageFactory: AmplifySecureStorage.factoryFrom(
61+
macOSOptions:
62+
// ignore: invalid_use_of_visible_for_testing_member
63+
MacOSSecureStorageOptions(useDataProtection: false),
64+
),
65+
);
66+
try {
67+
await Amplify.addPlugin(authPlugin);
68+
await Amplify.configure(amplifyconfig);
69+
safePrint('Successfully configured');
70+
} on Exception catch (e) {
71+
safePrint('Error configuring Amplify: $e');
72+
}
73+
}
74+
75+
/// Simulates fetching user location and populating the address field
76+
Future<void> _fetchAndPopulateAddress() async {
77+
// In a real app, you would use a geolocation service here
78+
await Future<void>.delayed(const Duration(seconds: 1));
79+
80+
// Simulate fetched address
81+
final fetchedAddress = '123 Main Street, Seattle, WA 98101';
82+
83+
// Update the address field programmatically
84+
_addressController.text = fetchedAddress;
85+
86+
if (mounted) {
87+
ScaffoldMessenger.of(context).showSnackBar(
88+
SnackBar(content: Text('Address populated: $fetchedAddress')),
89+
);
90+
}
91+
}
92+
93+
@override
94+
Widget build(BuildContext context) {
95+
return Authenticator(
96+
// Custom sign-up form with controller support
97+
signUpForm: SignUpForm.custom(
98+
fields: [
99+
// Username field with controller - can be pre-populated or modified
100+
SignUpFormField.username(controller: _usernameController),
101+
102+
// Email field with controller
103+
SignUpFormField.email(controller: _emailController, required: true),
104+
105+
SignUpFormField.password(),
106+
SignUpFormField.passwordConfirmation(),
107+
108+
// Address field with controller - can be populated from GPS/API
109+
SignUpFormField.address(controller: _addressController),
110+
111+
// Phone number field with controller
112+
SignUpFormField.phoneNumber(controller: _phoneController),
113+
],
114+
),
115+
116+
child: MaterialApp(
117+
title: 'Authenticator with Controllers',
118+
theme: ThemeData.light(useMaterial3: true),
119+
darkTheme: ThemeData.dark(useMaterial3: true),
120+
debugShowCheckedModeBanner: false,
121+
builder: Authenticator.builder(),
122+
home: Scaffold(
123+
appBar: AppBar(title: const Text('Controller Example')),
124+
body: Center(
125+
child: Column(
126+
mainAxisAlignment: MainAxisAlignment.center,
127+
children: [
128+
const Text(
129+
'You are logged in!',
130+
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
131+
),
132+
const SizedBox(height: 20),
133+
134+
// Display current controller values
135+
Card(
136+
margin: const EdgeInsets.all(16),
137+
child: Padding(
138+
padding: const EdgeInsets.all(16),
139+
child: Column(
140+
crossAxisAlignment: CrossAxisAlignment.start,
141+
children: [
142+
const Text(
143+
'Form Field Values:',
144+
style: TextStyle(
145+
fontSize: 18,
146+
fontWeight: FontWeight.bold,
147+
),
148+
),
149+
const SizedBox(height: 10),
150+
Text('Username: ${_usernameController.text}'),
151+
Text('Email: ${_emailController.text}'),
152+
Text('Address: ${_addressController.text}'),
153+
Text('Phone: ${_phoneController.text}'),
154+
],
155+
),
156+
),
157+
),
158+
159+
const SizedBox(height: 20),
160+
161+
ElevatedButton.icon(
162+
onPressed: _fetchAndPopulateAddress,
163+
icon: const Icon(Icons.location_on),
164+
label: const Text('Fetch GPS Address'),
165+
),
166+
167+
const SizedBox(height: 20),
168+
const SignOutButton(),
169+
],
170+
),
171+
),
172+
),
173+
),
174+
);
175+
}
176+
}
177+
178+
void main() {
179+
runApp(const AuthenticatorWithControllers());
180+
}

packages/authenticator/amplify_authenticator/example/lib/main.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,13 @@ class _MyAppState extends State<MyApp> {
210210
// Widget build(BuildContext context) {
211211
// return const AuthenticatorWithCustomAuthFlow();
212212
// }
213+
214+
// Below is an example showing TextEditingController support for
215+
// programmatic form field control
216+
// @override
217+
// Widget build(BuildContext context) {
218+
// return const AuthenticatorWithControllers();
219+
// }
213220
}
214221

215222
/// The screen which is shown once the user is logged in. We can use

packages/authenticator/amplify_authenticator/lib/amplify_authenticator.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export 'package:amplify_authenticator/src/utils/dial_code.dart' show DialCode;
4242
export 'package:amplify_authenticator/src/utils/dial_code_options.dart'
4343
show DialCodeOptions;
4444

45+
export 'src/controllers/authenticator_text_field_controller.dart';
4546
export 'src/enums/enums.dart' show AuthenticatorStep, Gender;
4647
export 'src/l10n/auth_strings_resolver.dart' hide ButtonResolverKeyType;
4748
export 'src/models/authenticator_exception.dart';
@@ -94,7 +95,6 @@ export 'src/widgets/form_field.dart'
9495
TotpSetupFormField,
9596
VerifyUserFormField;
9697
export 'src/widgets/social/social_button.dart';
97-
export 'src/controllers/authenticator_text_field_controller.dart';
9898

9999
/// {@template amplify_authenticator.authenticator}
100100
/// # Amplify Authenticator

packages/authenticator/amplify_authenticator/lib/src/controllers/authenticator_text_field_controller.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,5 @@ import 'package:flutter/widgets.dart';
1111
class AuthenticatorTextFieldController extends TextEditingController {
1212
AuthenticatorTextFieldController({super.text});
1313

14-
AuthenticatorTextFieldController.fromValue(TextEditingValue value)
15-
: super.fromValue(value);
14+
AuthenticatorTextFieldController.fromValue(super.value) : super.fromValue();
1615
}

packages/authenticator/amplify_authenticator/lib/src/widgets/form_field.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,10 @@ abstract class AuthenticatorFormField<
128128
..add(DiagnosticsProperty<bool>('required', required))
129129
..add(DiagnosticsProperty<bool?>('requiredOverride', requiredOverride))
130130
..add(EnumProperty<UsernameType?>('usernameType', usernameType))
131-
..add(IterableProperty<String>('autofillHints', autofillHints));
132-
properties.add(
133-
DiagnosticsProperty<TextEditingController?>('controller', controller),
134-
);
131+
..add(IterableProperty<String>('autofillHints', autofillHints))
132+
..add(
133+
DiagnosticsProperty<TextEditingController?>('controller', controller),
134+
);
135135
}
136136
}
137137

packages/authenticator/amplify_authenticator/lib/src/widgets/form_fields/confirm_sign_in_form_field.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,7 @@ class _ConfirmSignInPhoneField extends ConfirmSignInFormField<String> {
524524
this.controller,
525525
}) : super._(customAttributeKey: attributeKey);
526526

527+
@override
527528
final TextEditingController? controller;
528529

529530
@override
@@ -590,6 +591,7 @@ class _ConfirmSignInTextField extends ConfirmSignInFormField<String> {
590591
this.controller,
591592
}) : super._(customAttributeKey: attributeKey);
592593

594+
@override
593595
final TextEditingController? controller;
594596

595597
@override

packages/authenticator/amplify_authenticator/lib/src/widgets/form_fields/confirm_sign_up_form_field.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ class _ConfirmSignUpTextField extends ConfirmSignUpFormField<String> {
138138
this.controller,
139139
}) : super._();
140140

141+
@override
141142
final TextEditingController? controller;
142143

143144
@override
@@ -220,6 +221,7 @@ class _ConfirmSignUpUsernameField
220221
this.controller,
221222
}) : super._();
222223

224+
@override
223225
final TextEditingController? controller;
224226

225227
@override

packages/authenticator/amplify_authenticator/lib/src/widgets/form_fields/email_setup_form_field.dart

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,22 @@ part of '../form_field.dart';
99
/// {@endtemplate}
1010
class EmailSetupFormField
1111
extends AuthenticatorFormField<EmailSetupField, String> {
12+
/// Creates an email FormField for the email setup step.
13+
const EmailSetupFormField.email({
14+
Key? key,
15+
FormFieldValidator<String>? validator,
16+
Iterable<String>? autofillHints,
17+
TextEditingController? controller,
18+
}) : this._(
19+
key: key ?? keyEmailSetupFormField,
20+
field: EmailSetupField.email,
21+
titleKey: InputResolverKey.emailTitle,
22+
hintTextKey: InputResolverKey.emailHint,
23+
validator: validator,
24+
autofillHints: autofillHints,
25+
controller: controller,
26+
);
27+
1228
/// {@macro amplify_authenticator.email_setup_form_field}
1329
///
1430
/// Either [titleKey] or [title] is required.
@@ -24,24 +40,9 @@ class EmailSetupFormField
2440
this.controller,
2541
}) : super._();
2642

43+
@override
2744
final TextEditingController? controller;
2845

29-
/// Creates an email FormField for the email setup step.
30-
const EmailSetupFormField.email({
31-
Key? key,
32-
FormFieldValidator<String>? validator,
33-
Iterable<String>? autofillHints,
34-
TextEditingController? controller,
35-
}) : this._(
36-
key: key ?? keyEmailSetupFormField,
37-
field: EmailSetupField.email,
38-
titleKey: InputResolverKey.emailTitle,
39-
hintTextKey: InputResolverKey.emailHint,
40-
validator: validator,
41-
autofillHints: autofillHints,
42-
controller: controller,
43-
);
44-
4546
@override
4647
bool get required => true;
4748

packages/authenticator/amplify_authenticator/lib/src/widgets/form_fields/reset_password_form_field.dart

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,6 @@ part of '../form_field.dart';
99
/// {@endtemplate}
1010
class ResetPasswordFormField
1111
extends AuthenticatorFormField<ResetPasswordField, String> {
12-
/// {@macro amplify_authenticator.sign_up_form_field}
13-
///
14-
/// Either [titleKey] or [title] is required.
15-
const ResetPasswordFormField._({
16-
super.key,
17-
required super.field,
18-
super.titleKey,
19-
super.hintTextKey,
20-
super.validator,
21-
super.autofillHints,
22-
this.controller,
23-
}) : super._();
24-
25-
final TextEditingController? controller;
26-
2712
const ResetPasswordFormField.verificationCode({
2813
Key? key,
2914
Iterable<String>? autofillHints,
@@ -65,6 +50,22 @@ class ResetPasswordFormField
6550
controller: controller,
6651
);
6752

53+
/// {@macro amplify_authenticator.sign_up_form_field}
54+
///
55+
/// Either [titleKey] or [title] is required.
56+
const ResetPasswordFormField._({
57+
super.key,
58+
required super.field,
59+
super.titleKey,
60+
super.hintTextKey,
61+
super.validator,
62+
super.autofillHints,
63+
this.controller,
64+
}) : super._();
65+
66+
@override
67+
final TextEditingController? controller;
68+
6869
@override
6970
bool get required => true;
7071

0 commit comments

Comments
 (0)