How to manage state of two different stateful widget - flutter

The basic outline of the widget is shown Below. MyListViewBuilder1 and MyListViewBuilder2 both stateful widget are defined in separate dart file. Both of them consists of ListViewBuilder.
What I want to achieve is that when the item of MyListViewBuilder1 gets deleted then deleted item appears in MyListViewBuilder2, But the problem is this happens only when I restart the screen.
So how can I change solve this?
How can I change the state of next when state of one is changed?
Column(
children: <Widget>[
MyListViewBuilder1(),
MyListViewBuilder2()
]
)

You could use the provider package to manage state in different widgets throughout the application. In the below example, when an item is deleted in the MyListViewBuilder1, it is removed from the list and added to the list of deleted items in the ItemChangeNotifier class. The MyListViewBuilder2 has its own independent list of items, however in its build method it watches for any changes to the list of deleted items in the ItemChangeNofifier class and adds these deleted items to its own independent list of items.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<ItemChangeNotifier>(
create: (context) {
return ItemChangeNotifier();
},
),
],
child: MyApp(),
),
);
}
class Item {
int id;
String title;
Item({
this.id,
this.title,
});
}
class ItemChangeNotifier extends ChangeNotifier {
final _deletedItems = <Item>[];
List<Item> get deletedItems => List.unmodifiable(_deletedItems);
void deleteItem(Item item) {
_deletedItems.add(item);
notifyListeners();
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: MyListViewBuilder1(),
),
Divider(
thickness: 8,
),
Expanded(
child: MyListViewBuilder2(),
),
],
),
);
}
}
class MyListViewBuilder1 extends StatefulWidget {
#override
_MyListViewBuilder1State createState() => _MyListViewBuilder1State();
}
class _MyListViewBuilder1State extends State<MyListViewBuilder1> {
List<Item> _items;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
return ListTile(
title: Text(_items[index].title),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
context.read<ItemChangeNotifier>().deleteItem(_items[index]);
setState(() {
_items.removeAt(index);
});
},
),
);
},
itemCount: _items.length,
);
}
#override
void initState() {
super.initState();
_items = List.generate(
10,
(index) => Item(
id: index + 1,
title: 'Item ${index + 1}',
),
);
}
}
class MyListViewBuilder2 extends StatefulWidget {
#override
_MyListViewBuilder2State createState() => _MyListViewBuilder2State();
}
class _MyListViewBuilder2State extends State<MyListViewBuilder2> {
List<Item> _items;
#override
Widget build(BuildContext context) {
final items = [
..._items,
...context.select<ItemChangeNotifier, List<Item>>(
(itemChangeNotifier) => itemChangeNotifier.deletedItems,
),
];
return ListView.builder(
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index].title),
);
},
itemCount: items.length,
);
}
#override
void initState() {
super.initState();
_items = List.generate(
10,
(index) => Item(
id: index + 101,
title: 'Item ${index + 101}',
),
);
}
}

Related

Animated moveable list in flutter?

any tips or help how can I make this on tap moveable list in flutter?
https://files.fm/f/txdn29dg3
The provided component is exactly what CupertinoPicker could offer you.
Also, as suggested in the documentation, you should combine the CupertinoPicker with showCupertinoModalPopup to display the picker modally at the bottom of the screen.
This is how the code could look like:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: const Scaffold(
body: Center(
child: PickerPage(),
),
),
);
}
}
class PickerPage extends StatefulWidget {
const PickerPage();
#override
_PickerPageState createState() => _PickerPageState();
}
class _PickerPageState extends State<PickerPage> {
final _items = [
'Flat Rate',
'Hourly',
'Request for Price',
];
int _selectedItem = 0;
void _onSelectedItemChanged(int value) => setState(
() => _selectedItem = value,
);
void _showPicker() {
showCupertinoModalPopup(
context: context,
builder: (_) => PickerExample(
items: _items,
selectedItem: _selectedItem,
onSelectedItemChanged: _onSelectedItemChanged,
),
);
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_items[_selectedItem]),
const SizedBox(height: 10.0),
ElevatedButton(
child: const Text('Show picker'),
onPressed: _showPicker,
),
],
);
}
}
class PickerExample extends StatefulWidget {
final List<String> items;
final int selectedItem;
final ValueSetter<int> onSelectedItemChanged;
const PickerExample({
required this.items,
required this.selectedItem,
required this.onSelectedItemChanged,
});
#override
_PickerExampleState createState() => _PickerExampleState();
}
class _PickerExampleState extends State<PickerExample> {
late final FixedExtentScrollController _controller;
#override
void initState() {
super.initState();
_controller = FixedExtentScrollController(initialItem: widget.selectedItem);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SizedBox(
height: 300,
child: CupertinoPicker(
scrollController: _controller,
backgroundColor: Colors.white,
itemExtent: 30.0,
children: [
for (final item in widget.items) Center(child: Text(item)),
],
onSelectedItemChanged: widget.onSelectedItemChanged,
),
);
}
}
You could also find an interactive example in this DartPad.

