fix(auth-field): save state in stateful widget; retrieve from there on rebuild

This commit is contained in:
Kiril Tijsma 2024-04-03 11:23:48 +02:00
parent 7a4bcd8b3f
commit 98ac73ee66
6 changed files with 198 additions and 160 deletions

View file

@ -64,6 +64,7 @@ class _AuthScreenState extends State<AuthScreen> {
final _animationDuration = const Duration(milliseconds: 300); final _animationDuration = const Duration(milliseconds: 300);
final _animationCurve = Curves.ease; final _animationCurve = Curves.ease;
bool _formValid = false; bool _formValid = false;
final _values = HashMap();
AppBar get _appBar => AppBar get _appBar =>
widget.customAppBar ?? widget.customAppBar ??
@ -89,6 +90,14 @@ class _AuthScreenState extends State<AuthScreen> {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
for (var step in widget.steps) {
for (var field in step.fields) {
setState(() {
_values[field.name] = field.value;
});
}
}
if (widget.steps.last == step) { if (widget.steps.last == step) {
var values = HashMap<String, dynamic>(); var values = HashMap<String, dynamic>();
@ -142,15 +151,17 @@ class _AuthScreenState extends State<AuthScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return widget.isLoading if (widget.isLoading) {
? const Center( return const Center(
child: SizedBox( child: SizedBox(
height: 120, height: 120,
width: 120, width: 120,
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
), ),
) );
: Scaffold( }
return Scaffold(
backgroundColor: widget.customBackgroundColor ?? Colors.white, backgroundColor: widget.customBackgroundColor ?? Colors.white,
appBar: _appBar, appBar: _appBar,
body: Form( body: Form(
@ -171,44 +182,77 @@ class _AuthScreenState extends State<AuthScreen> {
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Expanded( Spacer(
flex: widget.beforeTitleFlex ?? 3, flex: widget.beforeTitleFlex ?? 3,
child: Container(),
), ),
widget.titleWidget!, widget.titleWidget!,
Expanded( Spacer(
flex: widget.afterTitleFlex ?? 2, flex: widget.afterTitleFlex ?? 2,
child: Container(),
), ),
], ],
), ),
), ),
Column( ],
Expanded(
flex: widget.formFlex ?? 3,
child: Column(
children: [
for (AuthField field
in widget.steps[i].fields.map((field) {
if (!_values.containsKey(field.name)) return field;
field.value = _values[field.name]!;
return field;
}))
Align(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (field.title != null) ...[
field.title!,
],
field.build(context, (v) {
_values[field.name] = v;
_validate(i);
}),
],
),
),
],
),
),
Padding(
padding: EdgeInsets.only(
top: 15.0,
bottom: 30.0,
left: widget.previousButtonBuilder == null &&
widget.steps.first != widget.steps[i]
? 30.0
: 0.0,
right: widget.nextButtonBuilder == null &&
widget.previousButtonBuilder == null
? 30.0
: 0.0,
),
child: Column(
children: [ children: [
Row( Row(
mainAxisAlignment: mainAxisAlignment:
widget.buttonMainAxisAlignment != null widget.buttonMainAxisAlignment != null
? widget.buttonMainAxisAlignment! ? widget.buttonMainAxisAlignment!
: (widget.previousButtonBuilder != : (widget.previousButtonBuilder != null &&
null && widget.previousButtonBuilder?.call(
widget.previousButtonBuilder
?.call(
onPrevious, onPrevious,
widget widget.previousBtnTitle,
.previousBtnTitle,
i, i,
) == ) ==
null) null)
? MainAxisAlignment.start ? MainAxisAlignment.start
: widget.steps.first != : widget.steps.first != widget.steps[i]
widget.steps[i]
? MainAxisAlignment.center ? MainAxisAlignment.center
: MainAxisAlignment.end, : MainAxisAlignment.end,
children: [ children: [
if (widget.previousButtonBuilder == if (widget.previousButtonBuilder == null) ...[
null) ...[ if (widget.steps.first != widget.steps[i])
if (widget.steps.first !=
widget.steps[i])
ElevatedButton( ElevatedButton(
onPressed: onPrevious, onPressed: onPrevious,
child: Row( child: Row(
@ -219,29 +263,23 @@ class _AuthScreenState extends State<AuthScreen> {
), ),
Padding( Padding(
padding: padding:
const EdgeInsets.only( const EdgeInsets.only(left: 4.0),
left: 4.0), child: Text(widget.previousBtnTitle),
child: Text(
widget.previousBtnTitle),
), ),
], ],
), ),
), ),
] else if (widget.previousButtonBuilder ] else if (widget.previousButtonBuilder?.call(
?.call(onPrevious, onPrevious, widget.previousBtnTitle, i) !=
widget.previousBtnTitle, i) !=
null) ...[ null) ...[
widget.previousButtonBuilder!.call( widget.previousButtonBuilder!
onPrevious, .call(onPrevious, widget.previousBtnTitle, i)!
widget.previousBtnTitle,
i)!
], ],
widget.nextButtonBuilder?.call( widget.nextButtonBuilder?.call(
!_formValid !_formValid
? null ? null
: () async { : () async {
await onNext( await onNext(widget.steps[i]);
widget.steps[i]);
}, },
widget.steps.last == widget.steps[i] widget.steps.last == widget.steps[i]
? widget.submitBtnTitle ? widget.submitBtnTitle
@ -253,20 +291,17 @@ class _AuthScreenState extends State<AuthScreen> {
onPressed: !_formValid onPressed: !_formValid
? null ? null
: () async { : () async {
await onNext( await onNext(widget.steps[i]);
widget.steps[i]);
}, },
child: Row( child: Row(
children: [ children: [
Text( Text(
widget.steps.last == widget.steps.last == widget.steps[i]
widget.steps[i]
? widget.submitBtnTitle ? widget.submitBtnTitle
: widget.nextBtnTitle, : widget.nextBtnTitle,
), ),
const Padding( const Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(left: 4.0),
left: 4.0),
child: Icon( child: Icon(
Icons.arrow_forward, Icons.arrow_forward,
size: 18, size: 18,
@ -284,9 +319,12 @@ class _AuthScreenState extends State<AuthScreen> {
), ),
], ],
), ),
)
], ],
]), )
]), ],
)); ),
),
);
} }
} }

