How to show selected checkbox on prev screen? - flutter

I need to display checkboxes selected by the user on the previous page using pop()
I have a function that displays the user's message on the previous page and I need to pass the selected checkboxes in the same way. How to pass them as arguments to pop()?
Screen with checkboxes:
const TextScreen({Key? key}) : super(key: key);
#override
State<TextScreen> createState() => _TextScreenState();
}
class _TextScreenState extends State<TextScreen> {
// initial values for checkboxes
bool _privacy = false;
bool _termsOfUse = false;
// text controller for message input
TextEditingController textController = TextEditingController();
#override
void dispose() {
textController.dispose();
super.dispose();
}
// go to result screen
void getResult(BuildContext context) {
String valueResult = textController.text;
Navigator.pop(context, valueResult);
}
#override
Widget build(BuildContext context) {
//change state for privacy checkbox
_onPrivacyChange(value) {
setState(() {
_privacy = value!;
});
}
//change state for terms of use checkbox
_onTermsOfUSeChange(value) {
setState(() {
_termsOfUse = value!;
});
}
return Scaffold(
appBar: AppBar(
title: const Text('Enter data'),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: textController,
decoration: const InputDecoration(labelText: 'Message')),
const SizedBox(height: 20),
CheckboxListTile(
title: const Text('Privacy'),
controlAffinity: ListTileControlAffinity.leading,
value: _privacy,
onChanged: _onPrivacyChange,
contentPadding: EdgeInsets.zero,
),
CheckboxListTile(
title: const Text('Terms of use'),
controlAffinity: ListTileControlAffinity.leading,
value: _termsOfUse,
onChanged: _onTermsOfUSeChange,
contentPadding: EdgeInsets.zero,
),
ElevatedButton(
onPressed: () {
getResult(context);
},
child: const Text('Display result'))
],
)),
);
}
}
Screen with results display:
class ResultScreen extends StatefulWidget {
const ResultScreen({Key? key}) : super(key: key);
#override
State<ResultScreen> createState() => _ResultScreenState();
}
class _ResultScreenState extends State<ResultScreen> {
String? _valueText = '';
#override
Widget build(BuildContext context) {
// navigation to next screen
void _navToNextScreen(BuildContext context) async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const TextScreen()),
);
// update widget after result comes back
setState(() {
_valueText = result;
});
}
return Scaffold(
appBar: AppBar(
title: const Text('Results'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
_navToNextScreen(context);
},
child: const Text('Enter data'),
),
const SizedBox(height: 50),
Text('Message: $_valueText'),
const SizedBox(height: 20),
Text('Checkboxes: '),
],
)),
);
}
}

