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();
}
);
}
}
Related
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.
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),
);
}
In the video and picture below, the horizontal and vertical widgets are arranged in order.
If you scroll through this, each widget will move separately, just like a video.
I want to make this move at once.
please enter the videoLink
https://firebasestorage.googleapis.com/v0/b/coody-f21eb.appspot.com/o/%E1%84%92%E1%85%AA%E1%84%86%E1%85%A7%E1%86%AB%20%E1%84%80%E1%85%B5%E1%84%85%E1%85%A9%E1%86%A8%202020-09-28%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%208.06.33.mov?alt=media&token=8a9d3fd0-1256-4d92-9a57-
please enter Imglink
https://firebasestorage.googleapis.com/v0/b/coody-f21eb.appspot.com/o/KakaoTalk_Photo_2020-09-28-08-15-13.jpeg?alt=media&token=77cd7fba-5b62-4d68-b760-8
import 'package:flutter/material.dart';
import 'element_homepage/contents_carousel.dart';
import 'element_homepage/gridView_of_homepage.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'product_detail.dart';
class HomeScreen extends StatefulWidget {
var stopTrigger = 1;
var unchanging ;
List<bool>bool_list_each_GridSell =[];
List<String> styleList = [];
var tf_copy = [];
final FirebaseUser user;
HomeScreen(this.user);
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
void initState() {
super.initState();
if(widget.stopTrigger == 1){
setState(() {
widget.unchanging = Firestore.instance.collection("uploaded_product").snapshots();
});
}
}
#override
Widget build(BuildContext context) {
return Container(
child: Scaffold(
appBar: AppBar(title:Text("logo --- rec --- menu")),
body: _bodyBuilder()
),
);
}
Widget _bodyBuilder() {
return Column(
children: [
ContentsCarousel(),
_gridBuilder()
],
);
}
Widget _gridBuilder() {
return Expanded(
child: StreamBuilder <QuerySnapshot>(
stream: _commentStream(),
builder: (BuildContext context, AsyncSnapshot snapshot){
if(!snapshot.hasData){
return Center(child: CircularProgressIndicator());
}
var items = snapshot.data?.documents ??[];
var fF = items.where((doc)=> doc['style'] == "오피스룩").toList();
var sF = items.where((doc)=> doc['style'] == "로맨틱").toList();
var tF = items.where((doc)=> doc['style'] == "캐주얼").toList();
fF.addAll(sF);
fF.addAll(tF);
widget.tf_copy.addAll(fF);
if(widget.stopTrigger == 2 ){
fF.shuffle();
widget.unchanging = fF;
}
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 0.6,
mainAxisSpacing: 2.0,
crossAxisSpacing: 2.0,),
itemCount: fF.length,
itemBuilder: (BuildContext context, int index) {
for(var i=0; i<fF.length; i++){
widget.bool_list_each_GridSell.add(false);
}
return _buildListItem(context,widget.unchanging[index]);
}
);
},
),
);
}
Widget _buildListItem(context, document) {
return
InkWell(
onTap: (){
Navigator.push(context, MaterialPageRoute(builder: (context){
return ProductDetail(widget.user, document);
}));
},
child: Image.network(
document['thumbnail_img'],
fit : BoxFit.cover)
);
}
Stream<QuerySnapshot> _commentStream() {
widget.stopTrigger +=1;
if(widget.stopTrigger == 2 ){
return widget.unchanging;
}
}
}
I see you're attempting to achieve a behavior where a scroll on the GridView results in a scroll on the whole screen.
As the ContentsCarousel() and _gridBuilder() are in a Column, this behaviour cannot be achieved.
What I would suggest is wrapping your Column with a SingleChildScrollView widget.
I have class :
class FavoritesState extends State<Favorites> {
var configOk = isConfigLoaded;
#override
Widget build(BuildContext context) {
if (configOk) {
return Padding(
padding: EdgeInsets.only(top: 10),
child: ListView.builder(
itemCount: stationList.length,
itemBuilder: (context, position) {
return StationCard(stationList[position]);
},
),
);
} else {
return LoadingScreen();
}
}
void callback() {
setState(() {
this.configOk = true;
});
}
}
And I have method:
Future<http.Response> getConfig() async {
//Load Config
isConfigLoaded = true;
}
I need at the end this method call setstate in FavoritesState and upload information there. How I can do this? Thank you.
You can also use a FutureBuilder if you're only updating the state of the widget once. The FutureBuilder will force your widget to rebuild as soon as your getConfig() call has completed.
This would simplify things:
class Favorites extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getConfig(),
builder: (context, snapshot) {
if (snapshot.data == null) {
// display the loading screen as long as your getConfig() call hasn't finished
return LoadingScreen();
}
return Padding(
padding: EdgeInsets.only(top: 10),
child: ListView.builder(
itemCount: stationList.length,
itemBuilder: (context, position) {
return StationCard(stationList[position]);
},
),
);
},
);
}
}
And then tweak your call to something like this:
Future<http.Response> getConfig() async {
//Load Config
// return your result
return result;
}
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)],
);
}