View file

@ -8,7 +8,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_registration/flutter_registration.dart'; import 'package:flutter_registration/flutter_registration.dart';
class RegistrationOptions { class RegistrationOptions {
RegistrationOptions({ const RegistrationOptions({
required this.registrationRepository, required this.registrationRepository,
required this.registrationSteps, required this.registrationSteps,
required this.afterRegistration, required this.afterRegistration,
@ -35,8 +35,8 @@ class RegistrationOptions {
previousButtonBuilder; previousButtonBuilder;
final MainAxisAlignment? buttonMainAxisAlignment; final MainAxisAlignment? buttonMainAxisAlignment;
final Color? backgroundColor; final Color? backgroundColor;
Widget? titleWidget; final Widget? titleWidget;
Widget? loginButton; final Widget? loginButton;
static List<AuthStep> getDefaultSteps({ static List<AuthStep> getDefaultSteps({
TextEditingController? emailController, TextEditingController? emailController,

View file

@ -30,8 +30,9 @@ class AuthBoolField extends AuthField {
onChanged: (v) { onChanged: (v) {
value = v; value = v;
onChange?.call(value); onChange?.call(value);
onValueChanged(); onValueChanged(v);
}, },
initialValue: value,
validator: (value) { validator: (value) {
for (var validator in validators) { for (var validator in validators) {
var output = validator(value); var output = validator(value);

View file

@ -11,9 +11,7 @@ class AuthDropdownField extends AuthField {
this.textStyle, this.textStyle,
this.icon = const Icon(Icons.keyboard_arrow_down), this.icon = const Icon(Icons.keyboard_arrow_down),
required super.value, required super.value,
}) { });
selectedValue = value ?? items.first;
}
final List<String> items; final List<String> items;
final Function(String?) onChanged; final Function(String?) onChanged;
@ -30,7 +28,7 @@ class AuthDropdownField extends AuthField {
child: DropdownButtonFormField<String>( child: DropdownButtonFormField<String>(
icon: icon, icon: icon,
style: textStyle, style: textStyle,
value: selectedValue, value: value,
decoration: dropdownDecoration, decoration: dropdownDecoration,
items: items.map((String value) { items: items.map((String value) {
return DropdownMenuItem<String>( return DropdownMenuItem<String>(
@ -41,7 +39,7 @@ class AuthDropdownField extends AuthField {
onChanged: (newValue) { onChanged: (newValue) {
selectedValue = newValue; selectedValue = newValue;
onChanged(newValue); onChanged(newValue);
onValueChanged(); onValueChanged(newValue);
}, },
validator: (value) { validator: (value) {
if (validators.isNotEmpty) { if (validators.isNotEmpty) {

View file

@ -9,10 +9,10 @@ import 'package:flutter_registration/flutter_registration.dart';
class AuthPassField extends AuthField { class AuthPassField extends AuthField {
AuthPassField({ AuthPassField({
required super.name, required super.name,
TextEditingController? textEditingController,
super.title, super.title,
super.validators = const [], super.validators = const [],
super.value = '', super.value = '',
this.textEditingController,
this.textStyle, this.textStyle,
this.onChange, this.onChange,
this.iconSize, this.iconSize,
@ -24,6 +24,7 @@ class AuthPassField extends AuthField {
final double? iconSize; final double? iconSize;
final Function(String value)? onChange; final Function(String value)? onChange;
final InputDecoration? textFieldDecoration; final InputDecoration? textFieldDecoration;
final TextEditingController? textEditingController;
final EdgeInsets padding; final EdgeInsets padding;
@override @override
@ -33,11 +34,13 @@ class AuthPassField extends AuthField {
child: FlutterFormInputPassword( child: FlutterFormInputPassword(
style: textStyle, style: textStyle,
iconSize: iconSize ?? 24.0, iconSize: iconSize ?? 24.0,
initialValue: textEditingController == null ? value : null,
decoration: textFieldDecoration, decoration: textFieldDecoration,
controller: textEditingController,
onChanged: (v) { onChanged: (v) {
value = v; value = v;
onChange?.call(value); onChange?.call(value);
onValueChanged(); onValueChanged(v);
}, },
validator: (value) { validator: (value) {
for (var validator in validators) { for (var validator in validators) {

View file

@ -8,20 +8,17 @@ import 'package:flutter_registration/flutter_registration.dart';
class AuthTextField extends AuthField { class AuthTextField extends AuthField {
AuthTextField({ AuthTextField({
required super.name, required super.name,
TextEditingController? textEditingController,
super.title, super.title,
super.validators = const [], super.validators = const [],
super.value = '', super.value = '',
this.textEditingController,
this.textStyle, this.textStyle,
this.onChange, this.onChange,
this.textFieldDecoration, this.textFieldDecoration,
this.padding = const EdgeInsets.all(8.0), this.padding = const EdgeInsets.all(8.0),
}) { });
textController =
textEditingController ?? TextEditingController(text: value);
}
late TextEditingController textController; final TextEditingController? textEditingController;
final TextStyle? textStyle; final TextStyle? textStyle;
final Function(String value)? onChange; final Function(String value)? onChange;
final InputDecoration? textFieldDecoration; final InputDecoration? textFieldDecoration;
@ -34,11 +31,12 @@ class AuthTextField extends AuthField {
child: TextFormField( child: TextFormField(
style: textStyle, style: textStyle,
decoration: textFieldDecoration, decoration: textFieldDecoration,
controller: textController, controller: textEditingController,
initialValue: textEditingController == null ? value : null,
onChanged: (v) { onChanged: (v) {
value = v; value = v;
onChange?.call(value); onChange?.call(value);
onValueChanged(); onValueChanged(v);
}, },
validator: (value) { validator: (value) {
for (var validator in validators) { for (var validator in validators) {