Adding a widget to a listview with .contains - flutter

In the init state, I add an if statement to check to see if a custom widget exists on the list that I will pass to the Listview.builder, called an AddItemButtonTile. If it doesn't exist, I want to add it to the list.
#override
void initState() {
if (!displayedList.listContent.contains(AddItemButtonTile)) {
displayedList.listContent.add(AddItemButtonTile(openTextField));
}
super.initState();
}
This code results in a new AddItemButtonTile being added every time I navigate away and return to the page. Why is this If statement returning true?

Try:
#override
void initState() {
if (!displayedList.listContent.any((item) => item is AddItemButtonTile)) {
displayedList.listContent.add(AddItemButtonTile(openTextField));
}
super.initState();
}

Related

Flutter how to invoke initState() properly

Hi IM trying to load initial data from db and sharepref as user first open page.
...
List questionsList = [];
bool _languageA = false;
#override
void initState() {
super.initState();
loadData(); // seting for some dropdown menu
_getLanguageChoise(); //geting from sharepref bool value
_getData(arabic: _languageArabic).then((value) { //async db call load List ext...
setState(() {});
});
}
Problem is that "questionsList" and "_languageA" bool is not filed in initState , so I get null or initial value, only when I refresh state or reload
List get filed and var get value... So what I need to do in order to have initial filed variables before build method so user can see..
assign questionsList and _languageA values inside initState method just like this in order to initialize a field value when the widget is created
void initState() {
super.initState();
questionsList=["hello"];
_languageA=true;
}
Make your initstate like this
#override
void initState() {
super.initState();
questionsList=[""];
_languageA=true;
loadData(); // seting for some dropdown menu
_getLanguageChoise(); //geting from sharepref bool value
_getData(arabic: _languageArabic).then((value) { //async db call load List ext...
setState(() {});
});
}

Flutter: how to use .addListener of TextEditingController

i'm building a simple app that prints the result of the current TextFormField. Such as when the text changes it prints the new value.
I found out that you can achieve this with TextEditingController.addListener that listens for changes and executes a function.
So i wrapped it all in initState as follows
#override
void initState() {
addressController.addListener(() {
print(addressController.text);
});
The problem I have is that sometimes it records changes even when there aren't any:
This is what happens writing a word and then deleting it.
If you add listener then you should remove it somewhere, otherwise there can be situation when TextEditingController will have 2 or more listeners:
#override
void initState() {
addressController.addListener(_addressControllerListener);
super.initState()
}
void _addressControllerListener() {
print(addressController.text);
}
#override
void dispose() {
addressController.removeListener(_addressControllerListener);
super.dispose()
}

Initialize Flutter widget state with data

I have the following widget, which requires initializing with some data pulled from a DataClass class:
class FooWidgetState extends State<FooWidget> {
List<String> _someUsefulData;
#override
void initState() {
super.initState();
_someUsefulData = DataClass.getUsefulData(context);
}
#override
Widget build(BuildContext context) {
return Column(
children: _someUsefulData.map(_buildUsefulWidgets).toList(),
);
}
}
DataClass looks like this:
class DataClass {
static List<String> getUsefulData(BuildContext context) {
return [
BazLocalizations.of(context).usefulString1,
BazLocalizations.of(context).usefulString2,
];
}
}
and BazLocalizations is a class to retrieve localised strings.
The problem is that on running the above code, the following exception is thrown:
inheritFromWidgetOfExactType(_LocalizationsScope) or inheritFromElement() was called before FooWidgetState.initState() completed.
What I have tried:
Following the advice given here I wrapped the call in initState like this:
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_someUsefulData = DataClass.getUsefulData(context);
});
}
But then when I try to access _someUsefulData in the build widget, it is always null.
Since you are using the context to get to that data, you must get your data in the didChangeDependencies method, which gets call before the first build.
If you are using Provider you can check out this link: https://github.com/rrousselGit/provider#i-have-an-exception-when-obtaining-providers-inside-initstate-what-can-i-do
If you are not, the same concept applies to InheritedWidgets

Get InheritedWidget parameter in initState

