Enable/disable CupertinoDialogAction depending on CupertinoTextField is empty or not - flutter

I want to set CupertinoDialogAction to enable if CupertinoTextField is not empty else by default it should be disabled, also I have set the "isDefaultAction: false" but it is still clickable.
showDialog(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
actions: [
CupertinoDialogAction(
onPressed: () => (Navigator.of(context).pop()),
child: Text("Cancel"),
),
CupertinoDialogAction(
child: Text("Save"),
isDefaultAction: false,
),
],
title: Text("New Folder"),
content: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text("Enter a name for this folder"),
),
Container(
height: 30,
child: CupertinoTextField(
controller: folderName,
placeholder: "Name",
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
),
),
)
],
),
),
);

If you want to disable a CupertinoDialogAction, you need to set the onPressed property to null. It will look like this:
Bool isEnabled = false;
#override
void initState() {
super.initState();
folderName.addListener(enableButton); // addListened to your TextEditingController!
}
It will set isEnabled to true.
enableButton()
{
if(folderName.text != "")
{
setState(() {
isEnabled = true;
});
}
}
And, then you can use this boolean field.
CupertinoDialogAction(
onPressed: !isEnabled
? null
: () {
// Do what you need!
// Save method!
},
child: Text("Save"),
isDefaultAction: false,
),

Create a stateful widget that builds the list of actions and returns CupertinoAlertDialog with those actions. This widget should contain some state that indicates if the save action should be enabled or not. If it should not be enabled, put null into the onPressed handler.
Write some handler that uses setState to set this enabled/disabled state depending on what the user is doing.
Return your stateful widget from the showDialog builder

Related

How to make only two buttons active in Flutter/Dart?

In my code, there is a condition that pops up the following dialog:
Its code looks like this:
//Alert Dialog about questions and answers
void _showAlertDialog() {
// set up the buttons
Widget Answer1Button = TextButton(
child: Text(_listData[_listCount][3]),
onPressed: () {},
);
Widget Answer2Button = TextButton(
child: Text(_listData[_listCount][4]),
onPressed: () {},
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
// title: Text(),
content: Text(_listData[_listCount][2]),
actions: [
Answer1Button,
Answer2Button,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
I need my user to not be able to tap on the screen or press the back button. Only two buttons need to be active (Answer 1.1 and Answer 1.2 in this example).
How to do it? Thanks in advance.
Edit1. I don't know what I'm doing wrong. Here is my code where I followed your advice:
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
await showDialog;
// await showDialog or Show add banners or whatever
// return true if the route to be popped
return false; // return false if you want to disable device back button click
},
child: Scaffold(
appBar: AppBar(
title: const Text('New Game'),
),
body: Container(
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(assetPath),
fit: BoxFit.cover)),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Visibility(
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Stack(
children: <Widget>[
Positioned.fill(
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('files/sheet.jpg'),
fit: BoxFit.cover)),
),
),
Text(_listData[_listCount][0]),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ImageButton(label: 'OK', onButtonTap: _nextCSV),
ImageButton(label: 'Hide', onButtonTap: _isVisible),
ImageButton(label: 'Test1', onButtonTap: _showAlertDialog),
],
),
],
),
visible: isVisible,
),
// your other widgets
Visibility(
child: ImageButton(label: 'Show', onButtonTap: _isVisible),
visible: !isVisible,
)
],
),
),
),
);
}
But nothing has changed. I believe that I made a mistake here:
await showDialog;
However, when I do like this, nothing changes either. The back button still works:
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
return false; // return false if you want to disable device back button click
},
child: Scaffold(
...
As a result, I can still click on the back button. Although I want it to be inactive on the dialog. How to fix it?
You should wrap the AlertDialog with OnWillPop, not the Scaffold, since you need to set the action pop for the AlertDialog widget.
Here's a minimal example of how to implement the OnWillPop widget to match your case:
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: ElevatedButton(
onPressed: () {
_showAlertDialog();
},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blue),
),
child: const Text(
"Open Dialog",
style: TextStyle(color: Colors.white),
))),
);
}
void _showAlertDialog() {
// set up the buttons
Widget Answer1Button = TextButton(
child: Text("Button 1"),
onPressed: () {
Navigator.pop(context);
},
);
Widget Answer2Button = TextButton(
child: Text("Button 2"),
onPressed: () {
Navigator.pop(context);
},
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
// title: Text(),
content: Text("Alert Dialog"),
actions: [
Answer1Button,
Answer2Button,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return WillPopScope(
onWillPop: () async {
return false;
},
child: alert);
},
);
}
}
You can pass a parameter named 'barrierDismissible' in the showDialog, set it to false.
showDialog(
barrierDismissible: false,
builder: ...
)
There is one parameter called barrierDismissible which you can use to dismiss any tap of background of dialog,
showDialog(
barrierDismissible: false,
)
You can disable back button by wrapping the scaffold with WillPopScope.
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
// await showDialog or Show add banners or whatever
// return true if the route to be popped
return false; // return false if you want to disable device back button click
},
child: Scaffold(),
),
};
Here is the docs links
https://api.flutter.dev/flutter/widgets/WillPopScope-class.html

