The dialog box opens multiples time at a same time in flutter - flutter

whenever I click many times on wheel, open multiple dialog boxes at the same time.
I just want, it should be open after previous got dismissed.
I took an image and add animation on it and wrapped it with GestureDetector widget.
onTap: event i called alertDialogBox() method which is defined for Dialog box. watch above the gif image, and called the animation method with specific Condition
CODE:
Dialog box
alertDialogBox(BuildContext context) {
return showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16.0))),
contentPadding: EdgeInsets.only(top: 10.0),
content: Stack(
children: <Widget>[
....
],
),
);
});
}
The Wheel:
GestureDetector(
child: Container(
alignment: Alignment.center,
color: Colors.blue,
child: new AnimatedBuilder(
animation: _animationCtrl,
child: Container(
height:MediaQuery.of(context).size.height/2.3,
width: MediaQuery.of(context).size.width/1.3,
decoration: BoxDecoration(
color: Colors.blue,
image: DecorationImage(
image: AssetImage('assets/wheel.png', ),
fit: BoxFit.contain,
),
borderRadius: BorderRadius.all(Radius.circular(130.0)),
)
),
builder: (BuildContext context, Widget _widget) {
.......
},
),
),
onTap: ()async{
await Firestore.instance.collection('Users').document(uid).get().then((DocumentSnapshot documnet){
getIsSpin=documnet['isSpin'];
});
if(getIsSpin=="0"){
if (!_animationCtrl.isAnimating) {
//applying animation
}
DateTime now = DateTime.now();
// String lastSpinTime =DateFormat("yyyy-MM-dd hh:mm:ss").format(now);
.....//here updating isSpin value=1 and also updating spining Date time to firestore
}else {
oneDayDuration();
}
}
)
After 24 hours trying to spin the wheel
oneDayDuration():
void oneDayDuration()async{
int differenceTime;
await({
....here fetching last spin date time from firestore});
....//here getting difference hours between last spining time and current time
if(differenceTime>=24){
await({......//updating ispin=0 to firbase
})
.then((result) => {
print("Now you're able to spin"),
}).catchError((err) => print(err));
}else{
print("Please wait for 24 hours");
alertDialogBox(context);
}
}
}

Maybe this is because, you are trying to show dialog Asynchronously, where you don't have to. Just remove async, it is unnecessary while showing a simple dialog.
You better create a method that runs async in the if condition, and remove async in the onTap. This will separate your dialog code with async.

It is too late to answer this question, I came across the same scenario and solved it.
This is because the alertDialogBox function is invoked by build method every time the state is changed. you need to limit it by adding a variable to class like 'isAlertboxOpened' and make opening of alertDialogBox conditional and avoid opening multiple dialog boxes.
The following code should work
class _MyHomePageState extends State<MyHomePage> {
bool isAlertboxOpened; // add this
#override
void initState(){
isAlertboxOpened = false; // add this
}
alertDialogBox(BuildContext context) async {
setState(() => isAlertboxOpened = true); // add this
return showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16.0))),
contentPadding: EdgeInsets.only(top: 10.0),
content: Stack(
children: <Widget>[
....
// when press ok button on pressed add this
onPressed:(){
// your code
setState(() => isAlertboxOpened = false);
Navigator.of(context).pop();
}
],
),
);
});
}
void oneDayDuration()async{
int differenceTime;
await({
....here fetching last spin date time from firestore});
....//here getting difference hours between last spining time and current time
if(differenceTime>=24){
await({......//updating ispin=0 to firbase
})
.then((result) => {
print("Now you're able to spin"),
}).catchError((err) => print(err));
}else{
print("Please wait for 24 hours");
isAlertboxOpened ? (){} : // add this to make this conditional
alertDialogBox(context);
}
}
}

Related

Clicking the button does not show the loader in Flutter

I need to add a loader to the button. That is, when you click on the Save button, you need to show the CircularProgressIndicator. I wanted to implement this through Bloc, because if I do this through setState (() {}), then I will have to rebuild the widget and the new added data on the page will disappear. Therefore, I want to do it through Bloc. I created a variable there and assign values ​​to it, the values ​​change but the loader does not update, tell me how can I make the loader show?
return BlocBuilder<EditPhotoCubit, EditPhotoState>(
builder: (context, state) {
return SizedBox(
height: size.height,
width: size.width,
child: _child(size, topPadding, context, cubitEditPhoto,
mainState.charging),
);
});
}
return const SizedBox();
});
...
SizedBox(
width: 148,
child: cubitEditPhoto.isLoading
? const CircularProgressIndicator(
color: constants.Colors.purpleMain)
: DefaultButtonGlow(
text: 'Save',
color:Colors.purpleMain,
shadowColor: Colors.purpleMain,
textStyle: Styles.buttonTextStyle,
onPressed: () async {
cubitEditPhoto.isLoading = true;
await cubitEditPhoto
.uploadImage(widget.chargingId)
.then((value) {
cubitEditPhoto.isLoading = false;
});
},
),
),
cubit
class EditPhotoCubit extends Cubit<EditPhotoState> {
EditPhotoCubit() : super(EditPhotoInitial());
bool isLoading = false;
}

Data not being passed in Navigator.pop

