FutureBuilder keeps executing when navigating between pages using Bottom Navigation Bar - flutter

I have a bottom navigation bar from my MaterialApp and one of the pages use FutureBuilder to retrieve data from my RESTful API. My bottom navigation bar needs to save the state of the pages, So I came across this guide on how to keep the state of my bottom navigation bar using PageStorage.
The issue I have encountered is that whenever I navigate out of the FutureBuilder page and back again, it rebuilds the entire page and re-executes my Future method.
I also read another guide on using AsyncMemoizer to run my Future method only once (It still rebuilds the page, but much faster). The snippet of code below is how I have implemented it.
//Unsure why AsyncMemoizer somehow only works if I use StatelessWidget, and not StatefulWidget
class FuturePage extends StatelessWidget {
/*I had to comment this constructor out because AsyncMemoizer must be
initialised with a constant value */
//const FuturePage({Key key}) : super(key: key);
//To store my PageStorageKey into bucket
FuturePage({Key key}) : super(key: key);
final _memoizer = new AsyncMemoizer();
_fetchData() => this._memoizer.runOnce(_myFutureMethod);
Future<MyType> _myFutureMethod() async => print("Executed"); //await post and return data
#override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder(
future: _fetchData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
//set up my widgets
}
return Center(child: CircularProgressIndicator());
}
);
}
}
On the output log, Executed is only displayed once. However, I need to use a StatefulWidget instead of StatelessWidget, and AsyncMemoizer wouldn't work in my case.
class FuturePage extends StatefulWidget {
FuturePageState createState() => FuturePageState();
const FuturePage({Key key}) : super(key: key);
}
How do I save the state of my FutureBuilder Page using StatefulWidget? I'm still relatively new to flutter and the concepts of reactive programming. Sincerest apologies if I happen to be doing something wrongly!

To achieve this behavior you can use the AutomaticKeepAliveClientMixin
class FuturePage extends StatefulWidget {
const FuturePage({Key key}) : super(key: key);
#override
FuturePageState createState() => FuturePageState();
}
You need to implement AutomaticKeepAliveClientMixin
class FuturePageState extends State<FuturePage> with AutomaticKeepAliveClientMixin<FuturePage>{
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context); // call super method
return // Your Widget
}
}

Related

StatelessWidget gets rebuilt even though parameters stay the same; notifyListeners() with ChangeNotifier

I've got two StatelessWidgets, one is a child of another. There is also a progress update function which updates the state in the external TransferState.
Once updateProgress function is being called the TransferIndicator widget gets rebuilt immediately. On the other hand, its parent (TransfersListTile) build method isn't called.
It works as expected, however I can't really work out what's the mechanism that's being used here. How Flutter decides to rebuild the: _TransferIndicator given that the parameter is a string hash that's not being changed, but only used as a lookup ID to reach the map in TransferState and load the status and progress.
Documentation: https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html says:
"The build method of a stateless widget is typically only called in
three situations: the first time the widget is inserted in the tree,
when the widget's parent changes its configuration, and when an
InheritedWidget it depends on changes."
If: notifyListeners(); function is removed, the widget doesn't get rebuilt.
It seem to be closely related to: ChangeNotifier, but couldn't find the exact info how it works.
In doc here: https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple#changenotifier there is an example involving ChangeNotifier, however doesn't the receiving widget need to be wrapped around: Consumer (https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple#consumer)?
In my case there is no Consumer wrapping.
class TransfersListTile extends StatelessWidget {
TransfersListTile(this.transfer, {Key? key}) : super(key: key);
final Transfer transfer;
#override
Widget build(BuildContext context) {
return ListTile(
leading: _TransferIndicator(transfer.hash),
title: Text(transfer.name!),
);
}
}
class _TransferIndicator extends StatelessWidget {
const _TransferIndicator(this.hash, {Key? key}) : super(key: key);
final String? hash;
#override
Widget build(BuildContext context) {
final status = context.select((TransferState s) => s.map[hash]?.status) ?? TransferStatus.pending;
final progress = context.select((TransferState s) => s.map[hash].progress.percentage);
return CircularProgressIndicator(
value: status == TransferStatus.completed ? 100 : (progress / 100),
);
}
}
function:
class TransferState with ChangeNotifier {
updateProgress(String hash, TransferProgress progress) {
map[hash]?.progress = progress;
notifyListeners();
}
}
and provider part:
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => TransferState(),
],
child: MyApp(),
)
);
More info about the select method and other convenience methods can be found on the provider (https://pub.dev/packages/provider) package site.
Excerpt:
The easiest way to read a value is by using the extension methods on
[BuildContext]:
context.watch(), which makes the widget listen to changes on T
context.read(), which returns T without listening to it
context.select<T, R>(R cb(T value)), which allows a widget to listen to only a small part of T.

Flutter: How to show a loading page while another page is loading

I'm making a website and I would like to show a loading_page until the home_page is loaded and then transition from one to the other as soon as possible (no fixed timers).
There are multiple ways to do this (ie., using the simplest setState, Streams, multiple packages for state management, just to name a few). I'll give you a simple example just by using a StatefulWidget where you call your API on initState and then navigate when you're done to your new screen.
class LoadingPage extends StatefulWidget {
const LoadingPage({Key? key}) : super(key: key);
#override
_LoadingPageState createState() => _LoadingPageState();
}
class _LoadingPageState extends State<LoadingPage> {
#override
void initState() {
super.initState();
_fetchFromAPI();
}
Future<void> _fetchFromAPI() async {
// Call some API, do anything you want while loading
Navigator.pushReplacementNamed(context, '/home_page');
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: const CircularProgressIndicator(),
);
}
}
You can use future builder for this purpose.
Have a look at: https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
You can fetch the data as snapshot and use the snapshot.hasdata to check if data is being received or not and till then you can show CircularProgreswIndicator() to show the loading..

