SwitchListTile not turning on in a Dialog - flutter

I am trying to create a workable switchlist in a Dialog. The Switch with icon, title shows but is not turning on.
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
FlatButton.icon(
icon: Icon(AntDesign.sound),
label: Text('Sound Options'),
onPressed: () {
setState(() {
showDialog(
context: context,
child: SimpleDialog(
children: <Widget>[
SimpleDialogOption(
child: SwitchListTile(
title: Text('Mute'),
secondary: Icon(AntDesign.sound),
value: _mute,
onChanged: (bool value) {
setState(() {
_mute = value;
});
}),
),
],
));
});
},
)
],
),
),
),
);
}

setState rebuilds your build method and change the _mute value, but the SimpleDialog is open in another context, so that dialog is not rebuild. You should make a new StatefulWidget with the AlertDialog and setState when changing the value there
class Class1 extends StatefulWidget {
#override
_Class1State createState() => _Class1State();
}
class _Class1State extends State<Class1> {
bool _mute = false;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
FlatButton.icon(
icon: Icon(AntDesign.sound),
label: Text('Sound Options'),
onPressed: () {
showDialog(
context: context,
builder: (context) => MyDialog(_mute)
);
},
)
],
),
),
),
);
}
}
class MyDialog extends StatefulWidget {
bool mute;
MyDialog(this.mute);
#override
_MyDialogState createState() => _MyDialogState();
}
class _MyDialogState extends State<MyDialog> {
#override
Widget build(BuildContext context) {
return SimpleDialog(
children: <Widget>[
SimpleDialogOption(
child: SwitchListTile(
title: Text('Mute'),
secondary: Icon(AntDesign.sound),
value: widget.mute,
onChanged: (bool value) {
setState(() {
widget.mute = value;
});
}),
),
],
);
}
}
Another few tips, use builder instead of child with showDialog, child is deprecated, don't wrap the whole SimpleDialog in a setState, you should just wrap the value you're going to change, wrapping the dialog won't make it rebuild with the new value.
From the documentation
Calling setState notifies the framework that the internal state of
this object has changed in a way that might impact the user interface
in this subtree, which causes the framework to schedule a build for
this State object.
the setState you use rebuilds the subtree of your whole Scaffold Stateful Widget, but the Dialog is open in another tree, it's not part of that subtree, If you use Android Studio there is a flutter inspector (and I believe Visual Studio has it too), you can see there that the dialog and your StatefulWidget come from Material App (the dialog is not part of the subtree so it won't rebuild)

Related

Get Current Context for Overlay Flutter

I want to display notifications that are being processed by a provider on an open gRPC stream. This is handled by notificationProvider.dart:
if (notification.type == 0) {
showOverlayNotification(notification);
} else {
notificationList.add(notification);
notifyListeners();
}
showOverlayNotification() then displays custom notification Widget OverlayNotification() on top of the current screen that user is in, like:
showOverlayNotification() async {
OverlayState? overlayState = Overlay.of(**context**);
OverlayEntry overlayEntry = OverlayEntry(builder: (context) {
return Positioned.fill(child: OverlayNotification());
});
overlayState?.insert(overlayEntry);
}
Now the problem is that I don't know how to get to the current context the user is in?
Android was pretty straightforward with this, but I can't seem to find this in Flutter..
How do I display overlay widget to a current context?
Do I have to keep track of it in a global variable in the end?
Can I find it through NamedRoutes?
can do something like this, fyi if you don't want to use navigator to pop current view you can create a navigator key as shown here
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
_buildPopupMessage(BuildContext context){
return Center(
child: IconButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return FittedBox(
fit: BoxFit.scaleDown,
child: AlertDialog(
title: Text(
'title text',
textAlign: TextAlign.center,
),
content: Text('message text here',
textAlign: TextAlign.center),
actions: <Widget>[
TextButton(
onPressed: () {
debugPrint('closed pressed');
Navigator.pop(context);
},
child: Text('close'),
)
],
),
);
},
);
},
icon: Icon(
Icons.live_help,
),
),
);
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body:_buildPopupMessage(context),
);
}
}

