How to Set/Update State of StatefulWidget from other StatefulWidget in Flutter? - state

For Example in the below code plus button works and able to update
the text but the minus button does not.
But if we press FloatingActionButton then the State is refreshed .
The minus button is changing the value of the variable but not
updating the state of parent widget .
here is code .....
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
int number;
EdgeInsets globalMargin = const EdgeInsets.symmetric(horizontal: 20.0, vertical: 20.0);
TextStyle textStyle = const TextStyle(
fontSize: 100.0,
color: Colors.black,
);
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
super.initState();
number = number ?? 0;
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Column(
children: <Widget>[
new Text(
number.toString(),
style: textStyle,
),
new GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: <Widget>[
new InkResponse(
child: new Container(
margin: globalMargin,
color: Colors.green,
child: new Center(
child: new Text(
"+",
style: textStyle,
),
)),
onTap: () {
setState(() {
number = number + 1;
});
},
),
new Sub(),
],
),
],
),
floatingActionButton: new FloatingActionButton(
onPressed: () {
setState(() {});
},
child: new Icon(Icons.update),
),
);
}
}
class Sub extends StatefulWidget {
#override
_SubState createState() => new _SubState();
}
class _SubState extends State<Sub> {
#override
Widget build(BuildContext context) {
return new InkResponse(
child: new Container(
margin: globalMargin,
color: Colors.red,
child: new Center(
child: new Text(
"-",
style: textStyle,
),
)),
onTap: () {
setState(() {
number = number - 1;
});
},
);
}
}

1.On Child Widget : add parameter Function paramter
class ChildWidget extends StatefulWidget {
final Function() notifyParent;
ChildWidget({Key key, #required this.notifyParent}) : super(key: key);
}
2.On Parent Widget : create a Function for the child to callback
refresh() {
setState(() {});
}
3.On Parent Widget : pass parentFunction to Child Widget
new ChildWidget( notifyParent: refresh );
4.On Child Widget : call the Parent Function
widget.notifyParent();

Screenshot (Parent to child, Child to parent):
This examples shows calling a method
Defined in Child widget from Parent widget.
Defined in Parent widget from Child widget.
Code:
class ParentPage extends StatelessWidget {
final GlobalKey<ChildPageState> _key = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Parent")),
body: Center(
child: Column(
children: <Widget>[
Expanded(
child: Container(
color: Colors.grey,
width: double.infinity,
alignment: Alignment.center,
child: ElevatedButton(
child: Text("Call method in child"),
onPressed: () => _key.currentState!.methodInChild(), // calls method in child
),
),
),
Text("Above = Parent\nBelow = Child"),
Expanded(
child: ChildPage(
key: _key,
function: methodInParent,
),
),
],
),
),
);
}
methodInParent() => Fluttertoast.showToast(msg: "Method called in parent", gravity: ToastGravity.CENTER);
}
class ChildPage extends StatefulWidget {
final VoidCallback function;
ChildPage({Key? key, required this.function}) : super(key: key);
#override
ChildPageState createState() => ChildPageState();
}
class ChildPageState extends State<ChildPage> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.teal,
width: double.infinity,
alignment: Alignment.center,
child: ElevatedButton(
child: Text("Call method in parent"),
onPressed: () => widget.function(), // calls method in parent
),
);
}
methodInChild() => Fluttertoast.showToast(msg: "Method called in child");
}

