flutter - 如何正确使用 GetX 与 TextField 和一些服务器端验证?
问题描述
我正在使用get
包进行项目的状态管理。但是我对表单输入和验证的实现感到困惑,因为我在文档中找不到任何示例。我对这个问题有一些疑问。
- 我应该为每个动作创建单独的控制器吗?喜欢
PageOneController
和PageOneFormController
? - 状态变化时如何防止父级重建子级?
- 如何在
Model().obs
对象内添加数据时触发重建?
正如我在上面第 1 点提到的,我发现使用单独的控制器有点重复和不必要,但是在多个地方使用相同的控制器会阻止我在离开子页面时重置状态,因为控制器仅在我离开时被销毁已初始化状态的页面。为了防止我的解释产生任何混淆,请看下面的插图。
在第 2 点,我们知道TextField
小部件接受errorText
显示错误消息,该错误消息只接受一个字符串。考虑到这个包,当我尝试通过onChanged: (value) {}
事件更改错误状态时,每次我在其中输入一个值时它都会重建整个小部件,这导致输入指示器停留在开头。
在这种情况下,第 2 点不再发生,但现在它根本不会更新错误状态,并且它继续显示错误消息,即使我在其上键入了新值也是如此。
请帮忙,这是我的脚本:
education_info_create_page.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:prismahrfinal/app/controllers/account_info/education_info_controller.dart';
import 'package:prismahrfinal/app/ui/widgets/form_input.dart';
class EducationInfoCreatePage extends GetWidget<EducationInfoController> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
title: Text('Add Education Info'),
floating: true,
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
child: Column(
children: <Widget>[
Obx(
() => FormInput(
autofocus: true,
label: 'Institution name',
focusNode: controller.institutionFN,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.next,
errorText: controller.institutionError,
onChanged: (value) {
controller.institutionError = null;
controller.institution = value;
},
onSubmitted: (_) {
controller.graduationMonthFN.requestFocus();
},
),
),
Obx(
() => FormInput(
label: 'Graduation month',
focusNode: controller.graduationMonthFN,
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
errorText: controller.graduationMonthError,
onChanged: (value) {
controller.graduationMonthError = null;
controller.graduationMonth = int.parse(value);
},
onSubmitted: (_) {
controller.graduationYearFN.requestFocus();
},
),
),
Obx(
() => FormInput(
label: 'Graduation Year',
focusNode: controller.graduationYearFN,
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
errorText: controller.graduationYearError,
onChanged: (value) {
controller.graduationYearError = null;
controller.graduationYear = int.parse(value);
},
onSubmitted: (_) {
controller.qualificationFN.requestFocus();
},
),
),
Obx(
() => FormInput(
label: 'Qualification',
focusNode: controller.qualificationFN,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.next,
errorText: controller.qualificationError,
onChanged: (value) {
controller.qualificationError = null;
controller.qualification = value;
},
onSubmitted: (_) {
controller.locationFN.requestFocus();
},
),
),
Obx(
() => FormInput(
label: 'Location',
focusNode: controller.locationFN,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.next,
errorText: controller.locationError,
onChanged: (value) {
controller.locationError = null;
controller.location = value;
},
onSubmitted: (_) {
controller.fieldOfStudyFN.requestFocus();
},
),
),
Obx(
() => FormInput(
label: 'Field of study',
focusNode: controller.fieldOfStudyFN,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.next,
errorText: controller.fieldOfStudyError,
onChanged: (value) {
controller.fieldOfStudyError = null;
controller.fieldOfStudy = value;
},
onSubmitted: (_) {
controller.majorsFN.requestFocus();
},
),
),
Obx(
() => FormInput(
label: 'Majors',
focusNode: controller.majorsFN,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.next,
errorText: controller.majorsError,
onChanged: (value) {
controller.majorsError = null;
controller.majors = value;
},
onSubmitted: (_) {
controller.finalScoreFN.requestFocus();
},
),
),
Obx(
() => FormInput(
label: 'Final Score',
focusNode: controller.finalScoreFN,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.next,
errorText: controller.finalScoreError,
onChanged: (value) {
controller.finalScoreError = null;
controller.finalScore = double.parse(value);
},
onSubmitted: (_) {
controller.additionalInfoFN.requestFocus();
},
),
),
Obx(
() => FormInput(
label: 'Additional Info (optional)',
focusNode: controller.additionalInfoFN,
keyboardType: TextInputType.multiline,
maxLines: 5,
textInputAction: TextInputAction.go,
errorText: controller.additionalInfoError,
onChanged: (value) {
controller.additionalInfoError = null;
controller.additionalInfo = value;
},
onSubmitted: (_) {
controller.add();
},
),
),
],
),
),
),
],
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.send, color: Colors.white),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
backgroundColor: Theme.of(context).primaryColor,
tooltip: 'Add Education Info',
onPressed: controller.add,
),
);
}
}
education_info_controller.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:meta/meta.dart';
import 'package:pretty_json/pretty_json.dart';
import 'package:prismahrfinal/app/data/models/account_info/education_info.dart';
import 'package:prismahrfinal/app/data/models/account_info/education_info_error.dart';
import 'package:prismahrfinal/app/data/repositories/account_info/education_info_repository.dart';
class EducationInfoController extends GetxController {
EducationInfoController({@required this.repository})
: assert(repository != null);
final EducationInfoRepository repository;
final FocusNode _institutionFN = FocusNode();
final FocusNode _graduationMonthFN = FocusNode();
final FocusNode _graduationYearFN = FocusNode();
final FocusNode _qualificationFN = FocusNode();
final FocusNode _locationFN = FocusNode();
final FocusNode _fieldOfStudyFN = FocusNode();
final FocusNode _majorsFN = FocusNode();
final FocusNode _finalScoreFN = FocusNode();
final FocusNode _additionalInfoFN = FocusNode();
final Rx<ListEducationInfo> data = ListEducationInfo().obs;
final Rx<EducationInfo> education = EducationInfo().obs;
final Rx<EducationInfoError> errors = EducationInfoError().obs;
@override
void onInit() => fetchDataFromApi();
void fetchDataFromApi() async {
data.value = await repository.getData();
if (data.value == null) {
Get.snackbar("Error", "Can't connect to server");
}
}
void add() async {
this._unfocus();
debugPrint(prettyJson(education.value.toJson()));
final response = await repository.add(education.value.toJson());
if (response == null) {
Get.snackbar("Error", "Can't connect to server");
return;
} else if (response is EducationInfoError) {
errors.value = response;
return;
}
data.value.educations.add(response);
}
void _unfocus() {
this.institutionFN.unfocus();
this.graduationMonthFN.unfocus();
this.graduationYearFN.unfocus();
this.qualificationFN.unfocus();
this.locationFN.unfocus();
this.fieldOfStudyFN.unfocus();
this.majorsFN.unfocus();
this.finalScoreFN.unfocus();
this.additionalInfoFN.unfocus();
}
// Getters -- Focus Nodes
get institutionFN => this._institutionFN;
get graduationMonthFN => this._graduationMonthFN;
get graduationYearFN => this._graduationYearFN;
get qualificationFN => this._qualificationFN;
get locationFN => this._locationFN;
get fieldOfStudyFN => this._fieldOfStudyFN;
get majorsFN => this._majorsFN;
get finalScoreFN => this._finalScoreFN;
get additionalInfoFN => this._additionalInfoFN;
// Getters -- Values
get institution => this.education.value.institution;
get graduationMonth => this.education.value.graduationMonth;
get graduationYear => this.education.value.graduationYear;
get qualification => this.education.value.qualification;
get location => this.education.value.location;
get fieldOfStudy => this.education.value.fieldOfStudy;
get majors => this.education.value.majors;
get finalScore => this.education.value.finalScore;
get additionalInfo => this.education.value.additionalInfo;
// Getters -- Errors
get institutionError => this.errors.value.institution?.first;
get graduationMonthError => this.errors.value.graduationMonth?.first;
get graduationYearError => this.errors.value.graduationYear?.first;
get qualificationError => this.errors.value.qualification?.first;
get locationError => this.errors.value.location?.first;
get fieldOfStudyError => this.errors.value.fieldOfStudy?.first;
get majorsError => this.errors.value.majors?.first;
get finalScoreError => this.errors.value.finalScore?.first;
get additionalInfoError => this.errors.value.additionalInfo?.first;
// Setters -- Values
set institution(value) => this.education.value.institution = value;
set graduationMonth(value) => this.education.value.graduationMonth = value;
set graduationYear(value) => this.education.value.graduationYear = value;
set qualification(value) => this.education.value.qualification = value;
set location(value) => this.education.value.location = value;
set fieldOfStudy(value) => this.education.value.fieldOfStudy = value;
set majors(value) => this.education.value.majors = value;
set finalScore(value) => this.education.value.finalScore = value;
set additionalInfo(value) => this.education.value.additionalInfo = value;
// Setters -- Errors
set institutionError(value) => this.errors.value.institution = value;
set graduationMonthError(value) => this.errors.value.graduationMonth = value;
set graduationYearError(value) => this.errors.value.graduationYear = value;
set qualificationError(value) => this.errors.value.qualification = value;
set locationError(value) => this.errors.value.location = value;
set fieldOfStudyError(value) => this.errors.value.fieldOfStudy = value;
set majorsError(value) => this.errors.value.majors = value;
set finalScoreError(value) => this.errors.value.finalScore = value;
set additionalInfoError(value) => this.errors.value.additionalInfo = value;
}
app_pages.dart
import 'package:get/get.dart';
import 'package:prismahrfinal/app/bindings/education_info_binding.dart';
import 'package:prismahrfinal/app/bindings/employment_info_binding.dart';
import 'package:prismahrfinal/app/bindings/personal_info_binding.dart';
import 'package:prismahrfinal/app/ui/android/account_info/education_info/education_info.dart';
import 'package:prismahrfinal/app/ui/android/account_info/education_info/education_info_create.dart';
import 'package:prismahrfinal/app/ui/android/account_info/employment_info/employment_info.dart';
import 'package:prismahrfinal/app/ui/android/account_info/personal_info/personal_info.dart';
import 'package:prismahrfinal/app/ui/android/account_info/personal_info/personal_info_edit.dart';
import 'package:prismahrfinal/app/ui/android/home.dart';
import 'package:prismahrfinal/app/ui/android/login.dart';
import 'package:prismahrfinal/app/ui/android/account_info.dart';
part './app_routes.dart';
abstract class AppPages {
static final pages = [
GetPage(
name: Routes.EDUCATION_INFO,
page: () => EducationInfoPage(),
binding: EducationInfoBinding(),
),
GetPage(
name: Routes.EDUCATION_INFO_CREATE,
page: () => EducationInfoCreatePage(),
),
];
}
解决方案
问题 1 - 在我看来,每个屏幕都需要一个控制器。因此,您创建的每个屏幕也需要创建一个控制器。如果您需要在屏幕之间传递数据,则需要使用 Get.arguments 来捕获路线之间的参数。要通过,您只需通过大声笑。
Get.toNamed(yourRoute, arguments: yourArgument);
问题 2 - 每次更新列表错误时,所有观察者都会观察更新。
// Getters -- Errors
get institutionError => this.errors.value.institution?.first;
get graduationMonthError => this.errors.value.graduationMonth?.first;
get graduationYearError => this.errors.value.graduationYear?.first;
get qualificationError => this.errors.value.qualification?.first;
get locationError => this.errors.value.location?.first;
get fieldOfStudyError => this.errors.value.fieldOfStudy?.first;
get majorsError => this.errors.value.majors?.first;
get finalScoreError => this.errors.value.finalScore?.first;
get additionalInfoError => this.errors.value.additionalInfo?.first;
这就是所有小部件都在重建的原因....
问题 3 - 您可以使用更新方法来触发对模型的反应。
use model.update((model){
model.email = 'foo@bar'
});
推荐阅读
- php - PHP如何从数组中删除元素并按键添加到另一个数组?
- filter - 在 Spring 云网关过滤器中处理多部分数据和异常
- python - python numba 变量 $116setup_with.2 未定义
- javascript - 使用 React DocumentUpload 组件上传多个文件
- spring - 在 Spring Boot 应用程序中从 Amazon S3 存储桶中的某个文件夹下载文件时获取 403
- javascript - 使用 VueJS 递归嵌套组件
- php - Woocommerce:根据运输等级向产品价格添加固定费用
- angular - 为什么(可观察
) => 可观察的 解析为 OperatorFunction - android - Koltin 函数参考:此表达式未使用
- python - VSCode:使用非本地导致变量类型“从不”