in my Todo App list, I'm trying to make the global state in my task list view but I'm facing some issues. I'm using StatefulWidget and StatelessWidget. In StatelessWidget Checkbox onChanged I want to toggle the Checkbox value and comes from the parent Widget but it's showing some setState() build issue Please have a look If you can.
tasks_lists.dart
import 'package:flutter/material.dart';
class TasksList extends StatefulWidget {
const TasksList({super.key});
#override
State<TasksList> createState() => _TasksListState();
}
class _TasksListState extends State<TasksList> {
bool isChecked = false;
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(
'List 1',
style: TextStyle(
color: Colors.black,
decoration:
isChecked ? TextDecoration.lineThrough : TextDecoration.none,
),
),
trailing: TaskCheckbox(
checkBoxState: isChecked,
toggleCheckboxState: (bool checkedBoxState) {
setState(
() {
isChecked = checkedBoxState;
},
);
},
),
);
}
}
class TaskCheckbox extends StatelessWidget {
final bool checkBoxState;
final Function? toggleCheckboxState;
const TaskCheckbox({
super.key,
required this.checkBoxState,
this.toggleCheckboxState,
});
#override
Widget build(BuildContext context) {
return Checkbox(
value: checkBoxState,
onChanged: toggleCheckboxState!(checkBoxState),
activeColor: Colors.lightBlueAccent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(2.0),
),
side: MaterialStateBorderSide.resolveWith(
(states) => BorderSide(
width: 2.0,
color: Colors.black,
),
),
);
}
}
This means that the function is called during build,
onChanged: toggleCheckboxState!(checkBoxState),
while this calls the function when Checkbox is tapped.
onChanged: (checkBoxState) => toggleCheckboxState!(checkBoxState),
Take a look of Introduction to widgets.
Your toggleCheckboxState function call during building the UI, so you need to put it inside addPostFrameCallback so it run after UI builds, like this:
toggleCheckboxState: (bool checkedBoxState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(
() {
isChecked = checkedBoxState;
},
);
});
},
You are changing value/state so you have to make TaskCheckbox a Stateful widget.
onChange is a Fuction(bool). try to change TaskCheckbox to this:
class TaskCheckbox extends StatelessWidget {
final bool checkBoxState;
final Function(bool)? toggleCheckboxState;
const TaskCheckbox({
super.key,
required this.checkBoxState,
this.toggleCheckboxState,
});
#override
Widget build(BuildContext context) {
return Checkbox(
value: checkBoxState,
onChanged: toggleCheckboxState!,
activeColor: Colors.lightBlueAccent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(2.0),
),
side: MaterialStateBorderSide.resolveWith(
(states) => BorderSide(
width: 2.0,
color: Colors.black,
),
),
);
}
}
Related
How onTap clear TextField in below example?
Note TextField is positioned inside trailing in ListView
trailing: FittedBox(
fit: BoxFit.fill,
child: SizedBox(
height:40, width:100,
child: TextField(
controller: TextEditingController(text: _docData[index].quantity.toString()),
decoration: InputDecoration(
suffix: InkWell(
onTap: () {
setState(() {
});
},
child: Icon(
Icons.clear,
),
)
),
),
),
),
Try this
Create a list of TextEditingController
List<TextEditingController> controllers = List.empty(growable: true);
Then inside ListView.builder, create TextEditingController one by one and add it to controllers list.
Then assign to TextField and clear like this
child: TextField(
controller: controllers[index],
decoration: InputDecoration(
suffix: InkWell(
onTap: () {
setState(() {
controllers[index].clear();
});
},
child: Icon(
Icons.clear,
),
)
),
),
When we call the function:
setState(() {
});
The whole widget rebuilds hence your TextEditingController is initialized again so the text inside is reset to default which is empty
Full Code
1)First Screen
import 'package:flutter/material.dart';
import 'home_page.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: MyCustomForm(),
);
}
}
2)Second Screen
import 'package:flutter/material.dart';
class MyCustomForm extends StatefulWidget {
const MyCustomForm({Key? key}) : super(key: key);
#override
State<MyCustomForm> createState() => _MyCustomFormState();
}
class _MyCustomFormState extends State<MyCustomForm> {
TextEditingController _controller =TextEditingController();
#override
Widget build(BuildContext context) {
// question: delete textField inside the ListView
return Scaffold(
body: Container(
child: ListView(
children: [
TextField(
onChanged: (value){
_controller.text;
},
controller: _controller,
decoration: InputDecoration(
hintText: 'Enter Name',
suffix: InkWell(
onTap: (){
_controller.clear();
},
child:Icon(Icons.clear),
)
),
)
],
),
),
);
}
}
I am making a List App which a list of items followed by a checkBox .Then there is a floating action button with a plus sign .On click of it u get a bottomSheet .In the bottom sheet you can enter your task to the textField .On clicking on the add button the tasks gets added .
The ui Looks like this
I am not able to access tasks List that is defined in the task_screen.dart from add_taskScreen.dart . I am getting the below error despite importing the required files
error: Undefined name 'tasks'. (undefined_identifier at [todoey_flutter] lib\add_task_screen.dart:43)
here is my Code
Main.dart
import 'package:flutter/material.dart';
import 'tasks_screen.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: TaskScreen(),
);
}
}
Task_tile.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class TaskTile extends StatelessWidget {
#override
late final bool isChecked ;
late final String taskTitle;
final Function checkBoxCallBack;
TaskTile({required this.isChecked,required this.taskTitle,required this.checkBoxCallBack});
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(taskTitle,
style:TextStyle(
decoration: isChecked ? TextDecoration.lineThrough:null
),
),
trailing: Checkbox(
value:isChecked,
activeColor:Colors.lightBlueAccent,
onChanged: (newValue) {//onChanged here if we select the check box the value becomes true else it will become false
checkBoxCallBack(newValue);//widget.toggleCheckBoxState(value);
},
)
);
}
}
Task_list.dart
import 'package:flutter/material.dart';
import 'task_title.dart';
import 'Models/task.dart';
class TaskList extends StatefulWidget {
final List<Task> tasks;
TaskList(this.tasks);
#override
State<TaskList> createState() => _TitleListState();
}
class _TitleListState extends State<TaskList> {
#override
Widget build(BuildContext context) {
return ListView.builder(itemBuilder: (context,index){//in the listView Builder index is already defined and gets updated by itself
return TaskTile(
taskTitle:widget.tasks[index].name,
isChecked: widget.tasks[index].isDone,
checkBoxCallBack:(bool checkBoxState){
setState((){
widget.tasks[index].toggleDone() ;
});
}
);
},
itemCount: widget.tasks.length,//max no tasks that can fit in the screen ie..how many ever tasks are there on 'tasks' it will build that many
);
}
}
task.dart
import 'package:flutter/material.dart';
class Task{
late final String name;
late bool isDone;
Task
({required this.name,this.isDone=false});//give a default value to isDone
void toggleDone()
{
isDone = !isDone;
}
}
task_Screen.dart
import 'package:flutter/material.dart';
import 'tasks_list.dart';
import 'add_task_screen.dart';
import 'package:todoey_flutter/Models/task.dart';
class TaskScreen extends StatefulWidget {
const TaskScreen({Key? key}) : super(key: key);
#override
State<TaskScreen> createState() => _TaskScreenState();
}
class _TaskScreenState extends State<TaskScreen> {
List<Task> tasks = [
Task(name: 'Buy milk'),
Task(name: 'Buy eggs'),
Task(name: 'Buy bread'),
];
#override
Widget build(BuildContext context) {
return Scaffold(//Scaffold contains everything
backgroundColor: Colors.lightBlueAccent,
body:Column(
children: [
Container(
padding:const EdgeInsets.only(top:60,left:30,right:30,bottom: 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children:const <Widget>[
CircleAvatar(
child: Icon(
Icons.list,
color: Colors.lightBlueAccent,
size:30.0,
),
backgroundColor:Colors.white,
radius:30,
),
SizedBox(height:10),
Text(
'Todoey',
style:TextStyle(
color:Colors.white,
fontSize: 50,
fontWeight: FontWeight.w700,
)
),
Text('12 Tasks',
style:TextStyle(
color:Colors.white,
fontSize: 18,
)
),
]
),
),
Expanded(
child: Container(//designing the white part of the todoey
padding:const EdgeInsets.symmetric(horizontal: 20),
height:300,
decoration: const BoxDecoration(
color:Colors.white,
borderRadius: BorderRadius.only(
topLeft:Radius.circular(20.0),
topRight:Radius.circular(20.0),
),
),
child: TaskList(tasks),
),
),
],
),
floatingActionButton: FloatingActionButton(
backgroundColor:Colors.lightBlueAccent,
child: const Icon(
Icons.add,
),
onPressed: (){ //call the bottomSheet in builder
showModalBottomSheet(builder:(context)=>AddTaskScreen(
(newTaskTitle)
{
setState(){
tasks.add(Task(name:newTaskTitle));
}
}), context: context);//to create bottom drawer widget on pressing the button a new pop up appears at the bottom
},
),
);
}
}
add_Task_Screen.dart
import 'package:flutter/material.dart';
import 'task_title.dart';
import 'tasks_list.dart';
import 'tasks_screen.dart';
import 'package:todoey_flutter/Models/task.dart';
class AddTaskScreen extends StatelessWidget {
late String newTaskTitle;
final Function addTaskCallBack;
AddTaskScreen( this.addTaskCallBack, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(20.0),
child: Container(
//color:const Color(0xff757575),//you cant add color 2 times it will throw an exception
decoration:const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(topLeft:Radius.circular(20.0) ,topRight:Radius.circular(20.0)),
),
child:Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children:[
const Center(
child: Text('Add Task',
style: TextStyle(
fontSize:30.0,
color:Colors.lightBlueAccent,
),
),
),
TextField(
autofocus: true,
autocorrect: true,
textAlign: TextAlign.center,
onChanged: (newText){
newTaskTitle = newText;
},
),
FlatButton(
onPressed: (){
print(newTaskTitle);
tasks.add(name:newTaskTitle);//here is the error
},
child: const Text('Add'),
color: Colors.lightBlueAccent,
)
]
)
),
);
}
}
In task_screen.dart create a function like this
void _handleAddTask(String _name)=>setState(()=>tasks.add(Task(name: _name))));
Then modify showModalBottomSheet to look like this
showModalBottomSheet(builder:(context)=>AddTaskScreen(_handleAddTask,"Add task"), context: context);},),);}}
In add_Task_Screen.dart, modify
AddTaskScreen( this.addTaskCallBack, {Key? key}) : super(key: key); to this
AddTaskScreen( this.addTaskCallBack, this.newTaskTitle, {Key? key}) : super(key: key);
create a TextEditingController for your TextField and pass it to it like this
TextField(
autofocus: true,
controller: _textFieldController,
autocorrect: true,
textAlign: TextAlign.center,
onChanged: (newText){
newTaskTitle = newText;
},
),
Finally, in the onPress of Add button, modify it to something like this:
FlatButton(
onPressed: (){
print(newTaskTitle);
addTaskCallBack(_textFieldController.text);
},
child: const Text('Add'),
color: Colors.lightBlueAccent,
)
The tasks list does not exist in the child widget. You can access the widget via the callback method you provided.
Refactor your FlatButton to look like this:
FlatButton(
onPressed: () {
addTaskCallBack(newTaskTitle);
},
child: const Text('Add'),
color: Colors.lightBlueAccent,
)
My goal is to have a segmented slider button from Microsoft outlook android
Did anyone know some package to achieve this?
I think you can create using these two slutions
1)Using the CupertinoSlidingSegmentedControl widget.
2)using custom stateful widgets
1)Using the CupertinoSlidingSegmentedControl widget
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
int segmentedControlGroupValue = 0;
final Map<int, Widget> myTabs = const <int, Widget>{
0: Text("Focused"),
1: Text("Other")
};
#override
Widget build(BuildContext context) {
return CupertinoSlidingSegmentedControl(
groupValue: segmentedControlGroupValue,
children: myTabs,
thumbColor:Colors.blueAccent,
onValueChanged: (i) {
setState(() {
segmentedControlGroupValue = int.parse(i.toString());
});
});
}
}
2)Using custom stateful widgets
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
bool _forcused = true;
bool _other = false;
#override
Widget build(BuildContext context) {
return Container(
width: 200,
decoration: BoxDecoration(
border: Border.all(
color: Colors.grey,
),
color:Colors.grey,
borderRadius: BorderRadius.all(Radius.circular(20))),
child: Row(children: [
Expanded(
child: TextButton(
onPressed: () {
setState(() {
_forcused = true;
_other = false;
});
},
child: Text("Focused",style:TextStyle(color:Colors.black)),
style: ButtonStyle(
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
side: BorderSide(color: Colors.grey))
),
backgroundColor: MaterialStateProperty.all(_forcused
? Colors.blueAccent
: Colors.grey)
)
)),
Expanded(
child: TextButton(
onPressed: () {
setState(() {
_other = true;
_forcused = false;
});
},
child: Text("Other",style:TextStyle(color:Colors.black)),
style: ButtonStyle(
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
side: BorderSide(color: Colors.grey))
),
backgroundColor: MaterialStateProperty.all(_other
? Colors.blueAccent
: Colors.grey)
)
)),
]));
}
}
You probably want to checkout the CupertinoSlidingSegmentedControl widget .
I am creating a todo list application and I have wrapped a textform field in a gesture detector in a list tile however the onTap gesture does not fire but the onDoubleTap gesture does:
todolist_screen.dart:
class TodoListScreen extends StatefulWidget {
final void Function() onSettingsPress;
final void Function() onInit;
final void Function(int id) deleteTodo;
final void Function(Todo todo) toggleComplete;
final List<Todo> todos;
TodoListScreen(
{required this.onSettingsPress,
required this.onInit,
required this.deleteTodo,
required this.toggleComplete,
this.todos = const []});
#override
_TodoScreenState createState() => _TodoScreenState();
}
class _TodoScreenState extends State<TodoListScreen> {
dynamic myList = [];
#override
void initState() {
super.initState();
widget.onInit();
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
showModalBottomSheet(
context: context,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.vertical(top: Radius.circular(25))),
backgroundColor: Colors.white,
builder: (context) => FractionallySizedBox(
heightFactor: 0.8,
child: AddTodoModalContainer(),
));
},
child: const Icon(Icons.add),
),
appBar: AppBar(
title: Text('Todos'),
backgroundColor: Colors.blue,
actions: [
IconButton(
icon: Icon(Icons.settings), onPressed: widget.onSettingsPress)
],
),
body: SafeArea(
child: Column(
children: [
Expanded(
child: ListView(
children: widget.todos
.map((todo) => TodoItem(
id: todo.id,
title: todo.title,
completed: todo.completed,
deleteTodo: widget.deleteTodo,
toggleComplete: (value) {
widget.toggleComplete(todo);
}))
.toList(),
),
)
],
),
),
);
}
}
todo_item.dart:
class TodoItem extends StatefulWidget {
const TodoItem(
{Key? key,
required this.id,
required this.title,
required this.completed,
required this.toggleComplete,
required this.deleteTodo})
: super(key: key);
final int id;
final String title;
final bool completed;
final void Function(bool?) toggleComplete;
final void Function(int id) deleteTodo;
#override
_TodoItemState createState() => _TodoItemState();
}
class _TodoItemState extends State<TodoItem> {
late final TextEditingController _controller;
late final FocusNode _focusNode;
#override
void initState() {
super.initState();
_controller = TextEditingController();
_focusNode = FocusNode();
}
#override
void dispose() {
super.dispose();
_controller.dispose();
_focusNode.dispose();
}
bool isActive = false;
#override
Widget build(BuildContext context) {
return ListTile(
leading: Checkbox(
value: widget.completed,
onChanged: widget.toggleComplete,
),
trailing: IconButton(
icon: Icon(Icons.delete_forever_sharp),
onPressed: () {
widget.deleteTodo(widget.id);
}),
title: GestureDetector(
onTap: () {
print('tapped');
setState(() {
isActive = !isActive;
});
},
child: TextFormField(
focusNode: _focusNode,
initialValue: widget.title,
decoration: InputDecoration(
hintText: widget.title,
border: isActive
? UnderlineInputBorder(
borderSide: BorderSide(
width: 5,
style: BorderStyle.solid,
color: Colors.black))
: InputBorder.none),
readOnly: !isActive,
style: TextStyle(fontSize: 20),
),
),
);
}
}
P.S I tried adding the behaviour property to the Gesture Detector but they all did not work
After tweaking a few widgets I realised the problem. TextFormField already has an onTap property hence it took precedence over the Gesture Detector's onTap property.
i have a form which i decided to break into multiple widget for code re- usability. the problem i am having i dont know how to interact with each components. for example, if the main form declare a variable, how do i access that variable in the custom textfield widget which is store in a different dart file.
below is the code i have
form dart file (main.dart)
import 'package:flutter/material.dart';
import 'package:finsec/widget/row_text_input.dart';
import 'package:finsec/widget/text_form_field.dart';
import 'package:finsec/widget/save_button.dart';
import 'package:finsec/utils/strings.dart';
import 'package:finsec/utils/dimens.dart';
import 'package:finsec/utils/colors.dart';
import 'package:finsec/widget/column_text_input.dart';
void main() {
runApp(MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Simple Interest Calculator App',
home: ThirdFragment(),
theme: ThemeData(
brightness: Brightness.dark,
primaryColor: Colors.indigo,
accentColor: Colors.indigoAccent),
));
}
class ThirdFragment extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _ThirdFragmentState();
}
}
class _ThirdFragmentState extends State<ThirdFragment> {
var _formKey = GlobalKey<FormState>();
var _currentItemSelected = '';
bool isError = false;
bool isButtonPressed = false;
#override
void initState() {
super.initState();
}
TextEditingController amountController = TextEditingController();
TextEditingController frequencyController = TextEditingController();
#override
Widget build(BuildContext context) {
TextStyle textStyle = Theme.of(context).textTheme.title;
return Scaffold(
appBar: AppBar(
title: Text('Simple Interest Calculator'),
),
body: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column (children: [
Padding(
padding: EdgeInsets.only(top: 10.0, bottom: 5.0, left: 15.0, right: 15.0),
child: CustomTextField(textInputType:TextInputType.number,
textController: amountController,
errorMessage:'Enter Income Amount',
labelText:'Income Amount for testing'),
),
RowTextInput(inputName: 'Frequency:',
textInputType: TextInputType.number,
textController: frequencyController,
errorMessage: 'Choose Income Frequency',
labelText: 'Income Amount for testing'
),
RowTextInput(inputName: 'Date Paid:',
textInputType: TextInputType.number,
textController: datePaidController,
errorMessage: 'Pick Income Payment Date',
labelText: 'Income Amount for testing'
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
MaterialButton(
height: margin_40dp,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(margin_5dp)),
minWidth: (MediaQuery.of(context).size.width * .9) / 2,
color: Theme.of(context).primaryColor,
textColor: white,
child: new Text(save),
onPressed: () => {
setState(() {
if (_formKey.currentState.validate()) {
// amountController.text.isEmpty ? amountController.text='Value require' : amountController.text='';
//this.displayResult = _calculateTotalReturns();
}
})
},
splashColor: blueGrey,
),
MaterialButton(
height: margin_40dp,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(margin_5dp)),
minWidth: (MediaQuery.of(context).size.width * .9) / 2,
color: Theme.of(context).primaryColor,
textColor: white,
child: new Text(save_and_continue),
onPressed: () => {},
splashColor: blueGrey,
)
])
]
),
),
}
RowTextInput is a different dart file that contains this code. RowTextInput.dart
import 'package:flutter/material.dart';
import 'package:finsec/utils/hex_color.dart';
class CustomTextField extends StatelessWidget {
CustomTextField({
this.textInputType,
this.textController ,
this.errorMessage,
this.labelText,
});
TextInputType textInputType;
TextEditingController textController;
String errorMessage, labelText;
#override
Widget build(BuildContext context) {
bool isError = false;
return Container(
child: TextFormField(
keyboardType: textInputType,
style: Theme
.of(context)
.textTheme
.title,
controller: textController,
validator: (String value) {
if (value.isEmpty) {
return errorMessage;
}
},
decoration: InputDecoration(
labelStyle: TextStyle(
color: Colors.grey,
fontSize: 16.0
),
contentPadding: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0), //size of textfield
errorStyle: TextStyle(
color: Colors.red,
fontSize: 15.0
),
border: OutlineInputBorder(
borderSide: BorderSide(width:5.0),
borderRadius: BorderRadius.circular(5.0)
)
)
),
);
}
}
i want to access isError and isButtonPressed variables located in main.dart from RowTextInput.dart and be able to assign values. main.dart should then be able to see those values assign in RowTextInput.dart file.
also,i want to move the MaterialButton button in its own widget file (button.dart) but then i dont know how this dart file will interact with the main.dart file when button is click or to check values of isError and IS button pressed. basically, i am breaking the form into different components (textfield and button) and store them in their own separate file. but i want all the files main.dart, rowintputtext, button.dart(new) to be able to see values of variables in main.dart and change the values. is this possible? is there an easier way?
thanks in advance
If you think about it. In Flutter the Button and RawMaterialButton are already in other files. And the manage to do exactly what you want.
You should create a File mycustomButtons.dart.
In the file you should create a class that will build your Buttons...
But it must has two parameters in it's constructor actionSave actionSaveAndContinue.
You will then create two functions in your main something like:
void _save() {
setState(() {
if (_formKey.currentState.validate()) {
// amountController.text.isEmpty ? amountController.text='Value require' : amountController.text='';
//this.displayResult = _calculateTotalReturns();
}
})
}
Then you should pass your created functions as parameters:
MyCustomButtons(actionSave: _save, actionSaveAndContinue: _saveAndContinue)
So the button will have all needed information to update your main.dart variables.
The textField is pretty much the same. But you will need pass a validation function and a TextEditingController.
You can see the font of RawnMaterialButton, TextFormField to see how they receive (and pass) data from one class to an other.
I was also looking for breaking a form into multiple classes. This is that I did :
Form
Pass the onSaved function at the form level.
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_CustomFormField(
onSaved: (value) => _myModelForm.field1 = value),
),
_CustomFormField2(
onSaved: (value) => _myModelForm.field2 = value),
)
),
RaisedButton(
onPressed: () {
// Validate will return true if the form is valid, or false if
// the form is invalid.
if (_formKey.currentState.validate()) {
// Process data.
_formKey.currentState.save();
// Observe if your model form is updated
print(myModelForm.field1);
print(myModelForm.field2)
}
},
child: Text('Submit'),
),
],
),
);
}
_CustomFormField1
The onSaved function will be passed as argument. This class can be either in the same file than the form or in another dedicated file.
class _CustomFormField1 extends StatelessWidget {
final FormFieldSetter<String> onSaved;
//maybe other properties...
_CustomFormField1({
#required this.onSaved,
});
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: TextFormField(
// You can keep your validator here
validator: (value) {
if (value.isEmpty) {
return 'Please enter some text';
}
return null;
},
onSaved: onSaved,
),
);
}
}
Like onSaved, you can do the same way for focusNode, onFieldSubmitted, validator if needed in
I hope it will help you and others
There's probably a more elegant way to do it but I am currently experimenting with Singletons. See the code below:
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'dart:async';
class AppModel {
TextEditingController nameController;
TextEditingController surnameController;
StreamController<String> fullnameStreamController;
AppModel() {
nameController = TextEditingController();
surnameController = TextEditingController();
fullnameStreamController = StreamController.broadcast();
}
update() {
String fullname;
if (nameController.text != null && surnameController.text != null) {
fullname = nameController.text + ' ' + surnameController.text;
} else {
fullname = 'Please enter both names';
}
fullnameStreamController.add(fullname);
}
}
GetIt getIt = new GetIt();
final appModel = getIt.get<AppModel>();
void main() {
getIt.registerSingleton<AppModel>(AppModel());
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(title: 'Singleton Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String text;
update() {
setState(() {
});
}
#override
void initState() {
text = 'waiting for input';
appModel.fullnameStreamController.stream.listen((data) {
text = data;
update();
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(color: Colors.amberAccent),
child: Column(
children: <Widget> [
Card(
color: Colors.white,
child: Text('Name'),
),
Card(
color: Colors.yellow,
child: NameTextField()
),
Divider(),
Card(
color: Colors.white,
child: Text('Surname'),
),
Card(
color: Colors.yellow,
child: SurnameTextField()
),
OkButton(),
Card(
color: Colors.white,
child: Text('Full name'),
),
Card(
color: Colors.orange,
child: FullnameText(text),
),
],
),
),
);
}
}
class NameTextField extends StatefulWidget {
NameTextField({Key key}) : super(key: key);
_NameTextFieldState createState() => _NameTextFieldState();
}
class _NameTextFieldState extends State<NameTextField> {
#override
Widget build(BuildContext context) {
return Container(
child: TextField(
controller: appModel.nameController,
),
);
}
}
class SurnameTextField extends StatefulWidget {
SurnameTextField({Key key}) : super(key: key);
_SurnameTextFieldState createState() => _SurnameTextFieldState();
}
class _SurnameTextFieldState extends State<SurnameTextField> {
#override
Widget build(BuildContext context) {
return Container(
child: TextField(
controller: appModel.surnameController,
),
);
}
}
class FullnameText extends StatefulWidget {
FullnameText(this.text,{Key key}) : super(key: key);
final String text;
_FullnameTextState createState() => _FullnameTextState();
}
class _FullnameTextState extends State<FullnameText> {
#override
Widget build(BuildContext context) {
return Container(
child: Text(widget.text),
);
}
}
class OkButton extends StatefulWidget {
OkButton({Key key}) : super(key: key);
_OkButtonState createState() => _OkButtonState();
}
class _OkButtonState extends State<OkButton> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white10,
child: RaisedButton(
color: Colors.white,
child: Icon(Icons.check),
onPressed: () {appModel.update();},
),
);
}
}
Check how I use the three controllers in the update function of the AppModel class.
CustomTextFields must extends parent(widget where is form) in this case it is ThirdFragment
class CustomTextField extends ThirdFragment{
CustomTextField({
this.textInputType,
this.textController,
this.errorMessage,
this.labelText,
});