I've created my own simple bottom nav bar implementation in Flutter. When a tab is pressed, Flutter is currently re-creating the widget (initState() gets called every time) which is non-desirable.
I want the widgets to be persisted in memory so if they've already been created, they're simply popped straight in.
Main Widget
class _MainRootScreenState extends State<MainRootScreen> {
int _selectedIndex = 0;
List<Widget> _screens;
#override
void initState() {
// load pages
_screens = [
PageOne(),
PageTwo(),
PageThree()
];
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _screens[_selectedIndex],
bottomNavigationBar: _buildBottomTabBar(context)
);
}
}
so when _selectedIndex gets updated, the selected page is getting re-created.
I've tried using AutomaticKeepAliveClientMixin on the pages with no luck.
If you want that your widget/page should not rebuild when you click on tab button. You just need to follow this code
just add State<PageOne> with AutomaticKeepAliveClientMixin<PageOne> to your state class. after this you need to override a method called wantKeepAlive and make wantKeepAlive as true that's it.
By default wantKeepAlive is false because of it saves our memory .
PageOne
class PageOne extends StatefulWidget {
#override
_PageOneState createState() => _PageOneState();
}
class _PageOneState extends State<PageOne> with AutomaticKeepAliveClientMixin<PageOne> {
// Your code are here
#override
bool get wantKeepAlive => true;
}
Do the same from pageTwo and PageThree also that's it
Related
I have a Page that contains a PageView and this PageView contains a GridView, and this GridView contains Text. Right now I want to update only one Text, not all the Text, how should I do without reloading all the PageView? For example, right now I only have a Text(), I want to make a widget out of this. How should I do that so I can update the specific Text and not reload all PageView and its children?
PageView.builder(itemBuilder: (context, index) {
return GridView.builder(gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, ), itemBuilder: (context, index) {
return Text('$index');
});
}, itemCount: 3,),
)
My answer for you: unnecessary. Because GridView.builder cache your widget. You can change your text comfortably and setState in current Page. GridView will build the widgets have changed.
It depends how you get your updated data.
You can customize the GridView Item with a customized Widget, and call setState in that widget.
But you need a way to know when the data been updated.
class YourGridViewCell extends StatefulWidget {
final String label;
const YourGridViewCell({this.label});
#override
State<StatefulWidget> createState() => _YourGridViewCellState();
}
class _YourGridViewCellState extends State<YourGridViewCell> {
String _label;
#override
void initState() {
super.initState();
_label = widget.label;
}
#override
Widget build(BuildContext context) {
return Text(widget.label);
}
// you can call this to update cell.
void _updateText(String newLabel) {
setState(() {
_label = newLabel;
});
}
}
I have the following variables
String _warningMessage;
bool _warningVisibility;
Which I want to update via a Class which implements an interface
class _UserSignupInterface extends _SignupSelectUsernamePageState
implements UserSignupInterface {
#override
void onSuccess() {
_hideWarning();
_navigateToUserPage();
}
#override
void onError(String message) {
_isSignupClickable = true;
if(message != null) {
_displayWarning(message);
}
}
}
with the _displayWarning code (which is inside the _SignupSelectUsernamePageState)
void _displayWarning(String message) {
if (message != null) {
setState(() {
widget._warningMessage = message;
widget._warningVisibility = true;
});
}
}
However, whenever I call the _displayWarning(message) from outside the _SignupSelectUsernamePageState. I get an error saying
Unhandled Exception: setState() called in constructor
Is there a proper way of updating these variable states outside their class? Which in my case, I'm calling the _displayWarning(message) from another class that implements an interface
You have to decide whether this is a value that is changed internally within the widget, or if that's a value that changes externally to it.
If it's internal, the common thing is to place them in the State class with the _ on them, they could start with a value for instance set on initState and every time they change you call setState to indicate that.
However, if they change outside the widget, then you place them on the StatefulWidget class (as you seem to have done), you leave them without the _ as they are actually public and you even make them final and place them in the constructor to allow them to be set.
In this last case, if in the State class you must be aware of a change in the widget, you can implement didUpdateWidget, but that's not mandatory.
Of course you can mix both things, having a _warningMessage in the State, so you can update it with setState, but with an initial value defined in initState that comes from the widget.
Again, if the widget changes externally, you can again update the value of the _warningMessage with the new widgets value.
Something like that: (I didn't test this code)
class YourWidget extends StatefulWidget {
YourWidget({this.warningMessage});
final String warningMessage;
#override
State<YourWidget> createState() => new _YourWidgetState();
}
class _YourWidgetState extends State<YourWidget> {
String _warningMessage;
#override
void initState() {
super.initState();
_warningMessage = widget.warningMessage;
}
#override
didUpdateWidget(ReorderableListSimple oldWidget) {
super.didUpdateWidget(oldWidget);
_warningMessage = widget.warningMessage;
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text(_warningMessage),
RaisedButton(
child: Text("Change Message"),
onPressed: () {
setState(() {
_warningMessage = "new message from within the State class";
});
}
)
],
);
}
}
So in this example you can change the warningMessage externally, like in the parent Widget you are able to pass a different message. However, if you need, you can also set it internally using setState, as it's happening in the button's onPressed.
What you might check is wether you actually need that property exposed in the Widget, maybe you don't! Then, the example would look like that:
class YourWidget extends StatefulWidget {
#override
State<YourWidget> createState() => new _YourWidgetState();
}
class _YourWidgetState extends State<YourWidget> {
String _warningMessage;
#override
void initState() {
super.initState();
_warningMessage = "default message, no need for widget";
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text(_warningMessage),
RaisedButton(
child: Text("Change Message"),
onPressed: () {
setState(() {
_warningMessage = "new message from within the State class";
});
}
)
],
);
}
}
Just create a static value in the state of your widget class, then when you build the widget, set it's value to the widget. So whenever you want to call it to setState(), just call the static value.
I have an asset file that need to be processed before it can be used. This asset file will be heavily edited and I would like to not to have to restart the application each time I make an edit.
I'm aware of the existence of the reassemble method on the State class. However, this requires having a dummy widget that overrides this method and putting it inside the app somewhere to get notified about hot reload.
class WdHotReloadNotifier extends StatefulWidget
{
final Function callback;
WdHotReloadNotifier(this.callback);
#override
State<StatefulWidget> createState() => WdHotReloadNotifierState(this.callback);
}
class WdHotReloadNotifierState extends State<WdHotReloadNotifier>
{
Function callback;
WdHotReloadNotifierState(this.callback);
#override
void reassemble()
{
super.reassemble();
callback();
}
#override
Widget build(BuildContext context) {
return Container();
}
}
Then I can use it like this:
WdHotReloadNotifier((){print("HOT REALOADED 1");}),
WdHotReloadNotifier((){print("HOT REALOADED 2");}),
However, adding these to a single page means that it will work as long as the page is in the stack. And adding them to multiple pages means the hooks will execute more than once.
Is there a way in flutter to get notified globally about a hot reload?
Overriding the reassemble method on a State subclass is what you want.
But you can position the widget to a different location to change the behavior.
Consider the following widget which calls a callback on hot-reload and does nothing else:
class ReassembleListener extends StatefulWidget {
const ReassembleListener({Key key, this.onReassemble, this.child})
: super(key: key);
final VoidCallback onReassemble;
final Widget child;
#override
_ReassembleListenerState createState() => _ReassembleListenerState();
}
class _ReassembleListenerState extends State<ReassembleListener> {
#override
void reassemble() {
super.reassemble();
if (widget.onReassemble != null) {
widget.onReassemble();
}
}
#override
Widget build(BuildContext context) {
return widget.child;
}
}
You're free to insert that widget wherever you like.
Be it on a single page:
MaterialApp(
home: ReassembleListener(onReassemble: () => print("Foo"), child: Home()),
)
Or globally by wrapping the whole application:
ReassembleListener(
onReassemble: () => print('foo'),
child: MaterialApp(
home: Home(),
),
)
Logging into our Flutter app opens to dashboard that has a Scaffold with a Drawer full of menu items.
I'd like to perform some A/B testing with having the Drawer open on page load or at least animating the Drawer being opened immediately on load.
I'm aware of Scaffold.of(context).openDrawer() but I'm not sure where to place this code so that it will run immediately after the build() method. I'm also not aware of any fields on either Drawer or Scaffold which would load with the Drawer open.
Thanks for your time and help.
You need to wait after the first frame is loaded.
_onLayoutDone(_) {
//your logic here
}
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_onLayoutDone);
super.initState();
}
I wrote a post about this, you can take a look if you want : https://medium.com/#diegoveloper/flutter-widget-size-and-position-b0a9ffed9407
Override initState.
#override
void initState() {
super.initState();
// use this
Timer.run(() => Scaffold.of(context).openDrawer());
}
Store a state variable to hide and show drawer - isDrawerBeingShown.
Based on the state variable toggle the state of drawer. It is set to false by default so it will be displayed for the first time.
void _showDrawer(BuildContext context) async it must be marked as async so that it runs after build method.
Create showDrawerUtility method to show drawer on demand when ever required.
Edit:
Use GlobalKey
GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey();
class MainScreen extends StatefulWidget {
MainScreen({Key key }) : super(key: key);
#override
State<MainScreen> createState() => new MainScreenState();
}
class MainScreenState extends State<MainScreen> {
bool isDrawerBeingShown;
#override
void initState() {
super.initState();
isDrawerBeingShown = false;
_showDrawer(context);
}
void _showDrawer(BuildContext context) async {
if(!isDrawerBeingShown) {
_scaffoldKey.currentState.openDrawer();
setState(() => isDrawerBeingShown = true);
}
}
#override
Widget build(BuildContext context) { // build method goes here}
}
follow my code
import 'package:easy_debounce/easy_debounce.dart';
import 'package:flutter/material.dart';
GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey();
class openDrawerOnLoadPage extends StatefulWidget {
openDrawerOnLoadPage({Key? key}) : super(key: key);
#override
_openDrawerOnLoadPageState createState() => _openDrawerOnLoadPageState();
}
class _openDrawerOnLoadPageState extends State<openDrawerOnLoadPage> {
late bool isDrawerBeingShown;
#override
void initState() {
super.initState();
isDrawerBeingShown = false;
_showDrawer(context);
}
void _showDrawer(BuildContext context) async {
if (!isDrawerBeingShown) {
EasyDebounce.debounce('openDrawer', Duration(milliseconds: 100),
() async {
_scaffoldKey.currentState!.openDrawer();
setState(() => isDrawerBeingShown = true);
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
);
}
}
This is a simplified version of the scenario:
class ParentWdiegt extends StatelessWidget{
//
//
floatinActionButton: FloatingActionButtonWidget(onPressed:()=>CustomWidgetState.someMethod(someValue))
//
//somewhere in the ParentWidget tree
child: CustomWidget() //is stateful
}
CustomWidgetState
class CustomWidgetState extends State<CustomWidget>{
//trigger this function when FAB is pressed in parent widget
someMethod(SomeValue) {//}
}
Is there any way that I can expose someMethod in the state object to be triggered when FAB is pressed without using InheritedWidget?
While GlobalKey allows for an easy access to any widget's state ; avoid it.
Widgets should not interact with other widgets directly. This is one of the core principle of Flutter.
Flutter uses reactive programming instead. Where widgets communicate with each others by submitting events. Not by directly editing the desired widget.
The obvious benefit is that widgets stays independant. And potentially dozens of widgets can communicate with each others using the same principle.
I already made an example here on how to make two different widgets share a common editable value.
If you want to call methods instead, this uses the same principle : A Listenable or Stream shared between widgets. But without using AnimatedWidget or StreamBuilder for the listening.
Instead we'll do the listening manually (which requires slighly more boilerplate) to trigger a custom function.
Here's an example using Stream.
import 'dart:async';
import 'package:flutter/material.dart';
class ParentWidget extends StatefulWidget {
#override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
final changeNotifier = new StreamController.broadcast();
#override
void dispose() {
changeNotifier.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new AnotherWidget(
shouldTriggerChange: changeNotifier.stream,
),
new RaisedButton(
child: new Text("data"),
onPressed: () => changeNotifier.sink.add(null),
)
],
);
}
}
class AnotherWidget extends StatefulWidget {
final Stream shouldTriggerChange;
AnotherWidget({#required this.shouldTriggerChange});
#override
_AnotherWidgetState createState() => _AnotherWidgetState();
}
class _AnotherWidgetState extends State<AnotherWidget> {
StreamSubscription streamSubscription;
#override
initState() {
super.initState();
streamSubscription = widget.shouldTriggerChange.listen((_) => someMethod());
}
#override
didUpdateWidget(AnotherWidget old) {
super.didUpdateWidget(old);
// in case the stream instance changed, subscribe to the new one
if (widget.shouldTriggerChange != old.shouldTriggerChange) {
streamSubscription.cancel();
streamSubscription = widget.shouldTriggerChange.listen((_) => someMethod());
}
}
#override
dispose() {
super.dispose();
streamSubscription.cancel();
}
void someMethod() {
print('Hello World');
}
#override
Widget build(BuildContext context) {
return Container();
}
}
In this example, someMethod of AnotherWidget will be called whenever a click on the RaisedButton instantiated by _ParentWidgetState is performed.
You can use GlobalKey for that:
// some global place
final customWidgetKey = new GlobalKey<CustomWidgetState>();
...
// import the file with "customWidgetKey"
new CustomWidget(key: customWidetKey, ...)
...
// import the file with "customWidgetKey"
floatinActionButton: FloatingActionButtonWidget(
onPressed: ()=>customWidgetKey.currentState.someMethod(someValue))