I´m new in the bloc pattern. I want to show up an alert dialog when I press a button, but when I use showDialog func and showDialog I want to access Bloc from context or BlocListner doesn't contain bloc and throw error is there a way to access bloc in this situatuion
Error: Could not find the correct Provider above this BlocListener<TeacherFinancialCubit, TeacherFinancialState> Widget
#override
Widget build(BuildContext context) {
return BlocBuilder<UserBloc, UserState>(
builder: (context, state) {
if(state is! Authorize) {
return const BaBaFullScreenLoading();
}
return BlocProvider<TeacherFinancialCubit>(
lazy: false,
create: (context) => TeacherFinancialCubit(locator())..getTeacherFinancial(state.user, state.account, teacherId ?? 0),
child: _masterLayout(context),
);
},
);
}
Widget _masterLayout(BuildContext context) {
Authorize authorize = context.read<UserBloc>().state as Authorize;
return MasterLayout(
showBack: true,
floatingActionButton: authorize.account.type == master ? FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
Future.microtask(() => _showAddPaymentDialog(context));
},
) : null,
title: const Text(""),
child: WillPopScope(
onWillPop: () => Future.value(false),
child: _showTeacherFinancials(context, authorize),
),
);
}
void _showAddPaymentDialog(BuildContext context) async {
return showDialog(
barrierDismissible: false,
context: context,
builder: (_) {
return BlocListener<TeacherFinancialCubit, TeacherFinancialState>(
listener: (context, state) {
if(state is TeacherFinancialLoading) {
EasyLoadingHelper.showLoading();
}
else {
EasyLoadingHelper.dismiss();
if(state is TeacherFinancialFailed) {
EasyLoadingHelper.showToastError(context, message: state.error);
}
else if(state is TeacherFinancialSuccess) {
EasyLoadingHelper.showToastSuccess(context, message: successMessage);
Navigator.pop(context);
}
}
},
child: AlertDialog(
title: const Text(''),
content: Form(
key: addPaymentFormKey,
child: Text(""),
),
actions: [],
),
);
}
);
}
Make sure to provide the bloc to the AlertDialog. You can do something like this:
return showDialog(
...
builder: (dialogContext) => BlocProvider.value(
value: BlocProvider.of<TeacherFinancialCubit>(context),
child: BlocListener<TeacherFinancialCubit, TeacherFinancialState>(
...
),
),
Related
How to set global variable using OK button on AlertDialog in StatefulBuilder?
String stringMain = "My String";
Future<String?> dialogChangeMainContent(BuildContext context) {
return showDialog<String>(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(builder: (context, setState) {
return AlertDialog(
title: const Text('AlertDialog Title'),
content: const Text('AlertDialog description'),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context, 'OK');
setState(() {
// setState2();
stringMain = "New String";
});
},
child: const Text('OK'),
),
],
);
});
});
}
I changed StatefulWidget from StatelessWidget, but nothing changes. What I know is the problem is because the button is in the StatefulBuilder. If using BLoC etc, it's too wasteful for only small applications.
I found a way to solve my problem, redrawing (refreshing) the Text (or other widget) in a Stateless Widget from an AlertDialog in a StatefulBuilder. By creating a setState with a different name, for example setStateTarget.
Without having to rebuild the entire page, or without state management like BLoC. My code:
String stringMain = "My String";
#override
Widget build(BuildContext context) {
return Column(
// ....
children: [
StatefulBuilder(builder: (BuildContext context, setStateTarget) {
return Column(children: [
Text(stringMain), // <--- target
TextButton(
onPressed: () => dialogChangeMainContent(context, setStateTarget),
child: const Text('Show Dialog'))
]);
})
],
);
}
Future<String?> dialogChangeMainContent(BuildContext context, StateSetter setStateTarget) {
return showDialog<String>(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(builder: (context, setState) {
return AlertDialog(
title: const Text('AlertDialog Title'),
content: const Text('AlertDialog description'),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context, 'OK');
setStateTarget(() {
// setState2();
stringMain = "New String";
});
},
child: const Text('OK'),
),
],
);
});
});
}
I have a problem about filtering the data that I get from the json response. I already put the service initialization in initSate instead of future in FutureBuilder but it's still not working. Maybe I miss something in the filter function?
initState :
void initState() {
doctorService = DoctorService();
_doctorData = doctorService.getDoctors();
super.initState();
}
FutureBuilder:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Daftar Dokter"),),
body:
FutureBuilder<List<Doctor>>(
future: _doctorData,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if(snapshot.hasError) {
print(snapshot);
return Center(
child: Text("Error"),
);
}
else if (snapshot.hasData){
doctors = snapshot.data;
tempDoctorData = List.from(doctors);
return _buildListView(tempDoctorData);
}
else {
return Center(
child: Container(),
);
}
},
),
floatingActionButton: FloatingActionButton(
child: Icon(
Icons.add,
color: Colors.white,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (BuildContext buildContext)=>FormAlbum())
);
},
),
);
}
And this is the filter function
onItemChanged(String value) {
setState(() {
tempDoctorData = doctors.where((element) => element.name.toLowerCase().contains(value.toLowerCase())).toList();
});
}
I am trying to get the location permissions and I want to display a message before requesting the permission. but when I run the APP it doesn't wait until I close the showdialog
Future<void> checkPermission() async {
var status = await Permission.location.status;
print(status.toString());
if (status.isUndetermined) {
await showAlertPopup(context, '',
'OK')
.then((val) {
var statuses = Permission.location.request();
print(statuses);
});
}
if (status.isDenied ||
status.isPermanentlyDenied ||
status.isRestricted ||
status.isUndetermined) {
await showAlertPopup(context, '',
'error')
.then((val) {
openAppSettings();
});
}
if (status.isGranted) {
_geoAlowed = true;
}
}
and this is my code to display the popup
showAlertPopup(BuildContext context, String title, String detail) async {
showDemoDialog({BuildContext context, Widget child}) async {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return child;
});
}
return showDemoDialog(
context: context,
child: AlertDialog(
title: Text(title),
content: Text(detail),
backgroundColor: grayLight,
actions: [
FlatButton(
child: Text('OK'),
onPressed: () {
Navigator.pop(context, 'OK');
},
),
],
),
);
}
As per your question, you want to request permission when user clicks 'OK' button in dialog.
You can create a callback, that is when user clicks 'OK'
showAlertPopup(context, '','OK', (){
//Code you want to execute when user clicks 'OK'
});
And little changes in you showAlertPopup
showAlertPopup(BuildContext context, String title, String detail, Function onClick) async {
showDemoDialog({BuildContext context, Widget child}) async {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return child;
});
}
return showDemoDialog(
context: context,
child: AlertDialog(
title: Text(title),
content: Text(detail),
backgroundColor: grayLight,
actions: [
FlatButton(
child: Text('OK'),
onPressed: () {
//Your callback
onClick();
Navigator.pop(context, 'OK');
},
),
],
),
);
}
I'm having a problem calling Navigator.of(context).pop() on my onPressed property in SimpleDialogOption widget. I need to set the state and dismiss the dialog. But calling setState is preventing my dialog to close. Without setState the dialog closes. Here is my dialog
WidgetsBinding.instance.addPostFrameCallback((_) {
showDialog(
builder: (BuildContext context) {
return SimpleDialog(
children: _children(suburbs),
backgroundColor: Colors.white,
title: Text('Pick your suburb'),
);
},
context: context);
});
and the method I use for the list of the Dialog:
List<Widget> _children(List<Suburb> suburbs) {
return suburbs
.map((suburb) => SimpleDialogOption(
onPressed: () {
print('#####################');
setState(() {
postcode = suburb.name;
});
Navigator.of(context).pop();
},
child: Text(suburb.name)))
.toList();
}
you can await until the return value comes from the navigator.pop,
and then call a setState
WidgetsBinding.instance.addPostFrameCallback((_) async {
postcode = await showDialog(
builder: (BuildContext context) {
return SimpleDialog(
children: _children(suburbs),
backgroundColor: Colors.white,
title: Text('Pick your suburb'),
);
},
context: context);
setState(() {
postcode;
});
});
List<Widget> _children(List<Suburb> suburbs) {
return suburbs
.map((suburb) => SimpleDialogOption(
onPressed: () {
print('#####################');
Navigator.of(context).pop(suburb.name);
},
child: Text(suburb.name)))
.toList();
}
I want to show a dialog if I receive an error in a futurebuilder.
If I receiver an error, I want to show a dialog and force the user to click on the button, so that he can be redirected to another page.
The problems seems to be that it is not possible to show a dialog while widget is being built.
FutureBuilder(
future: ApiService.getPosts(),
builder: (BuildContext context, AsyncSnapshot snapShot) {
if (snapShot.connectionState == ConnectionState.done) {
if (snapShot.data.runtimeType == http.Response) {
var message =
json.decode(utf8.decode(snapShot.data.bodyBytes));
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
content: Text(message),
actions: <Widget>[
FlatButton(
child: Text("Ok"),
onPressed: () => null",
)
],
);
});
}
return ListView.separated(
separatorBuilder: (BuildContext context, int index) {
return Divider(
color: Colors.grey,
height: 1,
);
},
itemBuilder: (BuildContext context, int index) {
return _buildPostCard(index);
},
itemCount: snapShot.data.length,
);
return Center(
child: CircularProgressIndicator(),
);
},
)
If I return the AlertDialog alone, it works. But I need the showDialog because of the barrierDismissible property.
Does any one know if that is possible?
Also, is this a good way to handle what I want?
Thanks
UPDATE
For further reference, a friend at work had the solution.
In order to do what I was looking for, I had to decide which future I was going to pass to the futureBuilder.
Future<List<dynamic>> getPostsFuture() async {
try {
return await ApiService.getPosts();
} catch (e) {
await showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
content: Text(message),
actions: <Widget>[
FlatButton(
child: Text("Ok"),
onPressed: () => null",
)
],
);
});
}
}
}
Then in the futureBuilder I would just call
FutureBuilder(
future: getPostsFuture(),
Thanks
To avoid setState() or markNeedsBuild() called during build error when using showDialog wrap it into Future.delayed like this:
Future.delayed(Duration.zero, () => showDialog(...));
setState() or markNeedsBuild() called during build.
This exception is allowed because the framework builds parent widgets before its children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
So to avoid that Future Callback is used, which adds a call like this EventQueue.
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
Future futureCall() async {
await Future.delayed(Duration(seconds: 2));
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: futureCall(),
builder: (_, dataSnapshot) {
if (dataSnapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else {
Future(() { // Future Callback
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Employee Data'),
content: Text('Do you want to show data?'),
actions: <Widget>[
FlatButton(
onPressed: () =>
Navigator.of(context).pop('No'),
child: Text('NO')),
FlatButton(
onPressed: () =>
Navigator.of(context).pop('Yes'),
child: Text('YES'))
],
));
});
return Container();
}
},
);
}
}
The builder param expects you to return a Widget. showDialog is a Future.
So you can't return that.
You show Dialog on top of other widgets, you can't return it from a build method that is expecting a widget.
What you want can be implemented the following way.
When you receive an error, show a dialog on the UI and return a Container for the builder. Modify your code to this:
if (snapShot.data.runtimeType == http.Response) {
var message =
json.decode(utf8.decode(snapShot.data.bodyBytes));
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
content: Text(message),
actions: <Widget>[
FlatButton(
child: Text("Ok"),
onPressed: () => null",
)
],
);
});
return Container();
}