Pass form to multiple widgets - flutter

I'm building an app with a long form in it. So I decided to seperate it into several steps.
Each step would be a widget containing formFields.
So I would have something like this:
int _currentStep = 0;
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
String _firstname;
String _lastname;
String _city;
List<Widget> formGroups = [
FormGroup1(),
FormGroup2(),
FormGroup3(),
];
The form would bind to the state like this
Form(
key: _formKey,
child: formGroups[_currentStep],
),
My idea is to be able to navigate to the next widget like this
void goToNext() {
setState(() {
if (_currentStep < formGroups.length - 1) {
_currentStep += 1;
}
});
}
Firstly, is it a good practice?
How can I get the main widget to get the input from the children widgets?
For example if the FormGroup2 contains the inputfield to set the _lastname, how can I make it availaibe at the form level?
Thank you for your help.

Whenever you want parent widget to get input from the child widget you always use the NotificationListener at parent and pass Notification containing data from the child.
This technique is used by many flutter widgets like DefaultTabController receives OverscrollNotification when user swipes to the last page and is still trying to swipe.
In your use case you can pass the value notifications from the child Widgets to the Form Widget.
Here is a Demo for your reference Run on dartpad
Following is a working code demonstrating the use of this widget:
import 'package:flutter/material.dart';
main() => runApp(MaterialApp(home: MyApp()));
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _string = 'You haven\'t pressed a button yet' ;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Notification demo'),),
body: NotificationListener<IntegerNotification>(
onNotification: (IntegerNotification notification){
print(notification.value);
setState(() {
_string = 'You have pressed button ${notification.value} times.';
});
return true;
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(_string),
ChildWidget(),
],
),
),
);
}
}
class ChildWidget extends StatefulWidget {
#override
_ChildWidgetState createState() => _ChildWidgetState();
}
class _ChildWidgetState extends State<ChildWidget> {
int _counter = 0;
#override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
child: Text('Increment'),
onPressed: () {
IntegerNotification(++_counter).dispatch(context);
},
),
);
}
}
class IntegerNotification extends Notification {
final value;
IntegerNotification(this.value);
}
I hope this helps, in case of any doubt please comment.

Related

How to pass a GlobalKey through Stateless Widget Children