I think this should be the job of a simple state management strategy; for communication between separate widgets (in this case, two page widgets), that's the cleanest approach. You should create a common service to which both page widgets are subscribed: one to trigger the changes, the other to capture them and display them, using a ChangeNotifier service along with Consumer widgets, as shown below:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => SelectedData(),
child: MyApp()
)
);
}
class SelectedData extends ChangeNotifier {
bool _privacy = false;
bool _termsOfUse = false;
String _valueResult = '';
bool get privacy => _privacy;
bool get termsOfUse => _termsOfUse;
String get valueResult => _valueResult;
set privacy(bool value) {
_privacy = value;
notifyListeners();
}
set termsOfUse(bool value) {
_termsOfUse = value;
notifyListeners();
}
set valueResult(String value) {
_valueResult = value;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: ResultScreen(),
),
),
);
}
}
class TextScreen extends StatefulWidget {
const TextScreen({Key? key}) : super(key: key);
#override
State<TextScreen> createState() => _TextScreenState();
}
class _TextScreenState extends State<TextScreen> {
// text controller for message input
TextEditingController textController = TextEditingController();
#override
void initState() {
super.initState();
}
#override
void dispose() {
textController.dispose();
super.dispose();
}
// go to result screen
void getResult(BuildContext context) {
Navigator.pop(context);
}
#override
Widget build(BuildContext context) {
SelectedData data = Provider.of<SelectedData>(context, listen: false);
textController.text = data.valueResult;
//change state for privacy checkbox
_onPrivacyChange(value) {
data.privacy = value;
}
//change state for terms of use checkbox
_onTermsOfUSeChange(value) {
data.termsOfUse = value;
}
return Scaffold(
appBar: AppBar(
title: const Text('Enter data'),
),
body: Consumer<SelectedData>(
builder: (context, selectedData, child) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: textController,
onChanged: (value) {
data.valueResult = value;
},
decoration: const InputDecoration(labelText: 'Message')),
const SizedBox(height: 20),
CheckboxListTile(
title: const Text('Privacy'),
controlAffinity: ListTileControlAffinity.leading,
value: selectedData.privacy,
onChanged: _onPrivacyChange,
contentPadding: EdgeInsets.zero,
),
CheckboxListTile(
title: const Text('Terms of use'),
controlAffinity: ListTileControlAffinity.leading,
value: selectedData.termsOfUse,
onChanged: _onTermsOfUSeChange,
contentPadding: EdgeInsets.zero,
),
ElevatedButton(
onPressed: () {
getResult(context);
},
child: const Text('Display result'))
],
));
}
),
);
}
}
class ResultScreen extends StatefulWidget {
const ResultScreen({Key? key}) : super(key: key);
#override
State<ResultScreen> createState() => _ResultScreenState();
}
class _ResultScreenState extends State<ResultScreen> {
#override
Widget build(BuildContext context) {
// navigation to next screen
void _navToNextScreen(BuildContext context) async {
await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const TextScreen()),
);
}
return Scaffold(
appBar: AppBar(
title: const Text('Results'),
),
body: Consumer<SelectedData>(
builder: (context, selectedData, child) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
_navToNextScreen(context);
},
child: const Text('Enter data'),
),
const SizedBox(height: 50),
Text('Message: ${selectedData.valueResult}'),
const SizedBox(height: 20),
const Text('Checkboxes: '),
Text('Privacy: ${selectedData.privacy}'),
Text('Terms of Use: ${selectedData.termsOfUse}')
],
));
}
),
);
}
}
Here's the output when you implement it this way:

So from what i see is you are only passing one value that is message and you what many values to pass at a time so here the map can be used and as pop() function takes dynamic returns you can pass any thing.
From your example i have created a sample example that will be a working proof which will demostrate the using map for passing data to previous screen.
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(
debugShowCheckedModeBanner: false,
home: ResultScreen(),
);
}
}
class TextScreen extends StatefulWidget {
const TextScreen({Key? key}) : super(key: key);
#override
_TextScreenState createState() => _TextScreenState();
}
class _TextScreenState extends State<TextScreen> {
// initial values for checkboxes
bool _privacy = false;
bool _termsOfUse = false;
// text controller for message input
TextEditingController textController = TextEditingController();
#override
void dispose() {
textController.dispose();
super.dispose();
}
// go to result screen
void getResult(BuildContext context) {
String valueResult = textController.text;
final data = {
"message":valueResult,
"privacy": _privacy,
'terms':_termsOfUse,
};
Navigator.pop(context, data);
}
#override
Widget build(BuildContext context) {
//change state for privacy checkbox
_onPrivacyChange(value) {
setState(() {
_privacy = value!;
});
}
//change state for terms of use checkbox
_onTermsOfUSeChange(value) {
setState(() {
_termsOfUse = value!;
});
}
return Scaffold(
appBar: AppBar(
title: const Text('Enter data'),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: textController,
decoration: const InputDecoration(labelText: 'Message')),
const SizedBox(height: 20),
CheckboxListTile(
title: const Text('Privacy'),
controlAffinity: ListTileControlAffinity.leading,
value: _privacy,
onChanged: _onPrivacyChange,
contentPadding: EdgeInsets.zero,
),
CheckboxListTile(
title: const Text('Terms of use'),
controlAffinity: ListTileControlAffinity.leading,
value: _termsOfUse,
onChanged: _onTermsOfUSeChange,
contentPadding: EdgeInsets.zero,
),
ElevatedButton(
onPressed: () {
getResult(context);
},
child: const Text('Display result'))
],
)),
);
}
}
class ResultScreen extends StatefulWidget {
const ResultScreen({Key? key}) : super(key: key);
#override
State<ResultScreen> createState() => _ResultScreenState();
}
class _ResultScreenState extends State<ResultScreen> {
String? _valueText = '';
bool _privacyValue =false;
bool _termsOfUse = false;
#override
Widget build(BuildContext context) {
// navigation to next screen
void _navToNextScreen(BuildContext context) async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const TextScreen()),
);
if(result !=null)
{
setState(() {
if(result['message']!=null )_valueText = result['message'];
if(result['privacy']!=null) _privacyValue = result['privacy'];
if(result['terms']!=null) _termsOfUse = result['terms'];
});
}
}
return Scaffold(
appBar: AppBar(
title: const Text('Results'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
_navToNextScreen(context);
},
child: const Text('Enter data'),
),
const SizedBox(height: 50),
Text('Message: $_valueText'),
const SizedBox(height: 20),
Text('Privacy Value: $_privacyValue '),
const SizedBox(height: 20),
Text('Terms Value: $_termsOfUse '),
],
)),
);
}
}
You can make changes as per your needs, So let me know if it works.

