Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly use GetX with TextField and some server side validation?

Tags:

flutter

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.

  1. Should i create separate controller for each action? like PageOneController and PageOneFormController?
  2. How to prevent the parent from rebuilding the child when the state changes?
  3. How to trigger rebuild on data addition inside the 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.

visual explanation

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(),
    ),
  ];
}
like image 403
Tamma Avatar asked Jul 30 '20 06:07

Tamma


People also ask

How do I add validation to TextField?

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.

What is GetX OBX?

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.

What is bindings GetX?

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.


Video Answer


1 Answers

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'
});
like image 126
Katekko Avatar answered Nov 15 '22 11:11

Katekko