Flutter: how to prompt user for input using showDialog after tapping on ListTile, then using the input

Assuming i have the codes below,
Widget _myListView(BuildContext context) {
return ListView(
children: <Widget>[
ListTile(
title: Text('Sun'),
onTap:() async{
await showDialog<String>(
context:context,
--- not sure how to proceed from here --
}
}
Navigator.pop(context); //this code makes sure it go back to another page after done with the input processing.
),
ListTile(
title: Text('Moon'),
),
ListTile(
title: Text('Star'),
),
],
);
}
how to use the input onTap ListTile, run some method to use the input, and pop back to previous page.
Call function _showMyDialog(); and see data in the console:
I used Row() to work in landscape.
final myController = TextEditingController();
late String userData;
Future<void> _showMyDialog() async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Row(
children: [
Expanded(
child: TextField(
controller: myController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Enter any data',
hintText: 'Enter any data',
),
),
),
Expanded(
child: TextButton(
child: const Text('OK'),
onPressed: () {
userData = myController.text;
myController.text = '';
FocusManager.instance.primaryFocus?.unfocus();
print(userData);
Navigator.of(context).pop();
},
),
)
]
)
);
},
);
}
#override
void dispose() {
myController.dispose();
super.dispose();
}

Return variable from current screen to previous screen

So I am implementing a 'settings' view in my Flutter app. The idea is all settings will appear in a ListView, and when the user will click on a ListTile, a showModalBottomSheet will pop where the user will be able to manipulate the setting. The only problem I am having is I am unable to migrate the showModalBottomSheet to a separate class as I cannot make the new function (outside the class) return the manipulated setting variable. This has lead to a messy code, all in a single class.
class Page extends StatefulWidget {
Page({Key key}) : super(key: key);
#override
_Page createState() => _Page();
}
class _Page extends State<Page> {
var value;
#override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
ListTile(
title: Text("Age"),
trailing: Text(value),
onTap: () {
setState(() {
value = _valueSelector(); // This doesn't work, but to give an idea what I want
});
},
),
],
);
}
}
int _valueSelector(context) { // Doesn't return
var age = 0;
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Wrap(
children: [
Column(
children: <Widget>[
Slider(
value: age.toDouble(),
min: 0,
max: 18,
divisions: 18,
onChanged: (value) {
setState(() {
age = value.toInt();
});
},
),
],
),
],
);
});
},
).whenComplete(() {
return age; // Not sure if return is supposed to be here
});
}
How can I implement showModalBottomSheet in a separate class and just make it return the variable representing the setting chosen by the user?
You can try the below code,
First, create a class custom_bottom_sheet.dart and add the below code. You can use it everywhere in the project. And also use this library modal_bottom_sheet: ^0.2.0+1 to get the showMaterialModalBottomSheet.
customBottomSheet(BuildContext context, {#required Widget widget}) async {
return await showMaterialModalBottomSheet(
context: context,
backgroundColor: AppColors.transparent_100,
barrierColor: AppColors.black_75,
isDismissible: false,
enableDrag: true,
builder: (_, ScrollController scrollController) {
return widget;
},
);
}
Sample example code:
Create another class called bottom_sheet_example.dart and add the below code.
class BottomSheetExample {
static Future getSheet(BuildContext _context,
{ValueChanged<bool> onChanged}) async {
await customBottomSheet(
_context,
widget: SafeArea(
child: Container(
padding: EdgeInsets.only(left: 40.0, right: 40.0),
height: 170.0,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(27.0),
topRight: Radius.circular(27.0))),
child: Container(
padding: EdgeInsets.only(top: 32),
child: Column(
children: [
Text("Were you at Queen Victoria Building?"),
SizedBox(height: 48),
Row(
children: [
Expanded(
child: RaisedButton(
child: Text("No"),
onPressed: () {
Navigator.of(_context).pop();
onChanged(false);
},
),
),
SizedBox(width: 18),
Expanded(
child: RaisedButton(
child: Text("Yes"),
onPressed: () {
Navigator.of(_context).pop();
onChanged(true);
},
),
),
],
),
SizedBox(height: 24),
],
),
),
)),
);
}
}
Button click to show the bottom sheet
#override
Widget build(BuildContext context) {
return Scaffold(
body: yourBodyWidget(),
bottomNavigationBar: Container(
height: 40,
width: double.infinity,
child: FlatButton(
onPressed: () {
/// call BottomSheetExample class
BottomSheetExample.getSheet(
context,
onChanged: (bool result) async {
///
/// add your code
},
);
},
child: Text("show bottom sheet")),
),
);
}
In onChanged callback you can return your value(obj/String/num/bool/list).
Thank you!

