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.
- Should i create separate controller for each action? like
PageOneControllerandPageOneFormController? - How to prevent the parent from rebuilding the child when the state changes?
- How to trigger rebuild on data addition inside the
Model().obsobject?
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(),
),
];
}