Related

how to accept multiple variables in result?

I have an argument passed from one screen to the previous screen using pop().When I pass one variable, then everything is fine, but when I pass several, the application does not work.
How can I fix this and pass inputText and the value of two variables _privacy and _terms_of_use to me and output them? How can they be added to pop() and received in setState as a result?
My screen with arg:
class TextScreen extends StatefulWidget {
const TextScreen({Key? key}) : super(key: key);
#override
State<TextScreen> createState() => _TextScreenState();
}
class _TextScreenState extends State<TextScreen> {
// initial values for checkboxes
bool _privacy = false;
bool _termsOfUse = false;
// text controller for message input
TextEditingController textController = TextEditingController();
#override
void dispose() {
textController.dispose();
super.dispose();
}
void _getResult(BuildContext context) {
String inputText = textController.text;
Navigator.of(context).pop(inputText);
}
#override
Widget build(BuildContext context) {
void _onChangePrivacy(value) {
setState(() {
_privacy = value!;
});
}
void _onChangeTermsOfUse(value) {
setState(() {
_termsOfUse = value!;
});
}
return Scaffold(
appBar: AppBar(
title: const Text('Enter data'),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: textController,
decoration: const InputDecoration(labelText: 'Message'),
),
const SizedBox(height: 20),
CheckboxListTile(
title: const Text('Privacy'),
controlAffinity: ListTileControlAffinity.leading,
value: _privacy,
onChanged: (value) {
_onChangePrivacy(value);
},
contentPadding: EdgeInsets.zero,
),
CheckboxListTile(
title: const Text('Terms of use'),
controlAffinity: ListTileControlAffinity.leading,
value: _termsOfUse,
onChanged: (value) {
_onChangeTermsOfUse(value);
},
contentPadding: EdgeInsets.zero,
),
ElevatedButton(
onPressed: () {
_getResult(context);
},
child: const Text('Display result'))
],
)),
);
}
}
My Result Screen:
class ResultScreen extends StatefulWidget {
#override
State<ResultScreen> createState() => _ResultScreenState();
}
class _ResultScreenState extends State<ResultScreen> {
String? _valueText = '';
#override
Widget build(BuildContext context) {
// navigation to next screen
void _navToNextScreen(BuildContext context) async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const TextScreen()),
);
setState(() {
_valueText = result;
});
}
return Scaffold(
appBar: AppBar(
title: const Text('Results'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
_navToNextScreen(context);
},
child: const Text('Enter data')),
const SizedBox(height: 50),
Text('Message: $_valueText'),
const SizedBox(height: 20),
Text('Checkboxes: '),
],
)),
);
}
}
If we look at the implementation of the pop method it looks like this:
#optionalTypeArgs
static void pop<T extends Object?>(BuildContext context, [ T? result ]) {
Navigator.of(context).pop<T>(result);
}
This means we can optionally pass one generic object (T? result). In your case just passing a List containing multiple objects is the easiest solution.
The screen calling pop therefore could look like this:
Navigator.of(context).pop([inputText, inputText2]);
And your receiving screen could do something like this:
final results = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const TextScreen()),
);
var textOne = results[0];
var textTwo = results[1];
...
You can pass them in an array like that:
List<String> data = ['str1', 'str2', 'str3'];
Navigator.of(context).pop(data);
And Receive them like that:
List<String> result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const TextScreen()),
);
setState(() {
_value1 = result[0];
_value2 = result[1];
_value3 = result[2];
});

