Flutter ListView builder not updating with fresh data - flutter

I'm running a search filter function which retrieves players from a Floor DB. The functionality works fine and I can see through logs that the new player are returned, however my UI won't update which seems to be from the new list i assign not triggering a re-render.
Can anyone see what's wrong with my code?
import 'package:flutter/material.dart';
import '../database/LocalDatabase.dart';
import '../model/Player.dart';
class Pitch extends StatefulWidget {
const Pitch({super.key});
#override
_Pitch createState() => _Pitch();
}
class _Pitch extends State<Pitch> {
List<Player> playersList = <Player>[];
Offset positionOne = const Offset(100, 100);
Offset positionTwo = const Offset(200, 100);
#override
Widget build(BuildContext context) {
return Container(
constraints: BoxConstraints.expand(),
color: Colors.white,
child: Stack(
alignment: Alignment.center,
children: [
Image.asset("assets/images/pitch.png"),
Positioned(
left: positionOne.dx,
top: positionOne.dy,
child: Draggable(
feedback: playerImage(
"https://cdn.sofifa.net/players/158/023/22_120.png"),
childWhenDragging: Opacity(
opacity: 0,
child: playerImage(
"https://cdn.sofifa.net/players/158/023/22_120.png"),
),
child: playerImage(
"https://cdn.sofifa.net/players/158/023/22_120.png"),
onDragEnd: (details) {
setState(() {
positionOne = details.offset;
});
},
),
),
Positioned(
left: positionTwo.dx,
top: positionTwo.dy,
child: Draggable(
feedback: playerImage(
"https://cdn.sofifa.com/players/notfound_0_120.png"),
childWhenDragging: Opacity(
opacity: 0,
child: playerImage(
"https://cdn.sofifa.com/players/notfound_0_120.png"),
),
child: playerImage(
"https://cdn.sofifa.com/players/notfound_0_120.png"),
onDragEnd: (details) {
setState(() {
positionTwo = details.offset;
});
},
),
)
],
),
);
}
#override
void initState() {
super.initState();
// getPlayers().then((value) {
// debugPrint("playerfromdb: ${value[0].name}");
// });
}
Future<List<Player>> getPlayers() async {
final database =
await $FloorLocalDatabase.databaseBuilder('local_database.db').build();
final playerDao = database.playerDao;
final players = playerDao.getAllPlayers();
return players;
}
Widget playerImage(String imageUrl) {
return GestureDetector(
onTap: () => showDialog<void>(
context: context,
builder: (BuildContext context) => Dialog(
backgroundColor: Colors.white,
child: SizedBox(
height: 300,
width: 300,
child: Column(
children: [
const SizedBox(height: 24),
Container(
margin: const EdgeInsets.only(left: 16),
height: 48,
child: TextField(
decoration: const InputDecoration.collapsed(
hintText: 'Enter player name',
focusColor: Colors.transparent),
onChanged: (value) {
searchPlayers(value);
},
)),
const SizedBox(height: 24),
SizedBox(
height: 200,
child: ListView.builder(
itemCount: playersList.length,
itemBuilder: (context, index) {
return playerItem(playersList.elementAt(index));
}),
),
],
)))),
child: SizedBox(
width: 48,
height: 48,
child: Image.network(imageUrl),
));
}
Widget playerItem(Player? player) {
return Container(
height: 48,
margin: const EdgeInsets.all(8),
padding: const EdgeInsets.only(left: 8, right: 8),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: const [BoxShadow(blurRadius: 8)]),
child: Row(
children: [
SizedBox(
height: 36,
width: 36,
child: Image.network(player?.playerImageUrl ?? "")),
const SizedBox(width: 8),
Text(player?.name ?? "")
],
),
);
}
Future<void> searchPlayers(String query) async {
final database = await $FloorLocalDatabase
.databaseBuilder('local_database.db')
.build();
final playerDao = database.playerDao;
// await List<Player> filteredPlayers =
playerDao.searchPlayers(query).then((value) {
setState(() => playersList = value);
debugPrint(value[0].name);
});
}
}