Old / Not recommended / Failed solutions:
Solution 1: Create a global instance of _MyHomePageState. Use this instance in _SubState as _myHomePageState.setState
Solution 2: No need to create a global instance. Instead, just pass the parent instance to the child widget
Solution 3: Passing a callback from parent widget to child widget to update state of parent widget from child widget
Best Solution: use the package stream_mixin
As of Feb '23
class Counter with StreamMixin<int> { // CODE TO NOTICE
Counter._();
static Counter instance = Counter._();
increment() {
update((lastUpdate ?? 0) + 1);
}
decrement() {
update((lastUpdate ?? 0) - 1);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('SO Help'),
),
body: Column(
children: <Widget>[
StreamBuilder<int>( // CODE TO NOTICE
initialData: 0,
stream: Counter.instance.onChange,
builder: (context, snapshot) {
return Text(snapshot.data.toString());
},
),
GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: <Widget>[
InkResponse(
onTap: Counter.instance.increment, // CODE TO NOTICE
child: const Text("+"),
),
const Sub(),
],
),
],
),
);
}
}
class Sub extends StatelessWidget {
const Sub({super.key});
#override
Widget build(BuildContext context) {
return InkResponse(
onTap: Counter.instance.decrement, // CODE TO NOTICE
child: const Text("-"),
);
}
}
Observe that:
I'm using StatelessWidget, which will increase the performance. (You may choose to use StatefulWidget depending on your requirements
Instead of StatefulWidget I'm using StreamBuilder, which will update only the Text widget inside the SteamBuilder. Unlike setState() updates the whole app
The logic is separated from UI

I would like to extend Mohamed Elrashid answer, in case you require to pass a variable from the child widget to the parent widget
On child widget:
class ChildWidget extends StatefulWidget {
final Function() notifyParent;
ChildWidget({Key key, #required this.notifyParent}) : super(key: key);
}
On parent widget
void refresh(dynamic childValue) {
setState(() {
_parentVariable = childValue;
});
}
On parent widget: pass the function above to the child widget
new ChildWidget( notifyParent: refresh );
On child widget: call the parent function with any variable from the the child widget
widget.notifyParent(childVariable);

Old one but I would add my answer as per my findings:
var ancestralState = context.findAncestorStateOfType<ParentState>();
ancestralState.setState(() {
// here you can access public vars and update state.
...
});

class HomePage extends StatefulWidget {
#override
HomePageState createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
int selectedIndex = 0;
void setSelectedIndex(int index){
setState(() {
selectedIndex = index;
});
}
}
class TestPage extends StatefulWidget {
#override
TestPageState createState() => TestPageState();
}
class TestPageState extends State<TestPage> {
int selectedIndex = 0;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (){
final HomePageState state = context.findAncestorStateOfType<HomePageState>();
state.setSelectedIndex(4);
},
child: Container(
width: 100,
height: 100,
color: Colors.green
)
);
}
}

Although most of these previous answers will work, I suggest you explore the provider or BloC architectures, both of which have been recommended by Google.
In short, the latter will create a stream that reports to widgets in the widget tree whenever a change in the state happens and it updates all relevant views regardless of where it is updated from.
Here is a good overview you can read to learn more about the subject: https://bloclibrary.dev/#/

Here is the solution which worked for me.
OUTPUT:
State of Cart Widget is updated, upon addition of items.
Create a globalKey for the widget you want to update by calling the trigger from anywhere
final GlobalKey<CartWidgetState> cartKey = GlobalKey();
Make sure it's saved in a file have global access such that, it can be accessed from anywhere.
I save it in globalClass where is save commonly used variables through the app's state.
class CartWidget extends StatefulWidget {
CartWidget({Key key}) : super(key: key);
#override
CartWidgetState createState() => CartWidgetState();
}
class CartWidgetState extends State<CartWidget> {
#override
Widget build(BuildContext context) {
//return your widget
return Container();
}
}
Call your widget from some other class.
class HomeScreen extends StatefulWidget {
HomeScreen ({Key key}) : super(key: key);
#override
HomeScreenState createState() => HomeScreen State();
}
class HomeScreen State extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return ListView(
children:[
ChildScreen(),
CartWidget(key:cartKey)
]
);
}
}
class ChildScreen extends StatefulWidget {
ChildScreen ({Key key}) : super(key: key);
#override
ChildScreenState createState() => ChildScreen State();
}
class ChildScreen State extends State<ChildScreen> {
#override
Widget build(BuildContext context) {
return InkWell(
onTap: (){
// This will update the state of your inherited widget/ class
if (cartKey.currentState != null)
cartKey.currentState.setState(() {});
},
child: Text("Update The State of external Widget"),
);
}
}

