Flutter does not display stateful children Widget in ListView - android

As in the titel, I have a problem with ListView and I hope you can help me out.
I am using a basic ListView to build "Card Widgets" (with their own state). The ListView uses a List of Ids, which are used to build those "Card Widgets"
The problem:
Any time I remove a card from the list by deleting an Id the ListView always removes the top most Child Widget. My backend deletes the right things, becouse after I restart the app so that the page gets populated anew, the deleted card is actually deleted and the one the removed by the ListView is visible again. It seems like ListView does not redraw it's children. Any Idea what is going on?
I created basic DartPad code to illustrate the problem
import 'package:flutter/material.dart';
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: 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> {
int _counter = 0;
List<String> dd = new List<String>();
#override
void initState() {
super.initState();
dd.add('A');
dd.add('B');
dd.add('C');
dd.add('D');
}
void _incrementCounter() {
setState(() {
_counter++;
dd.insert(1, 'Q');
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title + _counter.toString()),
),
body: ListView.builder(
addAutomaticKeepAlives: false,
itemBuilder: (context, index) {
print('calling: $index :' + _counter.toString() + ' -> ' + dd[index] );
return new CRD(title: dd[index]);
},
itemCount: dd.length
),
/*
ListView(
children: dd.map((str) {
return CRD(title: str);
}).toList()
),
*/
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class CRD extends StatefulWidget {
CRD({Key key, this.title}) : super(key: key);
final String title;
#override
_CRD createState() => _CRD();
}
class _CRD extends State<CRD> {
String _val;
#override
void initState() {
super.initState();
_val = widget.title + ' ' + widget.title;
}
#override
Widget build(BuildContext context) {
return Text(_val);
}
}
So after clicking once on the Add button the list content is [A,Q,B,C,D] but the app displays [A,B,C,D,D]. Whats going on here? Am i missing something?

Your CRD widget is a StatefulWidget and the state will be reused when rebuilding since the type of the widget is the same an you did not give it a key.
To solve your issue there are a few possibilities:
Add a key to all the items in the list
Implement the didUpdateWidget method in the state of your widget
Use a statelesswidget and do the string concatination in the build method

Related

Is it possible to update TextFormField using shared state (and without violating good practices)?

I am trying to make test project according to good practices.
Please note that I DON'T want any "hacky" approach. I am willing to learn good way of solving it.
My understanding of "lifting state up" is that any change updates the state, and then view is redrawn (rebuild) using current state. It is great in theory, but it DOES NOT work with TextFormField/TextEditingController.
I want to have a SharedState and bi-directonal TextFormField/TextEditingController, as follows:
case 1 (works):
TextFormField changes -> state is updated -> readonly Text (in WidgetTwo) is updated
case 2 (does not work):
button (in WidgetOne) is clicked -> state is updated -> TextFormField (in WidgetThree) shows new value from state
I have code in 3 different widgets + main file + SharedSate:
main.dart
void main() {
runApp(ChangeNotifierProvider(
create: (_) => sharedState(), child: const MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
const MyHomePage({Key? key, required this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
WidgetOne(),
WidgetTwo(),
WidgetThree(),
]),
),
);
}
}
shared_state.dart
class SharedState extends ChangeNotifier {
int counter = 0;
void setCounter(int c) {
counter = c;
notifyListeners();
}
void incrementCounter() {
counter++;
notifyListeners();
}
void decrementCounter() {
counter--;
notifyListeners();
}
Future fetchCounterFromWeb() async {
// simulate external call
await Future.delayed(Duration(milliseconds: 500));
setCounter(42);
}
}
widget_one.dart
class WidgetOne extends StatelessWidget {
#override
Widget build(BuildContext context) {
var state = Provider.of<SharedState>(context, listen: false);
return Row(
children: [
ElevatedButton(
onPressed: () => state.decrementCounter(),
child: Text('decrement')),
ElevatedButton(
onPressed: () => state.incrementCounter(),
child: Text('increment')),
ElevatedButton(
onPressed: () => state.fetchCounterFromWeb(),
child: Text('fetch counter from web')),
],
);
}
}
widget_two.dart
class WidgetTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
var state = Provider.of<SharedState>(context, listen: true);
return Row(
children: [Text('Value of counter is: ${state.counter}')],
);
}
}
widget_three.dart (problem is here)
class WidgetThree extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return WidgetThreeState();
}
}
class WidgetThreeState extends State<WidgetThree> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late TextEditingController _controller;
#override
void initState() {
super.initState();
var state = Provider.of<SharedState>(context, listen: false);
_controller = TextEditingController(text: state.counter.toString());
}
#override
Widget build(BuildContext context) {
var state = Provider.of<SharedState>(context, listen: true);
// THE ISSUE:
// It is NOT possible to update Controller (or TextEditing field)
// without this hacky line (which is not good practice)
_controller.text = state.counter.toString();
return Form(
key: _formKey,
child: Column(children: [
TextFormField(
controller: _controller,
keyboardType: TextInputType.number,
onChanged: (v) {
state.setCounter(int.parse(v.isEmpty ? '0' : v));
},
)
]),
);
}
}
I know I can possible move TextEditingController to SharedState, but SharedState should be UI agnostic, and TextEditingController is a UI widget.

