setState is not updating UI in Flutter - flutter

I have a dialog box which is a stateful widget with multiple tabs wrapped inside Animated Switcher.
Inside it I have a button which on clicked calls a function switchPage() which has a switch statement with each case setting the state of Widget? currentTab variable to a different one.
The problem here arrives when I use this switchPage() function to change the value of currentTab to a different widget also wrapped in a different method getWidget2()
The code for the
Example on DartPad
Try clicking as I suggest...
Click Floating Button.
Click on the first checkbox.
Now click on PAGE 2 button.
Click the second checkbox only once. Now notice the checkbox doesn't work when clicked.
Click PAGE 1 again to go the working checkbox.
Now click on PAGE 2 button again. The Checkbox value and state did change but did not update the time it was clicked, but it has updated when we forcefully re visited the checkbox.
I cannot find solution to this anywhere and I really need the code structure to be as optimized as possible.
Please, if anyone has any explanation or any suggestions, it would be greatly appreciated..
Thanks, in advance.

This is a very rough working example, but you can build upon it.
In the code below, you can see that I have created two methods that handle setting the state of the checkbox for each widget. I have also reset the page once this method is triggered. What this does, is trigger the redrawing of the inner widgets (which is what I explained in my comment).
class _DemoDialogState extends State<DemoDialog> {
Widget? currentTab;
bool valueOfCheckbox1 = false;
bool valueOfCheckbox2 = false;
void switchPage(name) {
switch (name) {
case 1:
setState(() {
currentTab = getWidget1(setCheckbox1State); // <---- Notice the change here
});
break;
case 2:
setState(() {
currentTab = getWidget2(setCheckbox2State); // <---- Notice the change here
});
break;
}
}
void setCheckbox1State(bool? newState) {
if (newState != null) {
setState(() { // <---- Notice the change here
valueOfCheckbox1 = newState;
currentTab = getWidget1(setCheckbox1State);
});
}
}
void setCheckbox2State(bool? newState) {
if (newState != null) {
setState(() { // <---- Notice the change here
valueOfCheckbox2 = newState;
currentTab = getWidget2(setCheckbox2State);
});
}
}
Widget getWidget1(Function(bool?) checkboxFunction) {
return Container(
child: Row(
children: [
Text('Hello from widget 1'),
Checkbox(
value: valueOfCheckbox1,
onChanged: (value) { // <---- Notice the change here
checkboxFunction(value);
})
],
));
}
Widget getWidget2(Function(bool?) checkboxFunction) {
return Container(
child: Row(
children: [
Text('Hello from widget 2'),
Checkbox(
value: valueOfCheckbox2,
onChanged: (value) { // <---- Notice the change here
checkboxFunction(value);
})
],
));
}
#override
Widget build(BuildContext context) {
return Dialog(
child: Container(
width: 280,
height: 600,
color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
AnimatedSwitcher(
duration: Duration(milliseconds: 250),
reverseDuration: Duration(milliseconds: 250),
transitionBuilder: (child, animation) {
var begin = Offset(0.5, 0);
var end = Offset.zero;
var curve = Curves.easeIn;
var tween = Tween(begin: begin, end: end)
.chain(CurveTween(curve: curve));
var begin2 = 0.0;
var end2 = 1.0;
var curve2 = Curves.easeIn;
var tween2 = Tween(begin: begin2, end: end2)
.chain(CurveTween(curve: curve2));
return SlideTransition(
position: animation.drive(tween),
child: FadeTransition(
opacity: animation.drive(tween2), child: child),
);
},
layoutBuilder: (widget, list) {
return Align(
alignment: Alignment.topCenter,
child: widget,
);
}, // <---- Notice the change here
child: currentTab == null ? getWidget1(setCheckbox1State) : currentTab,
),
TextButton(
onPressed: () {
switchPage(1);
},
child: Text('PAGE 1')),
TextButton(
onPressed: () {
switchPage(2);
},
child: Text('PAGE 2'))
],
),
),
);
}
}
This is just an example that makes things work, but it in no way represents how you should build things appropriately. I would look into separating the code into stateless and stateful widgets.