For those who are wanting to pass a variable back and forth between child and parent without the use of third-party libraries, here is an example I wrote with two counter buttons. Granted, I don't know if this is best practice, but I wanted to provide the simplest answer I could create:
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: ParentWid(),
),
),
);
}
}
class ParentWid extends StatefulWidget {
#override
State<ParentWid> createState() => _ParentWidState();
}
class _ParentWidState extends State<ParentWid> {
int counter = 0;
void refresh(int childValue) {
setState(() {
counter = childValue;
});
}
#override
Widget build(BuildContext context) {
return Column(children:[
IconButton(
onPressed: () {
setState(() {
counter++;
});
},
icon: const Icon(Icons.add),
),
ChildWid(counter, refresh)
]);
}
}
class ChildWid extends StatefulWidget {
final Function(int) notifyParent;
final int? counter;
const ChildWid(this.counter, this.notifyParent);
#override
State<ChildWid> createState() => _ChildWidState();
}
class _ChildWidState extends State<ChildWid> {
#override
Widget build(BuildContext context) {
return Column(children:[
Text(
'Current Counter ${widget.counter ?? 0}',
style: Theme.of(context).textTheme.headline4,
),
IconButton(
onPressed: () {
int counterHolder = widget.counter ?? 0;
counterHolder++;
widget.notifyParent(counterHolder);
},
icon: const Icon(Icons.add),
),
]);
}
}

This is a little unorthodox, but it works: you keep state references of unrelated widgets in a common object and call them accordingly:
class Fuzz {
State<A>? a;
State<B>? b;
int c = 0;
}
class A extends StatefulWidget {
A(this.fuzz, {Key? key}) : super(key: key);
Fuzz fuzz;
#override
State<A> createState() => _AState();
}
class _AState extends State<A> {
#override
void initState() {
super.initState();
widget.fuzz.a = this;
}
#override
Widget build(BuildContext context) {
return Center(
child: TextButton(
child: Text("More fuzz (${widget.fuzz.c})"),
onPressed: () {
widget.fuzz.b?.setState(() {
widget.fuzz.c++;
});
},
));
}
}
class B extends StatefulWidget {
B(this.fuzz, {Key? key}) : super(key: key);
Fuzz fuzz;
#override
State<B> createState() => _BState();
}
class _BState extends State<B> {
#override
void initState() {
super.initState();
widget.fuzz.b = this;
}
#override
Widget build(BuildContext context) {
return Center(
child: TextButton(
child: Text("Less fuzz (${widget.fuzz.c})"),
onPressed: () {
widget.fuzz.a?.setState(() {
widget.fuzz.c--;
});
},
));
}
}
class TestView extends StatelessWidget {
TestView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
var fuzz = Fuzz();
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: Row(
children: [Expanded(child: A(fuzz)), Expanded(child: B(fuzz))],
)));
}
}
Result:

Related

Flutter: How to Display Timer-Updating Widget List