Because your put ListView.builder in Dialog it will create a new stack and a new stack can't rerender from another stack
You can change your code with create a new stateful widget for dialogs
import 'package:flutter/material.dart';
import '../database/LocalDatabase.dart';
import '../model/Player.dart';
class Pitch extends StatefulWidget {
const Pitch({super.key});
#override
_Pitch createState() => _Pitch();
}
class _Pitch extends State<Pitch> {
Offset positionOne = const Offset(100, 100);
Offset positionTwo = const Offset(200, 100);
#override
Widget build(BuildContext context) {
return Container(
constraints: BoxConstraints.expand(),
color: Colors.white,
child: Stack(
alignment: Alignment.center,
children: [
Image.asset("assets/images/pitch.png"),
Positioned(
left: positionOne.dx,
top: positionOne.dy,
child: Draggable(
feedback: playerImage(
"https://cdn.sofifa.net/players/158/023/22_120.png"),
childWhenDragging: Opacity(
opacity: 0,
child: playerImage(
"https://cdn.sofifa.net/players/158/023/22_120.png"),
),
child: playerImage(
"https://cdn.sofifa.net/players/158/023/22_120.png"),
onDragEnd: (details) {
setState(() {
positionOne = details.offset;
});
},
),
),
Positioned(
left: positionTwo.dx,
top: positionTwo.dy,
child: Draggable(
feedback: playerImage(
"https://cdn.sofifa.com/players/notfound_0_120.png"),
childWhenDragging: Opacity(
opacity: 0,
child: playerImage(
"https://cdn.sofifa.com/players/notfound_0_120.png"),
),
child: playerImage(
"https://cdn.sofifa.com/players/notfound_0_120.png"),
onDragEnd: (details) {
setState(() {
positionTwo = details.offset;
});
},
),
)
],
),
);
}
Widget playerImage(String imageUrl) {
return GestureDetector(
onTap: () => showDialog<void>(
context: context,
builder: (BuildContext context) => Dialog(
backgroundColor: Colors.white,
child: const PlayersDialog(),
),
),
child: SizedBox(
width: 48,
height: 48,
child: Image.network(imageUrl),
),
);
}
}
class PlayersDialog extends StatefulWidget {
const PlayersDialog({super.key});
#override
_PlayersDialog createState() => _PlayersDialog();
}
class _PlayersDialog extends State<PlayersDialog> {
List<Player> playersList = <Player>[];
Future<void> searchPlayers(String query) async {
final database =
await $FloorLocalDatabase.databaseBuilder('local_database.db').build();
final playerDao = database.playerDao;
// await List<Player> filteredPlayers =
playerDao.searchPlayers(query).then((value) {
setState(() => playersList = value);
debugPrint(value[0].name);
});
}
#override
Widget build(BuildContext context) {
return SizedBox(
height: 300,
width: 300,
child: Column(
children: [
const SizedBox(height: 24),
Container(
margin: const EdgeInsets.only(left: 16),
height: 48,
child: TextField(
decoration: const InputDecoration.collapsed(
hintText: 'Enter player name',
focusColor: Colors.transparent),
onChanged: (value) {
searchPlayers(value);
},
)),
const SizedBox(height: 24),
SizedBox(
height: 200,
child: ListView.builder(
itemCount: playersList.length,
itemBuilder: (context, index) {
return playerItem(playersList.elementAt(index));
},
),
),
],
),
);
}
Widget playerItem(Player? player) {
return Container(
height: 48,
margin: const EdgeInsets.all(8),
padding: const EdgeInsets.only(left: 8, right: 8),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: const [BoxShadow(blurRadius: 8)]),
child: Row(
children: [
SizedBox(
height: 36,
width: 36,
child: Image.network(player?.playerImageUrl ?? "")),
const SizedBox(width: 8),
Text(player?.name ?? "")
],
),
);
}
#override
void initState() {
super.initState();
// getPlayers().then((value) {
// debugPrint("playerfromdb: ${value[0].name}");
// });
}
Future<List<Player>> getPlayers() async {
final database =
await $FloorLocalDatabase.databaseBuilder('local_database.db').build();
final playerDao = database.playerDao;
final players = playerDao.getAllPlayers();
return players;
}
}

Related

Flutter : how to change text data by bottomSheet?