Related

How can i build a form with one textfield at the time?

I was trying to make a form that shows one field at the time that acomplishes the next things:
When pressing a "next" button, it validates the field and let the user go to the next field if everything is ok.
When the user presses the next button in the keyboard it should do the same.
If possible, a sliding animation between fields.
What i came up with was something like this:
List<Widget> questions = [List of textFieldForm]
int pageCount = 0;
GlobalKey _formKey = GlobalKey<FormState>();
pickSection(pageCount) {
return questions[pageCount];
}
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
pickSection(widget.pageCount),
Container(
child: Row(children: [
Container(
width: screenWidth * 0.4,
child: TextButton(
onPressed: widget.pageCount == 0
? null
: () {
setState(() {
widget.pageCount--;
}),
child: Text("BACK"),
),
),
Container(
width: screenWidth * 0.4,
child: TextButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
if (widget.pageCount == questions.length - 1) {
print("FORM ENDS");
} else {
setState(() {
widget.pageCount++;
});
}
}
},
child: Text("NEXT"),
),
),
])
),
]),
),
}
This way i validate every field when it's index is called since i treat every widget as it's own form, but i couldn't think of a way to implement an animation, or make the field advance using the next button on the keyboard(again, because this treats every field as it's own form).
Please help me figure out how to implement this. Animation is not important but using the next button to advance fields is crucial.
Thanks in advance.
You can achieve this using Visibility widget(which is used to show a widget on condition) and maintaining flags.
bool goToNextField = false;
TextButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
goToNextField = true;
if (widget.pageCount == questions.length - 1) {
print("FORM ENDS");
} else {
setState(() {
widget.pageCount++;
});
}
}
},
And in the next widget use a Visibility widget
Widget nextField () {
return Visibility(
visible: goToNextField,
child :
// what you want to show when next button is pressed
);
}

How i can change the icon in the Icon button

I am trying to build a whatsapp clone and when I was working on the changing the camera from front and back. I was trying to change the Icon in the Icon button but it was not changing
I will attach my code file below
Widget bottomIcon({Icon icon,double size,Function onpress}){
return IconButton(
icon: icon,
iconSize: size,
color: Colors.white,
onPressed: onpress,
);
}
Icon iconForcam=Icon(Icons.camera_rear);
#override
Widget build(BuildContext context) {
if (!controller.value.isInitialized) {
return Container();
}
return MaterialApp(
home: Padding(
padding: const EdgeInsets.all(1.0),
child: Stack(
fit: StackFit.expand,
children: [
CameraPreview(controller),
Positioned(
bottom: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(width: 20.0,),
bottomIcon(icon: Icon(Icons.flash_on_rounded),size: 50.0),
SizedBox(width: 20.0,),
bottomIcon(icon: Icon(Icons.fiber_manual_record_outlined),size: 100.0),
SizedBox(width: 30.0,),
bottomIcon(icon: iconForcam,size: 50.0,onpress: (){
setState(() {
if(iconForcam == Icon(Icons.camera_front)){
iconForcam = Icon(Icons.camera_rear);
}else if(iconForcam == Icon(Icons.camera_rear)){
print('rearcam');
iconForcam = Icon(Icons.camera_front);
}
});
//toggleCamera();
}),
],
),
),
],
),
),
);
}
}
I have the doubt that in the if I can comapre two icons in the if Statement.
You can define a boolean variable
//Define
bool _isFront = true;
//Usage
bottomIcon(
icon: _isFront ?
Icons.camera_front : Icons.camera_rear,
size: 50.0, onpress: (){
setState(() {
_isFront = !_isFront;
});
//toggleCamera();
})
I tried like this and got that correct
//Defint
int _oldIndex=0;
Icon iconForcam=Icon(Icons.camera_rear);
//Inside code
bottomIcon(icon: iconForcam,size: 50.0,onpress: (){
setState(() {
if(_oldIndex == 0){
iconForcam = Icon(Icons.camera_rear);
_oldIndex = 1;
}else if(_oldIndex == 1){
//print('rearcam');
iconForcam = Icon(Icons.camera_front);
_oldIndex = 0;
}
});
toggleCamera(_oldIndex);
}),
You can store whether the front camera is on or not in shared_prefernces or database, use provider/stream/bloc to expose this value to UI. Now you can use this package to change icon with animation. Install this package to your flutter project, import it in the file, and then replace icon property of the camera button with the below code:
AdvancedIcon(
icon: Icons.camera_front,
secondaryIcon: Icons.camera_rear,
state: isFrontCameraOn ? AdvancedIconState.primary : AdvancedIconState.secondary,
)
Now the icon will automatically change depending on whether the front camera is on or not.
If you have problem with the database or provider part of this question just let me know.