When I add elements to listview, how to update listview in Flutter?

I am new to flutter and I would like to add element every 5 seconds to my list view. I have list view and I think I have the true adding method. However, I do not know how to update my list view every 5 seconds.
void randomCity(){
List <int> colors = [yellow,green,blue,red,black,white];
List <String> countryNames = ["Gdańsk","Warszawa","Poznań","Białystok","Wrocław","Katowice","Kraków"];
List <String> countryImages = [gdanskPic,warszawaPic,poznanPic,bialystokPic,wroclawPic,katowicePic,krakowPic];
Random random = new Random();
DateTime now = new DateTime.now();
Future.delayed(Duration(seconds: 5), (){
setState(() {
int randomCity = random.nextInt(countryNames.length);
int randomColor = random.nextInt(colors.length);
countrylist.add(Country(
countryNames[randomCity], countryImages[randomCity],
colors[randomColor], now.toString()));
});
});
}
In this code I am adding new element to my list view.
randomCity();
return Scaffold(
backgroundColor: Colors.grey[100],
appBar: AppBar(
backgroundColor: Colors.grey[100],
elevation: 0.0,
title: Text(
"Random City App",
style: TextStyle(fontSize: 20.0, color: Colors.black),
),
centerTitle: true,
actions: <Widget>[
IconButton(
icon: Icon(
Icons.add,
color: Colors.black,
size: 32,
),
onPressed: () {})
],
),
body: ListView.builder(
itemCount: countrylist.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => CountryDetails(countryName: countrylist[index].name,
appBarColor: countrylist[index].color, date: countrylist[index].date, image: countrylist[index].image,))
);
},
title: Text(countrylist[index].name + " ${countrylist[index].date}"),
tileColor: Color(countrylist[index].color),
),
);
},
));
}
And this is my ListView.Builder.
You have to convert your widget into StatefulWidget and then rebuild it with setState (more info on ways to manage state https://flutter.dev/docs/development/data-and-backend/state-mgmt/options)
class MyApp extends StatelessWidget { // your main widget
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: MyWidget(),
),
);
}
}
class MyWidget extends StatefulWidget { // create new StatefulWidget widget
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
List<Country> countrylist = []; // mover other variables in here
...
void randomCity(){
...
setState(() {}); // this will rebuild your widget again and again
}
#override
Widget build(BuildContext context) {
Future.delayed(Duration(seconds: 5), (){
randomCity();
});
return ListView.builder(
itemCount: countrylist.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
onTap: () {},
title: Text(countrylist[index]),
),
);
},
);
}
}
You have to tell the ListView to rebuild which you can do with the setState method (if you are using a StefulWidget). Also, I would use Timer instead of Future.delayed for periodic updates. Here would be a simplified example of your usecase:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Timer timer;
final countryNames = ['Germany', 'Croatia', 'Turkey', 'USA'];
List<String> countryList = [];
#override
void initState() {
Timer.periodic(Duration(seconds: 5), (timer) {
int randomCity = Random().nextInt(countryNames.length);
countryList.add(countryNames[randomCity]);
setState(() {});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('List Updater'),
),
body: ListView.builder(
itemBuilder: (context, index) {
return Card(
child: Text(countryList[index]),
);
},
itemCount: countryList.length,
),
);
}
#override
void dispose() {
timer?.cancel();
super.dispose();
}
}

Can't add or update a list