How to receive data from another statefull widget on the same screen?

So i need to receive data from my RadioDialog stf widget to BoxScreen widget.
I want to show different text depends of random value from boxItems, what i do on this line _randomBoxItem == boxOptionValue ? Text('text1') : Text('text2'),
And i dont know how to get value from _boxOption to boxOptionValue like exmple.
Here is my code:
class BoxScreen extends StatefulWidget {
const BoxScreen({Key? key}) : super(key: key);
static const routeName = '/box';
#override
State<BoxScreen> createState() => _BoxScreenState();
}
class _BoxScreenState extends State<BoxScreen> {
String? boxOptionValue;
final List<String> boxItems = [
'Banana',
'Apple',
'Orange',
];
Future<void> whatIsInTheBox(BuildContext context) async {
return await showDialog(
context: context,
builder: (BuildContext context) => const RadioDialog(),
);
}
#override
Widget build(BuildContext context) {
final String _randomBoxItem = (boxItems..shuffle()).first;
return Scaffold(
appBar: AppBar( title: const Text('BOX Game'),),
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(_randomBoxItem),
_randomBoxItem == boxOptionValue ? Text('text1') : Text('text2'),
ElevatedButton.icon(
icon: const Icon(Icons.play_arrow_rounded),
onPressed: () {
whatIsInTheBox(context);
},
label: const Text('What is in BOX?'),
),
],
),
);
}
}
class RadioDialog extends StatefulWidget {
const RadioDialog({Key? key}) : super(key: key);
#override
State createState() => RadioDialogState();
}
class RadioDialogState extends State<RadioDialog> {
// SingingCharacter? _character = SingingCharacter.banana;
String? _boxOption;
void _handleBoxItemChange(String? value) {
setState(() {
_boxOption = value;
});
}
#override
Widget build(BuildContext context) {
return AlertDialog(
contentPadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 10),
title: const Text('Select option'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
RadioListTile<String>(
title: const Text('Banana'),
value: 'Banana',
groupValue: _boxOption,
onChanged: _handleBoxItemChange,
),
RadioListTile<String>(
title: const Text('Apple'),
value: 'Apple',
groupValue: _boxOption,
onChanged: _handleBoxItemChange,
),
RadioListTile<String>(
title: const Text('Orange'),
value: 'Orange',
groupValue: _boxOption,
onChanged: _handleBoxItemChange,
),
],
),
actions: [
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('I choose this!'),
),
],
);
}
}
I am with Flutter 2 month. Thanks
In the widget calling the dialog:
someVariable = await showDialog(...);
In the dialog widget:
Navigator.of(context).pop(returnedValue);
Are you able to declare a string outside of your widgets? Then you should be able to use it like so..
String newValue = ''; //**define a string outside the widgets**
class BoxScreen extends StatefulWidget {
const BoxScreen({Key? key}) : super(key: key);
static const routeName = '/box';
#override
State<BoxScreen> createState() => _BoxScreenState();
}
class _BoxScreenState extends State<BoxScreen> {
String? boxOptionValue;
final List<String> boxItems = [
'Banana',
'Apple',
'Orange',
];
Future<void> whatIsInTheBox(BuildContext context) async {
return await showDialog(
context: context,
builder: (BuildContext context) => const RadioDialog(),
);
}
#override
Widget build(BuildContext context) {
final String _randomBoxItem = (boxItems..shuffle()).first;
return Scaffold(
appBar: AppBar( title: const Text('BOX Game'),),
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(_randomBoxItem),
_randomBoxItem == boxOptionValue ? Text('text1') : Text('text2'),
ElevatedButton.icon(
icon: const Icon(Icons.play_arrow_rounded),
onPressed: () {
whatIsInTheBox(context);
},
label: const Text('What is in BOX?'),
),
],
),
);
}
}
class RadioDialog extends StatefulWidget {
const RadioDialog({Key? key}) : super(key: key);
#override
State createState() => RadioDialogState();
}
class RadioDialogState extends State<RadioDialog> {
// SingingCharacter? _character = SingingCharacter.banana;
String? _boxOption;
void _handleBoxItemChange(String? value) {
setState(() {
newValue = value; //**Make it equal the new value**
});
}
#override
Widget build(BuildContext context) {
return AlertDialog(
contentPadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 10),
title: Text(newValue), //**use it in any widget like this**
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
RadioListTile<String>(
title: const Text('Banana'),
value: 'Banana',
groupValue: _boxOption,
onChanged: _handleBoxItemChange,
),
RadioListTile<String>(
title: const Text('Apple'),
value: 'Apple',
groupValue: _boxOption,
onChanged: _handleBoxItemChange,
),
RadioListTile<String>(
title: const Text('Orange'),
value: 'Orange',
groupValue: _boxOption,
onChanged: _handleBoxItemChange,
),
],
),
actions: [
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('I choose this!'),
),
],
);
}
}