I want to change my text, after selecting by bottomsheet text is not changing when i am refreshing then my text is changing.
how to fix this issue.
This is my code.
import 'package:flutter/material.dart';
import '../../../Utils/GlobalColor/global_color.dart';
import '../../../Utils/GlobalTextStyles/global_text_styles.dart';
class NonVegPizzaPage extends StatefulWidget {
const NonVegPizzaPage({Key? key}) : super(key: key);
#override
State<NonVegPizzaPage> createState() => _NonVegPizzaPageState();
}
class _NonVegPizzaPageState extends State<NonVegPizzaPage> {
String itemSize = "Regular";
List itemSizes = [];
#override
void initState() {
super.initState();
itemSizes = [
{
"sizes": "Regular",
},
{
"sizes": "Medium",
},
{
"sizes": "Large",
},
];
}
sizeBottomSheet() {
showModalBottomSheet(
barrierColor: Colors.transparent,
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(15.0),
topRight: Radius.circular(15.0),
),
),
builder: (context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState1) {
return Container(
decoration: BoxDecoration(
color: MyColor.whiteColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(15.0),
topRight: Radius.circular(15.0),
),
boxShadow: [
BoxShadow(
color: MyColor.greyColor.withOpacity(0.8),
spreadRadius: 5,
blurRadius: 7,
offset: Offset(0, 0), // changes position of shadow
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding:
const EdgeInsets.only(left: 43.0, top: 10, bottom: 11),
child: Text(
"Select Size",
style: textStyleWith14500(MyColor.blackColor2),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: itemSizes.length,
itemBuilder: (_, index) {
return Padding(
padding: EdgeInsets.only(
top: 0,
),
child: InkWell(
onTap: () {
Navigator.pop(context);
setState1(() {
itemSize = itemSizes[index]["sizes"];
});
print(itemSize);
},
child: Container(
margin: EdgeInsets.only(left: 2, right: 2),
height: 36,
width: double.infinity,
color: itemSize == itemSizes[index]["sizes"]
? MyColor.lightBlueColor
: MyColor.whiteColor,
child: Padding(
padding:
const EdgeInsets.only(left: 43.0, top: 10),
child: Text(
"${itemSizes[index]["sizes"]}",
style:
textStyleWith12400(MyColor.blackColor3),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
),
);
}),
],
),
);
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child: Column(crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("$itemSize"), // here
SizedBox(height: 20,),
InkWell(
onTap: () {
sizeBottomSheet();
},
child: Container(
color: Colors.red,
height: 44,
width: 150,
child: Center(child: Text("show bottomSheet")),
),
)
],
),
),
),
);
}
}
showModalBottomSheet is a future method, you can await to close it and then call setState to update the main ui.
sizeBottomSheet() async {
final result = await showModalBottomSheet(
....
onTap: () {
Navigator.pop(context, itemSizes[index]["sizes"]);
.......
//end of showModalBottomSheet
if (result != null) {
itemSize = result;
setState(() {});
}
Or you can do
child: InkWell(
onTap: () {
setState1(() {
itemSize = itemSizes[index]["sizes"];
});
setState(() {});//updating the parent
Navigator.pop(context);
},

setState updating inside StatefulBuilder but not updating when using a method outside

I am having a showMyModalBottomSheet that calls showModalBottomSheet which returns StatefulBuilder that has some widgets. The problem i am having is that if i setState it just works fine but if i setState in a method that i have called it does not work.
Below is my showMyModalBottomSheet method
showMyModalBottomSheet() {
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(25))),
builder: (context) {
return StatefulBuilder(builder: (context, setState) {
return Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom),
child: SingleChildScrollView(
child: Container(
width: double.infinity,
child: Column(children: [
Container(
width: double.infinity,
margin: const EdgeInsets.only(
left: 10, right: 10, top: 20, bottom: 20),
child: Text(
_titleText,
textAlign: TextAlign.center,
style:
TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
),
),
Container(
width: double.infinity,
margin: const EdgeInsets.only(
left: 20, right: 20, top: 10, bottom: 10),
child: TextFormField(
key: _titleFormKey,
decoration: InputDecoration(labelText: "Enter Title"),
controller: _titleController,
validator: (title) {
if (title!.isEmpty) {
return "Please enter title";
} else {
return null;
}
},
),
),
Container(
child: ElevatedButton(
onPressed: () {
if (_titleFormKey.currentState!.validate()) {
// updating title
_titleText = _titleController.text.trim().toString();
}
},
child: Text("Update Title"),
),
)
]),
),)
);
});
});
}
When i am updating my _titleText using below way it just works fine
onPressed: () {
if (_titleFormKey.currentState!.validate()) {
_titleText = _titleController.text.trim().toString();
}
},
But when i am using my method updateTitle like below it does not update
onPressed: () {
if (_titleFormKey.currentState!.validate()) {
updateTitle(_titleController.text.trim().toString());
}
},
And below is my updateTitle method
updateTitle(String title){
setState(() {
_titleText = title;
});
}
Below is my entire code
import 'package:flutter/material.dart';
class MyPage extends StatefulWidget {
const MyPage({Key? key}) : super(key: key);
#override
State<MyPage> createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
String _titleText = "Test Title";
final _titleFormKey = GlobalKey<FormFieldState>();
TextEditingController _titleController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("My Page"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
showMyModalBottomSheet();
},
child: Text("Show Bottom Sheet"),
),
),
);
}
showMyModalBottomSheet() {
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(25))),
builder: (context) {
return StatefulBuilder(builder: (context, setState) {
return Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom),
child: SingleChildScrollView(
child: Container(
width: double.infinity,
child: Column(children: [
Container(
width: double.infinity,
margin: const EdgeInsets.only(
left: 10, right: 10, top: 20, bottom: 20),
child: Text(
_titleText,
textAlign: TextAlign.center,
style:
TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
),
),
Container(
width: double.infinity,
margin: const EdgeInsets.only(
left: 20, right: 20, top: 10, bottom: 10),
child: TextFormField(
key: _titleFormKey,
decoration: InputDecoration(labelText: "Enter Title"),
controller: _titleController,
validator: (title) {
if (title!.isEmpty) {
return "Please enter title";
} else {
return null;
}
},
),
),
Container(
child: ElevatedButton(
onPressed: () {
if (_titleFormKey.currentState!.validate()) {
updateTitle(_titleController.text.trim().toString());
}
},
child: Text("Update Title"),
),
)
]),
),)
);
});
});
}
updateTitle(String title){
setState(() {
_titleText = title;
});
}
}
The problem is that its not updating text whenever i setState using updateTitle method.
updateTitle is using state class(_MyPageState) setState, that's why it is not updating. You can pass setState of StatefulBuilder while StatefulBuilder's setState is out of scope.
updateTitle(
_titleController.text.trim().toString(),
setState);
And
updateTitle(String title, setState) {
setState(() {
_titleText = title;
});
}

Screen does not update even though build function is running flutter

