FutureBuilder reloading whenever the BottomNavigationBarItem is changed - flutter

I'm using FutureBuilder on a screen with BottomNavigationBar. But whenever I click on a different tab and come back, FutureBuilder reloads everything again. I'm already using AutomaticKeepAliveClientMixin, I'm having trouble saving getLessons() so I don't have to load it again. Can someone help me?
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Lesson>>(
future: getLessons(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.none) {
} else if (snapshot.connectionState == ConnectionState.waiting) {
} else {
return Container();
}
});
}
This is my getLessons():
Future<List<Lesson>> getLessons() async {
String url = "...";
http.Response response = await http.get(url);
var data = json.decode(response.body);
(...)
return lessons;
}
How can I maintain the state so as not to update?

// Create instance variable
Future myFuture;
#override
void initState() {
super.initState();
// assign this variable your Future
myFuture = getLessons();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Lesson>>(
future: future, // use your future here
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.none) {
} else if (snapshot.connectionState == ConnectionState.waiting) {
} else {
return Container();
}
});
}
Credit to CopsOnRoad

The problem is that I was calling the screens without using PageView. I started the 4 screens outside the build() and called them all within a PageView, now it works.
body: PageView(
controller: _pageController,
onPageChanged: (index) {
setState(() {
_index = index;
});
},
children: [_container1, _container2, _container3, _container4],
),

If you replace the PageView with PreloadPageView, the FutureBuilders will not be called again
just install preload_page_view here

Related

Handling multiple futures in Build

This is my build:
#override
Widget build(BuildContext context) {
return FutureBuilder<Position>(
future: _init,
builder: (context, snapshot) {
...
final Position position = snapshot.data!;
return FlutterMap(
...
layers: [
...
MarkerLayerOptions(
markers: markers, //this is the future list
),
],
);
});
}
Now, markers is a Future and I build it with this methods:
late Future<List<Marker>> markers;
#override
void initState() {
...
markers = getMarkers();
}
Future<List<Marker>> getMarkers() async {
List<Marker> markerTemp = [];
for (var friend in friendsList) {
DocumentSnapshot document = await locationRef.doc(friend).get();
if (document.exists)
markerTemp.add(Marker(...));
}
return markerTemp;
}
So when I run my application I get an error saying that markers is not initialized. How can I have my list ready when called in the build method?
I tried things like nested FutureBuilder or using Future.wait([item1,item2]) but since I'm newbie to this language I'm having troubles implementing it the right way probably
Try using FutureBuilder in some way similar to this:
return FutureBuilder<List<Marker>>(
future: markers,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return Center(child: CircularProgressIndicator());
if (snapshot.hasError)
return Text("${snapshot.error}");
// You can access the list here, use this newly created list
List<Markers> markerList = snapshot.data as List<Marker>;
return FutureBuilder<Position>(...)
});
I believe FutureBuilder will solve your problem. try this:
#override
Widget build(BuildContext context) {
return FutureBuilder(
initialData: null,
future: Future.wait(getMarkers()),
builder: (context, snapshot) {
...
},
);
}
it's that simple. if something goes wrong just make sure your getMarkers() actually return a list of Futures and you'll be alright

How To Work with Flutter Data Model and Future Builder

