英語で申し訳ありませんが、私はフランス人です。
Flutter (dart) で開発していますが、フォームの送信 (「リアクティブ フォーム」パッケージを使用しますが、クラシック フォームも使用) とステップの後にページにリダイレクトするときに、Cubit (Bloc) を使用してコードで奇妙な動作が発生します。 Cubit がロードされた状態: ページへの 2 つの呼び出し (2 つのビルド) が見られます。これは一種の「フラッピング」効果をもたらします。これは、最終ユーザーがインターフェイスの充電を 2 回見ることを意味します。
Flutter での初めてのアプリケーションです。
ログインフォームを含むアプリケーションを作成しました。フォームが送信されると、別のフォームを印刷します。
アプリケーションの開始時に「auto_route」パッケージを使用していましたが、ログイン プロセス後にテキスト フィールド内をクリックするたびにページが更新されました。そのため、テキスト フィールド内に何も書き込むことができませんでした。
問題は「Reactive forms」パッケージにあると考えていたので、このパッケージの github リポジトリに問題を開きました: issue open
しかし、どこに問題があるのか分からなかったので、アプリケーションのより基本的な開発に戻り、「リアクティブ フォーム」パッケージの管理者に問題を説明するために、ページ ルーティングを管理するためのより基本的な方法に戻りました。 、本当に私を助けようとした本当にいい人。
しかし、メンテナーでさえ、なぜ私がこの問題を抱えているのか理解できません。
これで、より単純なコードを 1 ページにまとめました。
現時点では、テキスト フィールド内をクリックしても問題はありませんが、インターフェイスが 2 回ビルドされ、Cubit がロードされた状態になっていることがわかります。これが、最初の問題が発生した理由を説明している可能性があります。
そのため、元のコードをデバッグする前に、インターフェイスが 2 回ビルドされる理由を理解しようとしています。
私の主な問題は、キュービットのロード状態が同期ウィジェットの戻りを待っていることだと思いますが、別のページにリダイレクトしようとすると、非同期アクションが必要になります (「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",
),
);
},
),
),
),
);
}
}
私の「flutter doctor -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!