I'm trying to create a custom menu bar in my app. Right now, the biggest issue I'm having is passing a state for when it's expanded to it's children after a setState occurs.
I thought about inheritance, but from what I've tried all inheritance needs to be in-line. I can't create a widget where the children [] are fed into the constructor on an ad-hoc basis.
My current approach is to use a GlobalKey to update the State of the children widgets being inserted into the StateFul while updating them directly.
The children for my MenuBar are declared as:
List<MenuBarItem> menuItems;
MenuBarItem is an abstract interface class that I intend to use to limit the widgets that can be fed in as menuItems to my MenuBar.
abstract class iMenuItem extends Widget{}
class MenuBarItem extends StatefulWidget implements iMenuItem{
At some iterations of this script, I had a bool isExpanded as part of the iMenuItem, but determined it not necessary.
Here is my code at its current iteration:
My Main:
void main() {
// runApp(MainApp());
//runApp(InherApp());
runApp(MenuBarApp());
}
class MenuBarApp extends StatelessWidget{
#override
Widget build(BuildContext context){
return MaterialApp(
home: Scaffold(
body: MenuBar(
menuItems: [
// This one does NOT work and is where I'm trying to get the
// value to update after a setState
MenuBarItem(
myText: 'Outsider',
),
],
),
),
);
}
}
My Code:
import 'package:flutter/material.dart';
/// Primary widget to be used in the main()
class MenuBar extends StatefulWidget{
List<MenuBarItem> menuItems;
MenuBar({
required this.menuItems,
});
#override
State<MenuBar> createState() => MenuBarState();
}
class MenuBarState extends State<MenuBar>{
bool isExpanded = false;
late GlobalKey<MenuBarContainerState> menuBarContainerStateKey;
#override
void initState() {
super.initState();
menuBarContainerStateKey = GlobalKey();
}
#override
Widget build(BuildContext context){
return MenuBarContainer(
menuItems: widget.menuItems,
);
}
}
class MenuBarContainer extends StatefulWidget{
List<MenuBarItem> menuItems;
late Key key;
MenuBarContainer({
required this.menuItems,
key,
}):super(key: key);
#override
MenuBarContainerState createState() => MenuBarContainerState();
}
class MenuBarContainerState extends State<MenuBarContainer>{
bool isExpanded = false;
#override
void initState() {
super.initState();
isExpanded = false;
}
#override
Widget build(BuildContext context){
List<Widget> myChildren = [
ElevatedButton(
onPressed: (){
setState((){
this.isExpanded = !this.isExpanded;
});
},
child: Text('Push Me'),
),
// This one works. No surprise since it's in-line
MenuBarItem(isExpanded: this.isExpanded, myText: 'Built In'),
];
myChildren.addAll(widget.menuItems);
return Container(
child: Column(
children: myChildren,
),
);
}
}
/// The item that will appear as a child of MenuBar
/// Uses the iMenuItem to limit the children to those sharing
/// the iMenuItem abstract/interface
class MenuBarItem extends StatefulWidget implements iMenuItem{
bool isExpanded;
String myText;
MenuBarItem({
key,
this.isExpanded = false,
required this.myText,
}):super(key: key);
#override
State<MenuBarItem> createState() => MenuBarItemState();
}
class MenuBarItemState extends State<MenuBarItem>{
#override
Widget build(BuildContext context){
GlobalKey<MenuBarState> _menuBarState;
return Row(
children: <Widget> [
Text('Current Status:\t${widget.isExpanded}'),
Text('MenuBarState GlobalKey:\t${GlobalKey<MenuBarState>().currentState?.isExpanded ?? false}'),
Text(widget.myText),
],
);
}
}
/// To give a shared class to any children that might be used by MenuBar
abstract class iMenuItem extends Widget{
}
I've spent 3 days on this, so any help would be appreciated.
Thanks!!
I suggest using ChangeNotifier, ChangeNotifierProvider, Consumer and context.read to manage state. You have to add this package and this import: import 'package:provider/provider.dart';. The steps:
Set up a ChangeNotifier holding isExpanded value, with a setter that notifies listeners:
class MyNotifier with ChangeNotifier {
bool _isExpanded = false;
bool get isExpanded => _isExpanded;
set isExpanded(bool isExpanded) {
_isExpanded = isExpanded;
notifyListeners();
}
}
Insert the above as a ChangeNotifierProvider in your widget tree at MenuBar:
class MenuBarState extends State<MenuBar> {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyNotifier(),
child: MenuBarContainer(
menuItems: widget.menuItems,
));
}
}
After this you can easily read and write the isExpanded value from anywhere in your widget tree under the ChangeNotifierProvider, for example:
ElevatedButton(
onPressed: () {
setState(() {
final myNotifier = context.read<MyNotifier>();
myNotifier.isExpanded = !myNotifier.isExpanded;
});
},
child: Text('Push Me'),
),
And if you want to use this state to automatically build something when isExpanded is changed, use Consumer, which will be notified automatically upon every change, for example:
class MenuBarItemState extends State<MenuBarItem> {
#override
Widget build(BuildContext context) {
return Consumer<MyNotifier>(builder: (context, myNotifier, child) {
return Row(
children: <Widget>[
Text('Current Status:\t${myNotifier.isExpanded}'),
Text(widget.myText),
],
);
});
}
}

Flutter UI doesn't update when custom widget is used

I have a Flutter where I display a list of elements in a Column, where the each item in the list is a custom widget. When I update the list, my UI doesn't refresh.
Working sample:
class Test extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return TestState();
}
}
class TestState extends State<Test> {
List<String> list = ["one", "two"];
final refreshKey = new GlobalKey<RefreshIndicatorState>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(40),
child: Row(
children: <Widget>[
Container(
child: FlatButton(
child: Text("Update"),
onPressed: () {
print("Updating list");
setState(() {
list = ["three", "four"];
});
},
)
),
Column(
children: list.map((s) => ItemView(s)).toList(),
)
],
),
)
);
}
}
class ItemView extends StatefulWidget {
String s;
ItemView(this.s);
#override
State<StatefulWidget> createState() => ItemViewState(s);
}
class ItemViewState extends State<ItemView> {
String s;
ItemViewState(this.s);
#override
Widget build(BuildContext context) {
return Text(s);
}
}
When I press the "Update" button, my list is updated but the UI is not. I believe this has something to do with using a custom widget (which is also stateful) because when I replace ItemView(s) with the similar Text(s), the UI updates.
I understand that Flutter keeps a track of my stateful widgets and what data is being used, but I'm clearly missing something.
How do I get the UI to update and still use my custom widget?
You should never pass parameters to your State.
Instead, use the widget property.
class ItemView extends StatefulWidget {
String s;
ItemView(this.s);
#override
State<StatefulWidget> createState() => ItemViewState();
}
class ItemViewState extends State<ItemView> {
#override
Widget build(BuildContext context) {
return Text(widget.s);
}
}

