Related
Hello everybody working on a project and get this code from a repo and have some types errors and I cant understand them because I cant have the knowledge to solve and I didnt find nothing on google about these errors.
The problem is the #require this.#property and error as null value. I cant understand the problem, can explain me the problem?
Home Widget
class Home extends StatelessWidget {
const Home({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
AppColors.backgroundFadedColor,
AppColors.backgroundColor,
],
stops: [0.0, 1],
),
),
),
SafeArea(
child: _TodoListContent(
todos: fakeData,
),
),
const Align(
alignment: Alignment.bottomRight,
child: AddTodoButton(),
)
],
),
);
}
}
class _TodoListContent extends StatelessWidget {
const _TodoListContent({
Key? key,
#required this.todos,
}) : super(key: key);
final List<Todo> todos;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: todos.length,
padding: const EdgeInsets.all(16),
itemBuilder: (context, index) {
final _todo = todos[index];
return _TodoCard(todo: _todo);
},
);
}
}
class _TodoCard extends StatelessWidget {
const _TodoCard({
Key? key,
#required this.todo,
}) : super(key: key);
final Todo todo;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.of(context).push(
HeroDialogRoute(
builder: (context) => Center(
child: _TodoPopupCard(todo: todo),
),
),
);
},
child: Hero(
tag: todo.id,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Material(
color: AppColors.cardColor,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
_TodoTitle(title: todo.description),
const SizedBox(
height: 8,
),
if (todo.items.length != 0) ...[
const Divider(),
_TodoItemsBox(items: todo.items),
]
],
),
),
),
),
),
);
}
}
class _TodoTitle extends StatelessWidget {
const _TodoTitle({
Key? key,
#required this.title,
}) : super(key: key);
final String title;
#override
Widget build(BuildContext context) {
return Text(
title,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
);
}
}
class _TodoPopupCard extends StatelessWidget {
const _TodoPopupCard({Key key, this.todo}) : super(key: key);
final Todo todo;
#override
Widget build(BuildContext context) {
return Hero(
tag: todo.id,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Material(
borderRadius: BorderRadius.circular(16),
color: AppColors.cardColor,
child: SizedBox(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_TodoTitle(title: todo.description),
const SizedBox(
height: 8,
),
if (todo.items.length != 0) ...[
const Divider(),
_TodoItemsBox(items: todo.items),
],
Container(
margin: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.black12,
borderRadius: BorderRadius.circular(8),
),
child: const TextField(
maxLines: 8,
cursorColor: Colors.white,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(8),
hintText: 'Write a note...',
border: InputBorder.none),
),
),
],
),
),
),
),
),
),
);
}
}
class _TodoItemsBox extends StatelessWidget {
const _TodoItemsBox({
Key? key,
#required this.items,
}) : super(key: key);
final List<Item> items;
#override
Widget build(BuildContext context) {
return Column(
children: [
for (final item in items) _TodoItemTile(item: item),
],
);
}
}
class _TodoItemTile extends StatefulWidget {
const _TodoItemTile({
Key? key,
#required this.item,
}) : super(key: key);
final Item item;
#override
_TodoItemTileState createState() => _TodoItemTileState();
}
class _TodoItemTileState extends State<_TodoItemTile> {
void _onChanged(bool val) {
setState(() {
widget.item.completed = val;
});
}
#override
Widget build(BuildContext context) {
return ListTile(
leading: Checkbox(
onChanged: _onChanged,
value: widget.item.completed,
),
title: Text(widget.item.description),
);
}
}
On classes properties #required this.# error: The parameter '#' can't have a value of 'null' because of its type, but the implicit default value is 'null'. Try adding either an explicit non-'null' default value or the 'required' modifier.
Models file
import 'package:meta/meta.dart';
class Todo {
const Todo({
#required this.id,
#required this.description,
this.items,
});
final String id;
final String description;
final List<Item> items;
}
class Item {
Item({
#required this.id,
this.description = '',
this.completed = false,
});
final String id;
final String description;
bool completed;
}
On code
Todo
#required this.id,
#required this.description,
this.items,
and
Item
#required this.id,
error: The parameter '#' can't have a value of 'null' because of its type, but the implicit default value is 'null'. Try adding either an explicit non-'null' default value or the 'required' modifier.
TLDR: change #required => required
You are working with "null safety" enabled. This is a good thing, and helps avoid bugs by catching them at compile time.
In Todo, you have a field final String id;. With null safety enabled, null is not a valid value for a String (i.e. String id = null; is a compile time error).
This means that you can safely call methods on id without worrying about it being null, but it also means you must give it a value.
Consider:
final todo = Todo();
print(todo.id); // what happens here?
If the compiler allowed your code, this would print "null". But id is a String, not a String? (a nullable string), so this can't be allowed.
The main issue you are facing is the use of #required rather than just required. #required is a metadata annotation, that allows development tools (e.g. your IDE) to give helpful warnings.
On the other hand, required is a language keyword (when null safety is enabled). If you have a non-null named parameter, you must either:
mark it as required so it is a compile time error if you fail to provide it
give it a default value, so it will be non-null even if you don't provide it.
I have a Bottom Navigation in parent widget, and a few textfields in child widget. When user clicks on the navigation tab and if one of the textfields is empty, it will set focus on the particular textfields.
I am using the constructor method learnt from one of the developer however I couldn't get it work. It seems like I didn't pass over the context properly. I am not sure.
Anyone able to spot my mistakes or advise other methods which can achieve the same result?
login.dart
class Login extends StatefulWidget{
#override
State<StatefulWidget> createState() {
return _LoginState();
}
}
class _LoginState extends State<Login> {
FocusNode focusNode;
Page1 focus;
#override
void initState() {
super.initState();
focusNode = new FocusNode();
focus = new Page1(focusNode: focusNode);
}
int currentBottomNavIndex = 0;
List<Widget> bottomNav = [
Page1(),
Page2(),
];
onTapped(int index) {
//if(textfield not empty) {
//setState(() {
//currentBottomNavIndex = index;
//});
//}else {
focus.setFocus(context);
//}
}
#override
Widget build(BuildContext context){
return new Scaffold(
appBar: new AppBar(
title: Text('Login Page'),
),
body: bottomNav[currentBottomNavIndex],
bottomNavigationBar: BottomNavigationBar(
onTap: onTapped,
//onTap: requestFocus(context),
currentIndex: currentBottomNavIndex,
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text("Page1"),
),
BottomNavigationBarItem(
icon: Icon(Icons.mail),
title: Text('Page2'),
),
],
),
);
}
}
page1.dart
class Page1 extends StatefulWidget {
final FocusNode focusNode;
const Page1({Key key, this.focusNode}) : super(key: key);
void setFocus(BuildContext context) {
print("$focusNode requestFocus...");
FocusScope.of(context).requestFocus(focusNode);
}
#override
State<StatefulWidget> createState() {
return _Page1State();
}
}
class _Page1State extends State<Page1> {
TextEditingController name1 = TextEditingController();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: SingleChildScrollView(
child: Column(
children: <Widget>[
nameApp(),
],
)
)
);
}
Widget nameApp(){
return Container(
margin: EdgeInsets.all(50.0),
//width: 185,
child: Center(
child: Row(
children: [
Container(
child: Text("Name :", style: TextStyle(fontSize: 15), ),
),
Container(
child: Flexible(
child: TextField(
focusNode: widget.focusNode,
controller: name1,
onTap: (){
name1.clear();
},
onChanged: (String str){
},
textAlign: TextAlign.center,
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(vertical: 5),
hintText: "Full Name",
hintStyle: TextStyle(fontSize: 14),
),
),
),
),
]
)
)
);
}
}
When user click on the bottom tab, I expect to see the textfield is in focus however nothing happen.
I noticed the method in child widget has been called:
flutter: FocusNode#419f4 requestFocus...
flutter: FocusNode#419f4(FOCUSED) requestFocus...
however the textfield is still not focus.
I've create a simple sample project for this and its works for me just fine.
Please check out my solution:
The HomePage:
import 'package:flutter/material.dart';
import 'package:focus_node/widgets/MyInputWidget.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
FocusNode field1FocusNode = FocusNode(); //Create first FocusNode
FocusNode field2FocusNode = FocusNode(); //Create second FocusNode
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 35),
child: MyInputWidget(
focusNode: field1FocusNode, //Provide the first FocusNode in the constructor
hint: "Email",
onEditCompleted: (){
FocusScope.of(context).requestFocus(field2FocusNode); //Request focus
},
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 35),
child: MyInputWidget(
focusNode: field2FocusNode, //Provide the second FocusNode
hint: "Password",
onEditCompleted: (){
FocusScope.of(context).requestFocus(field1FocusNode); //Request focus
},
),
)
],
),
),
);
}
}
The Custom Widget required focus:
class MyInputWidget extends StatefulWidget {
final FocusNode focusNode;
final String hint;
final VoidCallback onEditCompleted;
MyInputWidget({this.focusNode, this.hint, this.onEditCompleted});
#override
_MyInputWidgetState createState() => _MyInputWidgetState();
}
class _MyInputWidgetState extends State<MyInputWidget> {
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(8),
child: TextField(
focusNode: widget.focusNode, //The FocusNode provided by the parent widget
decoration: InputDecoration(
hintText: widget.hint
),
onEditingComplete: widget.onEditCompleted,
),
);
}
}
Hope this helps.
I want to get value key which contains in Widget build(), but it says 'Undefined'. Also, I need to get this value to another class. How can I do it?
I`ve tried to just take this value, but it says undefined error
String newValue = s; // It says Undefined
I also tried to get this value to another class but this method gives more errors :c
myCard c = myCard();
String classValue = c.s; // It says 'Only static members can be accessed in initializers' and 'The getter 's' isn`t defined for the class 'myCard' '
Here`s part of main.dart file
class MyCard extends StatefulWidget {
#override
myCard createState() => myCard();
}
class myCard extends State<MyCard> {
int myCount = count - 1;
void click() {
setState(() {
print(titlecard);
Navigator.push(context, MaterialPageRoute(
builder: (context) => setNewText())
);
});
}
#override
Widget build(BuildContext context) {
Key s = Key(myCount.toString()); // I want to get this value
return Center(
key: s,
child: Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: Icon(Icons.album),
title: Text(titlecard[int.parse(s)]),
subtitle: Text(textcard),
),
ButtonTheme.bar( // make buttons use the appropriate styles for cards
child: ButtonBar(
children: <Widget>[
FlatButton(
child: const Text('Change Text'),
onPressed: click,
),
FlatButton(
child: const Text('LISTEN'),
onPressed: () {
print(s);
},
),
],
),
),
],
),
),
);
}
}
class setNewText extends StatefulWidget {
#override
SetNewText createState() => SetNewText();
}
class SetNewText extends State<setNewText> {
myCard c = myCard();
HomePageState s = HomePageState();
String v = c.s; // To here
final titleController = TextEditingController();
final textController = TextEditingController();
final formkey = GlobalKey<FormState>();
List<String> titles = [''];
void _submit() {
setState(() {
if (formkey.currentState.validate()) {
formkey.currentState.save();
Navigator.pop(context);
titlecard.removeAt(count-s.myCount);
titlecard.insert(count-s.myCount, titleController.text);
textcard = textController.text;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Change Title'),
),
body: Column(
children: <Widget>[
Card(
child: Padding(
padding: EdgeInsets.all(2.0),
child: Form(
key: formkey,
child: Column(
children: <Widget>[
TextFormField(
controller: titleController,
decoration: InputDecoration(
labelText: 'Title'
),
validator: (value) => value.length < 1 ? 'Invalid Title' : null,
onSaved: (value) => value = titleController.text,
),
TextFormField(
controller: textController,
decoration: InputDecoration(
labelText: 'Text'
),
validator: (text) => text.length < 1 ? 'Invalid Text' : null,
onSaved: (text) => text = textController.text,
)
],
),
),
),
),
FlatButton(
textColor: Colors.deepPurple,
child: Text('SUBMIT'),
onPressed: _submit,
),
],
)
);
}
}
Since you're just listening for values, one way of doing this is by listening to the value using Streams. You can initialize the class where the value can be Streamed and access it anywhere. Do note that once the Stream has been closed, you can only create a new Stream.
Here's a sample.
import 'dart:async';
import 'package:flutter/material.dart';
class SampleStream extends StatefulWidget {
const SampleStream({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<SampleStream> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<SampleStream> {
Counter counter = Counter();
#override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: counter.showCount,
builder: (context, AsyncSnapshot<int> snapshot) {
int count = snapshot.hasData ? snapshot.data! : 0;
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Text(
'You clicked the button $count times'),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.plus_one),
onPressed: () {
counter.setCount(++count);
},
),
);
});
}
#override
void dispose() {
super.dispose();
counter.disposeCount();
}
}
class Counter {
final _count = StreamController<int>.broadcast();
Stream<int> get showCount => _count.stream;
setCount(int count) {
debugPrint('Stream sink: $count');
_count.sink.add(count);
}
disposeCount() {
_count.close();
}
}
In this demo, Counter was initialized in _MyHomePageState and can only be accessed in the same class. Calling counter.setCount(int) updates the stream and the stream value can be listened to using StreamBuilder to fetch the snapshot.
I'm making Notes app. I made cards with text and buttons dynamically (Create by clicking the button). But I have problem with Changing Text on CURRENT card. For example, I have 3 cards with own texts and buttons and I want to change text on 2nd card but text is changing on the 3rd card. How can I solve this problem?
3 cards with texts and buttons
Change Text Page
In the past, I've tried making list to collect texts, but i dont know how to identify current card.
full main.dart
import 'package:flutter/material.dart';
import './changeTextPage.dart';
int count = 0;
String titlecard = '';
String textcard = '';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Notes',
theme: ThemeData(
primarySwatch: Colors.deepPurple
),
home: HomePage(title: 'Notes',),
);
}
}
class HomePage extends StatefulWidget {
HomePage({Key key, this.title}) : super(key: key);
final title;
#override
HomePageState createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
List<Widget> cards = new List.generate(count, (int i) => new MyCard());
return Scaffold(
appBar: AppBar(
title: Text('Notes'),
),
body: LayoutBuilder(
builder: (context, constraint) {
return Column(
children: <Widget>[
Container(
height: 650.0,
child: new ListView(
children: cards,
scrollDirection: Axis.vertical,
),
),
],
);
}
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
setState(() {
Navigator.push(context, MaterialPageRoute(
builder: (context) => changeText())
);
});
},
),
);
}
}
class MyCard extends StatefulWidget {
#override
myCard createState() => myCard();
}
class myCard extends State<MyCard> {
int myCount = count;
void click() {
setState(() {
Navigator.push(context, MaterialPageRoute(
builder: (context) => setNewText())
);
});
}
#override
Widget build(BuildContext context) {
return Center(
child: Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: Icon(Icons.album),
title: Text(titlecard),
subtitle: Text(textcard),
),
ButtonTheme.bar( // make buttons use the appropriate styles for cards
child: ButtonBar(
children: <Widget>[
FlatButton(
child: const Text('Change Text'),
onPressed: click,
),
FlatButton(
child: const Text('LISTEN'),
onPressed: () { /* ... */ },
),
],
),
),
],
),
),
);
}
}
class setNewText extends StatefulWidget {
#override
SetNewText createState() => SetNewText();
}
class SetNewText extends State<setNewText> {
final titleController = TextEditingController();
final textController = TextEditingController();
final formkey = GlobalKey<FormState>();
void _submit() {
setState(() {
if (formkey.currentState.validate()) {
formkey.currentState.save();
Navigator.pop(context);
titlecard = titleController.text;
textcard = textController.text;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Change Title'),
),
body: Column(
children: <Widget>[
Card(
child: Padding(
padding: EdgeInsets.all(2.0),
child: Form(
key: formkey,
child: Column(
children: <Widget>[
TextFormField(
controller: titleController,
decoration: InputDecoration(
labelText: 'Title'
),
validator: (value) => value.length < 1 ? 'Invalid Title' : null,
onSaved: (value) => value = titleController.text,
),
TextFormField(
controller: textController,
decoration: InputDecoration(
labelText: 'Text'
),
validator: (text) => text.length < 1 ? 'Invalid Text' : null,
onSaved: (text) => text = textController.text,
)
],
),
),
),
),
FlatButton(
textColor: Colors.deepPurple,
child: Text('SUBMIT'),
onPressed: _submit,
),
],
)
);
}
}
changeTextPage.dart
import 'package:flutter/material.dart';
import './main.dart';
class changeText extends StatefulWidget {
#override
ChangeText createState() => ChangeText();
}
class ChangeText extends State<changeText> {
myCard s = myCard();
final titleController = TextEditingController();
final textController = TextEditingController();
final formkey = GlobalKey<FormState>();
void _submit() {
setState(() {
if (formkey.currentState.validate()) {
formkey.currentState.save();
Navigator.pop(context);
count++;
titlecard = titleController.text;
textcard = textController.text;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Change Title'),
),
body: Column(
children: <Widget>[
Card(
child: Padding(
padding: EdgeInsets.all(2.0),
child: Form(
key: formkey,
child: Column(
children: <Widget>[
TextFormField(
controller: titleController,
decoration: InputDecoration(
labelText: 'Title'
),
validator: (value) => value.length < 1 ? 'Invalid Title' : null,
onSaved: (value) => value = titleController.text,
),
TextFormField(
controller: textController,
decoration: InputDecoration(
labelText: 'Text'
),
validator: (text) => text.length < 1 ? 'Invalid Text' : null,
onSaved: (text) => text = textController.text,
)
],
),
),
),
),
FlatButton(
textColor: Colors.deepPurple,
child: Text('SUBMIT'),
onPressed: _submit,
),
],
)
);
}
}
Okay, so you happen to make some common mistakes, one of which is critical.
most importantly don't use global variables! As you do with count, titlecard and textcard.
there is a practice to name stateful widgets with PascalCase and corresponding states just like the widget but prefixed with an underscore (_) to make it private and suffixed by the State word.
The correct approach for this (or one of them) would be to have a widget that would be your screen with a form to edit stuff and it would pop some struct with user values on submit:
class ChangeTextScreen extends StatefulWidget {
_ChangeTextScreenState createState() => _ChangeTextScreenState();
}
class _ChangeTextScreenState extends State<ChangeTextScreen> {
void _submit() {
setState(() {
formkey.currentState.save();
Navigator.pop(ChangeTextResult(title: titleController.text, text: textController.text));
});
}
// Rest of your code...
}
class ChangeTextResult {
final String title;
final String text;
ChangeTextResult({#required this.title, #required this.text});
}
You should also have a place where you store your notes in some kind of a list. Your main screen looks like a good place for it. Once your app will be bigger, think about using scoped_model or Redux or something.
So let's add a Note class and a list with your notes to your main screen:
class Note {
String title;
String text;
Note(this.title, this.text);
}
class HomePageState extends State<HomePage> {
List<Note> _notes = [Note('Test', 'Some test note')];
#override
Widget build(BuildContext context) {
ListView cards = ListView.builder(
itemCount: _notes.length,
itemBuilder: (context, index) => MyCard(
title: _notes[index].title,
text: _notes[index].text,
onEdit: (title, text) => setState(() { // We'll get back to that later
_notes[index].title = title;
_notes[index].text = text;
})
));
// (...)
Your MyCard widget (try to use better names next time) should contain some kind of information about its content, one of the best approaches would be to pass this info to its constructor, just like that:
class MyCard extends StatefulWidget {
final String title;
final String text;
final Function onEdit;
MyCard({Key key, #required this.title, #required this.text, #required this.onEdit}) : super(key: key);
#override
_MyCardState createState() => _MyCardState();
}
Having this Key parameter is a good practice.
And use those parameters in your _MyCardState class (I renamed it from _myCard):
// (...)
children: <Widget>[
ListTile(
leading: Icon(Icons.album),
title: Text(widget.title),
subtitle: Text(widget.text),
),
// (...)
Returning to the moment where you open your ChangeTextScreen, you should assign the result of Navigation.push() to a variable. This is your result, you can deal with it (once we check it for null, the user could have returned from this screen and then the result would be null).
void click() {
setState(() {
final ChangeTextResult result = Navigator.push(context, MaterialPageRoute(
builder: (context) => ChangeTextScreen())
);
if (result != null) {
widget.onEdit(result.title, result.text);
}
});
}
Do you remember that onEdit parameter (I mentioned it in a comment in the code above)? We call that parameter here.
That's it I think. I could have mixed some concepts of your app, but I think you'll manage to get my point anyways.
I quite rewrote all of your code. I think it will be easier for you to start again from scratch and have those tips in mind. Also, try to Google some similar things (like a simple Todo application) or do Getting started from flutter.io with part two! That should give you a nice idea on how to resolve that common problem in Flutter.
And also, read about good practises in Flutter and Dart. Things like correctly formatting your code are really important.
BTW that's my longest answer on Stack Overflow so far. I hope you'll appreciate that.
I'm trying to create an application to display the results of a search. I have the following extremely simple StatefulWidget. It has a single state variable data, which is passed in the constructor.
class Entry extends StatefulWidget {
Entry({Key key, #required this.data}) : super(key: key);
final String data;
#override
_EntryState createState() => _EntryState(data);
}
class _EntryState extends State<Entry> {
_EntryState(this.data) : super();
final String data;
#override
Widget build(BuildContext context) {
return Text(data);
}
}
To generate datasets of random length (between 0-9) I use the following helper function, which basically keeps track of the DateTime and the string that is passed to it:
List<String> render(String searchTerm) {
final Random rng = new Random();
final String date = DateTime.now().toString();
return List<String>.generate(
rng.nextInt(10),
(int i) => '$searchTerm $i $date',
);
}
Lastly, my main widget is as such. It uses a state variable _items to keep track of a list of Entrys. One can do a new query through a search bar, which could return a dataset of random length. The list is then displayed through a ListView.
class MyWidget extends StatefulWidget {
MyWidget({Key key}) : super(key: key);
final String title = "Demo";
#override
_State createState() => _State();
}
class _State extends State<MyWidget> {
List<String> _items = [];
Future<Null> getData(String searchTerm) async {
final entriesToAdd = render(searchTerm);
setState(() {
_items = entriesToAdd;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TextField(
onSubmitted: getData,
decoration:
InputDecoration(hintText: 'Search', icon: Icon(Icons.search)),
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) {
return Container(
padding: EdgeInsets.all(8.0), child: Entry(data: _items[index]));
})),
],
),
));
}
}
So my issue is that I expect that when I do a search through the button (i.e. call getData), the list displayed will be updated with the results of the new query, completely overwriting the old ones. This is not what happens. The displayed list is the correct length, but the items are often a mix of various previous/current searches like the following screenshot, which shows a mix of 2 searches. However, when doing print statements in the itemBuilder function of the ListView, the _item variable seems to be updating correctly, and regardless there shouldn't really be a mix of 2 different search results. What's causing this behavior?
Here's the full code:
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() => runApp(App());
class App extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo',
theme: ThemeData(
primarySwatch: Colors.green,
),
initialRoute: '/',
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => MyWidget(),
},
);
}
}
class MyWidget extends StatefulWidget {
MyWidget({Key key}) : super(key: key);
final String title = "Demo";
#override
_State createState() => _State();
}
class _State extends State<MyWidget> {
List<String> _items = [];
Future<Null> getData(String searchTerm) async {
final entriesToAdd = render(searchTerm);
setState(() {
_items = entriesToAdd;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TextField(
onSubmitted: getData,
decoration:
InputDecoration(hintText: 'Search', icon: Icon(Icons.search)),
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) {
return Container(
padding: EdgeInsets.all(8.0), child: Entry(data: _items[index]));
})),
],
),
));
}
}
class Entry extends StatefulWidget {
Entry({Key key, #required this.data}) : super(key: key);
final String data;
#override
_EntryState createState() => _EntryState(data);
}
class _EntryState extends State<Entry> {
_EntryState(this.data) : super();
final String data;
#override
Widget build(BuildContext context) {
return Text(data);
}
}
List<String> render(String searchTerm) {
final Random rng = new Random();
final String date = DateTime.now().toString();
return List<String>.generate(
rng.nextInt(10),
(int i) => '$searchTerm $i $date',
);
}
Don't cache Widgets! You should be building them fresh on each call to build. (If you are worried about this being inefficient, don't. This is how Flutter is designed and heavily optimized to support this.) Just keep the list of Strings and re-build the Texts each time.
Avoid classes with just static functions. These can be replaced with top-level functions.
Try this instead:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo',
theme: ThemeData(
primarySwatch: Colors.green,
),
initialRoute: '/',
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => const MyWidget(),
},
);
}
}
class MyWidget extends StatefulWidget {
const MyWidget({Key key}) : super(key: key);
#override
MyWidgetState createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
List<String> _items = <String>[];
void getData(String searchTerm) {
final List<String> newEntries = helper(searchTerm);
setState(() {
_items = newEntries;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TextField(
onSubmitted: getData,
decoration: const InputDecoration(
hintText: 'Search',
icon: Icon(Icons.search),
),
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: ListView.builder(
itemCount: _items.length,
itemBuilder: (BuildContext context, int index) => Container(
padding: const EdgeInsets.all(8.0),
child: Text(_items[index]),
),
),
),
],
),
),
);
}
}
List<String> helper(String searchTerm) {
final Random rng = new Random();
final String date = DateTime.now().toString();
return List<String>.generate(
rng.nextInt(10),
(int i) => '$searchTerm $i $date',
);
}
Here's a version that illustrates using StatefulWidget:
class MyWidget extends StatefulWidget {
const MyWidget({Key key}) : super(key: key);
#override
MyWidgetState createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
List<String> _items = <String>[];
void getData(String searchTerm) {
final List<String> newEntries = helper(searchTerm);
setState(() {
_items = newEntries;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TextField(
onSubmitted: getData,
decoration: const InputDecoration(
hintText: 'Search',
icon: Icon(Icons.search),
),
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: ListView.builder(
itemCount: _items.length,
itemBuilder: (BuildContext context, int index) => Container(
padding: const EdgeInsets.all(8.0),
child: Entry(data: _items[index]),
),
),
),
],
),
),
);
}
}
class Entry extends StatefulWidget {
const Entry({Key key, #required this.data}) : super(key: key);
final String data;
#override
_EntryState createState() => _EntryState();
}
class _EntryState extends State<Entry> {
bool bold = true;
#override
Widget build(BuildContext context) => GestureDetector(
onTap: () {
setState(() {
bold = !bold;
});
},
child: Text(
widget.data,
style: TextStyle(
fontWeight: bold ? FontWeight.bold : FontWeight.normal,
),
),
);
}