So I'm trying to make a list that contains some widgets and then add a new widget to it when I press a button, but it doesn't seem to be working
This is the code:
class MessagesProvider extends ChangeNotifier{
List<dynamic> mesgs = [
new chatBubbleSend(),
new chatBubbleReceiver(),
new chatBubbleReceiver()
];
bool loading = true;
addMesg(){
mesgs.add(chatBubbleSend());
print(mesgs.length);
print(mesgs);
notifyListeners();
}
printMesg(){
print(mesgs.length);
print(mesgs);
}
removeMesg(){
mesgs.removeLast();
print(mesgs.length);
print(mesgs);
notifyListeners();
}
}
and this is what i get when i press the add, remove or print buttons
add,remove,print
and this is the list builder code
ChangeNotifierProvider<MessagesProvider>(
create: (context) => MessagesProvider(),
child: ChatMessages()
),
class ChatMessages extends StatelessWidget {
#override
Widget build(BuildContext context) {
final mesgs = Provider.of<MessagesProvider>(context, listen: false).mesgs;
return ListView.builder(
shrinkWrap: true,
itemCount: mesgs.length,
itemBuilder: (context,index)=> mesgs[index],
);
}
}
I have looking for a solution for over 8 hours now, and still, I couldn't fix it.
I jumped the gun with my first answer sorry.
When trying to recreate I ran into the same frustrating issue - focusing on the the provider being the problem until I realised it's actually the rendering of the updated list that's the issue.
You need to use a list builder to render the updating list in a change notifier consumer in a stateful widget
Full working example below:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class WidgetListProvider with ChangeNotifier {
List<Widget> widgets = [];
int listLength = 0;
void addWidget(){
Widget _widget = Text('Hello');
widgets.add(_widget);
listLength = widgets.length;
print('Added a widget');
notifyListeners();
}
void removeWidget(){
if (widgets.length > 0) {
widgets.removeLast();
listLength = widgets.length;
print('Removed a widget');
notifyListeners();
}
}
}
class HomePage extends StatefulWidget {
HomePage({Key key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Widget _appBar (BuildContext context) {
return AppBar(
title: Text('My App'),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: _appBar(context),
// You need to define widgets that update when a provider changes
// as children of a consumer of that provider
body: Consumer<WidgetListProvider>(builder: (context, widgetProvider, child){
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
RaisedButton(
child: Text('Add widget'),
onPressed: () {
widgetProvider.addWidget();
},
),
RaisedButton(
child: Text('Remove Widget'),
onPressed: () {
widgetProvider.removeWidget();
},
),
Row(
children: [
Text('Number of Widgets: '),
Text(widgetProvider.listLength.toString()),
],
),
Container(
height: MediaQuery.of(context).size.height*0.6,
child: ListView.builder(itemCount: widgetProvider.widgets.length, itemBuilder: (BuildContext context, int index){
return widgetProvider.widgets[index];
})
)
],
),
);
}
),
);
}
}
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => WidgetListProvider(),
child: MyApp(),
)
);
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: HomePage(),
);
}
}

How to generate multiple Dropdown dynamically in Flutter?

