I am trying to assign an input value into my model in order to use it in the API POST request. However, I keep getting the error:
setter was called on null when saving
Model:
#JsonSerializable()
class Discount {
String value;
Discount();
set setValue(String value) {
this.value = value;
}
Dart Code:
children: <Widget>[
Expanded(
child: Container(
margin: EdgeInsets.only(right: 5),
child: TextFormField(
initialValue: _invoiceFormData.discount?.value ?? '0.00',
inputFormatters: [_amountValidator],
keyboardType: TextInputType.numberWithOptions(
decimal: true,
signed: false,
),
decoration: TextFormField.decoration(
labelText: Localizations.of(context)
.text('label_invoice_discount'),
),
onChanged: (String value) {
setState(() {
discountTotal = value;
});
},
onSaved: (value) => _invoiceFormData
.discount.setValue = value,
),
),
),
FlatButton(
onPressed: () {
setState(() {
discountType = !discountType;
});
},
),
],
Log:
The setter 'setValue=' was called on null.
Receiver: null
Tried calling: setValue="10.00"
I tried to set _invoiceFormData.discount.value = value but it is still showing the same error.
use StatefulWidget class and
change this
onSaved: (value) => _invoiceFormData
.discount.setValue = value,
to
onSaved: (value) => setState(() {
onSaved: (value) => _invoiceFormData
.discount.setValue = value,
});
Your log is saying that _invoiceFormData.discount is null when you are using the setter setValue=.
To resolve this, you first need to instantiate the field discount in your object named _invoiceFormData. For example, when you initialize this form data (maybe in your initState()), you should do _invoiceFormData.discount = Discount(); then it will not be null anymore
Related
I have create a validator to check value format then I want to check a datetime value and compare it if is it after the previous entered value (or great then it) if yes will be validate & save or will show error if is it before the previous datetime (like the example below)
child: TextFormField(
autofocus: true,
keyboardType: TextInputType.datetime,
controller: _controller,
decoration: InputDecoration(
hintText: 'JJ-MM-AAAA',
counterText: '',
),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9-]')),
LengthLimitingTextInputFormatter(10),
],
onSaved: (val) {
taskItems.addItem(val);
},
validator: (val) {
if (val!.length < 10) {
return 'Entre une date valide';
} else
return null;
},
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
_formKey.currentState?.reset();
}
},
child: Text('Add'),
),
It's simple, try this:
You first need to parse your input date and previous date into DateTime() using
DateTime prevDate = DateTime.parse(date1);
DateTime newDate = DateTime.parse(date2);
You can compare your date times using
if(newDate.isAfter(prevDate)){
// validate and save value
}
else {
// show error
}
How do I reset or select the first value from DropdownButtonFormField?
The answer from here How to reset value in Flutter DropdownButtonFormField is outdated and not suitable for the newer flutter version.
DropdownButtonFormField:
final etSkillScore1Key = GlobalKey<FormState>();
...
DropdownButtonFormField(
key: etSkillScore1Key,
decoration: const InputDecoration(labelText: 'Select value'),
onChanged: (val) async {
setState(() {
etSkillScore1 = val as int;
});
FocusScope.of(context).requestFocus(FocusNode());
},
value: etSkillScore1,
items: priorities2.map((db.Priority priorities) {
return DropdownMenuItem(
child: Text(priorities.name),
value: priorities.value,
);
}).toList(),
),
Button for resetting the value:
IconButton(
onPressed: () {
//ERORR: Null check operator used on a null value
etSkillScore1Key.currentState!.reset();
},
icon: Icon(
Icons.close,
))
Error:
======== Exception caught by gesture
The following _CastError was thrown while handling a gesture:
Null check operator used on a null value
If I use
etSkillScore1Key.currentState?.reset();
then nothing happens
First of all you are not using the correct key it should be a GlobalKey<FormFieldState>(), but even then the reset() would not work.
The reason for this is because of the implementation of DropdownButtonFormField:
DropdownButtonFormField({
// ...
T? value,
// ...
}) : super(
// ...
initialValue: value,
// ...
);
(source: Flutter Documentation)
As you can see the value property of DropdownButtonFormField is what defines the initialValue of the FormField so when you are rebuilding your form field and changing the value of etSkillScore1 it is also changing the value of your DropdownButtonFormField.initialValue.
Solution 1
If you want your reset() to work then you can remove the property value of DropdownButtonFormField so the initialValue won't change with etSkillScore1.
DropdownButtonFormField<int>(
key: etSkillScore1Key,
decoration: const InputDecoration(labelText: 'Select value'),
onChanged: (val) {
etSkillScore1 = val;
FocusScope.of(context).requestFocus(FocusNode());
},
// value: etSkillScore1,
items: priorities2.map((db.Priority priorities) {
return DropdownMenuItem<int>(
child: Text(priorities.name),
value: priorities.value,
);
}).toList(),
)
Try the full example on DartPad
Solution 2
Do not set the value property with etSkillScore1, if you want to initialize your widget with an initial value then do it like this:
DropdownButtonFormField<int>(
key: etSkillScore1Key,
decoration: const InputDecoration(labelText: 'Select value'),
onChanged: (val) {
etSkillScore1 = val;
FocusScope.of(context).requestFocus(FocusNode());
},
value: 1, // Use a fixed value that won't change
items: priorities2.map((db.Priority priorities) {
return DropdownMenuItem<int>(
child: Text(priorities.name),
value: priorities.value,
);
}).toList(),
)
As your value will be fixed then when DropdownButtonFormField is rebuild it will keep 1 as its initial value.
I'm working on a online survey app in flutter and the API in PHP with MySQL for the database.
I'm using TextFormField for the Name field and wanted to use Slider for Age field.
I can retrieve the value from TextFormField using onSaved: (e) => name = e,, But I don't know how to retrieve the value of the Slider.
I tried to change the value to string using age.toString() but It says:
The argument type 'String' can't be assigned to the parameter type.
Here is my Form() code:
Form(
key: _key,
child: ListView(
children: [
TextFormField(
onSaved: (e) => name = e,
decoration: InputDecoration(labelText: "Name"),
),
Slider(
min: 10,
max: 100,
value: age,
onChanged: (value) {
setState(() {
age = value;
});
},
label: "$age",
),
MaterialButton(
onPressed: () {
check();
},
child: Text("submit"),
)
]
)
),
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 PageOneController and PageOneFormController?
How to prevent the parent from rebuilding the child when the state changes?
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.
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(),
),
];
}
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'
});
As displayed in the GIF below, the TextFormField i am using is entering the values backwards. I have set the TextDirection property to ltr as well but it did not change anything. Other TextFormFields dont seem to be having this issue. The entered text is being sent back to another screen using Navigator.pop and it is sent in the same backwards manned.
Weird TextFormField
Code for the textFormField causing the issue:
TextFormField(
// validator: (String value) {
// return value.isEmpty ? "task must have a name" : null;
// },
textDirection: TextDirection.ltr,
maxLength: 100,
controller: newcontroller, // Just an ordinary TextController
onChanged: (value) {
setState(() {
newcontroller.text = value;
});
},
decoration: InputDecoration(
errorText: _validate // Just a boolean value set to false by default
? 'Value Can\'t Be Empty' : null,
labelText: "name of task"
),
style: TextStyle(height: 1.2, fontSize: 20, color: Colors.black87)
)
You don't have to set text in newcontroller.text when onChanged is called.
text enter in your TextFormField is assigned by default to newcontroller.
You are getting this error because for this piece of code,
So, try to remove below code
setState(() {
newcontroller.text = value;
});
you can send whatever you want in Navigator.pop(context,whtever you want to pass)
TextFormField(
// validator: (String value) {
// return value.isEmpty ? "task must have a name" : null;
// },
textAlign: TextAlign.end,
maxLength: 100,
controller: newcontroller, // Just an ordinary TextController
onChanged: (value) {
print(value);
},
decoration: InputDecoration(
errorText:
_validate // Just a boolean value set to false by default
? 'Value Can\'t Be Empty'
: null,
labelText: "name of task"),
style:
TextStyle(height: 1.2, fontSize: 20, color: Colors.black87),
),
MaterialButton(
onPressed: () {
Navigator.pop(context, newcontroller.text);
},
child: Text("GO BACK!"),
),
replace onChanged with onEditingComplete.
onEditingComplete: (value) {
newController.text = value;
FocusScope.of(context).unfocus(); //use this to dismiss the screen keyboard
}
Just remove the onChanged function. There is no need for it.
onChanged: (val) {
if (val.isNotEmpty) {
setState(() {
// remember to not add your controller value in onchanged
_messageController.text = val;
isShowSendButton = true;
});
} else {
setState(() {
// remember to not add your controller value in onchanged
_messageController.text = val;
isShowSendButton = false;
});
}
Unfortunately, onEditingComplete doesn't always fire (i.e. on a focus change), so if you are editing a text variable you can still use onChanged as the most reliable way to update changes, but you should not use the onChanged value parameter, nor should you use setState (which results in the reversed text).
TextEditingController tec = TextEditingController(text: myText);
return TextField(
controller: tec,
onChanged: (value) {
myText = tec.text;
},
);