Ok, so I want to get items from LinearView and get their visibility, so I can free memory on the hidden items.
Is it possible to do that?
Do you have any other ideas?
So basically, I want to show a stream of images being dynamically downloaded from URLs, I want them to be in a ListView, so the person can scroll through them, but if we just load the ListView with a normal builder like this,
ListView lvb = new ListView.builder(
cacheExtent: 1.0,
itemBuilder: (BuildContext context, int index) {
return new ImageView(MyApp.imageLinks.values.elementAt(index));
},
);
it'd download all items at once and the device would run out of memory. So I was thinking that I'd just free the memory taken up by image and then allow user to load more images without having to worry about memory.
Also, I'd like to only have 5 images loaded at a time, so 1 would be displayed, 2 above and 2 below for faster and more fluid scrolling.
My questions:
- Is there a way to determine if item is drawn(in view) or not?
- Can I limit how many ImageViews I want to have in LinearView, but keeping the scrolling ability.
EDIT: ImageView class as promised
class ImageView extends StatelessWidget {
String url;
ImageView([String url]) {
if (url != null) {
this.url = url;
}
print("New image");
}
@override
Widget build(BuildContext context) {
FractionallySizedBox fsb;
if (url != null) {
Image el = Image.network(
url,
scale: 0.1,
);
fsb = new FractionallySizedBox(
widthFactor: 1.0,
child: el,
);
print("New image created");
return fsb;
}
}
As soon as the ListView starts to list items, it starts spamming "New image created" into the console.
I'm going to answer this with a non-answer. It is theoretically possible to determine whether the item is drawn or not using Slivers and a CustomScrollView or possibly by using custom RenderObjects, but I don't think that's what you actually want.
Unless you're doing something weird with your images EDIT: SEE END, using the ListView.builder is designed to protect against the exact scenario you're describing.
From the first line of the documentation for ListView.builder (emphasis mine):
Creates a scrollable, linear array of widgets that are created on demand.
The idea of using ListView.builder as opposed to new ListView([items]) is that it should automatically handle exactly what you're talking about - creating only the currently visible elements.
If you've implemented this and you are actually running out of memory on a real device, then that would be a good case for raising a bug with the Flutter people =D.
Also, setting cacheExtent
to 1.0 will actually be worse for performance. From the cacheExtent docs:
The viewport has an area before and after the visible area to cache
items that are about to become visible when the user scrolls.
Items that fall in this cache area are laid out even though they are
not (yet) visible on screen. The cacheExtent describes how many pixels
the cache area extends before the leading edge and after the trailing
edge of the viewport.
By setting that to 1.0, you're making it so that as soon as a picture is out of the viewport, it's being deallocated. And that the next images don't load until they're pretty much in view. So next time you scroll (back or forward), the (next or last) image will have to load as it's coming into view.
If you set this higher (or leave it to the default which is currently 250.0) you can make it so that the next images will load ahead of time, exactly as you want.
EDIT:
So there's an additional factor at work here. Thanks for posting your ImageView code.
The issue is that you're using a FractionallySizedBox with a widthFactor but no HeightFactor. You're setting width to 1.0, but no height (which wouldn't work anyways), so it's taking up zero space and therefore keeps trying to load images forever.
Your best bet to specify a height using a SizedBox, ConstrainedBox, or AspectRatio for your images and then either contain
or cover
with the fit
option for Image.network.
Here's a fun example - infinitely loading dogs =)
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: Text("Title"),
),
body: ListView.builder(
cacheExtent: 1000.0,
itemBuilder: (BuildContext context, int index) {
return new ImageView(
num: index,
url: "https://loremflickr.com/640/480/dog?random=$index",
);
},
),
),
);
}
}
class ImageView extends StatelessWidget {
final int num;
final String url;
ImageView({@required this.num, @required this.url});
@override
Widget build(BuildContext context) {
print("Building image number $num");
return new AspectRatio(
aspectRatio: 3.0 / 2.0,
child: Image.network(
url,
fit: BoxFit.cover,
),
);
}
}