Saving information to dart flutter database and pulling information to listTile

I have an application. In the application the user will save data. When you log into a particular page, the record of logging into that page will be saved in the database.
My problem is this: I examined the sqflite database structure, but I could not understand it. It's a strange building. What I need to do is to save data in only 1 column and pull them and put them in listTile.
But as I said, I couldn't do what I wanted because I couldn't understand the sqflite structure.
How can I do it? How do I use sqflite?
The sqflite library provides the sqlite database to flutter. Your question leads me to assume that you first need to read a bit more about what it is and what is used for.
Once you are familiar with the fundamentals you will be able to grasp the usage of the library fairly easily.
For your application though, I would suggest going for simpler options. You might find a key-value store like shared_preferences, to be easier to grasp and get started with. Just put the data as a JSON list in the store and retrieve it for display when building the ListView.
EDIT:
Use the following as a starting point and take it further as per your requirement:
import 'package:flutter/material.dart';
import 'package:sqflite/sqflite.dart';
Database? db;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
db = await openDatabase(
'my_db.db',
version: 1,
onCreate: (Database db, int version) async {
await db.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)');
},
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Map<String, Object?>>? _records;
bool _loading = false;
int nextRecordId = 1;
#override
void initState() {
super.initState();
getRecords();
}
void getRecords() {
setState(() {
_loading = true;
});
db?.query('Test').then((value) {
setState(() {
_records = value;
_loading = false;
});
});
}
void _insertRandomRecord() {
db
?.insert(
'Test',
{
'id': '$nextRecordId',
'name': 'Random record $nextRecordId',
},
conflictAlgorithm: ConflictAlgorithm.replace)
.then((value) {
nextRecordId++;
getRecords();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: _loading
? const Center(
child: CircularProgressIndicator.adaptive(),
)
: ListView.builder(
itemBuilder: (context, index) {
final record = _records![index];
return ListTile(
title: Text(record['name'] as String),
);
},
itemCount: _records?.length ?? 0,
),
floatingActionButton: FloatingActionButton(
onPressed: _insertRandomRecord,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}

Flutter - setState to another class

I just started programming in Flutter. I want to create an app to keep track of the expiration dates of food.
My app is composed of:
main.dart that returns a MaterialApp class with inside the Home
home.dart that contains the AppBar, a Scaffold which contains a ListBuilder() as body and a FAB which should add a new item.
list_builder.dart that contains the stateful widget ListBuilder which takes a list of Strings from items_list.dart and creates a ListView with some tiles
items_list.dart that contains a List of Strings, a function to remove, add and retrieve the list.
What I made so far is a list of items with a trailing trash icon button that deletes the single item from the list. All works as expected.
Now I want that pressing the FAB, it triggers the ItemsList.addItem() which adds an item to the list. That works, of course, but the list on screen (created by the list_builder.dart) is not updated unless I delete one item.
I tried unsuccessfully to use callback functions, I'm sure I'm missing something.
This is the code:
main.dart
import 'package:flutter/material.dart';
import 'home.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Quando Scade?',
home: Home(),
theme: ThemeData(
primarySwatch: Colors.lightGreen,
),
);
}
}
home.dart
import 'package:flutter/material.dart';
import 'package:quando_scade/items_list.dart';
import 'list_builder.dart';
class Home extends StatelessWidget {
const Home({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Quando Scade?'),
backgroundColor: Theme.of(context).colorScheme.primary,
),
body: ListBuilder(),
floatingActionButton: FloatingActionButton(
onPressed: () {
ItemsList.addItem('ciao');
print('item added!!!');
},
child: const Icon(Icons.add),
),
);
}
}
list_builder.dart
import 'package:flutter/material.dart';
import 'items_list.dart';
class ListBuilder extends StatefulWidget {
const ListBuilder({Key key}) : super(key: key);
#override
_ListBuilderState createState() => _ListBuilderState();
}
class _ListBuilderState extends State<ListBuilder> {
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: ItemsList.getItems().length,
itemBuilder: (context, index) {
return _buildRow(ItemsList.getItems()[index], index);
},
);
}
Widget _buildRow(String item, int index) {
return ListTile(
title: Text(
item,
),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
setState(() {
ItemsList.removeItem(index);
});
},
),
);
}
}
items_list.dart
class ItemsList {
static List<String> _items = [
'banane',
'latte',
'caffè',
'vino',
'sushi',
'birra',
];
// to add items
static void addItem(String name) => ItemsList._items.add(name);
// to remove item
static void removeItem(int i) => ItemsList._items.removeAt(i);
// returns the list of items
static List<String> getItems() {
return ItemsList._items;
}
}
I see you are using setState on item delete, you should use it when adding an item as well:
...
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
ItemsList.addItem('ciao');
});
print('item added!!!');
},
child: const Icon(Icons.add),
),
...
Of course, make the Home widget as StatefulWidget before that.
Though this is not an optimal solution to what you're looking for, however, it's better than changing your Home class to a stateful widget and rebuilding your entire widget tree. I have modified your code to make it work exactly the way you want it without calling the setState function.
home.dart
class Home extends StatelessWidget {
const Home({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Quando Scade?'),
backgroundColor: Theme.of(context).colorScheme.primary,
),
body: ListBuilder(),
floatingActionButton: FloatingActionButton(
onPressed: () {
ItemsList.itemList.addItem('ciao');
print('item added!!!');
},
child: const Icon(Icons.add),
),
);
}
}
items_list.dart
class ItemsList {
final _list = [
'banane',
'latte',
'caffè',
'vino',
'sushi',
'birra',
];
StreamController<List<String>> _items =
StreamController<List<String>>.broadcast();
Stream<List<String>> get items => _items.stream;
// to add items
void addItem(String name) {
//_reOpenStream();
_list.add(name);
_items.sink.add(_list);
// _items.close();
}
// to remove item
void removeItem(int i) {
//_reOpenStream();
_list.removeWhere((element) => element == _list[i]);
_items.sink.add(_list);
}
void close() {
_items.close();
}
static final ItemsList _singleton = ItemsList._internal();
static ItemsList get itemList => ItemsList();
factory ItemsList() {
return _singleton;
}
ItemsList._internal();
}
list_builder.dart
class ListBuilder extends StatefulWidget {
const ListBuilder({Key key}) : super(key: key);
#override
_ListBuilderState createState() => _ListBuilderState();
}
class _ListBuilderState extends State<ListBuilder> {
#override
Widget build(BuildContext context) {
return StreamBuilder<List<String>>(builder: (_, snapshot) {
int itemCount = snapshot.data.length;
return ListView.builder(
itemCount: itemCount,
itemBuilder: (context, index) {
//Handle the empty list by replacing the container widget with your logic
return itemCount <=0 ? Container(): _buildRow(snapshot.data[index], index);
},
);
}, initialData: [],);
}
Widget _buildRow(String item, int index) {
return ListTile(
title: Text(
item,
),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
setState(() {
ItemsList.itemList.removeItem(index);
});
},
),
);
}
}
Only call the close() method when you no longer need the stream, otherwise, it'd throw a bad state error when you try accessing it again. Additionally, as you progress in your learning try refactoring your codes to use an architecture design.. Peace!