Can I use Dismissible without actually dismissing the widget?

I'm trying to make a widget that can be swiped to change the currently playing song in a playlist. I'm trying to mimic how other apps do it by letting the user swipe away the current track and the next one coming in. Dismissible is so close to what I actually want. It has a nice animation and I can easily use the onDismissed function to handle the logic. My issue is that Dismissible actually wants to remove the widget from the tree, which I don't want.
The widget I'm swiping gets updated with a StreamBuilder when the song changes, so being able to swipe away the widget to a new one would be perfect. Can I do this or is there a better widget for my needs?
Here's the widget I'm working on:
class NowPlayingBar extends StatelessWidget {
const NowPlayingBar({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder<ScreenState>(
stream: _screenStateStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
final screenState = snapshot.data;
final queue = screenState.queue;
final mediaItem = screenState.mediaItem;
final state = screenState.playbackState;
final processingState =
state?.processingState ?? AudioProcessingState.none;
final playing = state?.playing ?? false;
if (mediaItem != null) {
return Container(
width: MediaQuery.of(context).size.width,
child: Dismissible(
key: Key("NowPlayingBar"),
onDismissed: (direction) {
switch (direction) {
case DismissDirection.startToEnd:
AudioService.skipToNext();
break;
case DismissDirection.endToStart:
AudioService.skipToPrevious();
break;
default:
throw ("Unsupported swipe direction ${direction.toString()} on NowPlayingBar!");
}
},
child: ListTile(
leading: AlbumImage(itemId: mediaItem.id),
title: mediaItem == null ? null : Text(mediaItem.title),
subtitle: mediaItem == null ? null : Text(mediaItem.album),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (playing)
IconButton(
onPressed: () => AudioService.pause(),
icon: Icon(Icons.pause))
else
IconButton(
onPressed: () => AudioService.play(),
icon: Icon(Icons.play_arrow)),
],
),
),
),
);
} else {
return Container(
width: MediaQuery.of(context).size.width,
child: ListTile(
title: Text("Nothing playing..."),
));
}
} else {
return Container(
width: MediaQuery.of(context).size.width,
// The child below looks pretty stupid but it's actually genius.
// I wanted the NowPlayingBar to stay the same length when it doesn't have data
// but I didn't want to actually use a ListTile to tell the user that.
// I use a ListTile to create a box with the right height, and put whatever I want on top.
// I could just make a container with the length of a ListTile, but that value could change in the future.
child: Stack(
alignment: Alignment.center,
children: [
ListTile(),
Text(
"Nothing Playing...",
style: TextStyle(color: Colors.grey, fontSize: 18),
)
],
));
}
},
);
}
}
Here's the effect that I'm going for (although I want the whole ListTile to get swiped, not just the song name): https://i.imgur.com/ZapzpJS.mp4
This can be done by using the confirmDismiss callback instead of the onDismiss callback. To make sure that the widget never actually gets dismissed, you need to return false at the end of the function.
Dismissible(
confirmDismiss: (direction) {
...
return false;
}
)

GestureDetector and exclusive activation while calling onTap in a widget list

