Set() is not working in Flutter when I use Flutter Hooks - flutter

I wanted to create a checklist for the programming languages I know. I have used Set() to contain the data. But when I use it in the CheckboxListTile() it is not checking the box in the UI. I wanted to do it with using flutter_hooks.
I have created an enum for ProgrammingLanguages.
enum ProgrammingLanguages { C, DART, PYTHON, JAVASCRIPT }
Then I have initialised the set in stateless Widget class like this
final _selectedLanguages = useState<Set>(Set<ProgrammingLanguages>());
In the build I have added a CheckBoxListTile like this.
CheckboxListTile(
title: Text('C'),
value: _selectedLanguages.value.contains(ProgrammingLanguages.C),
onChanged: (value) {
value
? _selectedLanguages.value.remove(ProgrammingLanguages.C)
: _selectedLanguages.value.add(ProgrammingLanguages.C);
}),
But in the final UI I am not able to activate the check box. Please help.

Related

Flutter: Need some help on approach for multiple Stateful Widgets in parent Widget with some degree of global accessibility

How would I go about maintaining the state of many individual material switches that represent the same information across the app? I have a rough representation of my pages and their widgets below.
Account Page: I have a Form with 10 fields which have 10 concurrent switches for displaying/hiding the info submitted in the fields.
Home Page: I have an expandable Personal Card that has 10 switches for quick display/hide of the same info from the Form.
Any examples I find seem to just demonstrate managing a single widget's state. I've looked into Riverpod and like the ideas of it being 'global' and separate from the widget tree, to me this sounds like the most viable option. But not having used Riverpod, I am a bit confused with how to implement it with my case.
You can create StateNotifier/ChangeNotifier for maintaining the state and then wrap all your switches inside Consumer widgets.
Here is an example with ChangeNotifier:
You can use a primitive type like an array or maybe a map for storing the state of switches.
class SwitchesNotifier extends ChangeNotifier {
final switchesStateMap = <String, bool>{};
void changeSwitchState(String switchKey, bool value) {
if (switchesStateMap.containsKey(switchKey)) {
switchesStateMap[switchKey] = value;
} else {
switchesStateMap[switchKey] = true;
}
notifyListeners();
}
}
final switchesNotifierProvider = ChangeNotifierProvider((ref) => SwitchesNotifier());
Then you can wrap all your switches inside consumer widgets and to read the switch value based on your key and listen for state changes. Also don't forget to call changeSwitchState() inside onChanged callback of switch widget.
Consumer(
builder: (_, ScopedReader watch, __) {
final switchNotifier = watch(switchesNotifierProvider);
final switchValue = switchNotifier.switchesStateMap['subtitles']
?? false;
return Switch(
value: switchValue,
onChanged: (value) {
context.read(switchesNotifierProvider).changeSwitchState('subtitles',value);
}
);
},
),

Can't add a function to onChanged property