Flutter - How to render a widget on button click?

Hi I'm new to flutter and I have an issue. I created simple app for better explanation. In my main.dart I call Button1() which is in button1.dart. When i press the button it should call Button2() in button2.dart. But the second button is not rendering. How can i do it? And how can i change some data in the button2.dart? For example change text of the button. I set text of the button to some variable and how can i pass it when i click the first button?
Thanks
My main.dart code
import 'package:flutter/material.dart';
import 'button1.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
MyHomePageState createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("My app"),
),
body: Center(
child: Column(
children: <Widget>[
Button1(),
],
),
),
);
}
}
My button1.dart code
import 'button2.dart';
class Button1 extends StatefulWidget {
#override
_Button1State createState() => _Button1State();
}
class _Button1State extends State<Button1> {
#override
Widget build(BuildContext context) {
return Center(
child: Column(
children: <Widget>[
RaisedButton(
child: Text("button1"),
onPressed: () {
setState(() {
Button2();
});
},
),
],
),
);
}
}
and here is my button2.dart code
class Button2 extends StatefulWidget {
#override
_Button2State createState() => _Button2State();
}
class _Button2State extends State<Button2> {
#override
Widget build(BuildContext context) {
return Center(
child: Column(
children: <Widget>[
RaisedButton(
child: Text("Button2"),
onPressed: () {},
),
],
),
);
}
}
I assume you are new to programming and I am trying to explain the concept here as easy as possible..
Let you have your main class (Parent). It contains your two widget/buttons (Children). To pass data from one children to another you can have a variable in the parent class and share your data through it. Here is an example..
class Parent{
String sharedData = "";
bool isVisible = false;
build(context){
//...
Child1((String newData){
setState(() {
sharedData = newData;
isVisible = true;
});
}),
if(isVisible) Child2(sharedData),
}
}
Here Child1 is using a callback to update the data. Inside setState it is updating the Parent class variable and also rebuilding the widget tree. Which updates the Child2 classes data.
Hope you got the point...