flutter build only a single widget

I have two widgets in a column. One is Text and second is TextButton. What i want that if i click on button then the Text widget rebuild only not the whole page.
I am new to flutter how can i achieve this? If i convert this to a statful widget and call setState method then whole page will be rebuild. but i want to know any trick to do rebuild only a single widget out of whole page.
class Page3 extends StatelessWidget {
Color color = Colors.red;
changeColor() {
// do something to rebuild only 1st column Text not the whole page
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page3'),
),
body: Column(
children: [
//First widget
Text(
'Title',
style: TextStyle(color: color),
),
//Second widget
TextButton(
onPressed: () => changeColor(),
child: Text('change color of title'),
)
],
));
}
}
Please refer to below code
ValueListenableBuilder widget. It is an amazing widget. It builds the widget every time the valueListenable value changes. Its values remain synced with there listeners i.e. whenever the values change the ValueListenable listen to it. It updates the UI without using setState() or any other state management technique.
In Dart, a ValueNotifier is a special type of class that extends a ChangeNotifer . ... It can be an int , a String , a bool or your own data type. Using a ValueNotifier improves the performance of Flutter app as it can help to reduce the number times a widget gets rebuilt.
ValueListenableBuilder will listen for changes to a value notifier and automatically rebuild its children when the value changes.
For more info refer to this link description
Solution 1
class Page3 extends StatelessWidget {
Color color = Colors.red;
final ValueNotifier<bool> updateColor = ValueNotifier(false);
changeColor(Color changedColor) {
// do something to rebuild only 1st column Text not the whole page
color = changedColor;
updateColor.value = !updateColor.value;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page3'),
),
body: Column(
children: [
//First widget
ValueListenableBuilder<bool>(
valueListenable: updateColor,
builder: (context, val, child) {
return Text(
'Title',
style: TextStyle(color: color),
);
}),
//Second widget
TextButton(
onPressed: () => changeColor(Colors.purple),
child: Text('change color of title'),
)
],
));
}
}
Solution 2
In ValueListenable we pass our created ValueNotifier variable whose changes will be notified and in builder we will return a widget that will be reflected every time when the value of ValueNotifier will be changed.
class Page3 extends StatelessWidget {
// Color color = Colors.red;
final ValueNotifier<Color> updateColor = ValueNotifier(Colors.red);
changeColor(Color changedColor) {
// do something to rebuild only 1st column Text not the whole page
updateColor.value = changedColor;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page3'),
),
body: Column(
children: [
//First widget
ValueListenableBuilder<Color>(
valueListenable: updateColor,
builder: (context, val, child) {
return Text(
'Title',
style: TextStyle(color: val),
);
}),
//Second widget
TextButton(
onPressed: () => changeColor(Colors.purple),
child: Text('change color of title'),
)
],
));
}
}
Here's the code of what you need to do
class Demo extends StatefulWidget {
const Demo({Key? key}) : super(key: key);
#override
State<Demo> createState() => _DemoState();
}
class _DemoState extends State<Demo> {
var isTextChanged = false;
Void changeColor() {
setState(() {
isTextChanged = true;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page3'),
),
body: Column(
children: [
//First widget
Text(
'Title',
style: TextStyle(color: isTextChanged ? Colors.red : Colors.black),
),
//Second widget
TextButton(
onPressed: () => changeColor(),
child: Text('change color of title'),
)
],
));
}
}
setStatefunction can not be called inside StatelessWidget widget. if you want to rebuild the widget tree, you have to convert it to StatefulWidget.
This is what you can do.
class Page3 extends StatefulWidget {
const Page3();
#override
_Page3State createState() => _Page3State();
}
class _Page3State extends State<Page3> {
Color color = Colors.red;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: [
Text(
'Title',
style: TextStyle(color: color),
),
TextButton(
onPressed: () => changeColor(),
child: Text('change color of title'),
)
]),
);
}
changeColor() {
setState(() {
color = Colors.green;
});
}
}
If you want to rebuild the Text widget without rebuilding the whole Page3 then you need to got for 'state management' solution.
Try below code hope its help to you. you must used StateFulWidget for that
Create one bool variable
bool isButtonPressed = true;
Your widgets:
Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Title',
style: isButtonPressed
? TextStyle(
color: Colors.black,
fontSize: 20,
)
: TextStyle(
color: Colors.green,
fontSize: 20,
),
),
),
TextButton(
child: new Text('Change color'),
onPressed: () {
setState(() {
isButtonPressed = !isButtonPressed;
});
},
),
],
),
Your Screen without button pressed:
Your Screen with button pressed:
You need to understand how setState works.
Lets assume you have a class named HomeScreen, within the homescreen you are overriding the build method to build your own widgets.
Widget build(BuildContext context){
return Column(
children:<Widget> [
FirstTextWidget();
SecondTextWidget();
ThirdTextWidget(),
])
}
when you call SetState function within that "homesceeen" class, the homescreen class itself calls the build method again and all of componenets you have returned within build function get re-rendered. Every text within homescreen class gets rerendered.
So whats the solution?
The Solution is separate your stateful widget with different class so that only those widgets gets rerendered when needed not whole. I will prefer you to use State Management plugin like Provider, bloc etc.