I'm developing an app and my current task is to implement localization for several languages. Now that I've translated most of it, I need to create a button that I would nest in the app bar that would drop down and offer the different languages.
To that extent, I've followed this tutorial : https://www.youtube.com/watch?v=yX0nNHz1sFo&list=PLyHn8N5MSsgEfPAxCytQDPATDlHwpP5rE&index=3.
Now, since I plan on reusing this button for different pages, I created its own class, so far for good. But when comes time to add the onChanged property that would call the function responsible for language switching, it doesn't work. In fact, I can't call any function at all.
Here's the code snippet:
class LanguageButton extends StatelessWidget {
void changeLanguage(Language lang) {
print(lang.languageCode); //placeHolder
}
Widget build(BuildContext context) {
return DropdownButton(
underline: SizedBox(),
onChanged: (Language lang) {
changeLanguage(lang);
},
icon: Icon(
Icons.language,
color: Colors.white,
),
items: Language.languageList()
.map<DropdownMenuItem<Language>>((lang) => DropdownMenuItem(
value: lang,
child: Row(children: [
Text(lang.flag),
Text(lang.language),
])))
.toList(),
);
}
}
Which returns the error The argument type 'void Function(Language)' can't be assigned to the parameter type 'void Function(Language?)?'..
I've tried replacing by onChanged: ()=>print('hello') but it's still not working with a similar error message.
Any help is appreciated!
The onChanged method of a DropdownButton (see the documentation) is a ValueChanged<T?>?. That means it can itself be null (for example if you don't set it at all), but if you set it, you need to set it to a method with the signature void Function(T?). Your method does not confirm to that signature, because it does not allow nulls.
Change yourmethod to:
void changeLanguage(Language? lang) {
and either pass it directly:
onChanged: changeLanguage,
or make the anonymous method have a nullable parameter, too:
onChanged: (Language? lang) {
changeLanguage(lang);
},

Difference between flutter onTap() and javascript/jquery $(this) in click listener

I'm still in the process of learning dart/flutter. I'm experimenting with ListView/ListTile and specifically onTap() from ListTile.
I'm somewhat familiar with JS/Jquery and am having trouble understanding the differences in approach between JS and flutter.
Please try to ignore any semantic or technical errors in the code below. I'm more concerned with theory/approach than if this is a perfectly formatted and syntactically correct example.
In JS, it's common to do something like make an AJAX call to an API to get a list of items for sale from a database. Assume the API returned 4 fields (primary key ID, isAvailable, item name, and item price). You then process the list and create/insert DOM elements for display.
<div id="234" data-isavailable="y" class=".itemsForSale">TV: $800</div>
<div id="345" data-isavailable="y" class=".itemsForSale">Microwave: $200</div>
<div id="456" data-isavailable="n" class=".itemsForSale">Book: $30</div>
<div id="567" data-isavailable="y" class=".itemsForSale">Sofa: $450</div>
You can then set listeners arbitrarily. For instance, you could do...
$( ".itemsForSale" ).click(function() {
// do something
});
The click handler gets applied in "real-time". The code executed is aware of what was clicked and can analyze/interact with that item in the list. So, you can do something like:
$( ".itemsForSale" ).click(function() {
var isAvailable = $(this).attr('data-isavailable');
if( isAvailable == 'n' )
alert('sorry but that item is out of stock!');
else
addItemToCart( $(this).attr('id') );
});
The point being, that the click handler doesn't necessarily know or care about the underlying data of each item. It becomes "context" aware during runtime and then pulls the relevant values out of the UI/etc.
With flutter, I'm looking to recreate similar functionality using a ListView and ListTile. Specifically, ListTile's onTap(). I'm getting confused because it seems like everything is coded "ahead of time".
Here is a basic example (ignore for now I'm not displaying price/etc)...
import 'package:flutter/material.dart';
class SecondScreen extends StatelessWidget {
var mapper = {
'234': 'TV',
'345': 'Microwave',
'456': 'Book',
'567': 'Sofa'
};
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body:
ListView.builder(
padding: const EdgeInsets.all(8.0),
itemCount: mapper.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
key: new Key(mapper.keys.elementAt(index).toString()),
title: Text('${mapper.values.elementAt(index)}'),
onTap: () {
print('you clicked: ' + mapper.keys.elementAt(index).toString() );
addToShoppingCart( mapper.keys.elementAt(index).toString() ); // this doesnt exist yet and is just an example
}
);
}
)
);
}
}
First of all, I'm not even sure I need to assign a custom key to each ListView item (based on the database's primary key), but that's irrelevant for now.
What I'm getting stuck on is that the onTap handler doesn't seem to have an equivalent of "$(this)". Does flutter give each ViewTile it's "own" copy of onTap() and that each relevant key info is "hardcoded" into each one (for each itemBuilder loop, the current value of mapper.keys.elementAt(index).toString() is substituted into onTap() )?
I'm probably not describing my confusion properly and I'm not even sure all of the issues I'm going to run into. I'm sure my way of doing this is very "old" and not aligned with react and other newer approaches/frameworks (combining data within the UI elements).
I'm just having trouble seeing how I'm going to be able to add more complex logic (like checking availability of what was clicked).
Does anyone have any references or explanations that help me bridge this gap? I don't even know how to describe the concept of $(this) and how I would search for it in comparison to flutter/etc. I just know that something feels very different.
Thanks!
I understand your confusion probably because I'd a similar question when I started with Flutter just a few months back. And here is what I think -
It doesn't really matter whether the ListTile item has it's own copy of onTap() or not. A. It does not have it's own copy. (Following code snippet will explain) and B. Every programming language / SDK / whatever has its own way of working. What you are trying to say, probably, is that you've a BMW. It has got certain type of breaks. And you are trying to make exact type of break in Audi. It may not be right to do it as the other systems related to the breaks may not work in the optimised way.
Now, look at the following code snippet :
#override
Widget build(BuildContext context) {
return ListView.separated(
separatorBuilder: (context, index) => ListViewDivider(),
itemCount: faceRecTransactions.length,
itemBuilder: (BuildContext context, int index) {
return FacerecTransactionListItem(facerecTransaction: faceRecTransactions[index], onTap:()=> _onTap(faceRecTransactions[index],context),);
},
);
}
void _onTap(FacerecTransaction facerecTransaction, BuildContext context) {
print('Status : ${facerecTransaction.userId}');
Navigator.push(context, MaterialPageRoute(builder: (context) => FacerecDetailPage(
facerecTransaction: facerecTransaction,
criminalList: this.criminalList,)));
}
There's no copy of onTap for every list item. It just 'feels' as it has because we write onTap inside ListView. If you look at the example above, when the user taps on certain item, then the onTap receives the information. We don't create array of onTap as such.
Since, there's no copy of onTaps, it's not really ahead of time code. It's pretty much works like in Ajax where the onTap doesn't really care about the payload. It just does the action(s) specified.
Now, in the above example, the Detailed Page can check the availability of the particular item. And if the item is not available then either appropriate message can be shown. Depending on the requirement, we can either write this code within onTap or on the next page. In short, the onTap can handle the complex logic as you need.