I'm trying to pass data from a screen with navigator.pop, it shows the result in the second screen, but in the first screen the data is always null. I tried some things on it but didn't work.
Here I'm calling the second screen:
onPressed: () async {
final result = await push(context, PickMaterialPage());
print("Result $result");
},
In this one is when I want to pass the data for the first screen with navigator.pop, It's an object.
Card card(int index, context) {
RequisicaoMaterial material = materiais[index];
try {
return Card(
color: Colors.grey[300],
elevation: 6.0,
margin: new EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
child: GestureDetector(
onTap: () => pop(context, material),
child: CustomListTile(
Icons.shopping_cart_outlined,
'${material.codMaterial} - ${material.nomeMaterial}',
'${material.codUnidade} - ${material.sigla} - ${material.classificacao}',
null,
),
));
} catch (e) {
print(e);
}
I don't want to use constructor in this one, i needed to make it simple and only get the picked data to show in the first screen.
output $result = null
PUSH THE SCREEN
String result = await Navigator.push(
context,
MaterialPageRoute(
builder: (_) => AddTenantToRoomFlyPage(),
),
);
POP THE SCREEN
Navigator.pop(context, "RETURN VALUE");
Do it like this
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => PickMaterialPage(),
)).then((value) => print(value)),

Only update UI when there are changes in the server, using streambuilder

I am using streambuilder to check whether a new order is placed or not.
I am checking the order status, if the order status is unknown I want to show a pop up, which works fine. but if i don't select an option to update the order status, streambuilder refreshes after a few seconds, and show another pop up on top of it.
Get Orders Function:
Future<Orders> getOrders() async {
String bsid = widget.bsid;
try {
Map<String, dynamic> body = {
"bsid": bsid,
};
http.Response response = await http.post(
Uri.parse(
"**API HERE**"),
body: body);
Map<String, dynamic> mapData = json.decode(response.body);
Orders myOrders;
print(response.body);
if (response.statusCode == 200) {
print("Success");
myOrders = Orders.fromJson(mapData);
}
return myOrders;
} catch (e) {}
}
Here's the stream function:
Stream<Orders> getOrdersStrem(Duration refreshTime) async* {
while (true) {
await Future.delayed(refreshTime);
yield await getOrders();
}}
StreamBuilder:
StreamBuilder<Orders>(
stream: getOrdersStrem(
Duration(
seconds: 2,
),
),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
CircularProgressIndicator.adaptive(),
);
}
var orders = snapshot.data.statedatas;
return ListView.builder(
itemCount: orders.length,
itemBuilder: (BuildContext context, int index) {
var orderResponse =
snapshot.data.statedatas[index].strAccept;
print(orderResponse);
if (orderResponse == "0") {
print("order status unknown");
Future.delayed(Duration.zero, () {
_playFile();
showCupertinoDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Center(
child: Text(
"#${orders[index].ordrAutoid}",
),
),
content: Row(
children: [
SizedBox(
width: 120,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty
.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(
MaterialState.pressed))
return Colors.black;
return Colors
.green; // Use the component's default.
},
),
),
onPressed: () async {
_stopFile();
Navigator.pop(context);
await changeOrderStatus(
orders[index].orid, "accept");
// setState(() {});
},
child: Text('Accept'),
),
),
SizedBox(
width: 15,
),
SizedBox(
width: 120,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty
.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(
MaterialState.pressed))
return Colors.black;
return Colors
.red; // Use the component's default.
},
),
),
onPressed: () async {
_stopFile();
Navigator.pop(context);
await changeOrderStatus(
orders[index].orid, "Reject");
// setState(() {});
},
child: Text('Reject'),
),
),
// TextButton(
// onPressed: () async {
// _stopFile();
// Navigator.pop(context);
// await changeOrderStatus(
// orders[index].orid, "reject");
// },
// child: Text('reject'),
// ),
],
),
),
);
}).then((value) {
_stopFile();
print("ENDING");
});
}
return Container();
Create a variable to check for the last known order status, outside your if statement, and when a new value comes, compare it to the old value first, then do the if statement logic.
//This is outside the stream builder:
String orderResponseCheck = "";
.
.
.
//This is inside your streambuidler, if the orderResponseCheck is still equal to "", the if statement will be executed,
//and the value of orderResponse wil be assigned to it. This will only show the alert dialog if the orderResponse status changes from the one that previously triggered it.
var orderResponse =snapshot.data.statedatas[index].strAccept;
print(orderResponse);
if (orderResponseCheck != orderResponse && orderResponse == "0") {
orderResponseCheck = orderResponse;
.
.
.
//logic same as before
You shouldn't call showCupertinoDialog (and probably _playFile()) from your build method. Wrapping it with Future.delayed(Duration.zero, () { ... }) was probably a workaround for an error that was given by the framework.
The build method can get executed multiple times. You probably want a way to run _playFile and show the dialog that isn't depending on the UI. I don't think StreamBuilder is the right solution for this.
You could use a StatefulWidget and execute listen on a stream from the initState method. initState will only be called once.
From what I'm reading, you're querying your API every two seconds.
Every time your API answers, you're pushing the new datas to your StreamBuilder, which explains why you're having multiple pop-ups are stacking.
One simple solution to your problem would be to have a boolean set to true when the dialog is displayed to avoid showing it multiple times.
bool isDialogShowing = false;
...
if (orderResponse == "0" && !isDialogShowing) {
isDialogShowing = true;
...
But there are a few mistakes in your code that you should avoid like :
Infinite loops
Querying your API multiple times automatically (it could DDOS your service if plenty of users are using your app at the same time)
Showing your Dialog in a ListView builder

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.