i have working with test app, its just display list of employees from api call, for that i have created data model for employee and calling it. but i get nothing i know somewhere it goes wrong help me to find out the problem(actually no errors but, its does not load the data).
here is the snippets
import 'package:flutter/material.dart';
import '../models/employee.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class EmployeeListScreen extends StatefulWidget {
EmployeeListScreen({Key key}) : super(key: key);
#override
_EmployeeListScreenState createState() => _EmployeeListScreenState();
}
class _EmployeeListScreenState extends State<EmployeeListScreen> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Employee List"),
),
body: FutureBuilder(
future: fetchEmployees(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.none) {
return Center(
child: Text("None"),
);
}
if (snapshot.connectionState == ConnectionState.active) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return Center(child: Text("No Employees"));
} else {
return Center(
child: ListView.builder(
itemCount: snapshot.data.length[![enter image description here][1]][1],
itemBuilder: (BuildContext context, int index) {
return Text(snapshot.data[index]["name"]);
},
),
);
}
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
return Container();
},
));
}
Future<List<Employee>> fetchEmployees() async {
final response = await http.get(
"http://192.168.1.199/projects/ci/employee/api/getEmployees",
headers: {"accept": "application/json"});
debugPrint("Api Finished...");
if (response.statusCode == 200) {
final result = jsonDecode(response.body);
Iterable list = result['employees'];
print(list);
return list.map((employee) => Employee.fromJson(employee)).toList();
} else {
throw Exception("Failed to Load Employees");
}
}
}
see the screen shots.
i have the result while am using traditional api call without using model and factory methods, its very confusing to me also suggest me for best sites to learn these things, even i saw the official document it not clear at all.
To help debug the issue, how about trying this simplified code below. Call your fetchEmployees() from inside loadSlowData() method.
(It's not good practice to make an async call directly in FutureBuilder future:. Instead, make the async call in initState of the StatefulWidget. Since FutureBuilder is inside the build() method, and build could be called up to 60 times a second, you can obviously see the problem. If you happen to use an animation on that part of the widget tree, which refresh at 60fps, you'll get that situation.)
import 'package:flutter/material.dart';
class FutureBuilderStatefulPage extends StatefulWidget {
#override
_FutureBuilderStatefulPageState createState() => _FutureBuilderStatefulPageState();
}
class _FutureBuilderStatefulPageState extends State<FutureBuilderStatefulPage> {
Future<String> _slowData;
#override
void initState() {
super.initState();
_slowData = loadSlowData();
}
Future<String> loadSlowData() async {
// replace with your async call ↓ ↓
return await Future.delayed(Duration(seconds: 2), () => 'The Future has arrived');
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('FutureBldr Stateful'),
),
body: FutureBuilder<String>(
future: _slowData,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Center(child: Text(snapshot.data));
}
return Center(child: Text('Loading...'));
},
),
);
}
}
You can possibly Try snapShot.hasData instead of snapshot.data

Error: 'await' can only be used in 'async' or 'async*' methods

I am trying to add distance from user to the Location object, but this requires using an asynchronous call that I can't figure out where and how to do exactly. From there I will sort Locations by distance from user. I tried the code below bc it's where the sorted locations would be used, but I get an error saying "await" can only be used in "async" or "async*" methods even though it is being used with an async function. How do I add distance from user to a Location object given it requires an asynchronous call?
class MapWidget extends StatefulWidget {
...
#override
_MapWidgetState createState() => _MapWidgetState();
}
class _MapWidgetState extends
State<MapWidget> {
Future <List<Location>> sortLocations() async {
return null;//function not done
}
#override
Widget build(BuildContext context) {
final List<Location> sortedLocations = await sortLocations();
...
You cannot use await functions in build method because it cannot be async.To use async operations in build you must use FutureBuilder or StreamBuilder.
Future<List<Location>> sortLocations() {
...
return <Location>[];
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Location>>(
future: sortLocations(),
builder: (context, snapshot) {
if(snapshot.hasError) {
return Center(child: Text(snapshot.error.toString()));
}
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator()));
}
return ListView(...);
},
);
}
Future<List<Location>> sortLocations() {
...
return <Location>[];
}
#override
Widget build(BuildContext context) {
return StreamBuilder<List<Location>>(
stream: sortLocations().asStream(),
builder: (context, snapshot) {
if(snapshot.hasError) {
return Center(child: Text(snapshot.error.toString()));
}
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator()));
}
return ListView(...);
},
);
}
In Flutter there is a widget call FutureBuilder, that helps you build UI after the data is returned from an async function. You can use it as:
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Location>>(
future: sortLocations(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Container(child: Center(child: CircularProgressIndicator()));
var sortedLocations = snapshot.data;
// Build your UI here
return ...
}
);
you cannot use await in build method instead use it in initState
final List<Location> sortedLocations= new List();
#override
void initState(){
super.initState();
getdata();
}
getdata()async{
sortedLocations.clear();
sortedLocations = await sortLocations();
setState((){});
}

Change the parent widget height without causing the child to rebuild Flutter