how can i call setState from difference class

FLUTER DART
I have two files like this
one is stful class like this
class Test extends StatefulWidget {
const Test({Key? key}) : super(key: key);
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
#override
Widget build(BuildContext context) {
return TextButton(
onPressed: () {
updateField() // here i call function from other page
},
child: null,
);
}
}
and one is function into other file or page like this
updateField()async{
FirebaseFirestore.instance.collection("users").doc(currentUser.uid).update({
"faceType" : currentUser.faceType ,
});
setState(() {
currentUser.faceType= faceType;
});
}
but once i use setstate it says setstate is not defined , how can i use it please
thanks
Make sure you're calling setState within the stateful widget.
The setState is a method available within only Stateful widgets.
Anyone outside the stateful widget is just a custom method you wrote.
That being said you can use a state management library of your choice and you'll be able to easily change different states within your stateful widget.
The best part is that the logic code would not have to be within the UI.
You should have a function in the Statefull widget that handles SetState Cases.
and within this function you call whatever functions in other files of your project.
So, in your case you can have a function in the Statfull widget as follows:
class Test extends StatefulWidget {
const Test({Key? key}) : super(key: key);
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
_updateScreen() async {
try{
await updateField();
setState(() {
// handle the new state with Provider or whatever you prefer
});
}catch(error){
// handle errors
}
}
#override
Widget build(BuildContext context) {
return TextButton(
onPressed: () {
_updateScreen()
},
child: null,
);
}
}
and the other file could has the function as follows:
updateField()async{
FirebaseFirestore.instance.collection("users").doc(currentUser.uid).update({
"faceType" : currentUser.faceType ,
});
}

Flutter PageView and FutureBuild Keeps loading from api every time i access the page

i am facing an issue with Pageview and Futurebuild, that every-time i switch between first page and second page the page will be rebuilt again...it will call new data from API even if there is nothing new...anyone can help me in this
class Home extends StatefulWidget {
const Home({Key key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
PageController pageController = PageController(keepPage: false);
#override
void initState() {
super.initState();
}
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
final homePage = HomeProjects(pageController: pageController);
final portfolioPage = Portfolio(pageController: pageController);
return Scaffold(
body: ScrollConfiguration(
behavior: MyBehavior(),
child: PageView(reverse: true, controller: pageController,
//physics: NeverScrollableScrollPhysics(),
children: [
homePage,
portfolioPage,
]),
),
);
}
}
If you are only making HTTP calls inside your pages that's a desired outcome. You'll need to cache the responses to avoid repeated network calls.
Moving api calls to initState is not something I would personally do - the data fetched on first page creation may become outdated without you knowing it.
Well, you build your pages every time build is called.
If you don't want that, move the lines that actually build the pages to a time and place where they will be called only once. initState might be a good place.

Hero widget inits child widget multiple times

Whenever, I have a Hero widget about a StatefulWidget, the State.initState method is called three times instead of once when navigating to that page.
This obviously only happens when the other page also has a Hero with the same tag.
class Page extends StatelessWidget {
const Page({Key key}) : super(key: key);
#override
Widget build(BuildContext context) => Scaffold(body: Hero(tag: 'tag', child: HeroContent()));
}
class HeroContent extends StatefulWidget {
HeroContent({Key key}) : super(key: key);
#override
createState() => _HeroContentState();
}
class _HeroContentState extends State<HeroContent> {
#override
void initState() {
print('_HeroContentState.initState'); // printed three times with `Hero` widget and once without
super.initState();
}
#override
Widget build(BuildContext context) => Container();
}
Whenever I navigate to Page, _HeroContentState.initState is printed three times (and twice when popping a route).
Fully reproducible example of this as a gist on GitHub.
If I change the build method of Page, to look like this (removing the Hero widget):
#override
Widget build(BuildContext context) => Scaffold(body: HeroContent());
Now, _HeroContentState.initState is only called once as it should be.
How do I avoid Hero inserting my widget three times? How can I ensure that initState is only called once or have a different method that is only called once?
There is nothing you can do about that.
The way Hero works is that it moves in different locations in the widget tree in 3 steps:
the original location
inside Overlay, during the Hero transition
in the new page
Usually, for such issue, you'd use a GlobalKey, but that is not compatible with Hero.
As such, it is probably better to refactor your code such that initState doesn't matter.