How to change the state of an Action widget in an AlertDialog

I want to validate the user input and then enable the Dialog's Submit action.
Using StatefulBuilder inside the dialog's content doesn't work here, since the actions does not live in the content Widget. So how would one go about changing a property on an action widget?
Currently my workaround is to not use actions, and have a Row of buttons inside the dialog's content, which is OK, but I'd like to know whether there is a better way.
I feel that using a Provider just to hold the "validation result" is a bit overkill.
EDIT: Example demonstrating that a StatefulBuilder allows only inner widgets to be updted with state changes. The button sets enabled, but the of two "action buttons" only the one inside the StatefulBuilder is rebuilt. The AlertDialog actions does not get updated.
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Dialogs'),
),
body: Center(
child: TextButton(
child: Text('Clickme'),
onPressed: _dialogWithAction,
),
),
);
}
Future<void> _dialogWithAction() async {
showDialog<bool>(
context: context,
builder: (BuildContext dialogContext) {
bool enabled = false;
return AlertDialog(
title: Text('Add Address'),
content: StatefulBuilder(builder: (context, innerSetState) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
child: Text('Enable Submit'),
onPressed: () {
enabled = true;
innerSetState(() {});
},
),
Row(
children: [
// This Workaround widget is rebuild by the stateSetter
TextButton(
onPressed: enabled ? () => Navigator.pop(context) : null,
child: Text('Submit'),
)
],
)
],
);
}),
actions: <Widget>[
// This Widget does not get rebuilt when "enabled" is changed
TextButton(
child: Text('Submit'),
onPressed: enabled
? () {
Navigator.of(dialogContext).pop();
}
: null,
),
],
);
},
);
}
}

How to warn a user before closing the expansion tile by showing a popup message in flutter?

I'm learning about expansion tiles in flutter. Is there any way to show a pop up before closing the expansion tile? If ok is pressed close the expansion tile, if cancel is pressed retain the state of expansion tile. How can I do this?
ExpansionTile docs state that it has a property onExpansionChanged:
Called when the tile expands or collapses.
When the tile starts expanding, this function is called with the value true. When the tile starts collapsing, this function is called with the value false.
One way would be to combine this property (for example, whenever you receive false) with something like showDialog:
Displays a Material dialog above the current contents of the app, with Material entrance and exit animations, modal barrier color, and modal barrier behavior (dialog is dismissible with a tap on the barrier).
Edit - addressing edited question:
It seems that programatically expanding/collapsing ExpansionTile is somewhat problematic. There's an issue in Flutter repository for it.
However, I believe you could achieve what you seek by extending ExpansionTile as discussed in this answer. It is a little involved, though.
Another way to do that would be employing ExpansionPanelList and ExapnsionPanels, as suggested here. I've included a working (but crude) proof-of-concept app below. Feel free to modify and adapt it to your needs.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Expanding panels',
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
bool expanded = true;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Pushing data'),
),
body: Center(
child: Card(
child: Column(
children: <Widget>[
ExpansionPanelList(
expansionCallback: (int index, bool status) {
if (status == true) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: new Text('Alert Dialog'),
content: new Text('What do you want to do?'),
actions: <Widget>[
FlatButton(
child: Text('Cancel'),
onPressed: () => Navigator.of(context).pop(),
),
FlatButton(
child: Text(
'Collapse',
style: TextStyle(color: Colors.red),
),
onPressed: () {
setState(() {
expanded = false;
});
Navigator.of(context).pop();
},
),
],
);
},
);
} else {
setState(() {
expanded = true;
});
}
},
children: [
ExpansionPanel(
canTapOnHeader: true,
isExpanded: expanded,
headerBuilder: (BuildContext context, bool expanded) {
return Text('Expansion Panel Header');
},
body: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text('Expanded panel body'),
),
),
),
],
),
],
),
),
),
);
}
}