I have a parent widget which is a Container() and a child widget which is a FutureBuilder...
In my app I need to change the height of the container so that it fits the newly added items in the FutureBuilder But the problem is when I setState and change the parent widget's (Container()'s) height the FutureBuilder gets rebuilt again
Now, that's to be expected and is the normal behavior...
Now the question. How can I prevent my child's widget from rebuilding and rebuild only the parent widget?
Like a way to save the data into the RAM or something...
Also, I'm using AutomaticKeepAliveClientMixin but to no avail;
Here is my code
Parent
\\ Somewhere here I call setState and change the value of _latestPostsHeight
Container(
child: LatestPosts(),
height: _latestPostsHeight,
),
And this my LatestPosts() which is a FutureBuilder
class _LatestPostsState extends State<LatestPosts>
with AutomaticKeepAliveClientMixin {
bool get wantKeepAlive => true;
bool _isFirstTime = true;
Future<List<Post>> _posts() async {
final Future<List<Post>> _posts =
context.read(fetchPostsProvider({'perPage': 5, 'pageNum': 1}).future);
return _posts;
}
#override
Widget build(BuildContext context) {
super.build(context);
return FutureBuilder(
future: _posts(),
builder: (BuildContext context, AsyncSnapshot<List<Post>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Column(
children: [
for (var i = 0; i < 7; i++) PostShimmer(),
],
);
} else if (snapshot.connectionState == ConnectionState.done) {
if (_isFirstTime) {
SchedulerBinding.instance
.addPostFrameCallback((_) => setState(() {
_isFirstTime = false;
final boolProvider =
context.read(latestPostsDataLoaded);
boolProvider.state = true;
}));
}
return Column(
children: [
for (var i = 0; i < snapshot.data.length; i++)
SimplePostContainer(
data: snapshot.data, index: i, type: SearchOrPost.post)
],
);
} else {
return Container();
}
});
}
}
What can I do?
bool futureCalled = false;
Future post(){
setState(() {
futureCalled = true;
});
}
Widget build(BuildContext context) {
return Container(
height:containerHeight ,
child: FutureBuilder(
future: futureCalled ? null : post(), //handle child redraw
builder: (BuildContext context, snapshot){
.....
);
}
Hope this may help you, let me know if this works.

FutureBuilder only works in Debug

I have a FutureBuilder with a ListView to display custom items (Widgets) with values which are read from .txt files.
The problem is that these items are only displayed if I launch the app in Debug-mode or run-mode. When I try to open the app with the AppLauncher (like a "normal" user would do it) the listView is empty. I tried this on an AVD and on a "real" device.
the Future "listFuture" is used to read the values from the files and return a list of Widgets
class Home extends StatefulWidget {
final Future listFuture = setupList();
#protected
#mustCallSuper
void initState() {
print("init complete");
}
#override
State<StatefulWidget> createState() {
return HomeState();
}
}
If the FutureBuilder gets the data correctly a listView with the list of my widgets should be displayed
child: FutureBuilder<List<SubListItem>>(
future: widget.listFuture,
// ignore: missing_return
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return new Text("None");
case ConnectionState.waiting:
return new Text("loading");
default:
if (snapshot.hasError) {
print("Error");
return Center(child: (Text("No data")));
} else {
return subListView(context, snapshot);
}
}
},
),
Widget subListView(BuildContext context, AsyncSnapshot snapshot) {
List<Widget> items = snapshot.data;
//This ScrollConfiguration is used to remove any animations while scrolling
return ScrollConfiguration(
behavior: CustomScrollBehavior(),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 4),
child: new ListView.builder(
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
return Column(
children: <Widget>[items[index]],
);
},
),
),
);
}
Thanks for helping!
Ok, I solved the problem. You just have to call "setState" when your Widget is built.
#protected
#mustCallSuper
void initState() {
super.initState();
Future.delayed(Duration.zero, () {
//This setState is necessary because it refreshes the listView
setState(() {});
});
}
It's looks like a async data issue, try these changes:
Remove listFuture from your StatefulWidget.
Add the listFuture var inside your State.
Move the setupList() method inside your State.
And finally call directly like this:
child: FutureBuilder<List<SubListItem>>(
future: setupList(),
// ignore: missing_return
builder: (BuildContext context, AsyncSnapshot snapshot) {
if(!snapshot.hasData) {
return new Text("loading");
}
else if (snapshot.hasError) {
print("Error");
return Center(child: (Text("No data")));
} else {
return subListView(context, snapshot);
}
}
},
),