How to setState inside showModalBottomSheet - flutter

Is there any limitation in BottomSheet that we cannot update the widget states? As you can see in the example below I'm using a Switch but its display is not changing, although the value update, it's just that it doesn't get re-render again.
This is part of StatefulWidget right now.
This same problem I'm experiencing with the DropdownButton widget. These both work in normal page fine.
Does anybody have the idea?
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return BottomSheet(
onClosing: () {},
builder: (BuildContext context) {
return Switch(
onChanged: (bool v) {
debugPrint('v is ${v.toString()}');
// b = v; <<-- This is also not working when using StatelessWidget
setState(() => b = v);
debugPrint(b.toString());
},
value: b,
);
},
);
},
);

The problem here is that the BottomSheet you are creating is not part of your StatefulWidget. If you only made your widget stateful for the purpose of using setState inside of showModalBottomSheet, you can revert that change now.
What you really want to do is set the state inside of your BottomSheet. You do that by either passing a StatefulWidget to the builder or by using a StatefulBuilder, which I will do for this example for the sake of simplicity:
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return BottomSheet(
onClosing: () {},
builder: (BuildContext context) {
bool b = false;
return StatefulBuilder(
builder: (BuildContext context, setState) => Switch(
onChanged: (bool v) {
setState(() => b = v);
},
value: b,
),
);
},
);
},
);
I also moved the b value inside the builder function of the BottomSheet.
If you want to use the value of b inside of your original StatefulWidget as well, you would move it out again and you probably want to call this.setState as well to also update the other widget (only if you need it to update).

I also faced the same problem. Its a small trick, you need to insert StatefulBuilder in the showModalBottomSheet. I will use a different code to make someone out there understand easily using checkbox now that the answer comes way late.
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (BuildContext context) {
mpesachecked =false;
return StatefulBuilder(builder: (BuildContext context, StateSetter mystate) {
return Padding(
padding: const EdgeInsets.fromLTRB(10.0, 70.0, 20.0, 20.0),
child: Column(
children: <Widget>[
Checkbox(
value: mpesachecked,
activeColor: Colors.green,
onChanged: (value) {
mystate(() {
mpesachecked = value;
});
}),
])
));
});
)
NOTE: the new state within the showModalBottomSheet is mystate

Related

Flutter: How to rebuild using Riverpod within showModalBottomSheet

#override
Widget build(BuildContext context, WidgetRef ref) {
final provider = counterNotifierProviders(Counter.initialize());
final counter = ref.watch(provider);
return Scaffold(
body: ElevatedButton(
onPressed: () => showModalBottomSheet<void>(
context: context,
builder: (context) {
return Column(
children: [
Text(counter.count),
TextButton(child: 'Add' onPressed: () => ref.read(provider.notifier).add()),
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Text(counter.count) is not updated. But, I know counter.count was updating from log.
How I can resolve that problem?
You can solve your problem by using Consumer to use its ref and rebuild the widget for you:
Consumer(
builder: (BuildContext context, WidgetRef ref, Widget? child) {
return Scaffold(...);
});

How to pass an argument from a data table to a modal in flutter

I'm trying to pass an argument from data table and pushing the values to a modal in a flutter web project. I tried reading a few topics but I'm struggling to find a solution.
Here is how am trying to pass the data to the modal, which I don't think is correct!-but am newbie in flutter. The solution works for me when I route to the page instead of a modal.
previewEstate(element) {
showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return PreviewEstateModal();
// Navigator.pushReplacement(
// context,
// PageRouteBuilder(
// pageBuilder: (_, __, ___) => PreviewEstateModal(),
// transitionDuration: Duration(seconds: 0),
// settings: RouteSettings(arguments: element),
// ),
// );
},
);
}
The commented part of my code works if I route to that page i.e am able to receive the route setting argument, but I want to do this by using a modal. What could I be doing wrong?
You can try with following code
previewEstate(element) {
showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return PreviewEstateModal(estate: element); // here you pass the arguments
},
);
}
In your PreviewEstateModal, initialize the argument(EstateSearch)
class PreviewEstateModal extends StatelessWidget {
final EstateSearch estate;
PreviewEstateModal({this.estate});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Text(
'${estate}',/// initilize estate property
),
),
);
}
}

How to pass context to second widget tree for provider

