Can I change the body of a widget using setState? - flutter

My application has scaffold.
But I want to change only the body of scaffold.
Normally I use setState() to change the state, but in this case, How can I use setState() or I can do some other way??
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
void onTapped() {
print ("tapped");
// I want to change only body of Scaffold like this
// body: new Text("new body");
};
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body:
GestureDetector(
onTap: () => onTapped(),
child:Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
],
),
),
),
);
}
}

If you want to just setState within the body of the scaffold, make the body a stateful widget and call setState within that widget. You can define your own stateless and stateful widgets by extending StatelessWidget or StatefulWidget. It is useful to define a particular thing as its own widget instead of just as a method that returns a widget because of how Flutter compartmentalizes the rebuilding process. If the body of the scaffold is its own widget, only that widget will be rebuilt when you call setState. If you do what the other answer suggests, you will rebuild MyHomePage, which includes the scaffold. On the other hand, if you define a stateful widget with a smaller scope, and then call setState() within that widget, only the widget with the smaller scope will be rebuilt.
For example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
Widget build(BuildContext context) {
print('scaffold rebuilt');
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: ScaffoldBody(),
);
}
}
class ScaffoldBody extends StatefulWidget {
#override
_ScaffoldBodyState createState() => _ScaffoldBodyState();
}
class _ScaffoldBodyState extends State<ScaffoldBody> {
int timesTapped = 0;
void onTapped() {
setState(() {
timesTapped++;
});
}
#override
Widget build(BuildContext context) {
print('scaffold body rebuilt');
return GestureDetector(
onTap: onTapped,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times: $timesTapped',
),
],
),
),
);
}
}

You can create a variable Widget _scaffoldBody; to hold the current Scaffold body.
You set an initial value to it, and then call setState when you need to change the body.
Something like this:
class _MyHomePageState extends State<MyHomePage> {
Widget _scaffoldBody;
#override
void initState(){
// Initialize it with the first body you want visible.
_scaffoldBody = GestureDetector(
onTap: () => onTapped(),
child:Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
],
),
),
);
}
// Note: move the onTapped method inside the state so you can call setState;
void onTapped()
// Call setState changing the body
setState((){
_scaffoldBody = Text("new body");
});
};
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: _scaffoldBody,
);
}
}

Related

Flutter: How to change the state of a variable in another dart file?

I am currently working on an app; I want to change the value of a String which is declared in another dart file and then pass that changed state to the stateful widget.
I.E;
I create a file called as "Body.dart" file where I have declared a String called as 'scale' who's value initially is "Empty".
Later when a button in another dart file "scale_button" is pressed, I want to assign the string scale = "Hello" in my Body.dart file. So that the stateful widget also displays the same on the screen.
You can use provider(or any other state management) package in that case. In yaml file add, provider: ^4.3.2+4
class HomeApp extends StatefulWidget {
#override
_HomeAppState createState() => _HomeAppState();
}
class _HomeAppState extends State<HomeApp> {
StringProvider _stringProvider;
#override
void initState() {
super.initState();
_stringProvider = Provider.of<StringProvider>(context, listen: false);
}
void updateString() {
_stringProvider.setString('hai');
}
#override
Widget build(BuildContext context) {
StringProvider _stringProvider = Provider.of<StringProvider>(context);
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child: Text(
_stringProvider.str,
style: TextStyle(
fontSize: 22,
),
),
),
RaisedButton(
onPressed: updateString,
child: Text('Click'),
),
],
),
),
);
}
}
// class for storing data(StringProvider.dart)
import 'package:flutter/material.dart';
class StringProvider extends ChangeNotifier { // create a common file for data
String _str = 'hello';
String get str => _str;
void setString(String st) {
_str = st;
notifyListeners();
}
}
When you create a new Flutter project the sample code of the counter shows you how to do this. Check out the comments in the next code:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
// here is passing a String to MyHomePage.
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
// And here you can see how to make the widget wait for a variable
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
Full code of Sample Counter App
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

Why is Flutter NotificationListener not catching my notifications?

