Related
I have found a little issue while learning Flutter and I'm wondering which is the better way to fix it.
Here is a very simple example code of the issue:
class SubWidget extends StatelessWidget {
final void Function() mainOnPressed;
SubWidget(this.mainOnPressed);
#override
Widget build(BuildContext context) => RaisedButton(onPressed: mainOnPressed,);
void actionA() { /* Do A */ };
void actionB() { /* Do B */ };
}
class MainWidget extends StatelessWidget {
final SubWidget _subWidget;
MainWidget() : _subWidget = SubWidget(_onSubPressed);
Widget _buildChildA() => RaisedButton(onPressed: _subWidget.actionA,);
Widget _buildChildB() => RaisedButton(onPressed: _subWidget.actionB,);
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
_buildChildA(),
_buildChildB(),
_subWidget,
]
);
}
void _onSubPressed() { /* Do something */ }
}
The above code has an error, because I'm passing the _onSubPressed method as argument in the MainWidget constructor and it cannot be done because its initialization isn't complete.
I also can't move the initialization of _subWidget outside the constructor because it would give me an error because it's final and I can't remove the final because I'd get a warning for having a non-final member in an immutable class.
For the same reason, I can't defer the initialization of mainOnPressed in the SubWidget class.
I thought about moving the _subWidget member inside the build() method and pass it to the _buildChildX() methods, but while it is quite simple in this example, it would be more annoying having to do it with multiple members or methods that have the same issue.
Another solution I found is to move the _subWidget member and the two _buildChildX() inside the build() method like in the following code:
class MainWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
final SubWidget _subWidget = SubWidget(_onSubPressed);
Widget _buildChildA() => RaisedButton(onPressed: _subWidget.actionA,);
Widget _buildChildB() => RaisedButton(onPressed: _subWidget.actionB,);
return Column(
children: <Widget>[
_buildChildA(),
_buildChildB(),
_subWidget,
]
);
}
void _onSubPressed() { /* Do something */ }
}
While it works as expected, I am a little worried about the readability of the code with longer and more complex methods nested inside the build method.
Which is the best way to solve this issue?
The reason why you're getting the error "This expression has a type of 'void' so its value can't be used." on MainWidget() : _subWidget = SubWidget(_onSubPressed()); is because _onSubPressed() may contain an unexpected expression. This is explained on the link provided in the error message: https://dart.dev/tools/diagnostic-messages#use_of_void_result
To solve this issue, you can move the function's contents to the constructor to initialize _subWidget.
final SubWidget _subWidget;
MainWidget() : _subWidget = SubWidget((){
// Do something
});
Here's a complete sample that you can try out.
import 'package:flutter/material.dart';
void main() {
runApp(MainWidget());
}
class SubWidget extends StatelessWidget {
final void Function() mainOnPressed;
SubWidget(this.mainOnPressed);
#override
Widget build(BuildContext context) => ElevatedButton(
child: Text('SubWidget'),
onPressed: mainOnPressed,
);
void actionA() {
/* Do A */
debugPrint('Child A');
}
void actionB() {
/* Do B */
debugPrint('Child B');
}
}
class MainWidget extends StatelessWidget {
final SubWidget _subWidget;
MainWidget() : _subWidget = SubWidget((){
debugPrint('SubWidget');
});
Widget _buildChildA() => ElevatedButton(
child: Text('Child A'),
onPressed: _subWidget.actionA,
);
Widget _buildChildB() => ElevatedButton(
child: Text('Child B'),
onPressed: _subWidget.actionB,
);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Column(children: <Widget>[
_buildChildA(),
_buildChildB(),
_subWidget,
]),
),
),
);
}
}
I have a listview.builder inside a stateful widget and i made a separate stateful widget for the item (ImageCard).
inside the ImageCard widget i have a like button when i click it its color change to red(like), gray(dislike).
the problem is that when i scroll down and return back the color is always gray which means that no state is saved!
how can i notify the parent stateful widget to keep the state?
Parent stateful widget
#override
Widget build(BuildContext context) {
return _buildListView(models, _scrollController);
}
Widget _buildListView(
List<PhotoModel> models, ScrollController scrollController) {
return Container(
child: ListView.builder(
controller: scrollController,
itemCount: models.length,
itemBuilder: (context, int index) {
if (index == models.length - 1) {
return SpinKitThreeBounce(
color: Colors.purple,
size: 30.0,
);
} else {
return ImageCard(
models[index].regularPhotoUrl,
models[index].mediumProfilePhotoUrl,
models[index].name,
models[index].color);
}
}));
}
child stateful widget
class ImageCard extends StatefulWidget {
final String imageUrl, userProfilePic, userName, color;
ImageCard(this.imageUrl, this.userProfilePic, this.userName, this.color);
#override
_ImageCardState createState() => _ImageCardState();
}
class _ImageCardState extends State<ImageCard> {
bool isLiked = false, isFollowing = false;
#override
Widget build(BuildContext context) {
return new Card( ....
void _onLikedBtnClicked() {
setState(() {
if (isLiked)
isLiked = false;
else {
isLiked = true;
}
});
}
Flutter will automatically disposes the widget that moves out of screen, and when they re-appear, they will be re-built rather than recovered.
So common practice is to save the state in a high-level widget, which contains at least a complete aspect of business logic and is not going to be disposed anytime soon. Then a change in the state is mapped into child widgets.
For your specific case, a simple solution is: you store the information in the parent widget, and maps them to a ImageCard inside the parent widget's build function.
Add isliked,isfollowing property to the model, then
class SomeParentState extends State<SomeParent> {
List<Model> models;
//.......
#override
Widget build(BuildContext context) {
return _buildListView(models, _scrollController);
}
Widget _buildListView(List<PhotoModel> models,
ScrollController scrollController) {
return Container(
child: ListView.builder(
controller: scrollController,
itemCount: models.length,
itemBuilder: (context, int index) {
if (index == models.length - 1) {
return SpinKitThreeBounce(
color: Colors.purple,
size: 30.0,
);
} else {
return ImageCard(
models[index].regularPhotoUrl,
models[index].mediumProfilePhotoUrl,
models[index].name,
models[index].color,
models[index].isLiked,
models[index].isFollowing,
() {
setState(() {
models[index].isLiked = !models[index].isLiked;
});
},
() {
setState(() {
models[index].isFollowing = !models[index].isFollowing;
});
},
);
}
}));
}
}
class ImageCard extends StatelessWidget{
ImageCard(
//...,
this.isLiked,
this.isFollowing,
this.likeBtnClickedListener,
this.followBtnClickedListener,
)
//...
Widget build(BuildContext context){
return Card(
//.......
IconButton(
onPressed: likeBtnClickedListener,
),
IconButton(
onPressed: followBtnClickedListener,
),
)
}
}
This should basically solve your problem. Anyway, it is easier to access and sync the data in the child widgets in this method.
If you find it easier to just keep the child widget alive, you can read the documentation of AutomaticKeepAliveClientMixin. It will stop flutter from killing this widget when it moves out of sight. But it is risky of causing memory leak.
To maintain the state of a widget inside a ListView, you need to AutomaticKeepAlive or AutomaticKeepAliveMixin (for custom widgets)
This will ensure the State instance is not destroyed when leaving the screen
ListView(
children: [
// Not kept alive
Text('Hello World'),
// kept alive
AutomaticKeepAlive(
child: Text("Hello World"),
),
]
),
You should keep your state separately then. You could make a List<bool> and have one value in there for each of the List items. You probably want to save or use the data at some point anyways, then this mechanism is going to be useless.
I have created an Homepage and from that user can sign in for the app and in the next screen user can see their profile info(Only profile name) and under that their is signOut button. User can signOut from the app using signOut button.But it's not working for me.
I want to call signOut method from main.dart by pressing signOut button in details.dart(both the classes are in different file)
But when i press signOut Button in details.dart nothing happens!
And code is given below:
main.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'details.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
theme: new ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
MyHomePageState createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
final GoogleSignIn googleSignIn = GoogleSignIn();
static bool _LoginButton = true;
void signOut(){
googleSignIn.signOut();
setState((){
_LoginButton = true;
});
print(_LoginButton);
print("User Signed Out");
}
Future<FirebaseUser> _signIn() async{
if(_LoginButton==true){
setState((){
_LoginButton=false;
});
GoogleSignInAccount googleSignInAccount = await googleSignIn.signIn();
GoogleSignInAuthentication googleSignInAuthentication = await googleSignInAccount.authentication;
FirebaseUser firebaseUser = await firebaseAuth.signInWithGoogle(idToken: googleSignInAuthentication.idToken, accessToken: googleSignInAuthentication.accessToken);
print("Username is "+firebaseUser.displayName);
setState((){
_LoginButton = true;
});
Navigator.push(context, MaterialPageRoute(builder: (context) => details(firebaseUser.displayName,signOut)));
return firebaseUser;
}
}
bool _LoginButtonBool(){
return _LoginButton;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Google auth with firebase"),),
body: Center(
child: _LoginButtonBool()?Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
MaterialButton(onPressed: _LoginButtonBool() ? () => _signIn().then((FirebaseUser firebaseuser ) =>print(firebaseuser)).catchError((e) => print(e)): null,
child: Text("Login"),color: Colors.orange,),
],
),
):CircularProgressIndicator(backgroundColor: Colors.greenAccent.withOpacity(0.01),),
),
);
}
}
details.dart
import 'package:flutter/material.dart';
import 'package:flutter_auth/main.dart';
class details extends StatelessWidget {
String name;
final Function callback;
details(this.name,this.callback);
#override
Widget build(BuildContext context) {
return Scaffold(
body:Center(child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Text(name),
MaterialButton(onPressed: () => callback,
child: Text("Log out"),color: Colors.orange),
],
),),
);
}
}
It is simple let me explain with an example
class Animals
{
var animalList = ['dog','cat','cow'];
// function for printing the list of animals
void animalListPrinter(){
for(var animal in animalList){
print(animal);
}
}
}
Calling the above function to another class
class ShowingAnimalList extends StatelessWidget {
final Animals ani= new Animals();
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap:()=> ani.animalListPrinter(),
);
}
}
You can call any Widget with this from the parent class
You must be careful with what you are trying to do because you might be accessing a page/widget that is not mounted. Imagine you do a pushReplacement(new MaterialPageroute(...)). The previous page is no longer available in the tree so you can't access it nor any of its methods.
Unless you have a clear parent child relationship in your tree, you should abstract away your logic to external or business logic classes. Thus you are sure that you are calling active instances of your classes.
Here is an example of what you could use passing around the Business object. It would be even better if you use other patterns like BLOC, ScopedModel, Streams, etc. But for the sake of simplicity I think this should be enough.
import "package:flutter/material.dart";
void main() {
runApp(MyApp(new Logic()));
}
class Logic {
void doSomething() {
print("doing something");
}
}
class MyApp extends StatelessWidget {
final Logic logic;
MyApp(this.logic);
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new HomePage(widget.logic),
);
}
}
class HomePage extends StatelessWidget {
final Logic logic;
HomePage(this.logic);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FlatButton(
onPressed: () { Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => AnotherPage(logic),
))},
child: Text("Go to AnotherPage"),
),
),
);
}
}
class AnotherPage extends StatelessWidget {
final Logic logic;
AnotherPage(this.logic);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FlatButton(
onPressed: logic.doSomething,
child: Text("Press me"),
),
),
);
}
}
If you still want to call a function in the other Page and you are sure the page is mounted (you have done a push instead of a pushReplacement) you could do the following. (handle with care)
class HomePage extends StatelessWidget {
HomePage();
void onCalledFromOutside() {
print("Call from outside");
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FlatButton(
onPressed: () { Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => AnotherPage(onCalledFromOutside),
))},
child: Text("Go to AnotherPage"),
),
),
);
}
}
class AnotherPage extends StatelessWidget {
final Function callback
AnotherPage(this.callback);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FlatButton(
onPressed: callback,
child: Text("Press me"),
),
),
);
}
}
We can access it easily just like below.
className().MethodName(),
you can create another logout() function and give context of home to push back to sign in screen/home screen , works for me as :
logout() async {
await googleSignIn.signOut();
Navigator.push(context, MaterialPageRoute(builder: (context) => Home()));
}
Import HomePage class in DetailsPage and make a new instance out of it, then call the method you want if it's a public one.
We can take help instance Make instance given below
var objectName = new ClassName(<constructor_arguments>)
Note: We can use an empty constructor like this example.
class Student{
void female(){
print('This is female method');
}
void male(){
print('This is malemethod'); }
}
step1: var _instance1 = new Student(); here empty constructor it dos't matter.
step2: _instance1.male(); Call method _instance1 what we want.
A global key given to a StatefulWidget can give us access to its methods from anywhere.
GlobalKey example
WidgetA below has a method login().
To access login() from other widgets, instantiate WidgetA with a GlobalKey.
Then use that key to access WidgetA state to call login().
The structure below is:
ExamplePage
WidgetA
LoginDialog
LoginDialog will call WidgetA.login() using the global key.
login() will update the AppBar in WidgetA with the user name.
WidgetA
Here is the StatefulWidget WidgetA:
class WidgetA extends StatefulWidget {
const WidgetA({Key? key}) : super(key: key);
#override
State<WidgetA> createState() => WidgetAState();
static GlobalKey<WidgetAState> createKey() => GlobalKey<WidgetAState>();
}
class WidgetAState extends State<WidgetA> {
String loginStatus = 'Signed Out';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('$loginStatus'),
),
body: Center(
child: ElevatedButton(
child: Text('Login'),
onPressed: () =>
showDialog(context: context,
builder: (context) => LoginDialog()),
),
),
);
}
void login(String msg) {
setState(() {
loginStatus = msg;
});
}
}
Notes
static method createKey() is a convenience method to create the type of global key we need, a GlobalKey with a Type of WidgetAState
when we instantiate WidgetA, we'll need to give it a key of type GlobalKey<WidgetAState>
GlobalKey<WidgetAState>
Here's the global key we'll create of type WidgetAState, using the convenience static method createKey() we added to WidgetA class.
final widgetA = WidgetA.createKey();
To make accessing WidgetAState cleaner, we can create an optional extension class on GlobalKey<WidgetAState> types that gives us direct access to WidgetAState:
extension WidgetAKeyExt on GlobalKey<WidgetAState> {
void login(String user) => currentState?.login(user);
}
This allows us to make this call:
widgetA.login()
instead of this:
widgetA.currentState?.login()
LoginDialog
This login dialog will access WidgetA's login() method using the global key.
class LoginDialog extends StatelessWidget {
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Login'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
child: Text('Login'),
onPressed: () => widgetA.login('Billy'), // WidgetA method call
),
ElevatedButton(
child: Text('Logout'),
onPressed: () => widgetA.login('Signed Out'), // WidgetA method call
),
],
),
);
}
}
Entire Code Sample
Here's the entire code sample in a single block to copy/paste. Also includes another page WidgetB which also accesses WidgetA state through the global key.
When a login is perfomed in the LoginDialog, the app bar in WidgetA will reflect the loginStatus state. That state is also accessible in WidgetB using the global key.
import 'package:flutter/material.dart';
/// Share access to a widget's methods & state by using a GlobalKey.
/// Create a GlobalKey<T> using that widget's state class as T.
/// For example below we use GlobalKey<WidgetAState>.
///
/// Give that key to WidgetA's constructor / instantiation.
///
/// Then use that key from anywhere to call methods on WidgetAState.
/// Create the key for [WidgetAState] globally so it can be accessed across your app.
/// i.e. do this outside of a Widget, such as in your main.dart or in a state
/// management solution that you access globally, etc.
///
/// For readability/ease we've created a static method [createKey] on [WidgetA] to
/// instantiate the global key we need.
///
/// This could also just be `final widgetA = GlobalKey<WidgetAState>();
final widgetA = WidgetA.createKey(); // see static method inside WidgetA
/// Just an empty page/route to hold [WidgetA] instantiation with global key.
class ExampleGlobalKeyPage extends StatelessWidget {
const ExampleGlobalKeyPage();
#override
Widget build(BuildContext context) {
return WidgetA(key: widgetA);
}
}
/// [WidgetA] takes a key argument. We'll give it the global key we created so
/// we can access its state object from anywhere.
class WidgetA extends StatefulWidget {
const WidgetA({Key? key}) : super(key: key);
#override
State<WidgetA> createState() => WidgetAState();
/// Convenience static method to create the [WidgetAState] key to provide to [WidgetA].
/// By being specific with the [Key] class as "GlobalKey<WidgetAState>" instead
/// of just "Key", we can use create & use an extension class [WidgetAKeyExt]
/// to make accessing the widget state cleaner & easier. See that extension
/// class below.
static GlobalKey<WidgetAState> createKey() => GlobalKey<WidgetAState>();
}
/// This is [WidgetA]'s state object. By default it's private, but we'll
/// make it public by removing '_' from the name, so [_WidgetAState]
/// becomes [WidgetAState].
///
/// Its [login] method will be available anywhere using the global key we gave
/// [WidgetA] constructor.
///
class WidgetAState extends State<WidgetA> {
String loginStatus = 'Signed Out'; // this state can be changed with the GlobalKey
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('$loginStatus'),
actions: [
IconButton(icon: Icon(Icons.arrow_circle_right_rounded), onPressed: gotoB,)
],
),
body: Center(
child: ElevatedButton(
child: Text('Login'),
onPressed: () => showDialog(context: context,
builder: (context) => LoginDialog()),
),
),
);
}
/// This method can be called from [LoginDialog] widget using the GlobalKey
void login(String msg) {
setState(() {
loginStatus = msg;
});
}
void gotoB() => Navigator.push(context, MaterialPageRoute(builder: (c) => WidgetB()));
}
/// This extension class is not needed, but makes access to the [login] method
/// a little cleaner.
///
/// This extension method on GlobalKey<WidgetAState> performs the null check
/// for [currentState] existence before calling [login],
/// wherever/whenever we use [widgetA] key.
///
/// Also it uses "currentState" for us.
/// So our call `widgetA.currentState?.login()` is now `widgetA.login()`.
///
extension WidgetAKeyExt on GlobalKey<WidgetAState> {
void login(String user) => currentState?.login(user);
String get loginStatus => currentState?.loginStatus ?? 'Signed Out';
}
/// This Dialog is a separate route & widget. It makes calls to [WidgetA] methods
/// using the global key [widgetA].
class LoginDialog extends StatelessWidget {
/// For visibility you may want to pass the global key as a constructor arg
/// rather than using it directly. If so, you'd do something like:
//final GlobalKey<WidgetAState> widgetA;
//const LoginDialog({super.key, required this.widgetA});
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Login'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
child: Text('Login'),
onPressed: () => widgetA.login('Billy'), // WidgetA method call
),
ElevatedButton(
child: Text('Logout'),
onPressed: () => widgetA.login('Signed Out'), // WidgetA method call
),
],
),
);
}
}
class WidgetB extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('${widgetA.loginStatus}'),
),
body: Center(
child: Text('Some other page accessing WidgetA'),
),
);
}
}
I want to update my ListView if i remove or add items. Right now i just want to delete items and see the deletion of the items immediately.
My application is more complex so i wrote a small example project to show my problems.
The TestItem class holds some data entries:
class TestItem {
static int id = 1;
bool isFinished = false;
String text;
TestItem() {
text = "Item ${id++}";
}
}
The ItemInfoViewWidget is the UI representation of the TestItem and removes the item if it is finished (whenever the Checkbox is changed to true).
class ItemInfoViewWidget extends StatefulWidget {
TestItem item;
List<TestItem> items;
ItemInfoViewWidget(this.items, this.item);
#override
_ItemInfoViewWidgetState createState() =>
_ItemInfoViewWidgetState(this.items, this.item);
}
class _ItemInfoViewWidgetState extends State<ItemInfoViewWidget> {
TestItem item;
List<TestItem> items;
_ItemInfoViewWidgetState(this.items, this.item);
#override
Widget build(BuildContext context) {
return new Card(
child: new Column(
children: <Widget>[
new Text(this.item.text),
new Checkbox(
value: this.item.isFinished, onChanged: isFinishedChanged)
],
),
);
}
void isFinishedChanged(bool value) {
setState(() {
this.item.isFinished = value;
this.items.remove(this.item);
});
}
}
The ItemViewWidget class builds the ListView.
class ItemViewWidget extends StatefulWidget {
List<TestItem> items;
ItemViewWidget(this.items);
#override
_ItemViewWidgetState createState() => _ItemViewWidgetState(this.items);
}
class _ItemViewWidgetState extends State<ItemViewWidget> {
List<TestItem> items;
_ItemViewWidgetState(this.items);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: new Text('Test'),
),
body: ListView.builder(
itemCount: this.items.length,
itemBuilder: (BuildContext context, int index) {
return new ItemInfoViewWidget(this.items, this.items[index]);
}),
);
}
}
The MyApp shows one TestItem and a button that navigates to the ItemViewWidget page.
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#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> {
int _counter = 0;
List<TestItem> items = new List<TestItem>();
_MyHomePageState() {
for (int i = 0; i < 20; i++) {
this.items.add(new TestItem());
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Column(
children: <Widget>[
ItemInfoViewWidget(this.items, this.items.first),
FlatButton(
child: new Text('Open Detailed View'),
onPressed: buttonClicked,
)
],
));
}
void buttonClicked() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ItemViewWidget(this.items)),
);
}
}
If i toggle the Checkbox of the first item, the Checkbox is marked as finished (as expected), but it is not removed from the UI - however it is removed from the list.
Then I go back to the Main page and I can observe that Item 1 is checked there as well.
So if I go to the ItemViewWidget page again, I can observe that the checked items are no longer present.
Based on these observations, I come to the conclusion that my implementation works, but my UI is not updating.
How can I change my code to make an immediate update of the UI possible?
Edit: This is not a duplicate, because
I dont want to create a new instance of my list just to get the UI updated.
The answer does not work: I added this.items = List.from(this.items); but the behavior of my app is the same as already described above.
I don't want to break my reference chain by calling List.from, because my architecture has one list that is referenced by several classes. If i break the chain i have to update all references by my own. Is there a problem with my architecture?
I dont want to create a new instance of my list just to get the UI updated.
Flutter uses immutable object. Not following this rule is going against the reactive framework. It is a voluntary requirement to reduce bugs.
Fact is, this immutability is here especially to prevents developers from doing what you currently do: Having a program that depends on sharing the same instance of an object between classes; as multiple classes may want to modify it.
The real problem lies in the fact that it is your list item that removes delete an element from your list.
The thing is since it's your item which does the computing, the parent is never notified that the list changed. Therefore it doesn't know it should rerender. So nothing visually change.
To fix that you should move the deletion logic to the parent. And make sure that the parent correctly calls setState accordingly. This would translate into passing a callback to your list item, which will be called on deletion.
Here's an example:
class MyList extends StatefulWidget {
#override
_MyListState createState() => _MyListState();
}
class _MyListState extends State<MyList> {
List<String> list = List.generate(100, (i) => i.toString());
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) {
return MyItem(list[index], onDelete: () => removeItem(index));
},
);
}
void removeItem(int index) {
setState(() {
list = List.from(list)
..removeAt(index);
});
}
}
class MyItem extends StatelessWidget {
final String title;
final VoidCallback onDelete;
MyItem(this.title, {this.onDelete});
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(this.title),
onTap: this.onDelete,
);
}
}
I want to create an App with Tabs to get the users input. The Problem is, that the different Tabs get different inputs, but i have to collect the inputs for the Database. My idea her was, that the main scaffold collects the inputs from all Tabs and write it in a database! My problem is that I don't know to send data from the tab (statefullWidget in an other file) to the parent class (Scaffold) or run a function from there!
Please help me and sorry for my bad English!
Jonas
You can pass a Function that can be called whenever you want.
Small example
MamaBear class
...
class _MamaBear extends State<MamaBear> {
void hungryBear(String babyBear) {
print("$babyBear is hungry");
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Column(children: <Widget>[
BabyBear(
"Mark",
(babyBear) {
hungryBear(babyBear);
},
)])));}
BabyBear class
class BabyBear extends StatefulWidget {
final String babyBearName;
final Function onBearAction;
BabyBear(this.babyBearName, this.onBearAction);
#override
_BabyBear createState() => _BabyBear();
}
class _BabyBear extends State<BabyBear> {
#override
Widget build(BuildContext context) {
return Card(
child: RaisedButton(
child: Text("Mama I'm hungry"),
onPressed: () {
widget.onBearAction(widget.babyBearName);
}),
);
}
}