Flutter - Enable/Disable button when length is greater or less than 0

I'm looking for the button to be enabled or disabled, while I'm writing in the textField. If the length of the text is greater than 0, I need the button to be enabled.
final myController = TextEditingController();
static var nameProduct;
#override
void initState() {
super.initState();
}
return Dialog(
shape: RoundedRectangleBorder(),
child: Container(
child: Stack(
children: <Widget>[
Container(child: Center(
child: Column(
children: <Widget>[
TextField(
autofocus: true,
controller: myController,
onChanged: (String text) {
nameProduct = text;
},
),
Padding(
child: Row(
children: <Widget>[
Container(
child: RaisedButton(
onPressed: (nameProduct.isNotEmpty)
? () => {
Navigator.of(context)
.pop(false),
myMethod()
}
: null,
),
),
],
),
),
],
),
),
)
],
),
));
With the code I publish here, it's not working for me.Thanks
The build method is not being called again to perform the redraw.
You can use the setState(() {}); like this:
onChanged: (String text) {
setState(() {
nameProduct = text;
});
},
Problem here is that the widget is only built once. Therefore, subsequent changes to your textfield do not tell Flutter to rebuild the widget.

Looking up a deactivated widget's ancestor is unsafe

I am new in Flutter and I am trying receive data with a Dialog.
When a click in textField the error of image2 appear...
show(BuildContext context){
var dialog = Dialog(
child: Container(
margin: EdgeInsets.all(8.0),
child: Form(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextFormField(
decoration: InputDecoration(
labelText: "Insira o número de telefone",
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(2.0)))),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("Cancelar")),
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("Aceitar"))
],
)
],
),
),
),
);
showDialog(context: context,builder: (context){
return dialog;
});
}
This is my code.
I/flutter (31032): Looking up a deactivated widget's ancestor is unsafe.
I/flutter (31032): At this point the state of the widget's element tree is no longer stable. To safely refer to a
I/flutter (31032): widget's ancestor in its dispose() method, save a reference to the ancestor by calling
I/flutter (31032): inheritFromWidgetOfExactType() in the widget's didChangeDependencies() method.
I/flutter (31032):
Declare a global variable
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
then register the key on your widget build's scaffold eg
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
...
then on the dialog
show(BuildContext context){
var dialog = Dialog(
child: Container(
margin: EdgeInsets.all(8.0),
child: Form(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextFormField(
decoration: InputDecoration(
labelText: "Insira o número de telefone",
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(2.0)))),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("Cancelar")),
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("Aceitar"))
],
)
],
),
),
),
);
Pass that scaffold context to the showDialog method
showDialog(context: _scaffoldKey.currentContext ,builder: (context){
return dialog;
});
}
Try This
Give different context name for dialog
showDialog(context: context,builder: (dialogContex){
return Dialog(
child: Container(
margin: EdgeInsets.all(8.0),
child: Form(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextFormField(
decoration: InputDecoration(
labelText: "Insira o número de telefone",
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(2.0)))),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(dialogContex).pop();
},
child: Text("Cancelar")),
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("Aceitar"))
],
)
],
),
),
),
);
});
I got the same error when attempting to open a dialog and I found a solution here: github flutter issues. Specifically, I followed the poster's recommendation, which was to create a GlobalKey and associate it with the Scaffold widget, and use the context from that key when creating the dialog. In my case, I have a globally accessible object which holds the GlobalKey:
MyGlobals myGlobals = MyGlobals();
class MyGlobals {
GlobalKey _scaffoldKey;
MyGlobals() {
_scaffoldKey = GlobalKey();
}
GlobalKey get scaffoldKey => _scaffoldKey;
}
In the Scaffold widget constructor call:
Scaffold(
appBar: ...,
body: ...,
drawer: ...,
key: myGlobals.scaffoldKey,
)
And in the showDialog call:
showDialog<String>(
barrierDismissible: ...,
builder: ...,
context: myGlobals.scaffoldKey.currentContext,
);
You’re trying to access a context that isn’t probably available. That happens because you’ve assigned your Dialog to a var and afterwards use a different context (the one from your dialog builder).
Either create your dialog directly after your return in the builder or make it a method instead that returns a Dialog and pass it a BuildContext parameter.
Widget myDialog(BuildContext context) => Dialog(/*your dialog here*/);
This is also a more convenient Flutter practice. You should use methods that return widgets instead of assigning it to variables.
use this:
Navigator.of(context,rootNavigator: true).pop();
instead of
Navigator.of(context).pop();
This might happen while you are popping from the context and trying to open new content on the context you are popping.
()async{
Navigator.of(context).pop();
_alertPopUp(); // shows a dialog
// might do some work after
}
if alert dialog is created on current context then it throws an error because context doesn't exist anymore
My problem was that I was using hot reload for pretty long time, I think at some point everything got messed up, doing a normal run of the app fixed the problem.
removing application from emulator and run below commands
flutter clean
flutter pub get
works for me
Though you got desired answer, just for better clarification for others I put my opinion here.
Reason :
It is happend due to context mismatch issue. Your passing context to Navigator.of(context).pop() is not matching with your MainApp BuildContext.
Solution : There has 2 way
U can use Global key
pass actual context to your Navigator
Below link I already mentioned how to solve this by passing actual context
https://stackoverflow.com/a/73543251/6109034
Try this:
Future<AlertDialog> myDialog(BuildContext context) {
return showDialog<AlertDialog>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
margin: EdgeInsets.all(8.0),
child: Form(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextFormField(
decoration: InputDecoration(
labelText: "Insira o número de telefone",
border: OutlineInputBorder(
borderRadius:
BorderRadius.all(Radius.circular(2.0)))),
),
],
),
),
),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("Cancelar")),
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("Aceitar"))
],
);
},
);
}
declare dialog and set in initState
late Dialog dialog;
#override
void initState() {
super.initState();
dialog = Dialog(
...
);
}
before calling a dialog when a page is just loading, call it by adding SchedulerBinding to it, call it like this
SchedulerBinding.instance?.addPostFrameCallback((_) => showDialog( context: context, barrierDismissible: false, builder: (context) { return dialogBox(context, "Fetching account data", 'Profile page', DialogType.processing, function: () {}, dismissText: "", ); }));
In my case I was using a provider where I used a context as an argument to a function, the thing was that when I passed that page I did it with pushnamedAndRemove Until then on the next page I was trying to use a function where I required the above context, so the error was mine because it was trying to get a parameter that I destroyed earlier, for that reason it didn't work. So be careful if you are deleting old pages.
Use this if You are using Stack in AlertDialog Not Closing on Navigator.of(context).pop();
late NavigatorState _navigator;
#override
void didChangeDependencies() {
_navigator = Navigator.of(context);
super.didChangeDependencies();
}
Use This
Positioned(right: 10.0,child: GestureDetector(
// behavior: HitTestBehavior.translucent,
onTap: () {
_navigator.pop(context);
},
child: Align(
alignment: Alignment.topRight,
child: CircleAvatar(
radius: 14.0,
backgroundColor: Colors.white,
child: Icon(Icons.close, color: black),
),
),
),
),
I simply solved this by wrapping the showDialog with a Builder widget, though for me the error came from a stream builder I simply wrap the stream builder with a builder widget and the remove the notify listeners from the a stream am calling in the stream builder, but in your case wrap the showDialog with a Builder widget and it will use the context from the builder, problem solved
first : declare a FormKey.
GlobalKey<FormState>myFormKey=GlobalKey<FormState>();
second : add the FormKey to your Form widget.
Form(
key:myFormKey,
child:child
)
In my case i was calling
setState(() {
Navigator.pop(context);
});