Flutter SizeTransition not working correctly.Size transition behaving like I am sliding widget

I was trying to learn Flutter SizeTransition. I used SizeTransition and provided sizeFactor as animation and provided tween from 0 to 1. I made a function execute in build that gets exectued after some seconds, What I expected was that the size of logo will increase and decrease when animation is forward and reverse respectively. But what I noticed that logo first moves down and then goes back up.(like a Slide Transition)
widget to test for SizeTransition
import 'dart:async';
import 'package:flutter/material.dart';
class LogoApp extends StatefulWidget {
_LogoAppState createState() => _LogoAppState();
}
class _LogoAppState extends State<LogoApp> with TickerProviderStateMixin {
AnimationController _animationController;
Animation<double> _animation;
#override
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: Duration(seconds: 4));
_animation = _animationController.drive(Tween(begin: 0, end: 1));
}
int ctr = 0;
#override
Widget build(BuildContext context) {
ctr += 1;
print("build$ctr");
execute(); //function that executes forward()/reverse() methods of animationController
return SizeTransition(
sizeFactor: _animation,
child: Center(
child: FlutterLogo(),
),
);
}
void execute() async {
Future.delayed(const Duration(seconds: 2), () {
_animationController.forward();
});
Future.delayed(const Duration(seconds: 4), () {
_animationController.reverse();
});
}
}
main.dart
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#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> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: LogoApp(),
);
}
}
I have tried a lot but no success. What else can I do?
I think what you actually meant to implement was a ScaleTransition() instead of a SizeTransition().
It's a pretty straightforward fix:
int ctr = 0;
#override
Widget build(BuildContext context) {
ctr += 1;
print("build$ctr");
execute(); //function that executes forward()/reverse() methods of animationController
return Center(
child: ScaleTransition(
scale: _animation,
child: FlutterLogo(),
),
);
}
You also need to move the Center() widget one level up (as shown in the code) to ensure that the entire animation is anchored to the center of the display - as you originally intended it to be.

Categories

Resources