I'm trying to use the Image.network constructor's loadingBuilder argument.
While the image loads, I want to show a CircularProgressIndicator with its value set to the ratio of downloaded divided by the expected file size. I expected I could do this using the ImageChunkEvent parameter's cumulativeBytesLoaded, expectedTotalBytes.
I found a sample in the documentation:
The following sample uses loadingBuilder to show a CircularProgressIndicator while an image loads over the network.
Image.network(
'https://example.com/image.jpg',
loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent loadingProgress) {
if (loadingProgress == null)
return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes
: null,
),
);
},
),
But the issue is that the loadingProgress is always null, so the whole loadingBuilder charade I'm doing is for nothing.
Why is ImageChunkEvent loadingProgress not passed to the loadingBuilder function with real values?
I had a similar issue. I used server-side image cropper, and because of the large photos it took some time for server to process an image and begin sending result. And before Image widget receives any response from a server, loadingProgress is null. And if your image is very small (like in my case), you can see a 1-2 seconds delay and then just an image. Without process indicator.
I think loadingProgress should be non-null from the beginning of the request. Otherwise it makes it required to add an additional check.
i found the solution after 3 years
loadingBuilder:(context, child, loadingProgress) {
if (loadingProgress != null) {
print(loadingProgress.cumulativeBytesLoaded);
return CircularProgressIndicator(
value: loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!);
}
return child;
}
Remember to put or gif in assets folder and cite it in pubspec.yaml
FadeInImage.assetNetwork(
placeholder: 'assets/loading.gif',
image: 'https://backend.example.com/image.png',
);
For more information:
https://api.flutter.dev/flutter/widgets/FadeInImage-class.html
Perhaps the image has a small size so it instantly loads, try testing with a different larger network image.
Related
I want to display a loading widget while compressing the image selected.
I am using the "image.dart" package to select an image, using ImagePicker.GetImage().
Then I want to display a loading widget while the image is being compressed, and when it's finished display the compressed image.
My code fundamentally works, my loading widget is cool (thanks spinkit), I can select any image, compress it and display the new one, but there is a small freeze (2-3s) while the image is being compressed.
So I'm trying to put a loading widget to not make the user panic and tap 10 000 times to select the image wanted, but I'm not completely comfortable with asynchronous code yet.
Here's my code :
import 'package:image/image.dart' as Img;
#override
Widget build(BuildContext context) {
if (loading == true) {
return LoadingScreen();
} else {
return Scaffold(
backgroundColor: Colors.brown[300],
body: Column(
children: [
SizedBox(
height: 50,
),
RaisedButton(
child: Text('Select an image'),
onPressed: () async {
await getImage();
// You can see here that I'm trying to trigger the loading widget
setState(() {
loading = true;
});
compressImage();
// And I want to disable the loading widget here, after the compression
setState(() {
loading = false;
});
},
),
SizedBox(
height: 10,
),
pickedImage != null ? printSelectedImage() : Center(child: Text('Please select an image')),
],
),
);
}
}
getImage() async {
pickedFile = await picker.getImage(source: ImageSource.gallery);
}
compressImage() async {
Img.Image selectedImage = Img.decodeJpg(File(pickedFile.path).readAsBytesSync());
if (selectedImage.width > selectedImage.height) {
Img.Image compressedImage = Img.copyResize(selectedImage, width: 500);
File(pickedFile.path).writeAsBytesSync(Img.encodePng(compressedImage));
} else {
Img.Image compressedImage = Img.copyResize(selectedImage, height: 500);
File(pickedFile.path).writeAsBytesSync(Img.encodePng(compressedImage));
}
if (pickedFile.path != null) {
setState(() {
pickedImage = pickedFile.path;
});
}
}
But the result is the same, the screen is still stuck in the file selector while compressing, and then directly display the compressed image.
I started learning dart/Flutter 2 weeks ago, so am I missing some fundamental principles of the dart language ? Am I not seeing something obvious ?
Thanks for reading
Making something async doesn't move it into some magical background thread. Dart uses isolates which are basically an execution context in which dart code can run. Flutter has a single isolate which runs your app, if you do too much work in it, your app gets slow or skips frames.
You can create other isolates to do calculations and offload work and communicate between isolates by passing messages with primitive content back and forth. The drawback in Flutter is, that other isolates don't have access to Flutter plugins thus a lot of things don't work there.
There are ways to work around this but they are pretty involved.
The easiest way to start an isolate is the compute(callback, message) function.
Spawn an isolate, run callback on that isolate, passing it message, and
(eventually) return the value returned by callback.
I am not sure if the image/image.dart library uses a plugin or some other features that are not available (dart:ui for example), you can try offloading your image compression into the callback. If it fails with a MissingPluginException or something similar, then it won't work.
Remember, you can only pass primitive messages so you can not path the pickedFile, only the pass as argument.
For your problem however there may be an easier solution. The image picker accepts parameters for max width/height and quality. If you need the image to be encoded as PNG however then you need to create a complex solution yourself.
I am getting image loaded to the CircularProfileAvatar by both means of camera and gallery successfully. But Image rendered on view is not in circular as shown in the screenshot. how should I make it sure that the image rendered to the child of CircularProfileAvatar is circular Image. In the image, you can see the orange portion which should not be visible instead the entire area inside the brown circle should be covered with the image either selected from the gallery or picture from the camera.
I am using building this new app in flutter.
This Dependency is used CircularProfileAvatar
Image imagepic() {
if (_imageFile == null) {
return Image(image: AssetImage('assets/images/logo.png'));
} else {
return Image.file(_imageFile);
}
}
final profilePic = CircularProfileAvatar(
null,
child:
imagepic(), //sets image path, it should be a URL string. default value is empty string, if path is empty it will display only initials
radius: 50,);
Add BoxFit.cover which cover fill the circular area
CircularProfileAvatar(
null,
child: Image(
image: AssetImage('assets/images/logo.png'),
fit: BoxFit.cover,
),
),
https://github.com/fluttercandies/extended_image
https://github.com/fluttercandies/extended_image/tree/master/example
https://github.com/fluttercandies/extended_image/blob/master/example/lib/pages/zoom_image_demo.dart
fluttercandies extended_image. How do I zoom in on an image with double tap of 1 finger instead of needing 2? How do I get a sliding image to navigator pop back when it slides off the page?
Trying to implement a zoom/pan image with double tap by 1 finger instead of the need for 2 to expand zoom an image. Have two issues with this code and was wondering if anyone has any ideas. The class is very simple with just 2 strings: image & title passed into it.
1, I need the image to expand on double tap. I would like the user to have to power to expand the image with one finger and not two. Think I need to put this code near the very end.
The good thing is that once it is expanded the double tap works to reduce the image size. How do I get it to do the opposite when it is at normal size?
2, the sliding of the image off the page results in a black screen. Thankfully, this doesn’t freeze or crash the app but it leaves the user with a blank screen and the need to press the system back button. I would like the slide to result in a navigator pop back to the original screen.
Firstly, here’s a sample code of how I’m passing an image and a title into expandimage.dart.
FlatButton(
child: Image.asset(_kAsset5),
onPressed: () async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ExpandImage(
image: _kAsset5,
title: "\'go help\' 1",
)),
);
},
),
Here’s the code that I’m using for this ‘expandimage.dart’ and a lot of it is based on the pan/zoom example from flutter candies / extended image example.
import 'package:flutter/material.dart';
import 'package:extended_image/extended_image.dart';
class ExpandImage extends StatelessWidget {
final String image, title;
ExpandImage({this.image, this.title});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.red[900],
appBar: AppBar(
backgroundColor: Colors.red[900],
leading: IconButton(
icon: Icon(Icons.close),
onPressed: Navigator.of(context).pop,
),
title: Text(
title,
style: TextStyle(
color: Colors.yellow,
inherit: true,
fontWeight: FontWeight.w300,
fontStyle: FontStyle.italic,
letterSpacing: 2.0, //1.2
),
),
centerTitle: true,
),
body: SizedBox.expand(
// child: Hero(
// tag: heroTag,
child: ExtendedImageSlidePage(
slideAxis: SlideAxis.both,
slideType: SlideType.onlyImage,
child: ExtendedImage(
//disable to stop image sliding off page && entering dead end without back button.
//setting to false means it won't slide at all.
enableSlideOutPage: true,
mode: ExtendedImageMode.gesture,
initGestureConfigHandler: (state) => GestureConfig(
minScale: 1.0,
animationMinScale: 0.8,
maxScale: 3.0,
animationMaxScale: 3.5,
speed: 1.0,
inertialSpeed: 100.0,
initialScale: 1.0,
inPageView: false,
),
// onDoubleTap: ? zoom in on image
fit: BoxFit.scaleDown,
image: AssetImage(
image,
),
),
),
),
),
);
}
}
Here is a sample image passed in. the page turns red when sliding the image around and then it goes black as the image slides off the page.
Hope it will help, if have any doubt ask in the comments.
class _DetailState extends State<Detail> with TickerProviderStateMixin{
#override
Widget build(BuildContext context) {
AnimationController _animationController = AnimationController(duration: Duration(milliseconds: 200),vsync: this);
Function() animationListener = () {};
Animation? _animation;
return Scaffold(
body: Container(
height: MediaQuery.of(context).size.height,
child: ExtendedImage.network(
widget.wallpaper.path,
fit: BoxFit.contain,
mode: ExtendedImageMode.gesture,
initGestureConfigHandler: (state) {
return GestureConfig(
minScale: 0.9,
animationMinScale: 0.7,
maxScale: 3.0,
animationMaxScale: 3.5,
speed: 1.0,
inertialSpeed: 100.0,
initialScale: 1.0,
inPageView: false,
initialAlignment: InitialAlignment.center,
);
},
onDoubleTap: (ExtendedImageGestureState state) {
///you can use define pointerDownPosition as you can,
///default value is double tap pointer down postion.
var pointerDownPosition = state.pointerDownPosition;
double begin = state.gestureDetails!.totalScale!;
double end;
_animation?.removeListener(animationListener);
_animationController.stop();
_animationController.reset();
if (begin == 1) {
end = 1.5;
} else {
end = 1;
}
animationListener = () {
//print(_animation.value);
state.handleDoubleTap(
scale: _animation!.value,
doubleTapPosition: pointerDownPosition);
};
_animation = _animationController
.drive(Tween<double>(begin: begin, end: end));
_animation!.addListener(animationListener);
_animationController.forward();
},
),
),
}
}
If it helps, mark it as right.
In their example https://flutter.dev/docs/cookbook/plugins/picture-using-camera we can take pictures using the camera plugin.
I modified the code and move into a separate widget. My main goal is to try to implement a picture area (like QRCode style) and take the picture and if necessary tell the user to corp the image or my be app will do it automatically. There is a barcode_scan plugin. It shows an area to scan the barcode. I like to implement that part to take pictures of an item.
https://codecanyon.net/item/qr-code-barcode-scanner-and-generator-for-ios-swift-with-admob/screenshots/20280521?index=1 screenshot2 has a 4 half square bracket on 4 edges. I like to make it similar but capture images only.
I am new to this plugin and corp idea. How can I create a picture area in Flutter so the user can center the item in that area and take a picture.?
Widget _buildPictureArea(BuildContext context) {
return new Container(
width: 200,
height: 200,
child: FutureBuilder<void>(
future: _initializeControllerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// If the Future is complete, display the preview
return CameraPreview(_controller);
} else {
// Otherwise, display a loading indicator
return Center(child: CircularProgressIndicator());
}
},
),
);
}
I am trying to render a list of ~250 images in 3 columns using FlatList in RN0.43, and I change the width of the images in the onLayout function of the FlatList to fit the width of screen.
The initial performance is ok, but after some scrolling up/down, sometimes it takes a second or 2 until images are shown.
it is even worse if I change to screen orientation, it takes 2~3 seconds to get screen updated.
a few findings:
after screen rotation, it takes a second or 2 until FlatList.onLayout is called
after FlatList.onLayout and update of image width, each image (about half of the list, ~150 images; while only ~15 are shown) is rendered 2~4 times, while render() is only called once.
question:
how can I modify the code to improve the performance?
in the getItemLayout() of a multicolumn list, should the offset be something like (itemHeight + separatorHeight) * (index%numColumns)?
Thanks.
tested on: GalaxySII (4.1.2) and Android SDK emulator (7.1.1)
var data = [
require('./res/img/Search.png'),
require('./res/img/test - Copy.png'),
// ~250 items
...];
class app extends React.Component {
renderItem (info, width) {
console.log('renderItem', info.index);
if(width !== this.width) {
this.imageStyle = {width: width-MarginHorizontal , height: width-MarginHorizontal, resizeMode: 'contain'};
}
return (
<Image
source = {info.item}
key = {info.index}
style={this.imageStyle}/>
);
}
render() {
console.log('Test.render');
return (
<View style={{
flex: 1,
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
backgroundColor: '#F5FCFF'
}}>
<GridList
numColumns={3}
columnWrapperStyle={{ alignItems: 'center', marginVertical: 5, justifyContent: 'space-around'}}
data={data}
renderItem={this.renderItem}
/>
</View>
);
}
}
class GridList extends Component {
onLayout(event) {
console.log('GridList.onLayout()');
let newColumnWidth = event.nativeEvent.layout.width/ this.numColumns;
this.layout = Object.assign({},event.nativeEvent.layout);
if( undefined === this.columnWidth || Math.abs(newColumnWidth - this.columnWidth) > WidthTolerance ) {
this.columnWidth = newColumnWidth;
if(this.isComponentMounted) {
this.setState({renderCount: this.state.renderCount+1});
} else {
this.state.renderCount +=1;
}
}
}
render() {
console.log('GridList.render()');
return (
<FlatList
{...(this.modifiedProps)}
renderItem={(info) => { return this.props.renderItem(info, this.columnWidth); }}>
{this.props.children}
</FlatList>
);
}
}
Disclaimer: I know that the question is old, but here is my response anyways.
My app has a hand full of lists with 500+ items. So, we got to a point where the app was crashing on popular not-bad phones. Then I've made this extensive research about performance on FlatLists.
The FlatList component was presented as a alternative for the old ScrollView. The problem is that ScrollViews render all your list at once so they perform visually better, but there is a trade off in memory consumption, that leads to app crashes.
So Flat Lists are a necessary evil. They essentially only render items that are visible, which is a huge gain on memory consumption, but a pain for visual performance, specially for heavy/complex items, that happens to be your case with those responsive images.
How to workaround?
There are a lot of strategies that you can implement to mitigate your problem.
Use cached and performatic images, such as react-native-fast-image. Every operation that you can remove or abbreviate for freeing the Javascript thread: do it (every image is a new Image(), so, if they are cached, you have your loaded hook called sooner)
Your list item component is a read-only component, which is supposed to be 'dumb'. Implement a shouldComponentUpdate() { return false } or a more solid update control method as needed. This is HUGE perf boost.
Remove console.logs anywhere near your list. They slow the Javascript thread really bad.
Build your app for production and test it. It becomes almost always twice or three times faster. Dev env is slow because of debugging.
Give this article a good read for more strategies.
Conclusion
FlatList IS a slow component. This is a known, open and well documented issue. Do what you can to improve it, and let's hope future releases may fix this.
Yes I was having the same problem - multiple images & videos in list in react native So I removed Flatlist instead of this I preferred to use ListView to render fast & to fix touchability issue on list item but Dont forget to set PureComponent to the list item
You're re-creating lots of styling objects for each row of the list individually. This puts a lot of traffic on the JS->Native bridge. Try using stylesheet instead of passing the styles inline.
I strongly recommend everybody to read the article in the link attached below to optimize your flatlist.
https://reactnative.dev/docs/optimizing-flatlist-configuration
Try to set unique key for each item using keyExtractor
For Example:
render() {
return (
<List>
<FlatList
...
keyExtractor={item => item.email}
/>
</List>
);
}