How to Count Number of Lines in flutter

I'm new to flutter, I am Trying to create Count Number of Lines from textfield, like this https://www.tools4noobs.com/online_tools/count_lines/. but, I still have a problem with how to calculate it.
Can you give an example, I will appreciate it.
So what I have found to get number of lines from a text is this flutter-how-to-get-the-number-of-text-lines
To read text you can use this Handling changes to a text field
Here's an example I did using onChanged function.. You can use a controller also
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
num lines = 0;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Column(
children: <Widget>[
TextField(
maxLines: null,
onChanged: (text) {
setState(() {
lines = '\n'.allMatches(text).length + 1;
});
},
),
Text(lines.toString())
],
)),
);
}
}

Flutter Bloc Design - Inform pages about changes

is there a best practice for this? (Im using this Todo example since its easier to explain my problem here)
TodoOverviewPage (Shows all todos)
TodoAddPage (Page to add todos)
Each page has an own Bloc.
Steps:
From the TodoOverviewPage I navigate wuth pushNamed to TodoAddPage.
In TodoAddPage I add several Todos.
Using the Navigation Back Button to go back to TodoOverviewPage
Question: How should I inform TodoOverviewPage that there are new Todos?
My approaches which Im not sure if this is the right way.
Solutions:
Overwriting the Back Button in TodoAddPage. To add a "refresh=true" property.
Adding the Bloc from TodoOverviewPage to TodoAddPage. And setting the State to something that the TodoOverviewPage will reload todos after building.
Thank you for reading.
EDIT1:
Added my temporary solution till I find something which satisfies me more.
You can achieve by different way
InheritedWidget
ValueCallback in TodoAddPage
For Example:
class Item {
String reference;
Item(this.reference);
}
class _MyInherited extends InheritedWidget {
_MyInherited({
Key key,
#required Widget child,
#required this.data,
}) : super(key: key, child: child);
final MyInheritedWidgetState data;
#override
bool updateShouldNotify(_MyInherited oldWidget) {
return true;
}
}
class MyInheritedWidget extends StatefulWidget {
MyInheritedWidget({
Key key,
this.child,
}): super(key: key);
final Widget child;
#override
MyInheritedWidgetState createState() => new MyInheritedWidgetState();
static MyInheritedWidgetState of(BuildContext context){
return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data;
}
}
class MyInheritedWidgetState extends State<MyInheritedWidget>{
/// List of Items
List<Item> _items = <Item>[];
/// Getter (number of items)
int get itemsCount => _items.length;
/// Helper method to add an Item
void addItem(String reference){
setState((){
_items.add(new Item(reference));
});
}
#override
Widget build(BuildContext context){
return new _MyInherited(
data: this,
child: widget.child,
);
}
}
class MyTree extends StatefulWidget {
#override
_MyTreeState createState() => new _MyTreeState();
}
class _MyTreeState extends State<MyTree> {
#override
Widget build(BuildContext context) {
return new MyInheritedWidget(
child: new Scaffold(
appBar: new AppBar(
title: new Text('Title'),
),
body: new Column(
children: <Widget>[
new WidgetA(),
new Container(
child: new Row(
children: <Widget>[
new Icon(Icons.shopping_cart),
new WidgetB(),
new WidgetC(),
],
),
),
],
),
),
);
}
}
class WidgetA extends StatelessWidget {
#override
Widget build(BuildContext context) {
final MyInheritedWidgetState state = MyInheritedWidget.of(context);
return new Container(
child: new RaisedButton(
child: new Text('Add Item'),
onPressed: () {
state.addItem('new item');
},
),
);
}
}
class WidgetB extends StatelessWidget {
#override
Widget build(BuildContext context) {
final MyInheritedWidgetState state = MyInheritedWidget.of(context);
return new Text('${state.itemsCount}');
}
}
class WidgetC extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Text('I am Widget C');
}
}
Temporary solution:
Each (root) Page which has a Bloc now always reloads when build.
The Bloc takes care for caching.
Widget build(BuildContext context) {
final PageBloc pBloc = BlocProvider.of<PageBloc >(context);
bool isNewBuild = true;
return Scaffold(
...
body: BlocBuilder<PageBlocEvent, PageBlocState>(
if (isNewBuild) {
pBloc.dispatch(PageBlocEvent(PageBlocEventType.GETALL));
isNewBuild = false;
return CircularProgressIndicator();
} else {
// Draw data
...
...
}

Widget rebuild after TextField selection Flutter

I'm developping a Flutter App that needed to have a form. So when the user open the app, a Splash Screen appear before the form that have the following code :
import 'package:flutter/material.dart';
import '../model/User.dart';
import './FileManager.dart';
import './MyListPage.dart';
class UserLoader extends StatefulWidget {
#override
_UserLoaderState createState() => new _UserLoaderState();
}
class _UserLoaderState extends State<UserLoader> {
final userFileName = "user_infos.txt";
User _user;
#override
Widget build(BuildContext context) {
print("build UserLoader");
final _formKey = new GlobalKey<FormState>();
final _firstNameController = new TextEditingController();
final _lastNameController = new TextEditingController();
final _emailController = new TextEditingController();
final _phoneController = new TextEditingController();
return new Scaffold(
appBar: new AppBar(
title: new Text("Informations"),
actions: <Widget>[
new IconButton(
icon: const Icon(Icons.save),
onPressed: () {
_user = _onFormValidate(
_formKey.currentState,
_firstNameController.text,
_lastNameController.text,
_emailController.text,
_phoneController.text);
})
],
),
body: new Center(
child: new SingleChildScrollView(
child: new Form(
key: _formKey,
child: new Column(children: <Widget>[
new ListTile(
leading: const Icon(Icons.person),
title: new TextFormField(
decoration: new InputDecoration(
hintText: "Prénom",
),
keyboardType: TextInputType.text,
controller: _firstNameController,
validator: _validateName,
),
),
new ListTile(
leading: const Icon(Icons.person),
title: new TextFormField(
decoration: new InputDecoration(
hintText: "Nom",
),
keyboardType: TextInputType.text,
controller: _lastNameController,
validator: _validateName,
),
),
Etc, etc ...
However when i tap the TextField, the keyboard appear and close immediately and all the component is rebuild. So it is impossible for me to complete the form..
Can someone have a solution please? Thanks in advance !
You haven't given us the entire code for this, so I don't know what the context is.
One pitfall I myself have fallen into (and might be affecting you, as I gather from your description) is having a stateful widget nested inside another stateful widget.
For instance,
class Parent extends StatefulWidget {
#override
ParentState createState() => ParentState();
(...)
}
class ParentState extends State<Parent> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Child(),
);
}
(...)
}
class Child extends StatefulWidget {
#override
ChildState createState() => ChildState();
(...)
}
class ChildState extends State<Child> {
#override
Widget build(BuildContext context) {
return TextField(...);
}
(...)
}
The problem here is that a rebuild of Parent means that ParentState().build() is run, and a new Child instance is created, with a new ChildState object. Which resets everything.
Try not recreating ChildWidget, but instead saving it on ParentState, like so:
class Parent extends StatefulWidget {
#override
ParentState createState() => ParentState();
(...)
}
class ParentState extends State<Parent> {
Child _child;
#override
void initState() {
_child = Child();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _child,
);
}
(...)
}
// The rest remains the same
Edit: You just need to remember that, if your widget tree is a bit more complex, you may need to 1) pass a callback from the Parent to notify of state changes, and 2) not forget to also call setState() on the Child.
you just need make a new class and import that on your target class that seen problem. for example :
I usually create a class like this :
class MiddleWare
{
static MiddleWare shared = MiddleWare();
_MiddleWare(){}
String myText = "my Text";
// every variables should be here...
}
and
import "MiddleWare.dart";
class myclass extends StatefulWidget {
#override
_myclassState createState() => _myclassState();
}
class _myclassState extends State<myclass> {
#override
Widget build(BuildContext context) {
return Container(child: Text(MiddleWare.shared.myText));
}
}
that's it.
hi dont use Scaffold key i.e
Scaffold (
...
key: _scaffoldKey, //remove this
...
)
on the page and do a complete page rebuild (not hot reload), and you should be fine worked for me tho!
In my case, I have two stateful widgets, the parent and the child. I used the pushReplacement method on the parent to fix the widget reload issue when the text form field is selected in the child widget.
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => WidgetChildren(idUser:
widget.idUser)),
);
try to create a function which receives context like this
class YourPage extends StatefulWidget {
const YourPage(Key key) : super(key: key);
static Future<void> show({ BuildContext context,}) async {
await Navigator.of(context, rootNavigator: true).push(
MaterialPageRoute(
builder: (context) => YourPage()
);}
#override
_YourPageState createState() => _YourPageState();
}
......YourPage Build.....
then provide context to your page, when rebuilding it will have core context that prevents parent rebuild.
onPressed: () async {
await YourPage.show(context: context);
Move your variables (controllers and keys) from build to class-fields level.
in my case it was related to this property in Scaffold widget: 'resizeToAvoidBottomInset'
I changed it to true and problem solved.