flutter: Another exception was thrown: No MaterialLocalizations found

I am trying to show an Alert Dialog on press of a button in Flutter.
Following is my code
main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Different Widgets",
debugShowCheckedModeBanner: false,
home: showAlertDialog()
);
}
void _dialogResult(String value) {
if (value == "YES") {
print("YES");
} else {
print("NO");
}
Navigator.pop(context);
}
Widget showAlertDialog() {
TextEditingController textEditingController = TextEditingController();
return Scaffold(
appBar: AppBar(
title: Text("Different Widgets"),
),
body: Container(
child: Center(
child: Column(
children: <Widget>[
TextField(
controller: textEditingController,
),
RaisedButton(
onPressed: () {
print("Hi");
AlertDialog dialog = AlertDialog(
title: Text("Hi"),
content: Text(
textEditingController.text,
style: TextStyle(fontSize: 30.0),
),
actions: <Widget>[
FlatButton(
onPressed: () {
_dialogResult("YES");
},
child: Text("YES")),
FlatButton(
onPressed: () {
_dialogResult("NO");
},
child: Text("NO")),
],
);
showDialog(context: context, builder: (BuildContext context) => dialog);
},
child: Text("Click Me"),
)
],
),
),
),
);
}
What does this has to do with Localisation, I cannot follow. I did the same steps as per the docs. I am able to see the button but on click of that button I keep getting error. I tried writing print statement inside of button click and the print statement appears in the log, definitely something wrong with AlertDialog.
You may get No MaterialLocalizations found error while showing dialog using showDialog() class in Flutter. The issue is putting child widget on home property of MaterialApp() widget without creating new widget class.
One way to solve is putting MaterialApp() inside runApp() and create new class for home property.
import 'package:flutter/material.dart';
main() {
runApp(
MaterialApp(
home: MyApp(),
title: "Different Widgets",
debugShowCheckedModeBanner: false,
),
);
}
/*
place MaterialApp() widget on runApp() and create
new class for its 'home' property
to escape 'No MaterialLocalizations found' error
*/
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return showAlertDialog();
}
void _dialogResult(String value) {
if (value == "YES") {
print("YES");
} else {
print("NO");
}
Navigator.pop(context);
}
Widget showAlertDialog() {
TextEditingController textEditingController = TextEditingController();
return Scaffold(
appBar: AppBar(
title: Text("Different Widgets"),
),
body: Container(
child: Center(
child: Column(
children: <Widget>[
TextField(
controller: textEditingController,
),
RaisedButton(
onPressed: () {
print("Hi");
AlertDialog dialog = AlertDialog(
title: Text("Hi"),
content: Text(
textEditingController.text,
style: TextStyle(fontSize: 30.0),
),
actions: <Widget>[
FlatButton(
onPressed: () {
_dialogResult("YES");
},
child: Text("YES")),
FlatButton(
onPressed: () {
_dialogResult("NO");
},
child: Text("NO")),
],
);
showDialog(
context: context,
builder: (BuildContext context) => dialog);
},
child: Text("Click Me"),
)
],
),
),
),
);
}
}