Flutter update list with state managment - flutter

I am showing listview in my 2 pages. On the first page just simply showing the listView and on the other page I am adding and view the array by listView builder. Now the issue I am facing is when I add something in an array it's not changing in ListView builder so I can manage it by setState but when I go back on the first screen result isn't changing on that screen.
I have generated some example code to clear
var listArray = [];
class FirstPage extends StatefulWidget {
const FirstPage({Key? key}) : super(key: key);
#override
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First'),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: (){
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ListWidget()),
);
},
child: Container(
color: Colors.blue,
height: 100,
width: 100,
child: Text('Go List'),
),
),
Expanded(
child: ListView.builder(
itemCount: listArray.length,
itemBuilder: (BuildContext context, int index) {
return Text('name ${listArray[index]['name']} id ${listArray[index]['id']}');
}),
),
],
),
);
}
}
class ListWidget extends StatefulWidget {
const ListWidget({Key? key}) : super(key: key);
#override
_ListWidgetState createState() => _ListWidgetState();
}
class _ListWidgetState extends State<ListWidget> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First'),
leading: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.black),
onPressed: () => Navigator.of(context).pop(),
),
),
body: Column(
children: [
GestureDetector(
onTap: (){
setState(() {
listArray.add({'name' : 'test', 'id' : 101, 'comments': 'xyzzz'});
});
},
child: Container(
color: Colors.blue,
height: 100,
width: 100,
child: Text('Add to list'),
),
),
Expanded(
child: ListView.builder(
itemCount: listArray.length,
itemBuilder: (BuildContext context, int index) {
return Text('name ${listArray[index]['name']} id ${listArray[index]['id']}');
}),
)
],
),
);
}
}
I am looking in documentation for app state managment but not able to find something for list update. If any help in code or some example where I can find answer similar to this will be great.

Try to update state of first widget when returning to it, so in _FirstPageState write this:
onTap: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ListWidget()),
);
setState({});
}

Declaring a global variable like listArray is a bad practice. You should probably use some state management packages like provider or similar.

Related

Delete Specific ListTile from ListView.builder with longPress

In ListView.builder I'm adding a new ListTile with the button Pressed.
Now when I press on ListTile I want to delete that widget.
I have tried to do that by wrapping the widget with InkWell but when I try to delete it deletes from the last ListTile.
How to delete that specific ListTile when I longPressed on that.
Below here is the code
import 'package:flutter/material.dart';
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
State<Home> createState() => _HomeState();
}
/*InkWell(
child: widgets[index],
onLongPress: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Delete?'),
actions: [
IconButton(
onPressed: () {
widgets.removeAt(index);
setState(() {
Navigator.pop(context);
});
},
icon: Icon(Icons.check))
],
));
},
);*/
class _HomeState extends State<Home> {
#override
List<Widget> widgets = [];
int inde = 0;
List<List> blogList = [];
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Note'),
centerTitle: true,
),
body: Column(children: [
Expanded(
child: ListView.builder(
itemCount: widgets.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return InkWell(
child: widgets[index],
onLongPress: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Delete?'),
actions: [
IconButton(
onPressed: () {
widgets.removeAt(index);
setState(() {
Navigator.pop(context);
});
},
icon: Icon(Icons.check))
],
));
},
);
},
),
),
FloatingActionButton(
onPressed: () {
setState(() {
widgets.add(Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
width: 150,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
border: Border.all(width: 2),
color: Color.fromARGB(255, 76, 178, 204),
),
child: ListTile(
leading: Icon(Icons.circle),
title: TextField(),
)),
));
});
},
child: Icon(Icons.add),
),
]));
}
}
Actually your code works, it deletes the ListTile which you use long press on.
The problem is that you do not assign different controllers to the TextField widgets. So if you enter some text into them, and call setState when deleting one, the values in the TextFields will be wrong, and it looks like the last one is deleted.
So you need to add the following logic to your code:
Create another list like widgets for the controllers.
When adding a new item, create a new controller and assign it to the TextField.
When deleting an item, dispose the controller and remove it from the controllers' list.
Don't forget to dispose all remaining controllers when the widget is disposed.
Here is a sample code, check for the comments where I added to your code. You can run it on DartPad.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: Home(),
),
),
);
}
}
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
List<Widget> widgets = [];
// this is the list for the controllers
List<TextEditingController> controllers = [];
int inde = 0;
List<List> blogList = [];
// you need to add this in order to dispose
// the controllers when the widget is disposed
#override
void dispose() {
for (var controller in controllers) {
controller.dispose();
}
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Note'),
centerTitle: true,
),
body: Column(children: [
Expanded(
child: ListView.builder(
itemCount: widgets.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return InkWell(
child: widgets[index],
onLongPress: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Delete?'),
actions: [
IconButton(
onPressed: () {
setState(() {
widgets.removeAt(index);
// dispose the controller
controllers[index].dispose();
// remove the controller from list
controllers.removeAt(index);
});
Navigator.pop(context);
},
icon: const Icon(Icons.check))
],
));
},
);
},
),
),
FloatingActionButton(
onPressed: () {
setState(() {
// create a new controller and add it to the list
final newController = TextEditingController();
controllers.add(newController);
widgets.add(Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
width: 150,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
border: Border.all(width: 2),
color: const Color.fromARGB(255, 76, 178, 204),
),
child: ListTile(
leading: const Icon(Icons.circle),
// assign the controller to the field
title: TextField(controller: newController),
)),
));
});
},
child: const Icon(Icons.add),
),
]));
}
}
I suggest that following the convention, begin all private members of your state class with an underscore, so rename controllers to _controllers etc.