Why is constructor of stateful widget called upon form interaction?

I have a question about flutter lifecycle.
Something strange is happening since I updated from flutter 1.7 to 1.9 yesterday. My forms that were working perfectly started to act weird.
I have a stateful widget, which if given no object instantiates a new one in its constructor, so that the user can add a new object or use this widget to edit an existing object. something like this:
final MyObject object;
MyForm() : this.object = MyObject(name: "", origin: Origin.EARTH);
MyForm.edit(this.object);
Name is a simple string and origin is an enum.
In the form state I have a global key: final _formKey = GlobalKey<FormState>();
Later in the code I have a field name, and a dropdown with the different values of the origin.
TextFormField(
initialValue: widget.object.name,
onSaved: (value) {widget.object.name = value;),
),
DropdownButtonFormField(
decoration: InputDecoration(labelText: "Origin"),
items: _originList,
value: widget._object.origin,
onChanged: (value) {
setState(() {
widget._object.origin = value;
});
},
onSaved: (value) {
widget._object.type = value;
},
),
So for instance when I change my dropdown value, to something like mars, it indeed selects mars in my object and it shows mars on the dropdown. (confirmed by debugger)
Then when I click on the name textfield to change the name of the object, the dropdown goes back to earth.
Now the really strange thing is, if I save my object... it saves it correctly ! (with mars and the name I chose)
I found out that for some reason (that I do not understand) the constructor of the form (not the form state, but the form) is called when I click on other fields of the form, which recalls my constructor MyForm() which sets up a new MyObject with the value earth...
But then why does it saves it correctly boggles my mind... If it would be consistent with this behavior it should save the object with earth selected and not mars...
Any idea on how this is happening ? (also I make it very clear nothing wrong was happening before the update to flutter 1.9 so it may be a bug in that new version...)
Also I am testing this with an iPhone simulator if that is of any help.
Thank you for any feedback !
if anyone is having the same issue as me, you can read how to fix it here: https://github.com/flutter/flutter/issues/27821
It is a temporary fix until flutter team fixes this issue on their framework !
Cheers !

