对不起我的英语我是法国人。
我在 Flutter (dart) 中开发,当我想在表单提交后重定向到页面时,我在使用 Cubit (Bloc) 的代码中遇到了一个奇怪的行为(使用“反应式表单”包,但也使用经典表单)和步骤Cubit 加载状态:我看到对页面的 2 次调用(2 次构建),这会产生一种“颤动”效果,这意味着最终用户会看到界面充电两次。
这是我在 Flutter 中的第一个应用程序。
我创建了一个包含登录表单的应用程序:提交表单后,我打印另一个表单。
在我的应用程序开始时,我使用“auto_route”包,每次在登录过程后单击文本字段时,我都会刷新页面。所以我无法在文本字段内写任何东西。
我在想问题来自“反应式表单”包,所以我向这个包的 github 存储库打开了一个问题:问题已打开
但是因为我没有看到问题出在哪里,所以我回到了我的应用程序的更基本的开发以及管理页面路由的更基本的方法,以便向“反应式表单”包的维护者解释我的问题,一个非常好的人,真的很想帮助我。
但即使是维护者也不明白我为什么会出现这个问题。
现在我在一页中减少了我更简单的代码。
目前,当我在文本字段内单击时,我没有遇到问题,但我看到该界面构建了两次,并且 Cubit 加载状态可能解释了为什么我遇到了最初的问题。
所以现在我试着理解为什么在调试我的原始代码之前会构建两次接口。
我认为我的主要问题是 Cubit 加载状态正在等待同步小部件返回但是当我尝试重定向到另一个页面时它需要一个异步操作(使用“auto_route”包或更简单地使用“Navigator.push()”操作)。
但我不知道如何在等待经典小部件的 Cubit 加载状态中调用 Future。
我试过这个:
Widget myAuthBuildLoaded(context) {
Timer.run(() {
Navigator.push(context, MaterialPageRoute(builder: (context) => HomePage(1)));
});
return Container();
}
所以我认为返回的小部件“return Container()”在 Navigator.push() 再次构建界面之后构建了一次界面。我尝试直接返回“Navigator.push”,但出现错误(因为它不是小部件)。
我真的很感激这个问题的一些帮助。谢谢。
这是我的完整代码(更简单的版本)。
我的 pubspec.yaml 文件:
name: myapi
description: MyApi mobile application
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: ">=2.12.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
bloc: ^7.0.0
flutter_bloc: ^7.0.0
reactive_forms: ^10.0.3
dependency_overrides:
dev_dependencies:
flutter:
generate: true
uses-material-design: true
assets:
- assets/images/
我的代码:
import 'dart:async';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:reactive_forms/reactive_forms.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(Application());
}
class AppColors {
static const Color PRIMARY_COLOR = Colors.blue;
static const Color ACCENT_COLOR = Colors.black;
static const Color BG_COLOR_01 = Color(0xFFFFFFFF);
static const Color BG_COLOR_02 = Color(0xFFDDE7DD);
static const Color BG_COLOR_03 = Color(0xFFCCCFBD);
static const Color TXT_COLOR_01 = Colors.black;
}
class Application extends StatefulWidget {
@override
ApplicationState createState() => ApplicationState();
}
class ApplicationState extends State<Application> {
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
log("Build MyApi Application");
return MaterialApp(
title: 'MYAPI',
showSemanticsDebugger: false,
debugShowCheckedModeBanner: false,
home: HomePage(0),
);
}
}
class HomePage extends StatefulWidget {
final int indexSelected;
HomePage(this.indexSelected) : super();
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<Widget> _pages = [];
int _indexSelected = 0;
@override
void initState() {
super.initState();
_pages.addAll([
AuthPage(),
ConnectedFirstPage(),
]);
}
@override
Widget build(BuildContext context) {
_indexSelected = widget.indexSelected;
return Scaffold(
body: Container(
child: _pages.elementAt(_indexSelected),
),
);
}
}
class AuthPage extends StatelessWidget {
AuthPage() : super();
@override
Widget build(BuildContext context) {
log("Build AuthPage");
final bool isPortrait = MediaQuery.of(context).orientation == Orientation.portrait;
final FormGroup form = FormGroup({
'client_code': FormControl(validators: [Validators.required]),
});
AuthCubit? authCubit;
return BlocProvider<AuthCubit>(
create: (context) {
authCubit = AuthCubit(Auth(), form);
// authCubit!.defineFormLogIn();
// form = authCubit!.form;
return authCubit!;
},
// child: Scaffold(
child: SafeArea(
child: Overlay(
initialEntries: [
OverlayEntry(
builder: (context) => Scaffold(
backgroundColor: Colors.white,
body: Container(
child: Center(
child: Stack(
children: [
Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Container(
padding: EdgeInsets.fromLTRB(10, 150, 10, 10),
margin: EdgeInsets.fromLTRB(10, 2, 10, 2),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: 35.0),
Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Wrap(
children: [
Container(
alignment: Alignment.topLeft,
child: RichText(
text: TextSpan(
text: "Login",
style: TextStyle(
fontSize: 26,
fontFamily: 'Times',
fontWeight: FontWeight.w700,
color: Theme.of(context).accentColor,
),
),
),
),
],
),
],
),
],
),
),
SizedBox(height: 10),
Container(
child: FractionallySizedBox(
widthFactor: 0.7,
// child: Form(
child: ReactiveForm(
formGroup: form,
// formGroup: authCubit!.form!,
// key: _formKey,
child: Column(
children: [
BlocConsumer<AuthCubit, AuthState>(
listener: (context, state) {
if (state is AuthError) {
myAuthBuildError(context, state.message);
}
},
builder: (context, state) {
if (state is AuthInitial) {
return myAuthBuildInitial(context);
} else if (state is AuthLoading) {
return myAuthBuildLoading(context);
} else if (state is AuthLoaded) {
return myAuthBuildLoaded(context);
} else {
// In case of error we call the initial widget here and we handle the error with the above listener
return myAuthBuildInitial(context);
}
},
)
],
),
),
),
),
Container(
child: SizedBox(height: 2.0),
),
],
),
),
),
),
],
),
],
),
),
),
),
),
],
),
),
// ),
);
}
void myAuthFormSubmit(context) async {
log("Form 'client code' submitted!");
final authCubit = BlocProvider.of<AuthCubit>(context);
try {
await authCubit.logIn();
} on FormatException catch (e) {
myAuthBuildError(context, e.message);
}
}
Widget myAuthBuildInitial(context) {
final form = BlocProvider.of<AuthCubit>(context).form;
return ReactiveFormBuilder(
form: () => form!,
// form: form,
builder: (context, form, child) {
String _fieldName = "client_code";
String _fieldTitle = "Enter your client code";
String _msgRequired = "required field";
double _padding = 10.0;
return Stack(
children: [
Column(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// MyFormInputText(
// fieldName: "client_code",
// fieldTitle: "client code",
// msgRequired: "required field",
// isRequired: true,
// ),
Container(
height: 60.0,
child: Row(
children: [
Expanded(
child: ReactiveTextField(
// autofocus: true,
formControlName: _fieldName,
validationMessages: (control) => {ValidationMessage.required: _msgRequired},
style: TextStyle(
fontSize: 20,
color: Theme.of(context).accentColor,
fontFamily: 'Times',
fontWeight: FontWeight.w400,
),
decoration: InputDecoration(
contentPadding: EdgeInsets.all(_padding),
focusColor: Theme.of(context).accentColor,
hoverColor: Theme.of(context).accentColor,
hintText: _fieldTitle,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(30.0),
borderSide: BorderSide(
color: Theme.of(context).primaryColor,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(30.0),
borderSide: BorderSide(
color: Theme.of(context).primaryColor,
),
),
),
),
),
],
),
),
],
),
SizedBox(height: 10.0),
ReactiveFormConsumer(
builder: (context, form, child) {
String mybuttonTitle = "Validate";
double mywidth = 100.0;
double myheight = 50.0;
double myradius = 20.0;
double myfontSize = 20;
String myfontFamily = "Times";
FontWeight myfontWeight = FontWeight.w400;
Color mybackgroundColor = AppColors.PRIMARY_COLOR;
Color mytextColor = Colors.white;
// return MyButtonValidate(buttonContext: context, buttonAction: () => myAuthFormSubmit(context));
return Container(
width: mywidth,
height: myheight,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: TextButton(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.resolveWith((state) {
return mytextColor;
}),
backgroundColor: MaterialStateProperty.resolveWith((state) {
return mybackgroundColor;
}),
overlayColor: MaterialStateProperty.resolveWith((state) {
return mybackgroundColor;
}),
padding: MaterialStateProperty.all(EdgeInsets.symmetric(vertical: 2.0, horizontal: 2.0)),
textStyle: MaterialStateProperty.all(
TextStyle(
fontSize: myfontSize,
fontFamily: myfontFamily,
fontWeight: myfontWeight,
),
),
shape: MaterialStateProperty.resolveWith((state) {
if (state.contains(MaterialState.disabled) && form != null && form.valid) {
return RoundedRectangleBorder(
borderRadius: BorderRadius.circular(myradius),
side: BorderSide(
color: AppColors.ACCENT_COLOR.withAlpha(90),
),
);
} else {
return RoundedRectangleBorder(
borderRadius: BorderRadius.circular(myradius),
side: BorderSide(
color: mybackgroundColor,
),
);
}
}),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(mybuttonTitle),
],
),
onPressed: () => myAuthFormSubmit(context),
),
),
],
),
);
},
),
],
),
],
);
},
);
}
Widget myAuthBuildLoading(context) {
return CircularProgressIndicator(backgroundColor: Theme.of(context).primaryColor);
}
Widget myAuthBuildLoaded(context) {
Timer.run(() {
Navigator.push(context, MaterialPageRoute(builder: (context) => HomePage(1)));
});
return Container();
}
myAuthBuildError(context, message) {
return Text("Error", style: TextStyle(fontWeight: FontWeight.bold, color: Colors.red));
}
}
class AuthCubit extends Cubit<AuthState> {
final Auth? _auth;
final FormGroup? form;
String? _clientCode = "";
AuthCubit(this._auth, this.form) : super(AuthInitial());
// bool _isFormValid = false;
Auth get getAuth => _auth!;
// defineFormLogIn() {
// log("Info: defineFormLogIn");
// form = FormGroup({
// 'client_code': FormControl(validators: [Validators.required]),
// });
// }
Future<void> logIn() async {
_clientCode = form!.control("client_code").value.toString();
log("Info: Form - _clientCode=$_clientCode");
try {
emit(AuthLoading());
await Future.delayed(const Duration(milliseconds: 2000), () {
log("AuthCubit - logIn: Handle something!");
});
emit(AuthLoaded(_auth!));
} on Exception {
emit(AuthError("impossible to connect to myapi"));
}
}
}
@immutable
abstract class AuthState {
const AuthState();
}
class AuthInitial extends AuthState {
const AuthInitial();
}
class AuthLoading extends AuthState {
const AuthLoading();
}
class AuthLoaded extends AuthState {
final Auth auth;
const AuthLoaded(this.auth);
@override
bool operator ==(Object o) {
if (identical(this, o)) return true;
return o is AuthLoaded && o.auth == auth;
}
@override
int get hashCode => auth.hashCode;
}
class AuthError extends AuthState {
final String message;
const AuthError(this.message);
@override
bool operator ==(Object o) {
if (identical(this, o)) return true;
return o is AuthError && o.message == message;
}
@override
int get hashCode => message.hashCode;
}
class Auth {
String _clientCode = "";
String state = "not connected";
bool isConnected = false;
Auth();
}
class ConnectedFirstPage extends StatelessWidget {
ConnectedFirstPage() : super();
final FormGroup form = FormGroup({
'event_id': FormControl(),
});
@override
Widget build(BuildContext context) {
log("Build ConnectedFirstPage");
return SafeArea(
child: Scaffold(
body: SingleChildScrollView(
// child: ReactiveForm(
// formGroup: form,
// child: ReactiveTextField(
// formControlName: "event_id",
// style: TextStyle(
// fontSize: 15,
// color: Theme.of(context).accentColor,
// fontFamily: 'Times',
// fontWeight: FontWeight.w400,
// ),
// decoration: InputDecoration(
// hintText: "My event",
// ),
// ),
// ),
child: ReactiveFormBuilder(
form: () => form,
builder: (context, form, child) {
return ReactiveTextField(
formControlName: "event_id",
style: TextStyle(
fontSize: 20,
color: Theme.of(context).accentColor,
fontFamily: 'Times',
fontWeight: FontWeight.w400,
),
decoration: InputDecoration(
hintText: "Event ID",
),
);
},
),
),
),
);
}
}
我的“颤振医生-v”结果:
[✓] Flutter (Channel stable, 2.0.6, on macOS 11.2.1 20D74 darwin-arm, locale fr-FR)
• Flutter version 2.0.6 at /opt/homebrew/Caskroom/flutter/1.22.6/flutter
• Framework revision 1d9032c7e1 (4 weeks ago), 2021-04-29 17:37:58 -0700
• Engine revision 05e680e202
• Dart version 2.12.3
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
• Android SDK at /Users/mycompany/Library/Android/sdk
• Platform android-30, build-tools 30.0.3
• Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS
• Xcode at /Applications/Xcode.app/Contents/Developer
• Xcode 12.4, Build version 12D4e
• CocoaPods version 1.10.1
[✓] Chrome - develop for the web
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[✓] Android Studio (version 4.1)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin can be installed from:
https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)
[✓] Connected device (2 available)
• sdk gphone arm64 (mobile) • emulator-5554 • android-arm64 • Android 11 (API 30) (emulator)
• Chrome (web) • chrome • web-javascript • Google Chrome 90.0.4430.212
• No issues found!