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.
Related
I'm working with flutter. After I input my id and password, I want to show a log in animation before entering the home page. I use a dialog but I feel like my code is very blunt and has potential bugs. Is there a better solution?
// this part is for the input content is legal
else {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return LoadingStyle.buildWidget(); // this is a simple loading animation
});
service.createSession(context, code, id).then((response) { // log in part
if (response != null) {
this.saveUser(response); // something about saving user info
} else {
print('null respose');
}
}).catchError((e) {
if (e is GrpcError && e.code == StatusCode.unauthenticated) {
setState(() {
this.errorMessage = "grpc network error";
});
}
}).whenComplete(() {
Navigator.of(context).pop(); // pop dialog here, is this right?
MyRoutersl.goNewPage(context); // enter the new page
});
}
I suggest to use FutureBuilder. There is also some default loading Widget like CircularProgressIndicator() can be used when in progress.
Because login is some sort of Asynchronous progress, you can use FutureBuilder like below:
FutureBuilder(
future: service.createSession(... // Use Async function to return the result
builder: (context, snapshot) {
if(snapshot.hasData && snapshot.connectionState == done ){
// return widget after login successfully
// result should equal to snapshot.data
} else {
// return CircularProgressIndicator();
}
}
)
If you need more fancy loading indicator, you can check this package flutter_spinkit
You can use Libraries from pub.dev like loading_overlay
or you can build your own loading widget, example :
class OverlayWidget extends StatelessWidget {
final Widget child;
final bool isLoading;
OverlayWidget({#required this.child, this.isLoading = false})
: assert(child != null);
#override
Widget build(BuildContext context) {
return Stack(
children: [
child,
Visibility(
visible: isLoading,
child: Container(
color: Colors.grey.withOpacity(0.4),
child: Center(
child: Platform.isIOS
? CupertinoActivityIndicator(
radius: 20,
)
: CircularProgressIndicator(),
),
),
)
],
);
}
}
Please follow this (modal_progress_hud)
import 'package:modal_progress_hud/modal_progress_hud.dart';
......
bool _saving = false
....
#override
Widget build(BuildContext context) {
return Scaffold(
body: ModalProgressHUD(child: Container(
Form(...)
), inAsyncCall: _saving),
);
}
Background: Currently I work on a remote server image viewer,I use a FutureBuilder to show image and swith between image,it work fine,but will show blank screen in some ms between switch images.
Question: I want to remain the old image widget when FutureBuilder loading or show loading circle over old image widget instead of showing new blank loading page when loading.Or any other solution without Futurebuilder (like Scrollable Widget)?
the skeleton code:
class Viewer extends StatefulWidget {
Viewer(this.filename, {Key key}) : super(key: key);
final String filename;
#override
_ViewerState createState() {
return _ViewerState();
}
}
class _ViewerState extends State<Viewer> {
int _index = 0;
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Container(
child: showImage(context, _index),
),
onPanDown: (DragDownDetails e) {
//it will change _index to move to next image when tap down
_index+=1;
}
);
}
Widget showImage(BuildContext context, int index) {
return FutureBuilder<SmbHalfResult>(
future: () async {
//load image from remote server or from memory cache,the SmbHalfResult contain a Uint8List image.
return SmbHalfResult();
}(),
builder: (BuildContext context, AsyncSnapshot<SmbHalfResult> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return Text("Error: ${snapshot.error}");
} else {
//show image.
return Image.memory(snapshot.data.result.content);
}
} else {
// show loading circle,it will new a blank page,but I even want to display old image other than a blank page when loading.
return Center(child: CircularProgressIndicator());
}
},
);
}
}
finally I end up with PreloadPageView with paramter physics: new NeverScrollableScrollPhysics() and preloadPageController.jumpToPage(index), it perfectly meet my need without any flicker.
GestureDetector(
child: Container(
child: PreloadPageView.builder(
preloadPagesCount: 5,
itemBuilder: (BuildContext context, int index) =>
FutureImage(index, widget.filename),
controller: preloadPageController,
physics: new NeverScrollableScrollPhysics(),
)),
onPanDown: (DragDownDetails e) {
Offset globalPosition = e.globalPosition;
RenderBox findRenderObject = context.findRenderObject();
Size size = findRenderObject.size;
Area area = getArea(globalPosition, size);
if (area == Area.lef) {
index--;
preloadPageController.jumpToPage(index);
} else if (area == Area.right) {
index++;
preloadPageController.jumpToPage(index);
}
},
)
Provider will be good solution for this.
In your widget tree, the widget above the Viewer widget must look like this
ChangeNotifierProvider<SmbHalfResult>(
create: (context) {
var smbHalfResult = SmbHalfResult();
smbHalfResult.fetchImage(0);
return smbHalfResult;
},
child: Viewer();
)
The SmbHalfResult class should look something similar to this
class SmbHalfResult extends ChangeNotifier{
Uint8List image;
void fetchImage(int index) async {
this.image = await downloadImage(index);
notifyListeners();
}
}
And finally your actual UI must be like this
class _ViewerState extends State<Viewer> {
int _index = 0;
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Container(
child: Consumer<SmbHalfResult>(
builder: (context, model) {
if(model.image != null) return Image.memory(model.image);
return CircularProgressIndicator();
}
),
),
onPanDown: (DragDownDetails e) {
Provider.of<SmbHalfResult>(context, listen: false).fetchImage();
}
);
}
}
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
I have a button and if pressed should return a future builder here is my code.
I already search some examples on the web but no luck, Im new in flutter development and trying to create a simple login with api call.
Future<AccessToken>fetchAccessToken() async{final token = await _repository.fetchToKen();
>>return token;
}
onPressed: () {FutureBuilder<AccessToken>(future:bloc.fetchAccessToken(),builder: (context, snapshot) {if (snapshot.hasError) {return Text('Error');} else if (snapshot.hasData) {return Text('data');} else {return `Center`(child: CircularProgressIndicator(),);}},);}
I want to show a progress indicator while waiting for the api response, but after I receive the response, my builder inside the future builder is not called.
You can't simply return a widget and place it in the widget tree like that. Maybe you can use conditional list for hiding and showing the FutureBuilder widget.
import 'package:flutter/material.dart';
class ApiWidget extends StatefulWidget {
#override
_ApiWidgetState createState() => _ApiWidgetState();
}
class _ApiWidgetState extends State<ApiWidget> {
Repository _repository = Repository();
Future<AccessToken> accessTokenFuture;
bool isButtonPressed = false;
#override
Widget build(BuildContext context) {
return Column(children: <Widget>[
FloatingActionButton(onPressed: () {
setState(() {
try {
isButtonPressed = true;
accessTokenFuture = fetchAccessToken();
} catch (_) {
print('Fetch error');
}
});
}, child: Icon(Icons.add),),
if(isButtonPressed)
FutureBuilder<AccessToken>(
future: bloc.fetchAccessToken(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Text('Error');
}
Column(
children: <Widget>[Text(snapshot.data)],
);
},
),
],);
}
}
You can do something like that:
#override
Widget build(BuildContext context) {
return Column(children: <Widget>[
FloatingActionButton(onPressed: () {
setState(() {
try {
isLoading = true;
accessTokenFuture = await fetchAccessToken();
isLoading = false;
} catch (_) {
isLoading = false;
print('Fetch error');
}
});
}, child: Icon(Icons.add),),
_buildAsyncInfo(),
],);
}
Widget _buildAsyncInfo() {
return isLoading ?
CircularProgressIndicator() :
Column(
children: <Widget>[Text(snapshot.data)],
);
}
I am trying to update the value of totalPricewith the value that comes from the response from API. I have created a currentTotal methods that contains setState(). Then passed snapshot.data.price.totalAmountvalue to currentTotal in order to update the value of totalPrice.But, it doesnt update the value. Can you help?
double totalPrice = 0;
#override
Widget build(BuildContext context) {
currentTotal(double x) {
setState(() {
totalPrice += x;
});
}
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: FutureBuilder<SearchResult>(
future: serviceOne.postCall(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data != null) {
return new Material(
child: CustomScrollView(
slivers: <Widget>[
SliverList(
delegate: SliverChildListDelegate([
ListTile(
title: new Text(totalPrice.toString()),
)
]),
),
]
)
}
currentTotal(snapshot.data.price.totalAmount);
else if (snapshot.hasError) {
return Text("error....${snapshot.error}");
}
There are many things needs to be fixed in your build.
1 - Your widget is StatefulWidget, to use FutureBuilder inside StatefulWidget read this:
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
Briefly, create Future future; instance field, then assign it inside the initState and use that future for FutureBuilder.
2 - your setState not inside a method, you have probably syntax error there. Create a void method and use setState inside it.
3 - You don't need to check twice like:
if (snapshot.hasData) {
if (snapshot.data != null) {
One of them enough, after the condition check, call your method includes setState, then display it.
Edit:
Here an example template for your solution:
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
Future<int> future;
int price = 0;
#override
void initState() {
future = fetchPrice();
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: FutureBuilder<int>(
future: future,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Center(
child: Text(price.toString()),
);
}
return Center(child: CircularProgressIndicator());
},
),
),
);
}
Future<int> fetchPrice() async {
final response =
await http.get('https://jsonplaceholder.typicode.com/posts/1');
final data = json.decode(response.body);
setState(() {
price = data['userId'];
});
return data['userId'];
}
}