What is the difference between TextFormField and TextField?

I am new to Flutter (and Dart) and when trying to build a form to edit an object I searched online for examples and tutorials, and I saw both of these used.
What is the difference between the two? Which one should I use?
If you making a Form where you require save, reset, or validate
operations- use TextFormField. Else For Simple user input capture
TextField is sufficient.
TextFormField, which integrates with the Form widget.
This is a convenience widget that wraps a TextField widget in a FormField.
A Form ancestor is not required. The Form simply makes it easier to save, reset, or validate multiple fields at once.
To use without a Form, pass a GlobalKey to the constructor and use GlobalKey.currentState to save or reset the form field.
sample:
TextFormField(
autovalidateMode: AutovalidateMode.always
decoration: const InputDecoration(
icon: Icon(Icons.person),
hintText: 'What do people call you?',
labelText: 'Name *',
),
onSaved: (String value) {
// This optional block of code can be used to run
// code when the user saves the form.
},
validator: (String value) {
return value.contains('#') ? 'Do not use the # char.' : null;
},
)
TextField, which is the underlying text field without the Form integration.
The text field calls the onChanged callback whenever the user changes the text in the field. If the user indicates that they are done typing in the field (e.g., by pressing a button on the soft keyboard), the text field calls the onSubmitted callback.
Short answer
If you don't know what you need, then use a TextField. This is the most basic Flutter widget for getting text input from a user. It's the one you should master first.
TextField
Using a TextField is an easy way to allow user input.
TextField(
decoration: InputDecoration(
hintText: 'Name'
),
);
To get the text that the user entered, you can either get notified every time there is a change like this:
TextField(
decoration: InputDecoration(
hintText: 'Name'
),
onChanged: (text) {
// do something with text
},
),
Or you can use a TextEditingController, as described here. This will give you access to the text state.
TextFormField
If you find yourself needing to validate user text input before you save it, you might consider using a TextFormField. Imagine something like this:
There are lots of validation checks that you might want to do on a username and password.
Of course, you could still just use a couple TextFields, but TextFormField has extra builtin functionality that will make your life easier. Generally, you will only use a TextFormField when you are using it inside of a Form widget (though that isn't a strict requirement).
Here is a stripped down example from the documentation:
class MyCustomForm extends StatefulWidget {
#override
MyCustomFormState createState() {
return MyCustomFormState();
}
}
class MyCustomFormState extends State<MyCustomForm> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
validator: (value) {
// validation logic
},
),
RaisedButton(
child: Text('Submit'),
onPressed: () {
if (_formKey.currentState.validate()) {
// text in form is valid
}
},
),
],
),
);
}
}
See also
Realistic Forms in Flutter — Part 1
Forms with Flutter
TextField is a simple text field. (you don't care about user input)
TextFormField is a text field to be used in a form (you care about user input).
If you don't need to validate TextField.
If you need to validate user input, use TextFormField with validator.
TextFormField vs. TextField
TextFormField returns a TextField, but wraps the TextField with extra functionality you can use through a Form and also without (such as reset, validation, save, etc.).
Generally, you want to use TextFormField unless writing boiler-plate code is your thing.
How does TextFormField work? What can it do?
TextFormField extends FormField class, (a StatefulWidget).
FormField objects do a special thing when they are instantiated: they look up the widget tree for a Form and register themselves with that Form.
After registration, these FormField widgets can be changed by that parent Form.
Form is also a StatefulWidget. It has a FormState object.
FormState can get & set data on any/allFormFields registered to it.
For example to clear the entire form we can call reset() on FormState and FormState will iterate through all child FormFields registered, resetting each FormField to its initialValue (null by default).
Validation is another common use case for putting several FormField like TextFormField inside Form/FormState. This allows multiple fields to be validated with a single validate() call on the Form.
How? FormState has a validate() method that iterates through each registered FormField and calls that FormField's validate() method. Much more convenient calling validate() once on Form than you manually keeping track of all TextField and validating each one separately with custom code.
Details
How does a TextFormField register itself with a Form?
FormField (base class of TextFormField, etc.) make a call within their build() method:
Form.of(context)?._register(this);
In English this means:
Search up my context hierarchy until we find a Form widget (if any) and call that form's register method on myself.
The ? is in case there is no Form widget parent. The _register call will only be run if there is a Form & FormState somewhere above.
How does Form.of(context)?._register(this) work?
Form & FormState sneakily use InheritedWidget.
In FormState.build() you'll see this code:
return WillPopScope(
onWillPop: widget.onWillPop,
child: _FormScope( // ← sneaky
formState: this,
generation: _generation,
child: widget.child,
),
);
Looking at _FormScope we see:
class _FormScope extends InheritedWidget
When a parent widget is an InheritedWidget, any child can find that parent using a special "find me a parent of this exact Type" method.
Here's how that "find me" method is used/exposed inside Form as a static method we can call from anywhere:
static FormState of(BuildContext context) {
final _FormScope scope = context.dependOnInheritedWidgetOfExactType<_FormScope>();
return scope?._formState;
}
The naming of that method dependOnInheritedWidgetOfExactType is a bit tricky. It'd be more readable as findInheritedWidgetOfExactType, but it does more than just find. (We can trigger rebuilds of Form through children that have registered as dependOn this FormState).
Summary
TextFormField is the deluxe package, air-conditioned, Bluetooth-connected 8-speaker stereo version of TextField. It includes a lot of common functionality you would use when accepting user-entered information.
I think this might be the most concise and simple explanation about the differences between the two.
From the material library:
TextField: A material design text field.
TextFormField: A FormField that contains a TextField.
Similarly, you can wrap FormField around any cupertino input component such as CupertinoTextField
Below is an example about a custom CheckboxFormField, which is a FormField that wraps around the material design component Checkbox:
// A custom CheckboxFormField, which is similar to the built-in TextFormField
bool agreedToTerms = false;
FormField(
initialValue: false,
validator: (value) {
if (value == false) {
return 'You must agree to the terms of service.';
}
return null;
},
builder: (FormFieldState formFieldState) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Checkbox(
value: agreedToTerms,
onChanged: (value) {
// When the value of the checkbox changes,
// update the FormFieldState so the form is
// re-validated.
formFieldState.didChange(value);
setState(() {
agreedToTerms = value;
});
},
),
Text(
'I agree to the terms of service.',
style: Theme.of(context).textTheme.subtitle1,
),
],
),
if (!formFieldState.isValid)
Text(
formFieldState.errorText ?? "",
style: Theme.of(context)
.textTheme
.caption
.copyWith(color: Theme.of(context).errorColor),
),
],
);
},
),
Rule of thumb: If your box only have a single input field, just use the raw material input like TextField (FormField is a bit overkill in this case though). If your box has many input fields, you need to wrap each one of them in a FormField, and then integrate all of them to the Form widget to reap the benefits of validating and saving all form fields at once.
Extra tip: If u have a TextField wrapped in a FormField that doesn't allow the user to enter any text such as a CupertinoPickerFormField or a SimpleDialogFormField that offers the user a choice between several options (which is basically a material SimpleDialog widget wrapped in a FormField), just simply use the hintText param of InputDecoration without using TextEditingController to manipulate text. Make the hint text have the same color as the normal input text with hintStyle: const TextStyle(color: Color(0xdd000000)).
This video from Flutter Europe will help you master forms in Flutter in no time.