I'm using get
package for my project's state management. But i'm confused by the implementation for form inputs and validation since i couldn't find any example for it in the documentation. I have some questions regarding to this issue.
PageOneController
and PageOneFormController
?Model().obs
object?As I mentioned above at point number 1, i found that using separate controller is a bit repetitive and unnecessary, but using same controller at multiple places prevents me to reset the state when i leave the child page since the controller was only destroyed when I leave the page which has initialized the state. To prevent any confusion from my explanation, please have a look at the illustration below.
At point number 2, as we know that the TextField
widget accepts errorText
for displaying an error message which is only accept a string. With this package in mind, when I try to change state of the error by the onChanged: (value) {}
event, it rebuilds the entire widget everytime i type a value inside it which causing the input indicator stayed at the beginning.
In this case, point number 2 is no more happened but now it won't update the error state at all and it keep showing the error message, even when i typed a new value onto it.
Please help, here is my script:
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(),
),
];
}
Add a TextFormField with validation logic Validate the input by providing a validator() function to the TextFormField . If the user's input isn't valid, the validator function returns a String containing an error message. If there are no errors, the validator must return null.
Obx class Null safety The simplest reactive widget in GetX. Just pass your Rx variable in the root scope of the callback to have it automatically registered for changes.
The Binding class is a class that will decouple dependency injection, while "binding" routes to the state manager and dependency manager. This allows Get to know which screen is being displayed when a particular controller is used and to know where and how to dispose of it.
Problem 1 - In my opnion each screen needs one controller. So every screen you are creating you need to create a controller too. If you need pass data between screen you need use Get.arguments to catch the arguments between your routes. To pass you will need just pass lol.
Get.toNamed(yourRoute, arguments: yourArgument);
Problem 2 - Every time you are updating the list errors all yours observers will observer the update.
// 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;
thats the reason all widgets are rebuilding....
Problem 3 - You can use the update method to trigger the react on your model.
use model.update((model){
model.email = 'foo@bar'
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With