So I have created a chat app which draws from a pusher client. Whenever there is a new message, the build function does rebuild, and I believe the widget list does change, but there is no update on the screen. How do I fix this ?
Widget build(BuildContext context) {
// print(messageWidgetList.length);
return Scaffold(
backgroundColor: AppColors.lightGrey,
appBar: AppBar(
backgroundColor: Colors.transparent,
title: Text(
messageTo,
style: TextStyle(
color: AppColors.white,
fontSize: 22,
),
),
),
body: Stack(
children: [
Padding(
padding:
const EdgeInsets.only(top: 12, left: 12, right: 12, bottom: 70),
child: ValueListenableBuilder<List<Widget>>(
valueListenable: messageWidgetList,
builder: (context, value, widget) {
print("Updated");
print(value.length);
// print(widget);
return ListView.builder(
// controller: scrollController,
physics: AlwaysScrollableScrollPhysics(),
reverse: true,
addAutomaticKeepAlives: true,
itemCount: value.length,
itemBuilder: (ctx, index) {
// print(index);
return value[index];
},
);
},
),
),
Align(
alignment: Alignment.bottomCenter,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (xFilesImages.isNotEmpty)
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: xFilesImages.map<Widget>((element) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: SizedBox(
height: 100,
width: 80,
child: Image.file(
File(element.path),
frameBuilder:
(ctx, child, frame, wasSynchronouslyLoaded) {
return SizedBox(
width: MediaQuery.of(ctx).size.width,
height: MediaQuery.of(ctx).size.height,
child: Stack(
children: [
Align(
alignment: Alignment.topRight,
child: Container(
height: 25,
width: 25,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColors.lightestGrey,
),
child: FittedBox(
child: GestureDetector(
onTap: () {
xFilesImages.remove(element);
setState(() {});
},
child:
const Icon(Icons.cancel)),
),
),
),
child
],
),
);
},
),
),
);
}).toList(),
),
),
const SizedBox(height: 5),
Container(
height: 60,
width: MediaQuery.of(context).size.width,
child: Padding(
padding:
const EdgeInsets.only(left: 10, bottom: 10, right: 10),
child: Container(
// height: 30,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: AppColors.darkGrey,
),
child: TextFormField(
// expands: true,
style: TextStyle(color: AppColors.white),
focusNode: messageFocusNode,
controller: messageController,
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(
right: 8, left: 8, top: 14),
prefixIcon: InkWell(
onTap: () async {
if (!(await Permission.camera.isGranted)) {
await Permission.camera.request();
await Permission.photos.request();
}
ImagePicker _picker = ImagePicker();
xFilesImages =
await _picker.pickMultiImage() ?? [];
print("Got xFiles");
print(xFilesImages.length);
for (XFile xFile in xFilesImages) {
print(xFile.name);
print(xFile.path);
}
setState(() {});
},
child: Icon(
Icons.attachment,
size: 34,
color: AppColors.lightestGrey,
),
),
suffixIcon: GestureDetector(
onTap: () async {
//TODO: When you wake up, you have implemented picking images. Work on displaying picked images and then sending them
// loading = true;
// messageController.text = '';
if (messageController.text.isNotEmpty ||
xFilesImages.isNotEmpty) {
messageFocusNode.unfocus();
// messageWidgetList.add(sentMessage(
// {"message": messageController.text}));
setState(() {});
print("Sent button clicked");
ApiProvider.sendMessage(
widget.userModel.bearerToken,
widget.senderPhone.phoneNumbers.first,
messageTo,
messageController.text,
xFilesImages);
// loading = false;
messageController.text = '';
xFilesImages = [];
setState(() {});
}
},
child: const Icon(
Icons.send,
size: 30,
color: const Color(0xFF004b77),
),
),
fillColor: AppColors.lightGrey,
hintText: "Enter message...",
hintStyle:
TextStyle(color: AppColors.lightestGrey)),
),
),
),
),
],
),
),
if (loading)
Container(
height: double.infinity,
width: double.infinity,
color: AppColors.lightGrey.withOpacity(0.3),
child: Center(
child: SpinKitChasingDots(
color: AppColors.blue,
)),
)
],
),
);
}
Bad, does not work
static final List<Widget> items= [];
Widget build(BuildContext context) {
return ListView(children: items); // <-- look here
}
Good, does update properly
static final List<Widget> items= [];
Widget build(BuildContext context) {
return ListView(children: [...items]); // <-- look here
}
Grandious with the little extra mile
static final List<Widget> items= [];
Widget build(BuildContext context) {
return ListView(children: <Widget>[...items]); // <-- look here
}
setState needs a brand new object to update properly. It does not look into a List like here if something changed in there.

I try to use Lifting State Up in Flutter, but I get "Closure call with mismatched arguments"