Flutter: Adding button shows up behind ListView, "duplicate child" error

I'm trying to add a button to navigate to another screen but I'm not sure how to get it on the bottom of my list instead of behind it. This is my current list:
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.teal[800],
appBar: AppBar(
title: Text(widget.title),
),
body: SafeArea(
child: ListView.builder(
itemCount: Type.samples.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return BoardingDetail(boarding: Type.samples[index]);
},
),
);
},
child: buildBoardingCard(Type.samples[index]),
);
},
),
),
);
}
And I think this is the code I want to add to navigate to a new screen, I got this code from https://docs.flutter.dev/cookbook/navigation/navigation-basics
child: ElevatedButton(
child: const Text('Open route'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SecondRoute()),
);
},
),
I tried to integrate the navigation button into my code but it says I have "duplicate child". What is the proper way to do this?
You have to nest the ListView and ElevatedButton in a SingleChildScrollView with a Column
You can try running this to see how it is implemented:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "ListView.builder",
theme: ThemeData(primarySwatch: Colors.green),
debugShowCheckedModeBanner: false,
home: const ListViewBuilder());
}
}
class ListViewBuilder extends StatelessWidget {
const ListViewBuilder({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("ListView.builder")),
body: SingleChildScrollView(
child: Column(
children: [
ListView.builder(
shrinkWrap: true,
itemCount: 8,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: const Icon(Icons.list),
trailing: const Text(
"GFG",
style: TextStyle(color: Colors.green, fontSize: 15),
),
title: Text("List item $index"));
},
),
ElevatedButton(
child: const Text('Open route'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SecondRoute()),
);
},
),
],
),
),
);
}
}
class SecondRoute extends StatelessWidget {
const SecondRoute({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Second Route'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
// Navigate back to first route when tapped.
},
child: const Text('Go back!'),
),
),
);
}
}

Flutter List View Ripple Effect Doesn't Work

I have a ListView in AlertDialog container. There is an InkWell method but ripple effect doesn't work and I can't put the separator. How can I put separator and have ripple effect?
Widget setupAlertDialoadContainer(context) {
return Container(
color: Colors.white,
height: 300.0,
width: 300.0,
child: ListView.builder(
itemCount: showroomModel.length,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () => {
print(showroomModel[index]),
},
child: ListTile(
title: Text(showroomModel[index]),
),
);
}),
);
}
For the InkWell ripple effect, try wrapping the InkWell in a Material widget.
Material(
child: InkWell(
onTap: () {
print(showroomModel[index]);
},
child: ListTile(
title: Text(showroomModel[index]),
),
),
)
For the separator use ListView.separated as in Tasnuva oshin's answer.
As pointed out by TheFabbius, the above code can also be simplified by removing the InkWell and moving the onTap inside the ListTile.
Material(
child: ListTile(
onTap: () {
print(showroomModel[index]);
},
title: Text(showroomModel[index]),
),
)
For Separator & Ripple Effect Use
ListView.separated(
itemCount: 25,
separatorBuilder: (BuildContext context, int index) => Divider(height: 1),
itemBuilder: (BuildContext context, int index) {
return Inkwell(child:
ListTile(
title: Text('item $index'),
));
},
);
Check this out :
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('List app'),
),
body: Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Image Required"),
content: Container(
height: 200,
width: 200,
child: ListView.separated(
itemBuilder: (context, index) {
return Ink(
color: Colors.white,
child: ListTile(
leading: Text("Sample"),
title: Text("title"),
onTap: () {},
),
);
},
separatorBuilder: (context, index) {
return Divider();
},
itemCount: 4,
),
),
actions: <Widget>[
FlatButton(
child: Text("Close"),
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
});
},
child: Text('Press'),
),
),
));
}
}

