Reputation: 79
I'm making a quiz-type app for Android & iOS, which is art oriented. This means that image resolution is very important. Client provided ~30 images, 25mb each on average. I can probably get away with compressing them to about 4mb each. After that, loss becomes noticeable.
My main problem is that the images (assets) seem to load too slow. Every time the screen changes to the next one, the image needs > 300 ms to show.
Precaching the images doesn't seem to help.
Widget build(BuildContext context) {
precacheImage(Image.asset(data["painting-location"]).image, context);
return Scaffold(
appBar: null,
backgroundColor: Color(bgColor),
body: ...
PinchZoomImage(image: Image.asset(data["painting-location"]),),
...
);
}
I have also tried precaching in didChangeDependencies()
but i get the same result.
Are the images just too large, or am I missing something as far as precaching goes?
UPDATE I got away with compressing the images to less than 500 kb each and loading times are now acceptable.
Upvotes: 0
Views: 247
Reputation: 83
There's a couple of things that would not work with the approach you wrote.
precacheImage is an asynchronous function, and it completes when then image is ready for use. In your example, you are precaching in the build function which will not complete in time. You should use a FutureBuilder for this, or preache the image in didChangeDependancies and call setState when the image is ready
If you are precaching a large image, it may be evicted before you use it. You can also "pin" an image in memory to stop it from being evicted form the cache until you are done with it.
ex: FutureBuilder:
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: precacheImage(yourImageProvider, context),
builder: (context, snapshot) {
return snapshot.hasData
? const Image(
image: yourImageProvider,
)
: const SizedBox();
},
);
}
ex: precache image in didChangeDependancies (if you try this in initState, you will likely run into issues):
bool imageIsReady = false;
@override
void didChangeDependencies() {
precacheMyImage();
super.didChangeDependencies();
}
Future precacheMyImage() async {
await precacheImage(yourImageProvider, context);
setState(() => imageIsReady = true);
}
@override
Widget build(BuildContext context) {
return imageIsReady
? const Image(
image: yourImageProvider,
)
: const SizedBox();
}
ex: precache image in didChangeDependancies, and also pin it in memory to be sure it isn't evicted prematurely. Follow the previous example, by make these changes to precacheMyImage():
Future precacheMyImage() async {
await precacheImage(yourImageProvider, context);
final preachedImageStream = yourImageProvider.resolve(ImageConfiguration.empty);
preachedImageStream.addListener(myImageStreamListener);
setState(() => imageIsReady = true);
}
Just don't forget to remove your listener when you are done with the image, otherwise it will stay in the cache and waste memory:
preachedImageStream.removeListener(myImageStreamListener);
Additionally, it's also possible your image will fail to precache if your image cache is too small for your application needs. The image cache has two parameters that you can modify to allow for larger or more images, maximumSizeBytes, and maximumSize. If you want to override these parameters, create a custom image cache like so:
class CustomImageCache extends WidgetsFlutterBinding {
@override
ImageCache createImageCache() {
ImageCache imageCache = super.createImageCache();
imageCache.maximumSizeBytes = 200 << 20; // ~200 MB
imageCache.maximumSize = 1500; // 1500 images at once
return imageCache;
}
}
And initialize it at app startup like so:
void main(){
CustomImageCache();
runApp(MyApp())
}
Upvotes: 0