The goal of this section of my app is to display a brand new historyTile() every minute, with each tile having it's own respective time and date to when it was saved to the historyList.
I am not sure exactly how this sort of function might work, but this is what I have so far. It is divided into two dart files.
activityTab.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:indietools/pages/tabs/functions/historyTile.dart';
import 'package:intl/intl.dart';
class activityTab extends StatefulWidget {
const activityTab({Key? key}) : super(key: key);
#override
State<activityTab> createState() => _activityTabState();
}
class _activityTabState extends State<activityTab> {
List<Widget> historyList = [];
#override
void initState() {
super.initState();
Timer.periodic(Duration(minutes: 1), (Timer t) => historyList.add(historyTile())
);
}
#override
Widget build(BuildContext context) {
MediaQuery.of(context).size;
return Scaffold(
body: ListView(
children: [historyList]
)
);
}
}
historyTile.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class historyTile extends StatefulWidget {
const historyTile({Key? key}) : super(key: key);
#override
State<historyTile> createState() => _historyTileState();
}
class _historyTileState extends State<historyTile> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: 1,
shrinkWrap: true,
itemBuilder: (context, index) {
final now = DateTime.now();
String tileTime = DateFormat.yMMMMd().add_jm().format(now);
return ListTile(
leading: Icon(Icons.backup_outlined),
title: Text('Synced my_script.pdf with the cloud.'),
subtitle: Text('${tileTime}'),
tileColor: Colors.greenAccent,
);
}
)
);
}
}
Thank you for your time and advice.
Just copy and paste it onto DartPad to play with it.
import 'package:flutter/material.dart';
import 'dart:async';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late Timer _timer;
List<Widget> history = [];
int _counter = 0;
void _updateHistory(Timer timer) {
setState(() {
_counter++;
history.add(ListTile(title: Text(_counter.toString())));
});
}
#override
void initState() {
super.initState();
_timer = Timer.periodic(const Duration(seconds: 1), _updateHistory);
}
#override
void dispose() {
super.dispose();
_timer.cancel();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SingleChildScrollView(
child: Column(
children: history,
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
_counter = 0;
history.clear();
});
},
tooltip: 'Clear all',
child: const Icon(Icons.clear_all),
),
);
}
}

flutter listview overwriting the last item

I want to insert a person's name to the textField in the FirstClass then show it in the SecondClass inside a ListView.builder. As I go back and insert a new person's name the PersonAdd class work just fine, but it fills the whole list with the last inserted name. What do I miss?
This is my code;
Person class
class Person{
late String name;
void setName(String name){
this.name=name;
}
}
PersonAdd class
class PersonAdd{
List<Person> list=[];
void addList(Person person){
list.add(person);
}
List<Person> getList(){
return list;
}
}
Main and FirstClass
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: FirstClass(),
);
}
}
PersonAdd personAdd = PersonAdd();
class FirstClass extends StatefulWidget {
#override
State<FirstClass> createState() => _FirstClassState();
}
class _FirstClassState extends State<FirstClass> {
Person person= Person();
void setData(String? input){
setState(() {
person.name=input!;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
onChanged: (input) {
setData(input);
},
style: const TextStyle(fontSize: 40,),
),
const SizedBox(height: 15,),
TextButton(
style: TextButton.styleFrom(
backgroundColor: Colors.blue
),
onPressed: (){
setState(() {
personAdd.addList(person);
});
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondClass(persons:personAdd)),
);
}, child: const Text('SEND', style: TextStyle(fontSize: 40,color: Colors.black38),))
],
),
),
);
}
}
SecondClass
class SecondClass extends StatelessWidget {
PersonAdd persons;
SecondClass({required this.persons});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: persons.getList().length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
color: Colors.amber,
child: Center(child: Text(personAdd.getList()[index].name)),
);
}
),
),
);
}
}
I inserted Name1, Name2, Name3, and Name4 in order. However, the result is like the following
because all items on your list have the same address.
Let try this update:
setState(() {
personAdd.addList(person);
person= Person();
});
on this code we will create new person after add it to list.

Setstate is not working inside list of widgets flutter

My question is my
Raisedbutton of the list
Is not changing position ontap
While staying in the list of widgets
Please help me out
And tell me what's wrong in that code
class Sticker extends
StatefulWidget {
Sticker({Key key}) :
super(key: key);
#override
_StickerState
createState() => _
StickerState();
}
class _StickerState extends
State<Sticker> {
Offset position =
Offset(50, 100);
List<Widget> list = [];
#override
Widget build(BuildContext
context) {
return Container(
child: Stack(
children: [
Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
),
Positioned(
left: 50,
top: 50,
child:
RaisedButton(
onPressed: () {
setState(
() {
list.add(
Positioned(
left: position.dx,
top: position.dy,
child: RaisedButton(
onPressed: () {
setState(() => position = Offset(50, 150));
},
),
),
);
},
);
},
),
),
...list
],
),
);
}
}
I want that my raised change position after I tap on that while staying in the list
Please help me out dealing this problem console is giving no errors
You can copy paste run full code below
You can use StatefulBuilder
list.add(StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Positioned(
working demo
full code
import 'package:flutter/material.dart';
class Sticker extends StatefulWidget {
Sticker({Key key}) : super(key: key);
#override
_StickerState createState() => _StickerState();
}
class _StickerState extends State<Sticker> {
Offset position = Offset(50, 100);
List<Widget> list = [];
#override
Widget build(BuildContext context) {
print("build list.length ${list.length}");
return Container(
child: Stack(
children: [
Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
),
Positioned(
left: 50,
top: 50,
child: RaisedButton(
onPressed: () {
setState(
() {
list.add(StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Positioned(
left: position.dx,
top: position.dy,
child: RaisedButton(
onPressed: () {
setState(() {
position = Offset(50, 150);
});
},
),
);
}));
},
);
},
),
),
...list
],
),
);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
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> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Sticker(),
);
}
}
I think you have to create a position element for each button in the list
List<Offset> position = Offset();
setState(() => position.add(Offset(50,150)));