How to validate and send the data to another page in flutter

I am a newbie to flutter
and i would to like to create simple input data and pass the data to other page and show the text.
here is my code
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
const appTitle = 'Form Validation Demo';
return MaterialApp(
title: appTitle,
home: Scaffold(
appBar: AppBar(
title: const Text(appTitle),
),
body: const MyForm(),
),
);
}
}
class MyForm extends StatefulWidget {
const MyForm({Key? key}) : super(key: key);
#override
MyFormState createState() {
return MyFormState();
}
}
class MyFormState extends State<MyForm> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Page2(_formKey)),
);
}
},
child: const Text('Submit'),
),
],
),
);
}
}
class Page2 extends StatelessWidget {
Page2(final data){
this.data = data;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page 2'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(this.data),
RaisedButton(
child: Text('BACK'),
onPressed: () {
Navigator.pop(context);
},
),
],
),
),
);
}
}
But finally, it gets
The setter 'data' isn't defined for the type 'Page2'.
error
Any idea how to do it??
and what's wrong with my code
Thank you very much.
You can make a global variable and easily use it on other screen. all you need is updating some of your code to the following
final _formKey = GlobalKey<FormState>();
TextEditingController inputController = TextEditingController();
String result;
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
const appTitle = 'Form Validation Demo';
return MaterialApp(
title: appTitle,
home: Scaffold(
appBar: AppBar(
title: const Text(appTitle),
),
body: const MyForm(),
),
);
}
}
class MyForm extends StatefulWidget {
const MyForm({Key? key}) : super(key: key);
#override
MyFormState createState() {
return MyFormState();
}
}
class MyFormState extends State<MyForm> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
controller: inputController,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState.validate()) {
setState(() {
result = inputController.text;
});
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Page2()),
);
}
},
child: const Text('Submit'),
),
],
),
),
),
);
}
}
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page 2'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(result),
RaisedButton(
child: Text('BACK'),
onPressed: () {
Navigator.pop(context);
},
),
],
),
),
);
}
}
You should define data in Page2, something like this :
class Page2 extends StatelessWidget {
final data = GlobalKey<FormState>();
Page2(final data){
this.data = data;
}
See Send data to a new screen in the Flutter cookbook.

How to pass data to a widget inside of another widget

I was able to pass the data widget.value from the FirstPage to SecondPage. There's a widget called thirdWidget inside SecondPage.
How do I pass widget.value to thirdWidget?
class FirstPage extends StatefulWidget {
#override
State<StatefulWidget> createState() => FirstPageState();
}
class FirstPageState extends State< FirstPage > {
final myController = TextEditingController();
#override
void dispose() {
myController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Route'),
),
body: Column(
children: <Widget>[
TextField(
controller: myController,
decoration: new InputDecoration(labelText: "Enter a number"),
keyboardType: TextInputType.number,
),
RaisedButton(
child: Text("show text"),
onPressed: () {
return Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ThirdRoute(
selectedDate: selectedDate,
value: myController.text,
)),
);
},
);
},
),
],
),
);
}
}
class SecondPage extends StatefulWidget {
final String value;
ThirdRoute({Key key, this.value})
: super(key: key);
#override
SecodpageState createState() => SecodpageState();
}
class SecodpageState extends State< SecondPage > {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Calendar Page"),
),
body: Column(
children: <Widget>[
Text("${widget.value}"),
Row(
children: thirdWidget(),
),
Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
],
),
);
}
}
List<Widget> thirdWidget() {
return Text("${widget.value}”)
}
Use this in your SecondPage
Row(
children: thirdWidget(widget.value),
)
And update your thirdWidget like:
List<Widget> thirdWidget(var data) {
// data is widget.value
return [];
}
Just pass that info into the state class. Something like that:
class SecondPage extends StatefulWidget {
final String value;
ThirdRoute({Key key, this.value})
: super(key: key);
#override
SecodpageState createState() => SecodpageState(value);
}
class SecodpageState extends State< SecondPage > {
final String value;
SecodpageState(this.value);
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Calendar Page"),
),
body: Column(
children: <Widget>[
Text("${widget.value}"),
Row(
children: thirdWidget(),
),
Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
],
),
);
}
}
List<Widget> thirdWidget() {
return Text(value);
}