Accessing elements of a class in Flutter

I have this separate screen with 'FoodIcon' class and a list inside of it that is calling it:
class FoodIcon {
String name;
IconButton foodIcon;
FoodIcon(this.name, this.foodIcon);
List foods = [
FoodIcon('Beef',
IconButton(icon: Image.asset('assets/beef.png'), onPressed: null)),
];
}
Now how would I access the List 'foods' from it on another screen (page)? For example if I have a Container on another screen:
Widget _buildIcon() {
return AlertDialog(
title: Text('Add Icon'),
content: Container(//For example how do I access elements of the list from another page here),
I imported the page where the FoodIcon class is on the page I want it accessed on, but how would I go around getting to the List and the elements inside of it?
Added full code of the page I would need it on:
class FoodList extends StatefulWidget {
#override
_FoodListState createState() => _FoodListState();
}
class _FoodListState extends State<FoodList> {
#override
void initState() {
super.initState();
DatabaseProvider.db.getFoods().then(
(foodList) {
BlocProvider.of<FoodBloc>(context).add(SetFoods(foodList));
},
);
}
Widget _buildIcon() {
return AlertDialog(
title: Text('Add Icon'),
//content ce da bude lista mozda mi treba row/column ovde sa svim ikonicama i imenima, gde ce kada se klikne ikonica
//ontap biti setState da ikonica bude phIcon koji ce dole u ListTilu biti izabran i ocuvan dok se ne promeni, ali mora da bdue razlicit za svaki tile
content: Container(),
actions: [
FlatButton(
child: Text('Cancel'),
onPressed: () => Navigator.pop(context),
),
],
);
}
#override
Widget build(BuildContext context) {
return Container(
child: //maybe display it here for show purposes)}
Home.dart:
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
FoodIcon foodIcon = new FoodIcon(
"Maaz Kamal",
IconButton(
icon: Image.asset('assets/beef.png'),
),
);
// here i populated the list three times for the sake of the example.
myList.add(foodIcon);
myList.add(foodIcon);
myList.add(foodIcon);
return Scaffold(
body: Container(
child: Center(
child: FlatButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MyNextPage(myList),
)); // You can pass your list to next view like this
},
child: Text(
"Go to next page",
style: TextStyle(color: Colors.redAccent, fontSize: 30.0),
),
),
),
),
);
}
}
MyNextPage.dart:
class MyNextPage extends StatefulWidget {
List<FoodIcon> mySecondList;
MyNextPage(this.mySecondList, {Key key}) : super(key: key); //getting the list from previous view
#override
_MyNextPageState createState() => _MyNextPageState();
}
class _MyNextPageState extends State<MyNextPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: FlatButton(
onPressed: () => showDialog(
context: context,
builder: (dialogContext) =>
_buildIcon(dialogContext, widget.mySecondList), // using the list with widget.mySecondList and passing it to our Alert Method
),
color: Colors.lightBlueAccent,
child: Center(
child: Text(
"Click here to see List",
style: TextStyle(fontSize: 40.0),
),
),
),
),
);
}
Widget _buildIcon(dialogContext, List<FoodIcon> mySecondList) {
return AlertDialog(
title: Text('Add Icon'),
content: Container(
width: 300.0,
height: 300.0,
child: ListView.builder(
shrinkWrap: true,
itemCount: mySecondList.length,
itemBuilder: (context, index) => Column(
children: <Widget>[
Text(mySecondList[index].name),
mySecondList[index].foodIcon
],
),
), //For example how do I access elements of the list from another page here),
),
);
}
}

How to add navigation route to Card in Flutter