how can i update list item without rebuilding whole list

Someone asked me this question and I'd like to make the answer accessible
how can i update list item without rebuilding whole list?
A typical use case (that I will reproduce in the following answer)
could be a ListView that receives a List of Widgets possibly from an API
Providing a Key to the Widgets in the List will prevent those
from being removed from the widget tree and, consequently, being needlessly rebuilt
you may try yourself running this app in dartpad
note the logs in the terminal;
the code is posted below
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _navigatorKey = GlobalKey<NavigatorState>();
FakeApi _api;
#override
void initState() {
_api = FakeApi(_navigatorKey);
super.initState();
}
#override
void dispose() {
_api?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) => MaterialApp(
navigatorKey: _navigatorKey,
home: MyInheritedWidget(
api: _api,
child: const MyHomePage(),
),
);
}
class MyInheritedWidget extends InheritedWidget {
const MyInheritedWidget({
#required Widget child,
#required this.api,
}) : super(
key: const Key('MyInheritedWidget'),
child: child,
);
final FakeApi api;
static MyInheritedWidget of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
#override
bool updateShouldNotify(MyInheritedWidget old) => false;
}
class MyHomePage extends StatelessWidget {
const MyHomePage() : super(key: const Key('MyHomePage'));
#override
Widget build(BuildContext context) => Builder(
builder: (context) => Scaffold(
backgroundColor: Colors.blueGrey,
body: StreamBuilder<List<ItemWidget>>(
stream: MyInheritedWidget.of(context).api.stream,
initialData: [],
builder: (context, list) => list.hasError
? const Center(child: Icon(Icons.error))
: !list.hasData
? const Center(child: CircularProgressIndicator())
: list.data.isEmpty
? const Center(
child: Text(
'the list is empty',
textScaleFactor: 1.5,
))
: ListView.builder(
itemCount: list.data.length,
itemBuilder: (context, index) => list.data[index],
),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.white,
child: const Icon(Icons.add, color: Colors.blueGrey),
onPressed: MyInheritedWidget.of(context).api.add,
),
),
);
}
class ItemWidget extends StatelessWidget {
ItemWidget(this.text) : super(key: UniqueKey());
final String text;
#override
Widget build(BuildContext context) {
print('Item $text is building');
return Center(
child: Container(
padding: const EdgeInsets.only(bottom: 20),
width: MediaQuery.of(context).size.width * .5,
child: Card(
elevation: 10,
child: ListTile(
leading: GestureDetector(
child: const Icon(Icons.edit),
onTap: () => MyInheritedWidget.of(context).api.edit(key),
),
trailing: GestureDetector(
child: const Icon(Icons.delete),
onTap: () => MyInheritedWidget.of(context).api.delete(key),
),
title: Text(text),
),
),
),
);
}
}
class ItemDialog extends StatefulWidget {
const ItemDialog({this.text});
final String text;
#override
_ItemDialogState createState() => _ItemDialogState();
}
class _ItemDialogState extends State<ItemDialog> {
TextEditingController _controller;
#override
void initState() {
_controller = TextEditingController()..text = widget.text;
super.initState();
}
#override
void dispose() {
_controller?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) => AlertDialog(
content: Stack(
alignment: Alignment.center,
children: <Widget>[
Container(
width: double.infinity,
height: MediaQuery.of(context).size.height * .3,
child: Center(
child: TextField(
autofocus: true,
controller: _controller,
),
),
),
],
),
actions: <Widget>[
IconButton(
onPressed: () => Navigator.pop(context, _controller.text ?? ''),
icon: const Icon(Icons.save),
),
],
);
}
class FakeApi {
FakeApi(this.navigatorKey);
final GlobalKey<NavigatorState> navigatorKey;
final _list = <ItemWidget>[];
StreamController<List<ItemWidget>> _controller;
StreamController<List<ItemWidget>> get _c =>
_controller ??= StreamController<List<ItemWidget>>.broadcast();
Stream<List<ItemWidget>> get stream => _c.stream;
void dispose() => _controller?.close();
void delete(Key key) {
_list.removeWhere((ItemWidget item) => item.key == key);
_c.sink.add(_list);
}
void edit(Key key) async {
final _item = _list.firstWhere((ItemWidget item) => item.key == key);
final _index = _list.lastIndexOf(_item);
final _text = await showDialog<String>(
context: navigatorKey.currentState.overlay.context,
builder: (context) => ItemDialog(
text: _item.text,
),
);
_list.removeAt(_index);
_list.insert(_index, ItemWidget(_text));
_c.sink.add(_list);
}
void add() async {
final _text = await showDialog<String>(
context: navigatorKey.currentState.overlay.context,
builder: (context) => ItemDialog(),
);
_list.add(ItemWidget(_text));
_c.sink.add(_list);
}
}