How to populate a form from a listview on tap in flutter

I have a form widget, a list widget, and a "wrapper" widget or in other words, a parent/container widget. So to give an idea of the widget tree, it is as such.
Parent/Container Widget
Form Widget
Button Widget
List Widget
Notice that the form, buttons and list widget are all siblings, inside of the parent/container widget. What I want to happen, is tap on a list item in the list widget, and populate the form widget with the data that gets passed from the list widget.
Here is my parent widget.
import 'package:andplus_flutter_7_gui/model/user.dart';
import 'package:andplus_flutter_7_gui/services/user_service.dart';
import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
import 'crud_form.dart';
import 'crud_list.dart';
class Crud extends StatefulWidget {
Crud({Key key, this.title}) : super(key: key);
final String title;
_CrudContainerState createState() => _CrudContainerState();
}
class _CrudContainerState extends State<Crud> {
List<User> users;
User user = User();
UserService userService;
#override
void initState() {
super.initState();
if (userService == null) {
userService = UserService(user);
}
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
userService.dispose();
}
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: Text(widget.title),
),
body: Builder(
builder: (context) => Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
flex: 2,
child: StreamBuilder(
builder: (context, AsyncSnapshot<User> snapshot) {
return CrudForm(
user: snapshot.data,
onUserAdded: (user) {
userService.addUser(user);
},
);
},
stream: userService.userObservable,
),
),
Expanded(
child: Text("Future button widget"),
),
Expanded(
flex: 3,
child: StreamBuilder(
builder: (ctx, AsyncSnapshot<List<User>> snap) {
return CrudList(
onUserSelected: userService.userSelected,
users: snap.data,
);
},
stream: userService.usersObservable,
),
),
],
),
),
),
);
}
void onEditUser(User user) {
setState(() {
user = user;
});
}
}
The above widget wraps the three widgets I mentioned.
Here are the children widget:
Form:
import 'package:andplus_flutter_7_gui/model/user.dart';
import 'package:flutter/material.dart';
class CrudForm extends StatefulWidget {
CrudForm({Key key, this.onUserAdded, this.user}) : super(key: key);
final User user;
final void Function(User user) onUserAdded;
_CrudFormState createState() => _CrudFormState(user: user);
}
class _CrudFormState extends State<CrudForm> {
_CrudFormState({this.user});
User user = User();
var _key = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Container(
child: Builder(
builder: (context) => Container(
color: Colors.blueAccent[100],
child: Form(
key: _key,
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
Text(
"First Name",
style: TextStyle(fontSize: 20),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
initialValue: widget.user?.firstName == null ||
widget.user.firstName.isEmpty
? user.firstName
: widget.user.firstName,
validator: (value) {
if (value.isEmpty) {
return "First name is required";
}
return null;
},
onSaved: (value) {
setState(() {
user.firstName = value;
});
},
),
),
)
],
),
Row(
children: <Widget>[
Text(
"Last Name",
style: TextStyle(fontSize: 20),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
validator: (value) {
if (value.isEmpty) {
return "Last name is required";
}
return null;
},
onSaved: (value) {
setState(() {
user.lastName = value;
});
},
),
),
),
],
),
RaisedButton(
child: Text(
"Save",
),
splashColor: Colors.blueGrey,
onPressed: () {
if (!_key.currentState.validate()) {
return;
}
_key.currentState.save();
widget.onUserAdded(
new User(
firstName: user.firstName,
lastName: user.lastName,
),
);
},
)
],
),
),
),
),
),
);
}
}
Here is my list widget.
import 'package:andplus_flutter_7_gui/model/user.dart';
import 'package:flutter/material.dart';
class CrudList extends StatefulWidget {
CrudList({Key key, this.users, this.onUserSelected}) : super(key: key);
final List<User> users;
final SelectUser onUserSelected;
_CrudListState createState() => _CrudListState();
}
class _CrudListState extends State<CrudList> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
child: ListView.builder(
itemCount: widget.users?.length ?? 0,
itemBuilder: (BuildContext context, int index) {
var user = widget.users[index];
return ListTile(
key: Key(index.toString()),
title: Center(
child: Text(
"${user.firstName} ${user.lastName}",
style: TextStyle(color: Colors.white),
),
),
onTap: () {
print("${widget.users[index]} $index");
widget.onUserSelected(widget.users[index]);
},
);
},
),
);
}
}
typedef void SelectUser(User user);
And just for further context, here is my user service, responsible for adding the objects to the stream, and using the stream builder within rxdart to notify of state changes.
import 'package:andplus_flutter_7_gui/model/user.dart';
import 'package:rxdart/rxdart.dart';
class UserService {
User _editedUser = User();
List<User> _users = <User>[];
BehaviorSubject<User> _userSubject;
BehaviorSubject<List<User>> _usersSubject;
UserService(this._editedUser) {
_userSubject = BehaviorSubject<User>.seeded(_editedUser);
_usersSubject = BehaviorSubject<List<User>>.seeded(_users);
}
Observable<List<User>> get usersObservable => _usersSubject.stream;
Observable<User> get userObservable => _userSubject.stream;
addUser(User user) {
_users.add(user);
_usersSubject.add(_users);
}
dispose() {
_userSubject.close();
_usersSubject.close();
}
void userSelected(User user) {
_editedUser = user;
_userSubject.add(_editedUser);
}
}
What am I missing? It looks like my widget rebuilds, and tries to set the initial value in the form when I tap the user in the list widget. But the actual field doesn't get updated and I'm not sure why.
I'd appreciate any documentation or articles on how to better approach data and state management between sibling widgets within the flutter framework.
Here's a similar use case that I tried to implement locally. What I'm doing here is I generate TextFormFields dynamically and assign TextEditingController.
Column textField(int n) {
List<Widget> listForm = [];
while (n > 0) {
var textEditingController = TextEditingController();
listForm.add(
TextFormField(
controller: textEditingController,
onTap: () {
_selectedField = textEditingController;
},
),
);
n--;
}
return Column(children: listForm);
}
Clicking a ListView item updates the text of the currently selected TextFormField.
InkWell(
onTap: () {
debugPrint('Selected $index!');
if (_selectedField != null) {
_selectedField!.value = TextEditingValue(text: 'Item $index');
}
},
child: ListTile(
title: Text('Item $index'),
),
);
Complete sample
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController? _selectedField;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(flex: 1, child: textField(3)),
Expanded(flex: 1, child: listItems()),
],
),
),
),
);
}
Column textField(int n) {
List<Widget> listForm = [];
while (n > 0) {
var textEditingController = TextEditingController();
listForm.add(
TextFormField(
controller: textEditingController,
onTap: () {
debugPrint('Current Controller: $textEditingController');
_selectedField = textEditingController;
},
),
);
n--;
}
return Column(children: listForm);
}
ListView listItems() {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
if (_selectedField != null) {
_selectedField!.value = TextEditingValue(text: 'Item $index');
}
},
child: ListTile(
title: Text('Item $index'),
),
);
},
);
}
}
Demo