i need some help understanding how to obtain data from inherited widget.
I usually get the parameter from my widget directly from the build method using
#override
Widget build(BuildContext context) {
//THIS METHOD
var data = StateContainer.of(context).data;
return Container(child:Text("${data.parameter}"));
}
But this method cant be called from initState since there is no buildContext yet.
I need in the initState method to have that parameter (i call my fetch from server in that and i need to pass that data to my function), so, how should i do it?
#override
void initState() {
otherData = fetchData(data);
super.initState();
}
I tried using didChangeDipendencies() but it is called every time the view is rebuilt (popping from screen, etc.) so it is not what i want to use and neither the FutureBuilder widget.
Any suggestion?
First, note that you probably do want to use didChangeDependencies. But you can't just do your call there without any check. You need to wrap it in an if first.
A typical didChangeDependencies implementation should look similar to:
Foo foo;
#override
void didChangeDependencies() {
super.didChangeDependencies();
final foo = Foo.of(context);
if (this.foo != foo) {
this.foo = foo;
foo.doSomething();
}
}
Using such code, doSomething will be executed only when foo changes.
Alternatively, if you are lazy and know for sure that your object will never ever change, there's another solution.
To obtain an InheritedWidget, the method typically used is:
BuildContext context;
InheritedWidget foo = context.inheritFromWidgetOfExactType(Foo);
and it is this method that cannot be called inside initState.
But there's another method that does the same thing:
BuildContext context;
InheritedWidget foo = context.ancestorInheritedElementForWidgetOfExactType(Foo)?.widget;
The twist is:
- this method can be called inside initState
- it won't handle the scenario where the value changed.
So if your value never changes, you can use that instead.
1, If you only need InheritedWidget as a Provider of parameter for Widget.
You can using on initState as bellow:
#override
void initState() {
super.initState();
var data = context.ancestorInheritedElementForWidgetOfExactType(type)?.widget;
}
2, If you need listener to re-render widget when data of InheritedWidget change. I suggest you wrapper your StatefulWidget insider a StatelessWidget,
parameter of StatefulWidget is passed from StatelessWidget, when InheritedWidget change data, it will notify to StatelessWidget, on StatefulWidget we will get change on didChangeDependencies and you can refresh data.
This is code guide:
class WrapperDemoWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
DemoData data = StateContainer.of(context).data;
return Container();
}
}
class ImplementWidget extends StatefulWidget {
DemoData data;
ImplementWidget({this.data});
#override
_ImplementWidgetState createState() => _ImplementWidgetState();
}
class _ImplementWidgetState extends State<ImplementWidget> {
#override
void initState() {
super.initState();
//TODO Do sth with widget.data
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
//TODO Do change with widget.data
}
#override
Widget build(BuildContext context) {
return Container();
}
}
I prefer the solution with didChangeDependencies because Future.delayed solution is a bit hack, looks unprofessional and unhealthy. However, it works out of the box.
This is the solution I prefer:
class _MyAppState extends State<MyApp> {
bool isDataLoaded = false;
#override
void didChangeDependencies() {
if (!isDataLoaded) {
otherData = fetchData(data).then((_){
this.isDataLoaded = true;
});
}
super.didChangeDependencies();
}
...
You can also get the context in initState, try using a future with duration zero. You can find some examples here
void initState() {
super.initState();
Future.delayed(Duration.zero,() {
//use context here
showDialog(context: context, builder: (context) => AlertDialog(
content: Column(
children: <Widget>[
Text('#todo')
],
),
actions: <Widget>[
FlatButton(onPressed: (){
Navigator.pop(context);
}, child: Text('OK')),
],
));
});
}
i use it to make loading screens using inherited widgets and avoid some global variables

initialize data once in initState and call the setState when data is ready causes exception

Since flutter calls the build method many times in different condition, to avoid getting the data many times, I initialize the data in initState.
I want to re-build the widget when the data is ready.
Here is my code :
class Test extends StatefulWidget {
#override
_TestState createState() => new _TestState();
}
class _TestState extends State<Test> {
Data data;
bool dataReady = false;
#override
void initState() {
super.initState();
getData(context).then((Data data) async {
setState(() {
dataReady= true;
});
});
}
#override
Widget build(BuildContext context) {
if (dataReady) {
return createMainContent(context);
} else {
return new Container();
}
}
}
However, it results in following exception :
inheritFromWidgetOfExactType(_InheritedProvider) or inheritFromElement() was called before _TestState.initState() completed.
May I know am I doing something wrong here?
When I add the following line to implementation of getData(context)
await Future.delayed(new Duration(milliseconds: 300));
the exception does not happen.
For everyone coming here at a later point
It is best to use the #override void didChangeDependencies () method of the State class.
From the docs
This method is also called immediately after initState. It is safe to call BuildContext.inheritFromWidgetOfExactType from this method.
But make sure to check if you have already performed your initialization
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (bloc == null) { // or else you end up creating multiple instances in this case.
bloc = BlocProvider<MyBloc>.of(context);
}
}
Edit: Better answer below.
Apparently, you cannot access getData(context) during initState (more concrete: before it completed).
The reason, so I believe, is that getData tries to look up an InheritedWidget ancestor up in the tree, but the tree is just now being built (your widget is created during the parent widget's build).
The obvious solution would be to delay getData's lookup to a later point in time. There are several ways to achieve that:
Delay the lookup to a later time. scheduleMicrotask should work fine.
Look it up during the first build call. You could have an isInitialized field set to false and in you build, something like:
if (!isInitialized) {
isInitialized = true;
// TODO: do the getData(...) stuff
}
an alternative is to put it inside PostFrameCallback which is between initState and Build.
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) => getData());
super.initState();
}
getData() async {
}
I moved my code to my build method from initState and it worked
class _TestState extends State<Test> {
Data data;
bool dataReady = false;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
getData(context).then((Data data) async {
setState(() {
dataReady= true;
});
});
if (dataReady) {
return createMainContent(context);
} else {
return new Container();
}
}
}