In the code below, I have a method myMenu on a card. How do I navigate to another page when the card is tapped? There are going to be several of these cards which will link to its own page content. Each time I add a function to for an example it gives an error. How do I do it properly?
import 'package:flutter/material.dart';
import 'package:tarjous_app/gridview_demo.dart';
void main(List<String> args) {
runApp(
new MaterialApp(home: TarjousAle(), debugShowCheckedModeBanner: false));
}
class TarjousAle extends StatefulWidget {
#override
_TarjousAleState createState() => _TarjousAleState();
}
class _TarjousAleState extends State<TarjousAle> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: Text("Study Plan"),
backgroundColor: Colors.amber,
),
body: Container(
child: GridView.count(
crossAxisCount: 3,
children: <Widget>[
MyMenu(
title: "Records",
icon: Icons.account_balance_wallet,
shape: Colors.brown,
),
MyMenu(
title: "Academy",
icon: Icons.account_balance,
shape: Colors.grey,
),
],
),
),
);
}
}
class MyMenu extends StatelessWidget {
MyMenu({this.title, this.icon, this.shape});
final String title;
final IconData icon;
final MaterialColor shape;
#override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.all(9.0),
child: InkWell(
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => GridViewDemo()),
),
splashColor: Colors.amberAccent,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
icon,
size: 80.0,
color: shape,
),
Text(title, style: new TextStyle(fontSize: 18.0))
],
),
),
),
);
}
}
In the inkwell widget, I add a function that works for all the cards. But what I really want it for each card to navigate to its own page. E.g Records should navigate to its own records page, the same thing for Academy to academy page
You could receive the page in the constructor and then go to that page, like this:
class MyMenu extends StatelessWidget {
MyMenu({this.title, this.icon, this.shape, this.page});
final Widget page;
...
}
Then, in onTap:
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => page),
)
So now you can do this:
MyMenu(
...
page: GridViewDemo1(),
),
MyMenu(
...
page: GridViewDemo2(),
)
Note that to navigate to some page, your context must contain a Navigator instance of parent. So if you try to navigate directly from MaterialApp, you might run into issues. I will not belabour the point here since it was explained very well in this thread, but it is something to keep in mind in case you happen to run into it.
Edited to address comments:
I'd do something like this for your case. Named routes make it easy to specify which route you'd like the card to take you to, which you kind of need to do if you want the same widget to take you to different routes.
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(
new MaterialApp(
home: TarjousAle(),
debugShowCheckedModeBanner: false,
routes: {
GridViewDemo.route: (context) => GridViewDemo(),
AnotherDemo.route: (context) => AnotherDemo(),
},
),
);
}
class TarjousAle extends StatefulWidget {
#override
_TarjousAleState createState() => _TarjousAleState();
}
class _TarjousAleState extends State<TarjousAle> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: Text("Study Plan"),
backgroundColor: Colors.amber,
),
body: Container(
child: GridView.count(
crossAxisCount: 3,
children: <Widget>[
MyMenu(
title: "Records",
icon: Icons.account_balance_wallet,
shape: Colors.brown,
route: GridViewDemo.route
),
MyMenu(
title: "Academy",
icon: Icons.account_balance,
shape: Colors.grey,
route: AnotherDemo.route
),
],
),
),
);
}
}
class MyMenu extends StatelessWidget {
MyMenu({this.title, this.icon, this.shape, this.route});
final String title;
final IconData icon;
final MaterialColor shape;
final String route;
#override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.all(9.0),
child: InkWell(
onTap: () => Navigator.pushNamed(context, route),
splashColor: Colors.amberAccent,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
icon,
size: 80.0,
color: shape,
),
Text(title, style: new TextStyle(fontSize: 18.0))
],
),
),
),
);
}
}
class GridViewDemo extends StatelessWidget {
static String route = '/demo';
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.brown,
appBar: AppBar(title: Text('Grid view demo')),
body: Center(
child: Text('Grid view demo'),
),
);
}
}
class AnotherDemo extends StatelessWidget {
static String route = '/another';
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey,
appBar: AppBar(title: Text('Another demo')),
body: Center(
child: Text('Another demo'),
),
);
}
}
You can read more about the basics of navigation in official docs, and also another docs page if you fancy the named routes.
Wrap the card with GestureDetector and you can use opnTap property.
for more details Official Documentation
Try wrapping your Card in a GestureDetector like below:
GestureDetector (
child: Card(),
onTap: () {},
),
wrap the card with InkWell widget and define your navigator.push in the onTap method.
class CardWidget extends StatelessWidget {
final Function onTapCard;
const CardWidget({Key key, #required this.onTapCard}) : super(key: key);
#override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.all(9.0),
child: InkWell(
onTap: onTapCard,
splashColor: Colors.amberAccent,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
icon,
size: 80.0,
color: shape,
),
Text(title, style: new TextStyle(fontSize: 18.0))
],
),
),
),
);
}
}
then we have our list here
class CardList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
CardWidget(
onTapCard: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => YourSecondPage()),
),
),
CardWidget(
onTapCard: Navigator.push(
context,
MaterialPageRoute(builder: (context) => YourThirdPage()),
),
),
],
);
}
}