I am having a problem with the NotificationListener in flutter. I've build a simple testing app because I am struggling with it.
After clicking on the FlatButton the Notification should be dispatched and then caught by the NotificationListener in onNotification.
So the expected console output would be:
"TestNot"
"Bubble"
But all I am getting is "TestNot".
So the notification is not caught by the listener.
Any idea what I could be doing wrong?
Thank you :-)
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyNotification extends Notification {
final String title;
const MyNotification({this.title});
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: NotificationListener<MyNotification>(
onNotification: (notification) {
print("Bubble");
return true;
},
child: Center(
child: Column(
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
FlatButton(onPressed: () {print("TestNot"); MyNotification(title: "TestNot").dispatch(context);}, child: Text("TestNot")),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
)),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
When you need a child to notify its parent, you can use NotificationListener.
But, when you need communications to be implemented inversely, in other words, a parent to notify its children, you can use ValueListenableBuilder
A nice doc about it available here:
https://medium.com/flutter-community/flutter-notifications-bubble-up-and-values-go-down-c1a499d22d5f
"Flutter, notifications ‘bubble up’ and values ‘go down’"
You cannot receive the notification at the same level of where it was dispatched. Please refer to docs : https://api.flutter.dev/flutter/widgets/NotificationListener-class.html
NotificationListener class :
A widget that listens for Notifications bubbling up the tree.
I've updated your code to make it work.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyNotification extends Notification {
final String title;
const MyNotification({this.title});
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: NotificationListener<MyNotification>(
onNotification: (MyNotification notification) {
print("Bubble");
return true;
},
child: Center(
child: Column(
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
MyChild(),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
)),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class MyChild extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FlatButton(
onPressed: () {
print("TestNot");
MyNotification(title: "TestNot").dispatch(context);
},
child: Text("TestNot"));
}
}

How to keep the widget's state in Scaffold.drawer in Flutter?

I want to keep the widget's state in Scaffold.drawer. The Scaffold.drawer is a custom widget, which has a RaiseButton in it.
When click the button, the text in the button changed.
But when the drawer is closed, and reopen the drawer, the changed text is reseted.
I have use " with AutomaticKeepAliveClientMixin<> " in my custom Drawer, but it does't work.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Flutter Demo"),
),
drawer: Drawer(child: CustomDrawer(),),
body: Center(
child: Text("Flutter Demo"),
),
);
}
}
class CustomDrawer extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _CustomDrawerState();
}
}
class _CustomDrawerState extends State<CustomDrawer> with AutomaticKeepAliveClientMixin<CustomDrawer> {
String btnText = "Click!";
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
return Center(
child: RaisedButton(onPressed: () {
setState(() {
btnText = "Clicked!!";
});
}, child: Text(btnText),),
);
}
}
I expect the widget's state can keep, even if the Drawer is closed.
Create a separate widget for the drawer and just use in anywhere you need to.
Manage the Drawer State with a Provider
class DrawerStateInfo with ChangeNotifier {
int _currentDrawer = 0;
int get getCurrentDrawer => _currentDrawer;
void setCurrentDrawer(int drawer) {
_currentDrawer = drawer;
notifyListeners();
}
void increment() {
notifyListeners();
}
}
Adding State Management to the Widget tree
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiProvider(
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.teal,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
),
providers: <SingleChildCloneableWidget>[
ChangeNotifierProvider<DrawerStateInfo>(
builder: (_) => DrawerStateInfo()),
],
);
}
}
Creating The Drawer Widget for reuse in application
class MyDrawer extends StatelessWidget {
MyDrawer(this.currentPage);
final String currentPage;
#override
Widget build(BuildContext context) {
var currentDrawer = Provider.of<DrawerStateInfo>(context).getCurrentDrawer;
return Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text(
"Home",
style: currentDrawer == 0
? TextStyle(fontWeight: FontWeight.bold)
: TextStyle(fontWeight: FontWeight.normal),
),
trailing: Icon(Icons.arrow_forward),
onTap: () {
Navigator.of(context).pop();
if (this.currentPage == "Home") return;
Provider.of<DrawerStateInfo>(context).setCurrentDrawer(0);
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (BuildContext context) =>
MyHomePage(title: "Home")));
},
),
ListTile(
title: Text(
"About",
style: currentDrawer == 1
? TextStyle(fontWeight: FontWeight.bold)
: TextStyle(fontWeight: FontWeight.normal),
),
trailing: Icon(Icons.arrow_forward),
onTap: () {
Navigator.of(context).pop();
if (this.currentPage == "About") return;
Provider.of<DrawerStateInfo>(context).setCurrentDrawer(1);
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (BuildContext context) => MyAboutPage()));
},
),
],
),
);
}
}
Use of Drawer in one of your pages
class MyAboutPage extends StatefulWidget {
#override
_MyAboutPageState createState() => _MyAboutPageState();
}
class _MyAboutPageState extends State<MyAboutPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('About Page'),
),
drawer: MyDrawer("About"),
);
}
}
In your case, you have 2 choices:
You should keep your state in your Top level widget. in your case _MyHomePageState;
Use state managers like Redux, Bloc, ScopedModel. I think ScopedModel is great for you in this case.
otherwise, you can't control the state of Drawer. cause it re-creates every moment you call the Drawer by the action button in Appbar;

Stateful Widget child is not updated

There are two stateful widgets.
The state of MyHomePage contains the counter.
The MyHomePage wraps content in a second stateful widget SubPage.
The SubPage has a child widget with data from the MyHomePage.
To clarify the problem, the first textwidget which is inside of the SubPage child doesn't update when the counter changes.
The textwidget outside of the SubPage increments as expected.
What do we have to do if we want the content of the inner stateful widget updated?
We have to use a stateful widget there. In the real application this widget has a real use-case.
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> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: Column(
children: <Widget>[
SubPage(
child: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'You have pushed the button this many times:',
),
new Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
),
new Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
floatingActionButton: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class SubPage extends StatefulWidget {
SubPage({Key key, this.child}) : super(key: key);
final Widget child;
#override
SubPageState createState() => new SubPageState(child);
}
class SubPageState extends State<SubPage> {
final Widget child;
SubPageState(this.child);
#override
Widget build(BuildContext context) {
print("subpage build");
return this.child;
}
}
You don't have to set child as field of state. Actually it is cause of this bug. Here working code
class SubPage extends StatefulWidget {
SubPage({Key key, this.child}) : super(key: key);
final Widget child;
#override
SubPageState createState() => new SubPageState();
}
class SubPageState extends State<SubPage> {
#override
Widget build(BuildContext context) {
return widget.child;
}
}

Flutter: FloatingActionButton has no shadows and touch animation

The default FAB in the demo app has shadows and a ripple effect when touched.
However, when the FAB is moved to another class, it suddenly loses this effect, which makes it look rather plain.
Is it possible to bring the shadows and ripple animations to the plain looking FAB while keeping it in a separate class?
Main.dart
import 'package:test_app/TestItem1.dart';
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 TestItem1(),
);
}
}
TestItem1.dart
import 'package:flutter/material.dart';
class TestItem1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'test 1',
),
new Text(
'test1',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: new FloatingActionButton(
tooltip: 'Increment',
onPressed: null,
child: new Icon(Icons.add),
),
);
}
}
The floating action button is disabled by default if you gave nothing to
onPresss()
if you don't want to give in onPress just pass (){} an anonymous method