I am try pass context to second widget tree (in function) but I get error:
Tried to use Provider with a subtype of Listenable/Stream (Model2).
This is likely a mistake, as Provider will not automatically update
dependents when Model2 is updated. Instead, consider changing Provider
for more specific implementation that handles the update mecanism,
such as:
- ListenableProvider
- ChangeNotifierProvider
- ValueListenableProvider
- StreamProvider
Future<void> _neverSatisfied({Key key, #required BuildContext context}) async {
final model2 = Provider.of<Model2>(context, listen: false);
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return
Provider.value(value: model2, child:
AlertDialog(
title: Text('Rewind and remember'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text('You will never be satisfied.'),
Text('You\’re like me. I’m never satisfied.'),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('Regret'),
onPressed: () async {
await model2.getData();
Navigator.of(context).pop();
},
),
],
),
);
},
);
}
First widget in same StatefulWidget:
#override
Widget build(BuildContext context) {
return ChangeNotifierProxyProvider<Model1, Model2>(
initialBuilder: (_) => Model2(),
builder: (_, model1, model2) => model2
..string = model1.string,
),
child: Consumer<Model2>(
builder: (context, model2, _) =>
Second (I pass context from here):
#override
Widget buildStep(BuildContext context) {
Consumer<Model2>(
builder: (context, model2, _) =>
...
_neverSatisfied(context: context); //**pass context**
Instead of Provider.value you should use ChangeNotifierProvider.value

How to show a dialog inside a futurebuilder?

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();
}

Remove grey background for flutter alert dialog

I have a CupertinoAlertDialog and AlertDialog in my Flutter app. every time the dialog pops up, everything behind it becomes darker. I would like to remove the background. how do I do that?
CupertinoDialogAction(
child: Text('Delete',
style: TextStyle(color: Colors.red),
),
onPressed: () async {
await CommentActivity.delete(postData[index]['id'])
.then((response) {
if (response) {
setState(() {
postData.removeAt(index);
createPageActivity();
renderPageActivity();
});
Navigator.of(context).pop();
}
});
}
)
],
)
An alternative solution that partially solves the problem is using an almost transparent color for the barrier:
showDialog<void>(
barrierColor: Color(0x01000000),
)
Just launch the dialog with de navigator instead of using the showDialog() and use a PageRouteBuilder
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (context, _, __) => AlertDialog(),
opaque: false),
);
I think you're talking about the black fader in the background of the dialog...
Is part of the material/cupertino implementations, in Material has a fixed value of Colors.black54.
You will have to copy the showDialog() code, and modify it.
Demo:
// common Dialog widget shown in both implementation.
Widget buildDialog(BuildContext context) {
return CupertinoDialogAction(
child: Text(
'Delete',
style: TextStyle(color: Colors.red),
),
onPressed: () async {
Navigator.of(context).pop();
},
);
}
void openDialog(BuildContext context) {
// open custom dialog.
openCustomDialog(context);
// open default dialog.
// openFlutterDialog(context);
}
// regular Fluter showDialog()
void openFlutterDialog(BuildContext context) {
showDialog(
context: context,
builder: (ctx) {
return buildDialog(ctx);
},
);
}
void openCustomDialog(BuildContext context) {
showCustomDialog(
context: context,
builder: (ctx) {
return buildDialog(ctx);
},
);
}
// custom implementation of showDialog()...
Future<T> showCustomDialog<T>({
#required BuildContext context,
bool barrierDismissible = true,
WidgetBuilder builder,
}) {
assert(debugCheckHasMaterialLocalizations(context));
final ThemeData theme = Theme.of(context, shadowThemeOnly: true);
return showGeneralDialog(
context: context,
pageBuilder: (BuildContext buildContext, Animation<double> animation,
Animation<double> secondaryAnimation) {
final Widget pageChild = Builder(builder: builder);
return SafeArea(
child: Builder(builder: (BuildContext context) {
return theme != null
? Theme(data: theme, child: pageChild)
: pageChild;
}),
);
},
barrierDismissible: barrierDismissible,
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
// KEY PART TO MODIFY, Flutter doesn't allow a transparent Color,
// values under opacity .01 are considered transparent and will throw an error.
// But this value is transparent enough.
barrierColor: Colors.black.withOpacity(0.01),
// you can modify the default FadeTransition duration here.
transitionDuration: const Duration(milliseconds: 2000),
);
}
Is this what you were looking for?
Simple solution with barrierColor property in showDialog method which I set white color with opacity value zero and barrier shadow is vanished
AlertDialog alert = AlertDialog(
backgroundColor: Colors.transparent,
elevation: 0,
content: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Loader(),
],
),
);
showDialog(
barrierColor: Colors.white.withOpacity(0),
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return WillPopScope(
onWillPop: (){},
child: alert);
},
);