I have a Java background and new to Flutter. I have stuck in a scenario where I need to create multiple dropdown dynamically. For instance, There is a Pizza deal offers 2 Large pizza, 2 Small pizza and 1 drink. So, Whenever customer select any pizza He/She must need to select a flavor to it. If there is 2 Large pizza what i need to generate is 2 dropdown list with defined flavor so that customer can select 2 different flavor and want to save them in separate variable so that, I can get the value later on, and the same goes for 2 small pizza. In this deal, I have to create 5 dropdown and the quantity of dropdown varies along the deal they offer. How can I achieve this in Flutter?
You can copy paste run full code below
You can use ListView, when add data to List like List<CartItem>, DropdownButton will show
You can for loop List<CartItem> to summary data you need like quantity
code snippet
class _CartWidgetState extends State<CartWidget> {
#override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(child: Pizza(cartItem: widget.cart[widget.index])),
Expanded(child: Flavor(cartItem: widget.cart[widget.index])),
Expanded(
child: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
setState(() {
print(widget.index);
widget.cart.removeAt(widget.index);
widget.callback();
});
...
ListView.builder(
key: UniqueKey(),
itemCount: cart.length,
itemBuilder: (BuildContext ctxt, int index) {
return CartWidget(
cart: cart, index: index, callback: refresh);
}),
output of working demo when click print button
I/flutter (14508): Pizza 1
I/flutter (14508): Pizza 2
I/flutter (14508): Pizza 4
working demo
full code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
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 Flavor extends StatefulWidget {
CartItem cartItem;
Flavor({this.cartItem});
#override
_FlavorState createState() => _FlavorState();
}
class _FlavorState extends State<Flavor> {
String _value = "Flavor 1";
#override
void initState() {
super.initState();
_value = widget.cartItem.flavor;
}
#override
void didUpdateWidget(Flavor oldWidget) {
if (oldWidget.cartItem.flavor != widget.cartItem.flavor) {
_value = widget.cartItem.flavor;
}
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) {
return Container(
child: DropdownButton(
value: _value,
items: [
DropdownMenuItem(
child: Text("Flavor 1"),
value: "Flavor 1",
),
DropdownMenuItem(
child: Text("Flavor 2"),
value: "Flavor 2",
),
DropdownMenuItem(child: Text("Flavor 3"), value: "Flavor 3"),
DropdownMenuItem(child: Text("Flavor 4"), value: "Flavor 4")
],
onChanged: (value) {
setState(() {
_value = value;
widget.cartItem.flavor = value;
});
}),
);
}
}
class Pizza extends StatefulWidget {
CartItem cartItem;
Pizza({this.cartItem});
#override
_PizzaState createState() => _PizzaState();
}
class _PizzaState extends State<Pizza> {
String _value = "";
#override
void initState() {
super.initState();
_value = widget.cartItem.itemName;
}
#override
void didUpdateWidget(Pizza oldWidget) {
if (oldWidget.cartItem.itemName != widget.cartItem.itemName) {
_value = widget.cartItem.itemName;
}
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) {
return Container(
child: DropdownButton(
value: _value,
items: [
DropdownMenuItem(
child: Text("Pizza 1"),
value: "Pizza 1",
),
DropdownMenuItem(
child: Text("Pizza 2"),
value: "Pizza 2",
),
DropdownMenuItem(child: Text("Pizza 3"), value: "Pizza 3"),
DropdownMenuItem(child: Text("Pizza 4"), value: "Pizza 4")
],
onChanged: (value) {
setState(() {
_value = value;
widget.cartItem.itemName = value;
});
}),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class CartItem {
String productType;
String itemName;
String flavor;
CartItem({this.productType, this.itemName, this.flavor});
}
class CartWidget extends StatefulWidget {
List<CartItem> cart;
int index;
VoidCallback callback;
CartWidget({this.cart, this.index, this.callback});
#override
_CartWidgetState createState() => _CartWidgetState();
}
class _CartWidgetState extends State<CartWidget> {
#override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(child: Pizza(cartItem: widget.cart[widget.index])),
Expanded(child: Flavor(cartItem: widget.cart[widget.index])),
Expanded(
child: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
setState(() {
print(widget.index);
widget.cart.removeAt(widget.index);
widget.callback();
});
},
),
)
],
);
}
}
class _MyHomePageState extends State<MyHomePage> {
List<CartItem> cart = [];
void refresh() {
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: ListView.builder(
key: UniqueKey(),
itemCount: cart.length,
itemBuilder: (BuildContext ctxt, int index) {
return CartWidget(
cart: cart, index: index, callback: refresh);
}),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
RaisedButton(
onPressed: () {
cart.add(CartItem(
productType: "pizza",
itemName: "Pizza 1",
flavor: "Flavor 1"));
setState(() {});
},
child: Text("add Pizza"),
),
RaisedButton(
onPressed: () {
for (int i = 0; i < cart.length; i++) {
print(cart[i].itemName);
}
},
child: Text("Print Pizza"),
),
],
)
],
),
),
);
}
}
You can use collection-if in your UI code to show those dropdowns when a certain condition is met.
Widget build() {
return Column(
children: [
pizzaSelector(),
if (pizzaIsSelected)
flavorSelector(),
]
);
}
Andrea has a good video explaining collection-if and spread operators which I think will help you.

How can fix ListView in column and add RefreshIndicator to the ListView

I have a ListView in column ,and need add RefreshIndicator to the ListView,but it not work well
I tried contain the listView by Expanded,then list display well,but when call the RefreshIndicator ,app dump...
some one can help me ,how to fix this code,thanks
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
List<int> items = List.generate(16, (i) => i);
Future<Null> _handleRefresh() async {
await Future.delayed(Duration(seconds: 5), () {
print('refresh');
setState(() {
items.clear();
items = List.generate(40, (i) => i);
return null;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Refresh"),
),
body: Column(
children: <Widget>[RefreshIndicator(child:
ListView.builder(
itemCount: items.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return ListTile(
title: Text("Index$index"),
);
},
), onRefresh: _handleRefresh,)
],
)
);
}
}
I have two remarks :
You should use the Expanded widget with the flex param.
You do not need to return anything in the setState method.
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
List<int> items = List.generate(16, (i) => i);
Future<Null> _handleRefresh() async {
await Future.delayed(Duration(seconds: 5), () {
print('refresh');
setState(() {
items.clear();
items = List.generate(40, (i) => i);
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Refresh"),
),
body: Column(
children: <Widget>[
Expanded(
flex: 1,
child: RefreshIndicator(
child: ListView.builder(
itemCount: items.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return ListTile(
title: Text("Index$index"),
);
},
),
onRefresh: _handleRefresh,
),
)
],
),
);
}
}
The answer above( by Tarek Baz) is correct, however in some special cases (like deep/complicated widget tree) it might not be enough and you might have to pass the physics parameter to the ListView.builder() function.
ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
itemCount: .... ... )