I pass Data from parent to child, and one of these parameters is a Fn. What I need is to return data from child to parent again, but I get an error as below:
[ Closure call with mismatched arguments: function '_ReservationBranchesSlotsScreenState._changeData' Receiver: Closure:
({String areaId, int coastPerPerson, int selectedBranchChecked, String
formatted, bool showCreateReservationButton, bool isExpanded, int
expandedIndex}) => void from Function '_changeData#176179245':. Tried
calling: _ReservationBranchesSlotsScreenState._changeData(areaId:
"d98a4e0e-d408-40c8-b387-9a405683a389", coastPerPerson: 0,
expandedIndex: -1, formatted: null, isExpanded: false,
selectedBrnachChecked: 0, showCreateReservationButton: false) Found:
_ReservationBranchesSlotsScreenState._changeData({String areaId, int coastPerPerson, int selectedBranchChecked, String formatted, bool
showCreateReservationButton, bool isExpanded, int expandedIndex}) =>
void ]
I create a Function in the parent Widget that do some actions, and pass this Fn to the child widget as below.
This is the parent widget Fn:
void _changeData({
String areaId,
int coastPerPerson,
int selectedBranchChecked,
String formatted,
bool showCreateReservationButton,
bool isExpanded,
int expandedIndex
}){
setState(() {
_areaId = areaId;
_coastPerPerson = coastPerPerson;
_selectedBranchChecked = selectedBranchChecked;
_formatted = formatted;
_showCreateReservationButton = showCreateReservationButton;
_isExpanded = isExpanded;
_expandedIndex = expandedIndex;
});
}
also below when I pass this Fn to the child widget:
SelectBranchWidget(
branches: _branches,
coastPerPerson: _coastPerPerson,
areaId: _areaId,
selectedBranchChecked: _selectedBranchChecked,
formatted: _formatted,
isExpanded: _isExpanded,
showCreateReservationButton: _showCreateReservationButton,
expandedIndex: _expandedIndex,
***changeData: _changeData,***
),
and here is the child widget which I need to return a Data from it to the parent again:
class SelectBranchWidget extends StatefulWidget {
List<RestaurantBranch> branches;
int coastPerPerson;
String areaId;
int selectedBranchChecked;
String formatted;
bool isExpanded;
bool showCreateReservationButton;
int expandedIndex;
Function changeData;
SelectBranchWidget(
{this.branches,
this.coastPerPerson,
this.areaId,
this.selectedBranchChecked,
this.formatted,
this.isExpanded,
this.showCreateReservationButton,
this.expandedIndex,
this.changeData,
});
#override
_SelectBranchWidgetState createState() => _SelectBranchWidgetState();
}
class _SelectBranchWidgetState extends State<SelectBranchWidget> {
void _changedValues(int i, int branchAreaIndex) {
widget.coastPerPerson = widget.branches[i].branchAreas[branchAreaIndex].costPerSeat;
widget.areaId = widget.branches[i].branchAreas[branchAreaIndex].guid;
print('areaId IS ${widget.areaId}');
widget.selectedBranchChecked = i;
widget.formatted = null;
widget.isExpanded = false;
widget.showCreateReservationButton = false;
widget.expandedIndex = -1;
widget.changeData(
areaId: widget.areaId,
coastPerPerson: widget.coastPerPerson,
selectedBrnachChecked:widget.selectedBranchChecked,
formatted: widget.formatted,
showCreateReservationButton:widget.showCreateReservationButton,
isExpanded:widget.isExpanded,
expandedIndex: widget.expandedIndex
);
}
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).primaryColorDark,
borderRadius: BorderRadius.all(Radius.circular(10))),
padding: EdgeInsets.all(8),
margin: EdgeInsets.only(left: 16, right: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
'Select Branch',
style: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold),
),
),
SizedBox(
height: 8,
),
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
'Number of branches = ${widget.branches.length}',
style: TextStyle(color: Colors.grey, fontSize: 10),
),
),
),
SizedBox(
height: 16,
),
ConstrainedBox(
constraints:
BoxConstraints(maxHeight: 230, maxWidth: double.infinity),
child: ListView.builder(
shrinkWrap: true,
itemCount: widget.branches.length,
itemBuilder: (ctx, i) {
return GestureDetector(
onTap: () {
if (widget.branches[i].branchAreas.length == 1) {
_changedValues(i, 0);
return;
}
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Theme.of(context).backgroundColor,
title: Text(
'Select Area',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold),
),
content: Container(
decoration: BoxDecoration(
color: Theme.of(context).primaryColorDark,
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
// height: (_branches[i].branchAreas.length == 1)
// ?70
// : (_branches[i].branchAreas.length == 2)
// ? 100
// :150,
width: 100.0,
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: 120),
child: ListView.builder(
shrinkWrap: true,
itemCount:
widget.branches[i].branchAreas.length,
itemBuilder:
(BuildContext context, int index) {
return GestureDetector(
onTap: () {
_changedValues(i, index);
Navigator.of(context).pop();
},
child: Padding(
padding: const EdgeInsets.only(
top: 16, left: 36.0, right: 36),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
widget.branches[i]
.branchAreas[index].name,
style: TextStyle(
color: Colors.white),
),
Divider(
color: Colors.grey,
thickness: 1,
),
],
),
),
);
},
),
),
),
);
});
},
child: Card(
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
color: Theme.of(context).backgroundColor,
child: Container(
padding: EdgeInsets.all(20),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.branches[i].branchDistrict.name,
style: TextStyle(color: Colors.white, fontSize: 16),
),
(widget.selectedBranchChecked == i)
? Container(
height: 40,
width: 40,
child: Image.asset(
getAssetsName(AssetsImage.checkIcon),
fit: BoxFit.cover,
),
)
: Container(
padding: EdgeInsets.only(top: 5, bottom: 5),
height: 40,
width: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: SizedBox(),
),
],
),
),
),
);
},
),
),
],
),
);
}
}
You can use two approaches to achieve this.
User first one if you want to return from a route (from next screen to previous one)
Use second Approach if you want to make changes to Parent Widget from its child widget on same screen.
1st Approach (Returning an object from route)
Create a Model of data you are passing.
class MyModel {
int coastPerPerson;
String areaId;
int selectedBranchChecked;
String formatted;
bool isExpanded;
bool showCreateReservationButton;
int expandedIndex;
Function changeData;
MyModel({
this.areaId,
this.coastPerPerson,
this.selectedBranchChecked,
this.formatted ,
this.isExpanded,
this.showCreateReservationButton,
this.expandedIndex,
});
}
I'm assuming that You Parent Looks like this. And you're navigating from Parent Page to
SelectBranchWidget page
class YourParentWidget extends StatefulWidget {
YourParentWidget({Key key}) : super(key: key);
#override
_YourParentWidgetState createState() => _YourParentWidgetState();
}
class _YourParentWidgetState extends State<YourParentWidget> {
navigateAndChangeData(){
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return SelectBranchWidget();
})).then((value) {
///Use this to get value from next screen to PArent Screen
if(value != null) {
var model = value as MyModel;
//Now you have access to all returning values
//model.areaId
//model.coastPerPerson
//model.selectedBranchChecked
//...
///Make changes accordingly
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
///Your UI Conponents....
);
}
}
Now In SelectBranchWidget Widget, What you need to do in _changedValues function is to return model values. i.e.
class SelectBranchWidget extends StatefulWidget {
List<RestaurantBranch> branches;
int coastPerPerson;
String areaId;
int selectedBranchChecked;
String formatted;
bool isExpanded;
bool showCreateReservationButton;
int expandedIndex;
Function changeData;
SelectBranchWidget(
{this.branches,
this.coastPerPerson,
this.areaId,
this.selectedBranchChecked,
this.formatted,
this.isExpanded,
this.showCreateReservationButton,
this.expandedIndex,
this.changeData,
});
#override
_SelectBranchWidgetState createState() => _SelectBranchWidgetState();
}
class _SelectBranchWidgetState extends State<SelectBranchWidget> {
void _changedValues(int i, int branchAreaIndex) {
MyModel model = MyModel(
areaId: widget.branches[i].branchAreas[branchAreaIndex].guid,
coastPerPerson: widget.branches[i].branchAreas[branchAreaIndex].costPerSeat,
selectedBranchChecked: 1,
formatted: null,
isExpanded: false,
showCreateReservationButton: false,
expandedIndex: -1
);
Navigator.of(context).pop(model);
}
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).primaryColorDark,
borderRadius: BorderRadius.all(Radius.circular(10))),
padding: EdgeInsets.all(8),
margin: EdgeInsets.only(left: 16, right: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
'Select Branch',
style: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold),
),
),
SizedBox(
height: 8,
),
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
'Number of branches = ${widget.branches.length}',
style: TextStyle(color: Colors.grey, fontSize: 10),
),
),
),
SizedBox(
height: 16,
),
ConstrainedBox(
constraints:
BoxConstraints(maxHeight: 230, maxWidth: double.infinity),
child: ListView.builder(
shrinkWrap: true,
itemCount: widget.branches.length,
itemBuilder: (ctx, i) {
return GestureDetector(
onTap: () {
if (widget.branches[i].branchAreas.length == 1) {
_changedValues(i, 0);
return;
}
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Theme.of(context).backgroundColor,
title: Text(
'Select Area',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold),
),
content: Container(
decoration: BoxDecoration(
color: Theme.of(context).primaryColorDark,
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
// height: (_branches[i].branchAreas.length == 1)
// ?70
// : (_branches[i].branchAreas.length == 2)
// ? 100
// :150,
width: 100.0,
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: 120),
child: ListView.builder(
shrinkWrap: true,
itemCount:
widget.branches[i].branchAreas.length,
itemBuilder:
(BuildContext context, int index) {
return GestureDetector(
onTap: () {
_changedValues(i, index);
Navigator.of(context).pop();
},
child: Padding(
padding: const EdgeInsets.only(
top: 16, left: 36.0, right: 36),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
widget.branches[i]
.branchAreas[index].name,
style: TextStyle(
color: Colors.white),
),
Divider(
color: Colors.grey,
thickness: 1,
),
],
),
),
);
},
),
),
),
);
});
},
child: Card(
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
color: Theme.of(context).backgroundColor,
child: Container(
padding: EdgeInsets.all(20),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.branches[i].branchDistrict.name,
style: TextStyle(color: Colors.white, fontSize: 16),
),
(widget.selectedBranchChecked == i)
? Container(
height: 40,
width: 40,
child: Image.asset(
getAssetsName(AssetsImage.checkIcon),
fit: BoxFit.cover,
),
)
: Container(
padding: EdgeInsets.only(top: 5, bottom: 5),
height: 40,
width: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: SizedBox(),
),
],
),
),
),
);
},
),
),
],
),
);
}
}
2nd Approach (Making changes to Parent Widget while staying on child Widget)
Make your Parent Widget's State Class and changeData function public (i.e. remove underscore before the state class name)
i.e.
class YourParentWidget extends StatefulWidget {
YourParentWidget({Key key}) : super(key: key);
#override
YourParentWidgetState createState() => YourParentWidgetState();
}
class YourParentWidgetState extends State<YourParentWidget> {
String _areaId;
int _coastPerPerson;
int _selectedBranchChecked;
String _formatted;
bool _showCreateReservationButton;
bool _isExpanded;
int _expandedIndex;
void changeData({
String areaId,
int coastPerPerson,
int selectedBranchChecked,
String formatted,
bool showCreateReservationButton,
bool isExpanded,
int expandedIndex
}){
setState(() {
_areaId = areaId;
_coastPerPerson = coastPerPerson;
_selectedBranchChecked = selectedBranchChecked;
_formatted = formatted;
_showCreateReservationButton = showCreateReservationButton;
_isExpanded = isExpanded;
_expandedIndex = expandedIndex;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
///Your UI Conponents....
);
}
}
Then Pass widget.key fro YourParentWidget to yor SelectBranchWidget (If you'reusing it inside your UI of YourParentWidget) i.e.
return Scaffold(
body: Column(
children: [
///Your UI Conponents....
SelectBranchWidget(parentKey: widget.key,
///... Other PArameters as well
)
],
)
);
Now in Your SelectBranchWidget Widget, do the following
class SelectBranchWidget extends StatefulWidget {
GlobalKey<YourParentWidgetState> parentKey;
List<RestaurantBranch> branches;
int coastPerPerson;
String areaId;
int selectedBranchChecked;
String formatted;
bool isExpanded;
bool showCreateReservationButton;
int expandedIndex;
Function changeData;
SelectBranchWidget(
{
this.parentKey,
this.branches,
this.coastPerPerson,
this.areaId,
this.selectedBranchChecked,
this.formatted,
this.isExpanded,
this.showCreateReservationButton,
this.expandedIndex,
this.changeData,
});
#override
_SelectBranchWidgetState createState() => _SelectBranchWidgetState();
}
class _SelectBranchWidgetState extends State<SelectBranchWidget> {
void _changedValues(int i, int branchAreaIndex) {
widget.coastPerPerson = widget.branches[i].branchAreas[branchAreaIndex].costPerSeat;
widget.areaId = widget.branches[i].branchAreas[branchAreaIndex].guid;
print('areaId IS ${widget.areaId}');
widget.selectedBranchChecked = i;
widget.formatted = null;
widget.isExpanded = false;
widget.showCreateReservationButton = false;
widget.expandedIndex = -1;
///Then do this to make changes to your parent widget's state
widget.parentKey.currentState.changeData(
areaId: widget.areaId,
coastPerPerson: widget.coastPerPerson,
selectedBrnachChecked:widget.selectedBranchChecked,
formatted: widget.formatted,
showCreateReservationButton:widget.showCreateReservationButton,
isExpanded:widget.isExpanded,
expandedIndex: widget.expandedIndex
);
}
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).primaryColorDark,
borderRadius: BorderRadius.all(Radius.circular(10))),
padding: EdgeInsets.all(8),
margin: EdgeInsets.only(left: 16, right: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
'Select Branch',
style: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold),
),
),
SizedBox(
height: 8,
),
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
'Number of branches = ${widget.branches.length}',
style: TextStyle(color: Colors.grey, fontSize: 10),
),
),
),
SizedBox(
height: 16,
),
ConstrainedBox(
constraints:
BoxConstraints(maxHeight: 230, maxWidth: double.infinity),
child: ListView.builder(
shrinkWrap: true,
itemCount: widget.branches.length,
itemBuilder: (ctx, i) {
return GestureDetector(
onTap: () {
if (widget.branches[i].branchAreas.length == 1) {
_changedValues(i, 0);
return;
}
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Theme.of(context).backgroundColor,
title: Text(
'Select Area',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold),
),
content: Container(
decoration: BoxDecoration(
color: Theme.of(context).primaryColorDark,
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
// height: (_branches[i].branchAreas.length == 1)
// ?70
// : (_branches[i].branchAreas.length == 2)
// ? 100
// :150,
width: 100.0,
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: 120),
child: ListView.builder(
shrinkWrap: true,
itemCount:
widget.branches[i].branchAreas.length,
itemBuilder:
(BuildContext context, int index) {
return GestureDetector(
onTap: () {
_changedValues(i, index);
Navigator.of(context).pop();
},
child: Padding(
padding: const EdgeInsets.only(
top: 16, left: 36.0, right: 36),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
widget.branches[i]
.branchAreas[index].name,
style: TextStyle(
color: Colors.white),
),
Divider(
color: Colors.grey,
thickness: 1,
),
],
),
),
);
},
),
),
),
);
});
},
child: Card(
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
color: Theme.of(context).backgroundColor,
child: Container(
padding: EdgeInsets.all(20),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.branches[i].branchDistrict.name,
style: TextStyle(color: Colors.white, fontSize: 16),
),
(widget.selectedBranchChecked == i)
? Container(
height: 40,
width: 40,
child: Image.asset(
getAssetsName(AssetsImage.checkIcon),
fit: BoxFit.cover,
),
)
: Container(
padding: EdgeInsets.only(top: 5, bottom: 5),
height: 40,
width: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: SizedBox(),
),
],
),
),
),
);
},
),
),
],
),
);
}
}
Edit: You just need to call (wherever you're initiating it from) your YourParentWidget like YourParentWidget(key: GlobalKey<YourParentWidgetState>())
Pardon me if any typo occurs

ListView give so many junks on page change in Flutter

I'm a guy from android development and I'm in love with flutter. But I have experienced some issues with ListView, with this kind of problem is like the Main Thread is doing a lot of jobs and I resolve this using AsyncTask on android but in Flutter, this is giving to me in official documents.
Since Flutter is single threaded and runs an event loop (like
Node.js), you don’t have to worry about thread management or spawning
background threads. If you’re doing I/O-bound work, such as disk
access or a network call, then you can safely use async/await and
you’re all set. If, on the other hand, you need to do computationally
intensive work that keeps the CPU busy, you want to move it to an
Isolate to avoid blocking the event loop, like you would keep any sort
of work out of the main thread in Android.
In my home it is a stateful widget and here is the code :
return new WillPopScope(
onWillPop: () async => false,
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
automaticallyImplyLeading: false,
centerTitle: true,
backgroundColor: ThemeColor.AppPrimaryColor, // status bar color
brightness: Brightness.dark, // status bar brightness
elevation: 5,
title: AppBarContent(),
),
drawer: MenuBar(),
body: Container(
child: SingleChildScrollView(
controller: _scrollController,
child: Column(
children: <Widget>[
Column(
children: <Widget>[
Container(
child: CategorySlider(),
),
SizedBox(
height: screenHeight * 0.02,
),
Container(
child: BannerSlider(),
),
SizedBox(
height: screenHeight * 0.03,
),
Container(
child: Container(
margin: EdgeInsets.only(left: 20.0),
child: Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Text(
'Vitrines em destaque',
style: CustomFontStyle.titleList(),
),
],
),
),
),
Padding(
padding: const EdgeInsets.all(9.0),
child: CompanyList(true),
),
],
),
],
),
),
),
),
);
In bottom I have this line that calls a widget CompanyList:
Padding(
padding: const EdgeInsets.all(9.0),
child: CompanyList(true),
),
And this is my CompanyList class
class CompanyList extends StatefulWidget {
final filtered;
final isShrink;
final MasterCategoryModel masterCategoryModel;
CompanyList(
[this.isShrink = false, this.masterCategoryModel, this.filtered = false]);
// const CompanyList({Key key}) : super(key: key);
#override
State<StatefulWidget> createState() => new CompanyListState();
}
class CompanyListState extends State<CompanyList> {
CompanyController companyController = new CompanyController();
CompanyService companyService;
Map<String, dynamic> companyList = {};
bool loadingData = true;
bool loadData = true;
bool loadMoreData = false;
int pageCounter = 1;
#override
void initState() {
super.initState();
companyService = Provider.of<CompanyService>(context, listen: false);
companyService.loadMoreData$.listen((value) {
if (value) {
if (loadData) {
loadData = false;
getMoreData();
}
}
});
widget.filtered
? getCompanyByMsCateg(widget.masterCategoryModel)
: getRandomActiveCompanys();
}
#override
void dispose() {
super.dispose();
}
getRandomActiveCompanys() {
setState(() {
loadingData = true;
});
companyController.getRandomActiveCompanys(pageCounter).then((value) {
setState(() {
pageCounter++;
companyList = value;
loadingData = false;
});
}).catchError((error) {
print(error);
setState(() {
loadingData = false;
});
});
}
getMoreData() {
setState(() {
loadMoreData = true;
});
companyController.getRandomActiveCompanys(pageCounter).then((value) {
setState(() {
pageCounter++;
companyList['data'] = companyList['data']..addAll(value['data']);
loadMoreData = false;
});
}).catchError((error) {
print(error);
setState(() {
loadMoreData = false;
});
});
}
getCompanyByMsCateg(MasterCategoryModel masterCategoryModel) {
setState(() {
loadingData = true;
});
companyController
.getCompanysByMsgCateg(masterCategoryModel.master_category_id)
.then((value) {
setState(() {
companyList.addAll(value);
loadingData = false;
});
}).catchError((error) {
print(error);
setState(() {
loadingData = false;
});
});
}
getMoreCompanyData() {
setState(() {
// companyList.add(totalCompanyList[i]);
});
Future.delayed(const Duration(milliseconds: 3000), () {
loadData = true;
});
}
getCompanyData(company) {
Navigator.push(
context,
PageTransition(
type: PageTransitionType.rightToLeft,
child: CompanyScreen(
company: company,
),
),
);
}
#override
Widget build(BuildContext context) {
final double screenHeight = MediaQuery.of(context).size.height;
final double screenWidth = MediaQuery.of(context).size.width;
return !loadingData
? ListView.builder(
addAutomaticKeepAlives: true,
shrinkWrap: widget.isShrink ? true : false,
physics: ClampingScrollPhysics(),
padding: const EdgeInsets.all(8),
itemCount: companyList['data'].length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () => getCompanyData(companyList['data'][index]),
child: Container(
child: Column(
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Flexible(
flex: 2,
child: Container(
height: screenHeight * 0.12,
decoration: new BoxDecoration(
border: Border.all(
color: ThemeColor.AppBorderGrey,
),
borderRadius: new BorderRadius.only(
topLeft: const Radius.circular(5.0),
bottomLeft: const Radius.circular(5.0),
),
),
child: Center(
child:
companyList['data'][index]['image'] != null
? Container(
height: 45,
width: 45,
child: ClipRRect(
borderRadius: BorderRadius.all(
Radius.circular(20.0)),
child: Image.network(
'${MainConfig.storageDoc + companyList['data'][index]['image']}',
width: 42,
height: 42,
cacheWidth: 42,
cacheHeight: 42,
fit: BoxFit.cover,
),
),
)
: new Container(
width: screenWidth * 0.13,
height: screenWidth * 0.13,
decoration: new BoxDecoration(
shape: BoxShape.circle,
image: new DecorationImage(
fit: BoxFit.fill,
image: AssetImage(
'assets/icons/error/no_image_v2.png'),
),
),
),
),
),
),
Flexible(
flex: 3,
child: Container(
height: screenHeight * 0.12,
decoration: new BoxDecoration(
border: Border.all(
color: ThemeColor.AppBorderGrey,
),
borderRadius: new BorderRadius.only(
topRight: const Radius.circular(5.0),
bottomRight: const Radius.circular(5.0),
),
),
child: Center(
child: Text(
'${companyList['data'][index]['storename']}',
),
),
),
),
],
),
SizedBox(
height: 10.0,
),
],
),
),
);
})
: Center(
child: Column(
children: <Widget>[
SizedBox(
height: 10.0,
),
Center(
child: SpinKitThreeBounce(
color: ThemeColor.AppPrimaryColor,
size: 30.0,
),
),
],
),
);
}
}
If I change page and go back, my application always junk and close so I make some research and find that problem was a problem with CacheImages so in GitHub I find a solution for put a fixed cacheWidth and cacheHeight and this make my app don't crash but have junk. So I'm thinking of isolating the function that load this widget. What I must do?
child: Image.network(
'${MainConfig.storageDoc + companyList['data'][index]['image']}',
width: 42,
height: 42,
cacheWidth: 42,
cacheHeight: 42,
fit: BoxFit.cover,
),