How to keep state, on scroll in Flutter?

I have a simple grid, that takes more space than the screen real-state, and can be scrolled up and down.
Each cell has an onTap() method that changes the cell color.
The problem is that once I scroll the changed cell out of view, the state is not kept.
Any ideas?
class GridWidget extends StatefulWidget {
#override
_GridWidgetState createState() => new _GridWidgetState();
}
class _GridWidgetState extends State<GridWidget> {
#override
Widget build(BuildContext context) {
Color cellColor = Colors.white;
return new GridView.count(
crossAxisCount: 10,
children: new List.generate(100, (index) {
return new CellWidget(
index: index,
color: cellColor,
text: new Text(index.toString()),
);
}),
);
}
}
The CellWidget
...
class _CellWidgetState extends State<CellWidget> {
Color cellColor = Colors.white;
Text cellText = new Text('white');
#override
void initState() {
super.initState();
cellColor = widget.color;
cellText = widget.text;
}
_changeCell(index) {
setState(() {
cellColor = Colors.lightBlue;
});
}
#override
Widget build(BuildContext context) {
return new GestureDetector(
onTap: () => _changeCell(widget.index),
child: new Container(
width: double.infinity,
height: double.infinity,
child: new Center(child: cellText),
),
);
}
}
You can use AutomaticKeepAliveClientMixin class to prevent your items to be disposed when scrolled.
Changing the code in CellWidget should resolve your problem:
class _CellWidgetState extends State<CellWidget> with AutomaticKeepAliveClientMixin {
Color cellColor = Colors.white;
Text cellText = new Text('white');
#override
bool get wantKeepAlive => true;
#override
void initState() {
super.initState();
cellColor = widget.color;
cellText = widget.text;
}
_changeCell(index) {
setState(() {
cellColor = Colors.lightBlue;
});
}
#override
Widget build(BuildContext context) {
super.build(context);
return new GestureDetector(
onTap: () => _changeCell(widget.index),
child: new Container(
width: double.infinity,
height: double.infinity,
child: new Center(child: cellText),
),
);
}
}
Here is the link to the documentation:
AutomaticKeepAliveClientMixin
in Flutter Document for ListView.builder has mentioned that ListView children will build on demand when you scroll... i think here same situation is happening with GridView.count