In my Flutter app I want to re-use a custom dialog that received user input. All works fine if I do the showDialog() from within the class and have the custom AlertDialog in another class. However, that forces me to re-type the button's onpressed and showdialog sections every single time. I'd prefer to have that also in another reusable class and pass user input to it (and get the button with all functionalities in return). The problem with this is that the user input is not present when the page is loaded, so I need to know the updated state when the button is pressed. And there I get stuck... guessing it should be something with passing the widget's state, but getting lost here.
I created a minimal app to recreate the problem, here is the code:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
var myTextFieldController = TextEditingController();
return Scaffold(
appBar: AppBar(title: const Text('Dialog test')),
body: ListView(
padding: const EdgeInsets.all(20),
children: [
TextField(controller: myTextFieldController),
TestButton().getButton(context, myTextFieldController.text),
],
),
);
}
}
class TestButton {
Widget getButton(BuildContext context, myText) {
return ElevatedButton(
child: const Text('Show Dialog'),
onPressed: () {
showDialog(
context: context,
builder: (builder) {
return AlertDialog(
title: Text(myText), // is always empty
);
});
},
);
}
}
The AlertDialog's text remains empty , despite text in the TextField. How can I get this to work? Thx!
CodePudding user response:
You can follow these steps:
- Create a new
Stringvariable to store the value in theTextField. - Update the
Stringvalue in the onChanged property of theTextFieldand call setState. - Pass the new variable created to
getButtoninstead ofmyTextFieldController.text
Code:
class _HomeScreenState extends State<HomeScreen> {
var myTextFieldController = TextEditingController();
String textValue = "";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Dialog test')),
body: ListView(
padding: const EdgeInsets.all(20),
children: [
TextField(
controller: myTextFieldController,
onChanged: (v){
textValue = v;
setState((){});
}
),
TestButton().getButton(context, textValue),
],
),
);
}
Why is the value passed always empty when I use myTextFieldController.text?
This is because the first value of myTextFieldController.text which is "" (empty) is what is being passed to getButton and it is not getting updated at getButton when the value of the TextField changes.
CodePudding user response:
Call your getbutton mehtod like this.........
TestButton().getButton(context, myTextFieldController.value.text);
It works.....Accessing the value attribute of your textfieldcontroller will fix this problem.
CodePudding user response:
This is because initially when you call TestButton().getButton a new widget is built with initial value of myTextFieldController.text which is empty. Now further down the line when you insert text the value of myTextFieldController.text changes, but flutter is still using the widget that was built at first and therefore dialog text does not get updated. The way you can force a rebuild is by using setState when myTextFieldController.text changes. But it is best to detach the widget and dialog from one another. Right now the widget responsible for showing the dialog is a simple ElevatedButton. If it had a fairly complex ui that needed to be used in other places it's better to implement a new widget instead of having a class that returns you a widget in its methods. If you detach the widget from showDialog there won't be any need to call setState anymore. The following code has two buttons to showcase the difference. The second one just works, for the first one though you'll have to uncomment the onChanged of TextField:
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
var myTextFieldController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Dialog test')),
body: ListView(
padding: const EdgeInsets.all(20),
children: [
TextField(
controller: myTextFieldController,
/// Uncomment the following 3 lines for the first button to work
// onChanged: (val) {
// // setState(() {});
// },
///
),
TestButton().getButton(context, myTextFieldController.text),
ElevatedButton(
onPressed: () => TestButton.showTestDialogue(context, myTextFieldController.text),
child: const Text('Show Test Dialog'),
),
],
),
);
}
}
class TestButton {
Widget getButton(BuildContext context, myText) {
return ElevatedButton(
child: const Text('Show Dialog'),
onPressed: () {
showDialog(
context: context,
builder: (builder) {
return AlertDialog(
title: Text(myText), // is always empty
);
},
);
},
);
}
static Future showTestDialogue(BuildContext context, myText) {
return showDialog(
context: context,
builder: (builder) {
return AlertDialog(
title: Text(myText),
);
},
);
}
}
