I am trying to clean up my code a lot. I have a form that I realise I use multiple times in my app. So I wanted to turn the form into its own stateful widget so I could easily re-use it. Unfortunately this is proving more difficult than I thought.
The form is first used in the app to add items. I then want to re-use that same form to edit items and change values in those text fields.
I call the new widget that I created:
body: const GearForm(
onSaveFunctionType: 'add',
itemID: 'none',
itemManufacturer: 'hi',
itemName: '',
itemType: 'test',
itemVolume: '',
itemWeight: '',
packCategory: '',
tempRating: '',
),
If I am passing arguments into the widget though they do not show up in the form when I am testing:
class GearForm extends StatefulWidget {
const GearForm(
{Key? key,
required this.onSaveFunctionType,
required this.itemID,
required this.itemName,
required this.itemManufacturer,
required this.itemType,
required this.packCategory,
required this.itemWeight,
required this.tempRating,
required this.itemVolume})
: super(key: key);
final String onSaveFunctionType;
final String itemID;
final String itemName;
final String itemManufacturer;
final String itemType;
final String packCategory;
final String itemWeight;
final String tempRating;
final String itemVolume;
#override
State<GearForm> createState() => _GearFormState();
}
//define category stream stream
Stream<DocumentSnapshot<Map<String, dynamic>>> streamUserCategories() {
FirebaseFirestore db = FirebaseFirestore.instance;
String userID = FirebaseAuth.instance.currentUser!.uid;
return db.collection('UserPackCategoryList').doc(userID).snapshots();
}
class _GearFormState extends State<GearForm> {
FirebaseAnalytics analytics = FirebaseAnalytics.instance;
FirebaseFirestore db = FirebaseFirestore.instance;
String userID = FirebaseAuth.instance.currentUser!.uid;
//text editing controllers
TextEditingController itemNameController = TextEditingController();
TextEditingController manufacturerController = TextEditingController();
TextEditingController itemTypeController = TextEditingController();
TextEditingController packCategoryController = TextEditingController();
TextEditingController itemWeightController = TextEditingController();
TextEditingController temperatureRatingController = TextEditingController();
TextEditingController volumeController = TextEditingController();
#override
Widget build(BuildContext context) {
//get entitlements from revenue cat
final Entitlement entitlement =
Provider.of<RevenueCatProvider>(context).entitlement;
print(widget.itemManufacturer);
//set initial controller values
itemNameController.text = widget.itemName;
manufacturerController.text = widget.itemManufacturer;
itemTypeController.text = widget.itemType;
packCategoryController.text = widget.packCategory;
itemWeightController.text = widget.itemWeight;
temperatureRatingController.text = widget.tempRating;
volumeController.text = widget.itemVolume;
The odd part is if I did a cmd+s in visual studio code then all the value would magically appear. Since the Cmd+s worked I thought it was showing the values on rebuild so I tried wrappign everythign in setState:
#override
Widget build(BuildContext context) {
//get entitlements from revenue cat
final Entitlement entitlement =
Provider.of<RevenueCatProvider>(context).entitlement;
print(widget.itemManufacturer);
setState(() {
//set initial controller values
itemNameController.text = widget.itemName;
manufacturerController.text = widget.itemManufacturer;
itemTypeController.text = widget.itemType;
packCategoryController.text = widget.packCategory;
itemWeightController.text = widget.itemWeight;
temperatureRatingController.text = widget.tempRating;
volumeController.text = widget.itemVolume;
});
but that didnt fix the issue either...
Update:
I did soem further troubleshooting and tried initState:
#override
void initState() {
super.initState();
print(widget.itemManufacturer);
itemNameController.text = widget.itemName;
manufacturerController.text = widget.itemManufacturer;
itemTypeController.text = widget.itemType;
packCategoryController.text = widget.packCategory;
itemWeightController.text = widget.itemWeight;
temperatureRatingController.text = widget.tempRating;
volumeController.text = widget.itemVolume;
//set initial controller values
}
What is super odd here is that my print(widget.itemManufacturer); works fine and I see the correct value. But it is not being assigned to the manufacturerController.text = widget.itemManufacturer; a few lines down.
Use initState or you can use late
late TextEditingController itemNameController =
TextEditingController.fromValue(TextEditingValue(text: widget.itemName));
The reason why the value were not showing in my form fields was because I am using autocomplete. Autocomplete upon further research has an initial Value field:
initialValue: TextEditingValue(text: manufacturerController.text),
Once I used that initalValue field all updated accordingly.
Related
I have arguments that are being passed through a namedRoute. I access them currently with:
final args = ModalRoute.of(context)!.settings.arguments as UserPack;
In my widget Build. I am trying to create a function for a stream that needs data from args but unfortunately the I cant access args outside of the Widget Build Which means I can not get the packID for the streamIndividualPackList(). I need to build that Stream before the Widget Build.
class IndividualPack extends StatefulWidget {
const IndividualPack({Key? key}) : super(key: key);
#override
State<IndividualPack> createState() => _IndividualPackState();
}
Stream<QuerySnapshot<Map<String, dynamic>>> streamIndividualPackList(
String packID) {
FirebaseFirestore db = FirebaseFirestore.instance;
return db
.collection('PackList')
.doc(packID)
.collection('PackContents')
.orderBy('itemName')
.snapshots();
}
class _IndividualPackState extends State<IndividualPack> {
FirebaseAnalytics analytics = FirebaseAnalytics.instance;
//gets the Firebase db
FirebaseFirestore db = FirebaseFirestore.instance;
String userID = FirebaseAuth.instance.currentUser!.uid;
final PageController pageViewController = PageController();
#override
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!.settings.arguments as UserPack;
ModalRoute depends on context, you can use nullable data with addPostFrameCallback to assign it.
int? data;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
data = ModalRoute.of(context)?.settings.arguments as int?;
});
}
You just nullable data inside build method.
int? data;
#override
Widget build(BuildContext context) {
data ??= ModalRoute.of(context)?.settings.arguments as int?;
Do a null check before using nullable data.
I have noticed one thing in a tutorial that it changes the value of final object and working well, but so far I know , final can't be changed...
Than why it is working well with this code..
here is the code
check my commented lines
class CompleteProfile extends StatefulWidget {
final UserModel userModel;
final User firebaseUser;
const CompleteProfile({Key? key, required this.userModel, required this.firebaseUser}) : super(key: key);
#override
_CompleteProfileState createState() => _CompleteProfileState();
}
class _CompleteProfileState extends State<CompleteProfile> {
File? imageFile;
TextEditingController fullNameController = TextEditingController();
void uploadData() async {
UIHelper.showLoadingDialog(context, "Uploading image..");
UploadTask uploadTask = FirebaseStorage.instance.ref("profilepictures").child(widget.userModel.uid.toString()).putFile(imageFile!);
TaskSnapshot snapshot = await uploadTask;
String? imageUrl = await snapshot.ref.getDownloadURL();
String? fullname = fullNameController.text.trim();
// here are the statement of changing value of final object....
widget.userModel.fullname = fullname;
widget.userModel.profilepic = imageUrl;
await FirebaseFirestore.instance.collection("users").doc(widget.userModel.uid).set(widget.userModel.toMap()).then((value) {
log("Data uploaded!");
Navigator.popUntil(context, (route) => route.isFirst);
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) {
return HomePage(userModel: widget.userModel, firebaseUser: widget.firebaseUser);
}),
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
```
Are you sure your UserModel class's fields: fullname and profilepic is final? Currently, you're changing these fields, not the UserModel object (which is final). If they're not marked as final, it's only logical for them to be changed.
final keyword does not apply to complex objects' fields. They ought to be final too if you want them to never change their values.
If you want to see an error try:
widget.userModel = UserModel();
or make fields fullname and profilepic final:
class UserModel{
final String? fullName;
final String? profilepic;
}
then try:
widget.userModel.fullName = 'Anything' // this will raise an error
In my first page I have this:
onPressed: () {
Navigator.push(context,MaterialPageRoute(
builder: (context) =>
UpdateAttackScreen(
idAttack: idAttack,
title: title,
descriptionAttack:descriptionAttack,
indexSensations:indexSensations)));
},
in the second page where I want to receive this params:
class UpdateAttackScreen extends StatefulWidget {
final String idAttack;
final String title;
final String descriptionAttack;
final List<dynamic> indexSensations;
UpdateAttackScreen(
{Key key,
#required this.idAttack,
#required this.title,
#required this.descriptionAttack,
#required this.indexSensations})
: super(key: key);
#override
_UpdateAttackScreenState createState() => _UpdateAttackScreenState(idAttack, title, descriptionAttack, indexSensations);
}
class _UpdateAttackScreenState extends State<UpdateAttackScreen> {
String idAttack;
String title;
String descriptionAttack;
List<dynamic> indexSensations;
_UpdateAttackScreenState(idAttack, title, descriptionAttack, indexSensations);
TextEditingController _titleController = TextEditingController()..text = title;
TextEditingController _descriptionController = TextEditingController()..text = descriptionAttack;
The response error on title and descriptionAttack:
The instance member 'title' can't be accessed in an initializer.
Try replacing the reference to the instance member with a different expression
this code
TextEditingController _titleController = TextEditingController()..text = title;
TextEditingController _descriptionController = TextEditingController()..text = descriptionAttack;
replace with this
TextEditingController _titleController;
TextEditingController _descriptionController;
#override
void initState() {
super.initState();
_titleController = TextEditingController()..text = title;
_descriptionController = TextEditingController()..text = descriptionAttack;
}
Solution 1 :
Wrap _titleController with initState method
String title = '';
TextEditingController _titleController;
#override
void initState() {
_titleController..text = title;
super.initState();
}
Solution 2:
Transform the title variable into a static variable to be able to use it everywhere in your class
static String title = '';
TextEditingController _titleController = TextEditingController()..text = title ;
I fixed it!
class UpdateAttackScreen extends StatefulWidget {
final String idAttack;
final String title;
final String descriptionAttack;
final List<dynamic> indexSensations;
UpdateAttackScreen(
{Key key,
#required this.idAttack,
#required this.title,
#required this.descriptionAttack,
#required this.indexSensations})
: super(key: key);
#override
_UpdateAttackScreenState createState() => _UpdateAttackScreenState(idAttack, title, descriptionAttack, indexSensations);
}
class _UpdateAttackScreenState extends State<UpdateAttackScreen> {
String idAttack;
String title;
String descriptionAttack;
List<dynamic> indexSensations;
_UpdateAttackScreenState(idAttack, title, descriptionAttack, indexSensations);
TextEditingController _titleController;
TextEditingController _descriptionController;
Inside the TextFieldForm
initialValue: widget.title,
I am new to flutter and I think I miss a little piece of information about constructor and stateful widget. I tried many ways but always have an error. I just want to pass data into my stateful widget to manipulate from there.
Here is my Error
The instance member 'widget' can't be accessed in an initializer.
Try replacing the reference to the instance member with a different expression
Here is my code
class CreateEducatorEventForm extends StatefulWidget {
final DateTime day = DateTime.now();
final String favoriteId = '';
CreateEducatorEventForm(DateTime day, String favoriteId);
#override
_CreateEducatorEventFormState createState() =>
_CreateEducatorEventFormState();
}
class _CreateEducatorEventFormState extends State<CreateEducatorEventForm> {
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
String _eventName = '';
String _eventDescription = '';
DateTime _eventDateStart = widget.day;
DateTime _eventDateFinish = widget.day;
You can just move it into initState
class _CreateEducatorEventFormState extends State<CreateEducatorEventForm> {
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
String _eventName = '';
String _eventDescription = '';
DateTime _eventDateStart;
DateTime _eventDateFinish;
#override
void initState() {
super.initState();
_eventDateStart = widget.day;
_eventDateFinish = widget.day;
}
}
To be fair, unless you really need to store this into your state (say, if it really participates in the lifecycle of your widget), you should just refer to it via widget.day whenever you need it.
I'm learning Flutter and I have in my app, two textFields linked to textControllers in an AlertDialog to get the input from a user as text and display it in cards in the body of the screen. My problem, that I can't solve on my own, is that after I added setState(() {}) in the 'Save' button of the AlertDialog, for the text to acutally get displayed on the screen in body, well after this change the text entered in the TextFields doesn't get cleared aymore after pressing 'Save'.
My Code:
class _HomeScreenState extends State<HomeScreen> {
final TextEditingController titleController = TextEditingController();
final TextEditingController textController = TextEditingController();
DummyDataProvider notes;
#override
void dispose() {
// Clean up the controller when the widget is disposed.
titleController.dispose();
textController.dispose();
super.dispose();
}
The textControllers in question:
MaterialButton(
onPressed: () {
setState(() {
final title = titleController.text;
final text = textController.text;
NoteProvider.insertNote({'title': title, 'text': text});
Navigator.pop(context);
});
What i mean by text not disposing:
https://imgur.com/a/8pyTPM7,
https://imgur.com/a/lr8a3Eh
Thank you in advance!
Why not just use clear()?
final _textController = TextEditingController();
.....
.....
onPressed: () {
_textController.clear();
}
You can reset your text controllers.
For example in onpressed:
titleController = new TextEditingController();
textController = new TextEditingController();
Set state is not required for this.
TextEditingController
Is very expensive object, don't create it often
just use
TextEditingController.text == '';
to clear the text,