I try to manage a state "playerList" in two screens:
The code below shows how the provider was built
class PlayerList with ChangeNotifier {
List<Player> _playerList = [];
List<Player> get playerList {
return [..._playerList];
}
void addPlayer(Player player) {
_playerList.add(player);
notifyListeners();
}
void deletePlayer(int index) {
_playerList.removeAt(index);
notifyListeners();
}
}
In screen 1, everything works perfect. However, when I do the same, but then within an AlertDialog, the updated value only shows after closing the dialog and opening it again.
The code below shows the relevant part of the build method.
#override
Widget build(BuildContext context) {
PlayerList _playerListData = Provider.of<PlayerList>(context);
List<Player> _playerList = _playerListData.playerList;
return WillPopScope(
onWillPop: () async => showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(
'Are you sure you want to leave? The current game will be lost.'),
actions: <Widget>[
ElevatedButton(
child: Text('Yes'),
onPressed: () => Navigator.of(context).pop(true)),
ElevatedButton(
child: Text('No'),
onPressed: () => Navigator.of(context).pop(false)),
])),
child: Scaffold(
backgroundColor: Color(0xFF6ca0dc),
body: Center(
child: Column(children: [
_playChallenge(_playerList),
]),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Player List'),
content: Column(mainAxisSize: MainAxisSize.min, children: [
Container(
height: MediaQuery.of(context).size.height *
0.5, // Change as per your requirement
width: MediaQuery.of(context).size.width *
0.5, // Change as per your requirement
child: ListView.builder(
shrinkWrap: true,
itemCount: _playerList.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(
_playerList[index].name,
style: Theme.of(context).textTheme.headline6,
),
trailing: IconButton(
icon: Icon(Icons.delete),
color: Theme.of(context).errorColor,
onPressed: () =>
_playerListData.deletePlayer(index),
));
},
),
),
How can I solve this issue?
I solved the problem by adding a StatefulBuilder and using the Provider.of inside the statefulbuilder. See:
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(
'Are you sure you want to leave? The current game will be lost.'),
actions: <Widget>[
ElevatedButton(
child: Text('Yes'),
onPressed: () => Navigator.of(context).pop(true)),
ElevatedButton(
child: Text('No'),
onPressed: () => Navigator.of(context).pop(false)),
])),
child: Scaffold(
backgroundColor: Color(0xFF6ca0dc),
body: Center(
child: Column(children: [
_playChallenge(Provider.of<PlayerList>(context).playerList),
]),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(builder: (context, setState) {
PlayerList _playerListData =
Provider.of<PlayerList>(context);
List<Player> _playerList = _playerListData.playerList;
return AlertDialog(
title: Text('Player List'),
content:
Column(mainAxisSize: MainAxisSize.min, children: [
Container(
height: MediaQuery.of(context).size.height *
0.5, // Change as per your requirement
width: MediaQuery.of(context).size.width *
0.5, // Change as per your requirement
child: ListView.builder(
shrinkWrap: true,
itemCount: _playerList.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(
_playerList[index].name,
style:
Theme.of(context).textTheme.headline6,
),
trailing: IconButton(
icon: Icon(Icons.delete),
color: Theme.of(context).errorColor,
onPressed: () =>
_playerListData.deletePlayer(index),
));
},
),
),
Related
I have been trying to make a dialog popup when pressing a popupmenubutton, however it does not seem to work, I run the app in debug mode through chrome as my Laptop is unable to run in debug mode through androud studio emulator.
child: Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: Color.fromRGBO(31, 31, 47, 1),
appBar: AppBar(
actions: [
PopupMenuButton<String>(
padding: EdgeInsets.all(0),
onSelected: (value) {
print(value);
},
itemBuilder: (BuildContext context) {
return [
PopupMenuItem(
child: Text('Cancel'),
onTap: () {BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
backgroundColor: Color(Color.getAlphaFromOpacity(0.5)),
child: _dialogContent(),
)
);
},
),
];
},
),
], ),
Widget _dialogContent() {return SimpleDialog(
title: Text('Enter Cancel Code'),
children: <Widget>[
// TextFormField()
ElevatedButton(
onPressed: () => Navigator.pop(context, false),
child: Text('Okay')),
],);}
}
the reason why I doesn't work is because you didn't specify a value to your popupMenuItem thats why your onSelected property dont know what to do when you tapped on an item, so to make it work you need to do like so
PopupMenuButton<int>(
padding: const EdgeInsets.all(0),
onSelected: (value) {
if (value == 0) {
showDialog(context: context, builder: (context) => _dialogContent,);
}
},
itemBuilder: (BuildContext context) {
return [
const PopupMenuItem<int>(
value: 0,
child: Text('Show dialog'),
),
];
},
),
:
if you want to use the onTap property of the PopupMenuItem, you need to delay your showDialog because when you call the onTap, it is triggering the Navigator.of(context).pop(). So it looks like nothing is happening when you try to show dialog because its being popped immediately. So to make it work, you need to do it like so:
PopupMenuButton<int>(
padding: const EdgeInsets.all(0),
itemBuilder: (BuildContext context) {
return [
const PopupMenuItem<int>(
value: 0,
child: Text('Show dialog'),
onTap: ()=> Future.delayed(Duration(milliseconds:1),(){
showDialog(context:context, builder: (context)=> _dialogContent)
})
),
];
},
),
I want to call 2 API endpoints at the same time, and I try to use MultiBlocProvider but it does not work, and here is some code.
https://pub.dev/documentation/flutter_bloc/latest/flutter_bloc/MultiBlocProvider-class.html
how could I use it in the correct ways, or I should another ways.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("${allTranslations.text('filterHistory')}"),
),
body: MultiBlocProvider(
providers: [
BlocProvider<HistoryBloc>(
create: (BuildContext context) => historyBloc,
child: BlocListener<HistoryBloc, HistoryState?>(
listener: (context, state) {
print('State loading ${state.runtimeType}');
if(state is HistoryStateSuccess) {
nextPageData(state.historyResModel);
}
if (state is HistoryStateError) {
appShowSnackBar(context, state.runtimeType.toString());
}
},
),
),
BlocProvider<PackingBloc>(
create: (BuildContext context) => packingBloc,
child: BlocListener<PackingBloc, PackingState?>(
listener: (context, state) {
print('State loading ${state.runtimeType}');
if(state is GetPackingStateSeccess) {
getParkingList(state.packingList);
}
if (state is GetPackingStateError) {
appShowSnackBar(context, state.runtimeType.toString());
}
},
),
),
],
child: _bodyBuilder()
),
floatingActionButton: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
heroTag: 'selectDateRange',
elevation: 10.0,
child: const Icon(Icons.calendar_today),
onPressed: () => {_selectDateRange()},
),
const SizedBox(height: 6),
FloatingActionButton(
heroTag: 'onMapTypeButtonPressed',
elevation: 10.0,
child: const Icon(Icons.map),
onPressed: _onMapTypeButtonPressed,
),
const SizedBox(height: 6),
FloatingActionButton(
heroTag: 'vehicleLocation',
child: const Icon(Icons.car_repair_sharp),
onPressed: () => _controller.animateCamera(
CameraUpdate.newCameraPosition(VehicleLocationPint())),
),
const SizedBox(height: 6),
FloatingActionButton(
heroTag: 'inittialCameraPosition',
elevation: 10.0,
child: const Icon(Icons.gps_fixed),
onPressed: () => _controller.animateCamera(
CameraUpdate.newCameraPosition(_inittialCameraPosition())),
),
],
),
);
};
Thanks you for your answer.
I want to add a function when the showModalBottomSheet is close or when the barrier tapped the function run(routing to the main page) but how?
I want to run this when the barrier tapped/modal closed
Navigator.pushNamedAndRemoveUntil(context, '/mainMenuPage', (route) => false);
Modal Code
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(25.0),
),
),
backgroundColor: Colors.white,
context: context,
builder: (context) => Widget(...),
);
}
Try below code hope its help to you.
Your Widget with bottomSheet:
Center(
child: ElevatedButton(
child: const Text('showModalBottomSheet'),
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return Container(
height: 200,
color: Colors.amber,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Modal BottomSheet'),
],
), //MyWidget()
),
);
},
).then(
(value) => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MyWidget(),
),
),
);
},
),
),
Other class:
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: Center(
child: Text(
'Hello, World!',
style: Theme.of(context).textTheme.headline4,
),
),
);
}
}
.then() -> This means that the next instructions will be executed. But it allows you to execute the code when the asynchronous method completes.
showModalBottomSheet(
context: context,
builder: (context){
return Text("example");
}
).then((value) => {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => const Page2()))
});
You can try like this.
I need to separate my dialog to separate widgets an call from anywhere on some button click. Now my code looks like:
import 'package:flutter/material.dart';
class PlacesListScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return _Map();
}
}
class _Map extends StatefulWidget {
#override
_MapState createState() => _MapState();
}
class _MapState extends State<_Map> {
final _titleController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('App name'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: () {
return showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text(
"Dialog title",
textAlign: TextAlign.center,
),
content: TextField(
decoration: InputDecoration(labelText: 'Title'),
controller: _titleController,
),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(ctx).pop();
},
child: Text("No"),
),
FlatButton(
onPressed: () {
return showDialog(
context: context,
builder: (ctx) => StatefulBuilder(builder:
(BuildContext context, StateSetter setState) {
return AlertDialog(
title: Text(
"Second subdialog title",
textAlign: TextAlign.center,
),
content: Container(
width: 150,
height: 200,
decoration: BoxDecoration(
border:
Border.all(width: 1, color: Colors.grey),
),
child: Text('Here will be file'),
alignment: Alignment.center,
),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(ctx).pop();
},
child: Text("No"),
),
FlatButton(
onPressed: () => Text('Here I will work with state'),
child: Text("Yes"),
),
],
);
}),
);
},
child: Text("Yes"),
),
],
),
);
},
),
],
),
body: Text('App body here'),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
FloatingActionButton(
onPressed: () => Text('Here will be called dialog function'),
child: Icon(Icons.add, color: Colors.white),
backgroundColor: Colors.green,
heroTag: 'mapZoomIn',
),
],
),
);
}
}
Now how I can this part of code (calling dialog) separate to another widget and call then using created widget.
showDialog(
context: context,
builder: (ctx) => StatefulBuilder(builder:
(BuildContext context, StateSetter setState) {
return AlertDialog(
title: Text(
"Second subdialog title",
textAlign: TextAlign.center,
),
content: Container(
width: 150,
height: 200,
decoration: BoxDecoration(
border:
Border.all(width: 1, color: Colors.grey),
),
child: Text('Here will be file'),
alignment: Alignment.center,
),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(ctx).pop();
},
child: Text("No"),
),
FlatButton(
onPressed: () => Text('Here I will work with state'),
child: Text("Yes"),
),
],
);
}),
);
Firstly, you can refactor your StatefulBuilder(builder: (BuildContext context, StateSetter setState) { return AlertDialog(... into one separate widget like MyFancyDialog extends StatefulWidget. Then just call showDialog(..., MyFancyWidget()) when you need it.
Secondly, if you do not want the first method, you can extract the whole showDialog(...) into a function like void showMyFancyDialog() { showDialog... }. Is it also OK, since in dart functions are first class.
Did you try to create a new Stateful widget class at the bottom of your code and try to call that widget? If you didn't try that,
Go to under of your code and create a new Stateful class. (Let's say that myDialog)
Add the code that you want to call, inside of your "myDialog" widget
Go for where you want to call that code and try to call that class.
Hope this works for you.
Create a separate file. Import material package.
then create "Widget myDialog(context){ // paste your showDialog code here }"
Now your myDialog widget has been ready for using anywhere on whole project just import and call it.
When you call it pass BuildContext in its argument.
Its work for me.
void showSimpleCustomDialog(BuildContext context, String listName) async{
if(randList.isEmpty){
randList = await theDb.queryWhere("$listName");
}else{
randList.clear();
randList = await theDb.queryWhere("$listName");
}
setState((){
});
String randValue = "Click generate new to get a random value";
Dialog simpleDialog = Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
child: Text(
"$randValue"
),
),
Padding(
child: Row(
children: <Widget>[
RaisedButton(
color: Colors.blue,
onPressed: ()
String lol = randList[0 + rng.nextInt(randList.length - 0)].getItem();
print(randValue);
setState(() {
randValue = lol;
});
},
child: Text(
'generate new',
style: TextStyle(fontSize: 18.0, color: Colors.white),
),
)
],
),
),
],
),
),
);
showDialog(
context: context, builder: (BuildContext context) => simpleDialog);}
When I am clicking on "generate new" button I want to update my randomValue and to display that randomValue as Text Widget. To do that dynamically Im using setState, but it is not working, and i dont understand why. Please help me.
You should use StatefulBuilder with dialogue.
showDialog(
context: context,
builder: (context) {
String contentText = "Content of Dialog";
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: Text("Title of Dialog"),
content: Text(contentText),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.pop(context),
child: Text("Cancel"),
),
FlatButton(
onPressed: () {
setState(() {
contentText = "Changed Content of Dialog";
});
},
child: Text("Change"),
),
],
);
},
);
},