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.
Related
I would like to get the value of the dropdown from the other widget in the real estate app. Say I have two widgets. First one is the dropdown widget, and the second one is Add New Property widget (or a page).. I would like to access the value of the dropdown from the Add New Property.
I could achieve this with final Function onChanged; but Im wondering if there is another way to achieve with the Provider package or the ValueNotifier
the code below is my Dropdown button widget
class PropertyType extends StatefulWidget {
final Function onChanged;
const PropertyType({
super.key,
required this.onChanged,
});
#override
State<PropertyType> createState() => _PropertyTypeState();
}
class _PropertyTypeState extends State<PropertyType> {
final List<String> _propertyTypeList = propertyType;
String? _propertyType = 'No Info';
#override
Widget build(BuildContext context) {
return ANPFormContainer(
fieldTitle: 'Property Type',
subTitle: 'အိမ်ခြံမြေအမျိုးအစား',
child: FormBuilderDropdown<String>(
name: 'a2-propertyType',
initialValue: _propertyType,
items: _propertyTypeList
.map(
(itemValue) => DropdownMenuItem(
value: itemValue,
child: Text(itemValue),
),
)
.toList(),
onChanged: (val) {
setState(() {
_propertyType = val;
widget.onChanged(val);
});
},
),
);
}
}
And this is the "Add New Property" form page
class ANPTest extends StatefulWidget {
const ANPTest({super.key});
#override
State<ANPTest> createState() => _ANPTestState();
}
class _ANPTestState extends State<ANPTest> {
final TextEditingController _propertyid = TextEditingController();
String _propertyType = 'No Info';
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: ZayyanColorTheme.zayyanGrey,
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
PropertyID(propertyID: _propertyid),
PropertyType(onChanged: (String value) {
_propertyType = value;
}),
addVerticalSpacer(25),
ANPNextButton(onPressed: _onpressed),
],
),
),
);
}
_onpressed() {
final anp = MdlFirestoreData(
propertyid: _propertyid.text, propertyType: _propertyType)
.toFirestore();
FirebaseFirestore.instance.collection('Selling Posts').add(anp);
}
}
Thank you for helping me out.
Best
yes, you could use Getx or provider package by creating a controller(function) and the package helps you to have access to variables in
your controller to use them everywhere in your program,
you may need to learn about Getx
it can help you manage your navigation and state
So, I have a textwidget in my main class and I made another class that contains a TextFormField.
Depending if the textfield is empty, I would like to change the color of the text from my main class.
If I put the textfield in the same class as my text widget, I'm ale to access the controller to read out if the textfield is empty or not.
Now the problem is, when I put the textfield in a seperate class I don't manage to find a way to access that controller from my main class.
I struggle a lot with passing data to and from within Dart, so hopefully this will brighten my mind.
Works when in same class
child: Text(
'This is a Text',
style: TextStyle(
color: txtFieldController.value.text.isEmpty? Colors.red : Colors.green,
),
),
I tried with "import '' as form"; and try to access it with form.textfieldClass().txtFieldcontroller, but that doesn't seem to work.
create a property for the controller in both classes, create the controller in the main class and then pass it to the form class.
class MainWidget extends StatelessWidget {
// create controller
final txtFieldController = TextEditingController();
// pass the controller to the other class \/
final formWidget = FormWidget(txtFieldController: txtFieldController);
// ...
}
class FormWidget extends StatelessWidget {
// create controller
final TextEditingController txtFieldController;
// ...
}
full example code:
the form widget
class FormWidget extends StatelessWidget {
// create a property for the controller \/
final TextEditingController txtFieldController;
const FormWidget({Key key, this.txtFieldController}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: TextFormField(
controller: txtFieldController,
),
);
}
}
the Main widget
class MainWidget extends StatefulWidget {
#override
_MainWidgetState createState() => _MainWidgetState();
}
class _MainWidgetState extends State<MainWidget> {
final txtFieldController = TextEditingController();
#override
void initState() {
// updates the widget on changes
// note, the docs say: 'Avoid calling setState() high up in the tree if the change is contained to a small part of the tree.'
// but it's not a big deal in this case
txtFieldController.addListener(() {
setState(() {});
});
super.initState();
}
#override
void dispose() {
txtFieldController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'This is a Text',
style: TextStyle(
color: txtFieldController.value.text.isEmpty ? Colors.red : Colors.green,
),
),
// pass the controller to the other class \/
FormWidget(txtFieldController: txtFieldController)
],
),
),
);
}
}
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.
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
...
...
}
I'm codeing an app with flutter an i'm haveing problems with the development. I'm trying to have a listview with a custom widget that it has a favourite icon that represents that you have liked it product. I pass a boolean on the constructor to set a variables that controls if the icons is full or empty. When i click on it i change it state. It works awesome but when i scroll down and up again it loses the lastest state and returns to the initial state.
Do you know how to keep it states after scrolling?
Ty a lot <3
Here is my code:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new ListView.builder(
itemCount: 100,
itemBuilder: (BuildContext context, int index){
return new LikeClass(liked: false);
},
),
);
}
}
class LikeClass extends StatefulWidget {
final bool liked;//i want this variable controls how heart looks like
LikeClass({this.liked});
#override
_LikeClassState createState() => new _LikeClassState();
}
class _LikeClassState extends State<LikeClass> {
bool liked;
#override
void initState() {
liked=widget.liked;
}
#override
Widget build(BuildContext context) {
return new Container(
child: new Column(
children: <Widget>[
new GestureDetector(
onTap:((){
setState(() {
liked=!liked;
//widget.liked=!widget.liked;
});
}),
child: new Icon(Icons.favorite, size: 24.0,
color: liked?Colors.red:Colors.grey,
//color: widget.liked?Colors.red:Colors.grey,//final method to control the appearance
),
),
],
),
);
}
}
You have to store the state (favorite or not) in a parent widget. The ListView.builder widget creates and destroys items on demand, and the state is discarded when the item is destroyed. That means the list items should always be stateless widgets.
Here is an example with interactivity:
class Item {
Item({this.name, this.isFavorite});
String name;
bool isFavorite;
}
class MyList extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyListState();
}
class MyListState extends State<MyList> {
List<Item> items;
#override
void initState() {
super.initState();
// Generate example items
items = List<Item>();
for (int i = 0; i < 100; i++) {
items.add(Item(
name: 'Item $i',
isFavorite: false,
));
}
}
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListItem(
items[index],
() => onFavoritePressed(index),
);
},
);
}
onFavoritePressed(int index) {
final item = items[index];
setState(() {
item.isFavorite = !item.isFavorite;
});
}
}
class ListItem extends StatelessWidget {
ListItem(this.item, this.onFavoritePressed);
final Item item;
final VoidCallback onFavoritePressed;
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(item.name),
leading: IconButton(
icon: Icon(item.isFavorite ? Icons.favorite : Icons.favorite_border),
onPressed: onFavoritePressed,
),
);
}
}
If you don't have many items in the ListView you can replace it with a SingleChildScrollview and a Column so that the Widgets aren't recycled. But it sounds like you should have a list of items where each item has an isFavourite property, and control the icon based on that property. Don't forget to setState when toggling the favorite.
Other answer are better for your case but this an alternative and can be used if you want to only keep several elements alive during a scroll. In this case you can use AutomaticKeepAliveClientMixin with keepAlive.
class Foo extends StatefulWidget {
#override
FooState createState() {
return new FooState();
}
}
class FooState extends State<Foo> with AutomaticKeepAliveClientMixin {
bool shouldBeKeptAlive = false;
#override
Widget build(BuildContext context) {
super.build(context);
shouldBeKeptAlive = someCondition();
return Container(
);
}
#override
bool get wantKeepAlive => shouldBeKeptAlive;
}
ListView.builder & GridView.builder makes items on demand. That means ,they construct item widgets & destroy them when they going beyond more than cacheExtent.
So you cannot keep any ephemeral state inside that item widgets.(So most of time item widgets are Stateless, but when you need to use keepAlive you use Stateful item widgets.
In this case you have to keep your state in a parent widget.So i think the best option you can use is State management approach for this. (like provider package, or scoped model).
Below link has similar Example i see in flutter.dev
Link for Example
Hope this answer will help for you
A problem with what you are doing is that when you change the liked variable, it exists in the Widget state and nowhere else. ListView items share Widgets so that only a little more than are visible at one time are created no matter how many actual items are in the data.
For a solution, keep a list of items as part of your home page's state that you can populate and refresh with real data. Then each of your LikedClass instances holds a reference to one of the actual list items and manipulates its data. Doing it this way only redraws only the LikedClass when it is tapped instead of the whole ListView.
class MyData {
bool liked = false;
}
class _MyHomePageState extends State<MyHomePage> {
List<MyData> list;
_MyHomePageState() {
// TODO use real data.
list = List<MyData>();
for (var i = 0; i < 100; i++) list.add(MyData());
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new ListView.builder(
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
return new LikeClass(list[index]);
},
),
);
}
}
class LikeClass extends StatefulWidget {
final MyData data;
LikeClass(this.data);
#override
_LikeClassState createState() => new _LikeClassState();
}
class _LikeClassState extends State<LikeClass> {
#override
Widget build(BuildContext context) {
return new Container(
child: new Column(
children: <Widget>[
new GestureDetector(
onTap: (() {
setState(() {
widget.data.liked = !widget.data.liked;
});
}),
child: new Icon(
Icons.favorite,
size: 24.0,
color: widget.data.liked ? Colors.red : Colors.grey,
),
),
],
),
);
}
}