I'm trying to create a simple vertical scrolling calendar.
Problem is that I can't manage to find a way to reset back to previous state in case I tap on a new container.
Here's the code:
class CalendarBox extends StatelessWidget {
BoxProprieties boxProprieties = BoxProprieties();
Map item;
CalendarBox({this.item});
bool selected = false;
#override
Widget build(BuildContext context) {
return Consumer<Producer>(
builder: (context, producer, child) => GestureDetector(
onTap: () {
print(item['dateTime']);
selected = producer.selectedState(selected);
},
child: AnimatedContainer(
duration: Duration(milliseconds: 100),
color: selected == true ? Colors.blue : Colors.grey[200],
height: 80,
width: 50,
margin: EdgeInsets.only(top: 5),
child: Column(
children: [
Text(
'${item['dayNum']}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: boxProprieties.dayColor(item['dateTime'])),
),
],
),
),
),
);
}
}
Here's the situation:
One way to achieve it is, create a model for boxes and keep a value current selected block, in your model you will have the index assigned to that block,
int currentSelected =1; //initial value
class Block{
int id;
..
.. // any other stuff
}
now in your code, the check modifies to
block.id == currentSelected ? Colors.blue : Colors.grey[200],
your on tap modifies to
onTap: () {
setState(){
currentSelected = block.id
};
},
If you want to prevent the rebuild of the whole thing every time you can use valueNotifire for current selected block. Hope this gives you an idea.

Flutter, how to automatically animate without a button trigger

I have some animation code that changes background color infinitely
but the problem is I want to do this without a button trigger
and I want this to be automated at first time (when it's firstly loading)
below is code
I`m so stuck could anyone help please
the purpose is to animate without a trigger, it automatically runs animation that changes background
import 'package:flutter/material.dart';
class ScreenTime extends StatefulWidget {
#override
_ScreenTimeState createState() => _ScreenTimeState();
}
class _ScreenTimeState extends State<ScreenTime> {
List<Color> colorList = [
Colors.red,
Colors.blue,
Colors.green,
Colors.yellow
];
List<Alignment> alignmentList = [
Alignment.bottomLeft,
Alignment.bottomRight,
Alignment.topRight,
Alignment.topLeft,
];
int index = 0;
Color bottomColor = Colors.black54;
Color topColor = Colors.white10;
Alignment begin = Alignment.bottomLeft;
Alignment end = Alignment.topRight;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
AnimatedContainer(
duration: Duration(seconds: 2),
onEnd: () {
setState(() {
index = index + 1;
// animate the color
bottomColor = colorList[index % colorList.length];
topColor = colorList[(index + 1) % colorList.length];
//// animate the alignment
// begin = alignmentList[index % alignmentList.length];
// end = alignmentList[(index + 2) % alignmentList.length];
});
},
decoration: BoxDecoration(
gradient: LinearGradient(
begin: begin, end: end, colors: [bottomColor, topColor])),
),
Positioned.fill(
child: IconButton(
icon: Icon(Icons.play_arrow),
onPressed: () {
setState(() {
bottomColor = Colors.blue;
});
},
),
)
],
));
}
}
To keep as much as your original implementation as possible while fixing the problem, doing
WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {bottomColor = Colors.blue;}));
in initState would not pose an issue. This is as long as it's done in initState. Calling it in build would lead to an undesirable extra infinite loop.
However, using an AnimationController on repeat would almost certainly be a cleaner implmentation.
the purpose of this question was basically "how to animate AnimatedContainer without a trigger"
and I think I found a solution
what I did is
just make a new function and then in the build method, invokes the method
and method code is like below
setState(() {
index = index + 1;
bottomColor = Colors.blue;
});
my second solution is this and I think it works
but I worry if it has a any side effects
WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {}));
Use this
bool selected = false;
#override
void initState() {
Future.delayed(const Duration(seconds: 1), () {
setState(() {
selected = true;
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.white
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
AnimatedAlign(
alignment: selected ? Alignment.topRight : Alignment.bottomLeft,
duration: const Duration(seconds: 1),
curve: Curves.fastOutSlowIn,
child: const FlutterLogo(size: 50.0),
),
],
));}
This Will automatically starts the animation. You can change the duration or animation type also.