Here is my code. I am fetching the value TotalVotes from Firebase and showing its value in a Text-widget and I wanted to increase TotalVotes count when anyone hit button and update the Text widget. But I'm not able to update the Text-widget, and the value of the Text-widget updates only after refreshing the page.
Row(
children: <Widget>[
Text("Total Votes: $TotalVotes",
style: TextStyle(color: Colors.white, fontSize: 12)),
SizedBox(width: 30),
FadeAnimation(
2,
Container(
decoration: BoxDecoration(
color: Color(0xFFF1f94aa).withOpacity(.6),
borderRadius: BorderRadius.circular(8)),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: FadeAnimation(
3,
InkWell(
child: ItemIcon(Icons.favorite_border, " Vote"),
onTap: () {
DatabaseReference database = FirebaseDatabase
.instance
.reference()
.child("Vote")
.child(Name);
database.once().then((DataSnapshot dataSnapshot) {
print(dataSnapshot.value['TotalVotes']);
String totalvote = (int.parse(
dataSnapshot.value['TotalVotes']) +
1)
.toString();
database.child("TotalVotes").set(totalvote);
setState(() {
TotalVotes = totalvote;
});
Fluttertoast.showToast(
msg: 'Voted Successfully');
});
},
)),
)),
),
],
),
Whole Widget Code
Widget VoteForTomorrowUi(String ImageUrl,String Name,String Price,String TotalVotes,)<------------------------------------
{
return
FadeAnimation(2,Container(
margin: EdgeInsets.only(right: 20),
height: 210,
width: 220,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Color(0xFFF082938)
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: Container(
width: 220,
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
image: DecorationImage(
image: NetworkImage(ImageUrl),
fit: BoxFit.fill
)
),
),
),
Container(
margin: EdgeInsets.all(10),
height: 51,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("Price: "+Price, style: TextStyle(
color: Colors.white,
fontSize: 12
)),
SizedBox(height: 5),
Row(
children: <Widget>[
Text("Total Votes: $TotalVotes" ,style: TextStyle(
color: Colors.white,
fontSize: 12
)),
SizedBox(width: 30),
FadeAnimation(2, Container(
decoration: BoxDecoration(color:Color(0xFFF1f94aa).withOpacity(.6),borderRadius: BorderRadius.circular(8)
),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: FadeAnimation(3,InkWell(
child: beacheItemIcon(Icons.favorite_border, " Vote"),
onTap: (){
DatabaseReference database = FirebaseDatabase.instance.reference()
.child("VoteForTomorrow").child(Name);
database.once().then((DataSnapshot dataSnapshot){
print(dataSnapshot.value['TotalVotes']);
String totalvote = (int.parse(dataSnapshot.value['TotalVotes'])+1).toString();
database.child("TotalVotes").set(totalvote);
setState(() {
TotalVotes = totalvote; <--------------------------
});
Fluttertoast.showToast(msg: 'Voted Successfully');
});
},
)
),
)),
),
],
)
])
)
],
),
) );
}
Fade Animation
class FadeAnimation extends StatelessWidget {
final double delay;
final Widget child;
FadeAnimation(this.delay, this.child);
#override
Widget build(BuildContext context) {
final tween = MultiTrackTween([
Track("opacity").add(Duration(milliseconds: 500), Tween(begin: 0.0, end: 1.0)),
Track("translateY").add(
Duration(milliseconds: 500), Tween(begin: -130.0, end: 0.0),
curve: Curves.easeOut)
]);
return ControlledAnimation(
delay: Duration(milliseconds: (500 * delay).round()),
duration: tween.duration,
tween: tween,
child: child,
builderWithChild: (context, child, animation) => Opacity(
opacity: animation["opacity"],
child: Transform.translate(
offset: Offset(0, animation["translateY"]),
child: child
),
),
);
}
}
Here is TotalVotes
void initState() {
super.initState();
DatabaseReference postRef = FirebaseDatabase.instance.reference().child("Category");
DatabaseReference postRef2 = FirebaseDatabase.instance.reference().child("VoteForTomorrow");
postRef2.once().then((DataSnapshot dataSnapshot2){
var Keys = dataSnapshot2.value.keys;
var Datas = dataSnapshot2.value;
voteForTomorrowList.clear();
for(var individualKey in Keys)
{
VoteForTomorrow voteForTomorrow = new VoteForTomorrow(
Datas[individualKey]['ImageUrl'],
Datas[individualKey]['Name'],
Datas[individualKey]['Price'],
Datas[individualKey]['TotalVotes'],
);
voteForTomorrowList.add(voteForTomorrow);
}
setState(() {
});
});
And this is how I'm calling the votefortommorow
VoteForTomorrowUi(voteForTomorrowList[index].ImageUrl, voteForTomorrowList[index].Name, voteForTomorrowList[index].Price, voteForTomorrowList[index].TotalVotes);
With out seeing your code as a whole I might be still a little confused but im using your widget and a FutureBuilder and this works
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<DataSnapshot>(
future: FirebaseDatabase.instance
.reference()
.child("VoteForTomorrow")
.child('Name')
.child('TotalVotes')
.once(),
builder:
(BuildContext context, AsyncSnapshot<DataSnapshot> snapshot) {
return Center(
child: VoteForTomorrowUi(
'https://static.photocdn.pt/images/articles/2017/11/29/articles/2017_8/iStock-641093712-min.jpg',
'Name',
'Price',
snapshot.data.value,
),
);
}),
);
}
}
I just put some dummy data in for the other values but the widget is the same the only thing I can think of is if you are reading and writing all your data properly you must be re-initializing TotalVotes every time the widget builds which is why you get the same value
Related
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;
}
}
I created an Item form where you can input title, description,etc...
When selecting the ngo and submitting the entire form, I don't get what I selected.
And when integrating the validator from the FormBuilderField nothing happens, and the item don't get uploaded.
Any idea how I can fix it?
Thank you in advance for your answers!
Here the code:
class NgoSelectorState extends State<NgoSelector> with TickerProviderStateMixin {
String selectedNgo = "Select a charity".tr();
FormFieldState<List<MapEntry<int, NgoDetails>>>? theField;
AnimationController? _controller;
AnimationController? _sizeController;
Animation? _colorAnimation;
bool isSelected = false;
String selectedName = "";
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 100));
_sizeController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 100),
value: 1.0,
lowerBound: 1.0,
upperBound: 1.1,
);
_colorAnimation =
ColorTween(begin: Colors.transparent, end: MyTheme.primary).animate(_controller!);
_controller!.addListener(() {});
_controller!.addStatusListener((status) {
if (status == AnimationStatus.completed) {
setState(() {
isSelected = true;
});
} else if (status == AnimationStatus.dismissed) {
setState(() {
isSelected = false;
});
}
});
}
#override
void dispose() {
super.dispose();
_controller!.dispose();
_sizeController!.dispose();
}
#override
Widget build(BuildContext context) {
return BlocBuilder<NgosBloc, NgosState>(builder: ((context, state) {
if (state is NgosLoadingState) {
return const MyCircularProgressIndicator();
}
if (state is NgosLoadedState) {
List<MapEntry<int, NgoDetails>>? ngosList = List.from(state.ngos.entries);
return FormBuilderField(
name: 'ngoId',
decoration: const InputDecoration(
enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: MyTheme.primary)),
border: OutlineInputBorder(),
filled: true,
fillColor: Color(0xAFBFDCFF),
hintText: "Please select your NGO",
),
builder: (FormFieldState<List<MapEntry<int, NgoDetails>>> field) {
theField = field;
return SizedBox(
height: 300,
child: ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.symmetric(),
physics: const AlwaysScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: ngosList.length,
itemBuilder: (BuildContext context, int index) {
String ngoName = ngosList[index].value.name;
String ngoLogo = ngosList[index].value.logo;
return AnimatedBuilder(
animation: _controller!,
builder: (BuildContext context, _) {
return Padding(
padding: const EdgeInsets.fromLTRB(10, 17, 10, 17),
child: GestureDetector(
key: Key('$index'),
onTap: () {
if (isSelected) {
_controller!.reverse();
_sizeController!.reverse().then(
(value) {
selectedName = ngoName;
_controller!.forward();
_sizeController!.forward();
return null;
},
);
} else {
selectedName = ngoName;
_controller!.forward();
_sizeController!.forward();
}
},
child: selectedName == ngoName
? ScaleTransition(
scale: _sizeController!,
child: Container(
width: 200,
decoration: BoxDecoration(
color: _colorAnimation!.value,
borderRadius:
const BorderRadius.all(Radius.circular(10))),
child: Padding(
padding: const EdgeInsets.all(2.0),
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(8),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
height: 10,
),
Expanded(
child: CachedNetworkImage(
imageUrl: '${ngoLogo}_SMALL.jpg',
placeholder: (context, url) =>
const CircularProgressIndicator(),
errorWidget: (context, url, error) =>
const Icon(Icons.error),
width: 120,
height: 100,
//fit: BoxFit.scaleDown,
),
),
const SizedBox(
height: 15,
),
Expanded(
child: Text(
ngoName,
style: const TextStyle(
color: Color(0xff003087),
fontSize: 16,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
maxLines: 7,
),
),
],
),
),
),
))
: Container(
width: 200,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
height: 10,
),
Expanded(
child: CachedNetworkImage(
imageUrl: '${ngoLogo}_SMALL.jpg',
placeholder: (context, url) =>
const CircularProgressIndicator(),
errorWidget: (context, url, error) =>
const Icon(Icons.error),
//fit: BoxFit.scaleDown,
width: 120,
height: 100,
),
),
const SizedBox(
height: 15,
),
Expanded(
child: Text(
ngoName,
style: const TextStyle(
color: Color(0xff003087),
fontSize: 16,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
overflow: TextOverflow.clip,
maxLines: 7,
),
)
],
),
),
),
);
});
},
),
);
},
/* validator: FormBuilderValidators.compose([
FormBuilderValidators.required(errorText: "Please select your NGO".tr()),
]), */
);
}
if (state is NgosErrorState) {
return Center(child: Text(state.error.toString()));
}
return Container();
}));
}
}
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 am new to flutter animation, I have a stopwatch screen with a container that contains the stopwatch, a container with a list of laps, and 3 floating action buttons that do three things, reset, play-pause and lap. I want the stopwatch container to animate up when clicking on the lap button and then animate down when clicking on the reset button.
class _StopWatchScreenState extends State<StopWatchScreen>
with SingleTickerProviderStateMixin {
final StopWatchTimer _stopWatchTimer = StopWatchTimer();
final _isHours = true;
late AnimationController controller;
bool isPlaying = false;
bool lapClicked = false;
double value = 150.0;
final ScrollController scrollController = ScrollController();
#override
void initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 200));
}
void dispose() {
super.dispose();
_stopWatchTimer.dispose();
scrollController.dispose();
controller.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Column(
children: [
Center(
child: Padding(
padding: EdgeInsets.only(top: value), // Issue here
child: AnimatedContainer(
child: Center(
child: StreamBuilder<int>(
stream: _stopWatchTimer.rawTime,
initialData: _stopWatchTimer.rawTime.value,
builder: (context, snapshot) {
final value = snapshot.data;
final displayTime = StopWatchTimer.getDisplayTime(
value!,
hours: _isHours);
return Text(
displayTime,
style: GoogleFonts.lato(
fontSize: 40.0, color: Colors.white),
);
})),
width: 350.0,
height: 450.0,
decoration: BoxDecoration(
border: Border.all(color: Colors.white, width: 5.0),
shape: BoxShape.circle),
duration: const Duration(milliseconds: 100),
curve: Curves.fastLinearToSlowEaseIn,
),
),
),
Visibility(
visible: lapClicked,
child: Container(
height: 280.0,
child: StreamBuilder<List<StopWatchRecord>>(
stream: _stopWatchTimer.records,
initialData: _stopWatchTimer.records.value,
builder: (context, snapshot) {
final value = snapshot.data;
if (value!.isEmpty) {
return Container();
}
Future.delayed(const Duration(milliseconds: 100), () {
scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: Duration(milliseconds: 200),
curve: Curves.easeOut);
});
return ListView.builder(
physics: BouncingScrollPhysics(),
itemCount: value.length,
itemBuilder: (context, index) {
final data = value[index];
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(left: 20.0),
child: Text(
'Lap ${index + 1}',
style: GoogleFonts.lato(
fontSize: 30.0, color: Colors.white70),
),
),
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: Text('${data.displayTime}',
style: GoogleFonts.lato(
fontSize: 30.0,
color: Colors.white70)),
)
],
),
const Padding(
padding: EdgeInsets.only(top: 5.0),
child: Opacity(
opacity: 0.1,
child: Divider(
thickness: 1.5,
color: Colors.white70,
),
),
)
],
);
},
controller: scrollController,
);
},
),
),
),
],
),
floatingActionButton: Padding(
padding: const EdgeInsets.only(left: 30.0, bottom: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
FloatingActionButton(
onPressed: () {
setState(() {
lapClicked = false;
value = 190.0;
});
controller.reverse();
_stopWatchTimer.onExecute.add(StopWatchExecute.reset);
},
child: const Icon(
Icons.refresh,
size: 30.0,
),
),
FloatingActionButton(
onPressed: () {
toggleIcon();
if (isPlaying == true) {
_stopWatchTimer.onExecute.add(StopWatchExecute.start);
} else {
_stopWatchTimer.onExecute.add(StopWatchExecute.stop);
}
},
child: AnimatedIcon(
icon: AnimatedIcons.play_pause,
size: 35.0,
progress: controller,
),
),
FloatingActionButton(
onPressed: () {
setState(() {
lapClicked = true;
value = 10.0;
});
_stopWatchTimer.onExecute.add(StopWatchExecute.lap);
},
child: const Icon(
Icons.timer,
size: 30.0,
),
),
],
),
),
),
);
}
void toggleIcon() {
setState(() {
isPlaying = !isPlaying;
isPlaying ? controller.forward() : controller.reverse();
});
}
}
Use Animated Container with visibility
see this for animated container
Visibility(
visible:true,//controll to click on your lap button
child: AnimatedContainer(),
)
Animated Container
I used AnimatedContainer to animate the stopwatch and fixed the animation issue by removing the padding property from the container and replacing it with a margin property
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,
),