I define a variable ui.Image? previewImage; (ui refers to dart:ui).
I then start an image stream from the camera that performs YOLOX classifications and pairs them with an image, and updates the variable previewImage with the classification results with a function onDetected():
onDetected(
List<YoloxResults> results,
ui.Image image,
) {
setState(() {
yoloxResults = results;
previewImage = image;
});
}
The yoloxResults and previewImage are then displayed with a widget:
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (previewImage == null) {
return const Center(
child: CircularProgressIndicator(),
);
}
final cameraWidget = FittedBox(
child: SizedBox(
width: previewImage!.width.toDouble(),
height: previewImage!.height.toDouble(),
child: CustomPaint(
painter: YoloxResultsPainter(
image: previewImage!,
results: results,
labels: labels,
),
),
),
);
return cameraWidget;
},
);
}
I have a page where I switch between the above widget and another body widget via a Ternary Operator...
The first time I load the cameraWidget, previewImage is null and displays the CircularProgressIndicator() before the previewImage is set via the onDetected() function (which is what I want).
The second time I open the cameraWidget previewImage is already defined and an old image is displayed before the onDetected() function updates... I have tried setState previewImage = null, which calls the CircleProgressIndicator() but then displays an old previewImage first, and before updating...
My Question: How can I reset the previewImage variable to being uninitialized?
How can I remove the data assigned to previewImage?
Related
Background
I have an image widget that loads an image remotely from the network.
Before/while the image is being retrieved I have a placeholder with a fixed height of 200.0 in its place.
Once the image is loaded, I update the Widget to display the image
The catch is I want to update the height of the container to the new height of the image. This is done in synchrony with changes to a scroll position so I can't just let the image container auto-adjust, I need the specific height of the newly downloaded image just before its displayed on screen.
My setup
I have a FutureBuilder as follows:
FutureBuilder(
future: getData(image:image, width:widget.width, height:widget.height),
builder: (ctx, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// If we got an error
if (snapshot.hasError) {
... placeholder
// if we got our data
} else if (snapshot.hasData) {
// Extracting data from snapshot object
final thisimage = snapshot.data as FutureImageTransporter;
final imageheight = thisimage.height
/// Do some stuff with image height here
return Container(
width: widget.width,
height:thisimage.height, ///This needs to have the height value retrieved when the image is downloaded
child: thisimage.imagewidget
);
}
else {
... placeholder
}
}
else
{
... placeholder
}
},
),
The future data retrieves data from getData, which is a sort of pass through to get the image from MyCustomImage which returns an image widget from the extended image package which is as follows:
Future<FutureImageTransporter> getData({image, width, height}) {
return Future.delayed(Duration(seconds: 6), () async {
var finalImageWidget = MyCustomImage(
input: image,
width:width,
height:height,
);
return finalImageWidget;
});
}
I'm using my own class called FutureImageTransporter to house the data, which consists of the actual image Widget and the height data (double).
class FutureImageTransporter{
late Widget imagewidget;
late double height;
}
The actual image widget is constructed as follows with MyCustomImage, and I also want to return back the height of the image, both pieces packaged in a FutureImageTransporter
FutureImageTransporter MyCustomImage ({
required input,
width,
height,
}) {
double _finalHeight = 0.0;
Widget finalImageWidget =
ExtendedImage.network(
input,
key: ValueKey(input),
width: width
height: height
enableMemoryCache: false,
handleLoadingProgress: true,
loadStateChanged: (ExtendedImageState state) {
switch (state.extendedImageLoadState) {
case LoadState.loading: return Container();
case LoadState.completed:
_finalHeight = state.extendedImageInfo?.image.height.toDouble() ?? 0.0;
debugPrint(_finalHeight.toString()); /// This is properly outputting the final height of the image
return ExtendedRawImage(
image: state.extendedImageInfo?.image,
fit: BoxFit.cover,
width: width
height: height
);
case LoadState.failed: return Container();
}
},
);
FutureImageTransporter tempvar = FutureImageTransporter();
tempvar.imagewidget = finalImageWidget;
tempvar.height = _finalHeight; ///This is not updating to the _finalHeight which updates above
return tempvar;
}
The Problem/Question
With the above setup, I'm using a FutureBuilder to successfully retrieve an image and then update the widget in my widget tree with the new image widget which is an ExtendedImage.network widget, which is something analogous to Image.Network().
Within ExtendedImage.network, the height of the image is successfully calculated _finalHeight = state.extendedImageInfo?.image.height.toDouble() ?? 0.0;.
However, which the widget does properly debugPrint the value of the image height, it never actually properly returns the value to my FutureBuilder. The FutureBuilder only sees the originally initialized value of 0.0 double _finalHeight = 0.0; (or whatever default value I stick in there).
This problem seems to have to do with how Im retrieving data from a future and setting it up to pass between the widgets, but I can't understand where I went wrong, or if this is simply impossible? This ends up being a TON of code, but what I originally thought would be a simple task. Any ideas?
I want to show different images for different outputs of JSONdecode. I get var between 01d and 50d. For example, when it gives out 04d, I want to show Image 'assets/night.png' and for 05d, I want to show Image 'assets/afternoon.png' and more. I am a complete beginner with flutter, this is what I thought about:
var current_icon;
Future getWeather () async {
http.Response response = await http.get(Uri.parse("https://api.openweathermap.org/data/2.5/weather?q=city&appid=****"));
var results = jsonDecode(response.body);
setState(() {
this.current_icon = results['weather'][0]['icon'];
});
}
#override
void initState () {
super.initState();
this.getWeather();
}
and then put it in my container here:
new Container(
height: double.infinity,
width: double.infinity,
child: new Image(
image: (What should I do here?),
fit: BoxFit.cover,
),
),
As per your query assuming all images comes from assets. You can make cases as per your requirement like this:
String getImageData(String id) {
String value = '';
switch (id) {
case '01d':
value = 'assets/a.png';
break;
case '02d':
value = 'assets/b.png';
break;
default:
value = 'assets/c.png';
}
return value;
}
pass below widget as child of your container widget
Widget imageWidget() {
return Image.asset(
getImageData('01d'), // pass your value here
fit: BoxFit.cover,
);
}
Due to CachedNetworkImage not working on flutter web, upon porting, I have tried to use this, but my question is do we really need this? Or we just use Image. Network and the browser and service worker will handle the cache part (which is then set by server's response header through, for example, cache-control= "max-age=43200, public"
This is used on the food delivery project I am working on, https://www.santaiyamcha.com
Below are the classes I use to replace CachedNetworkImage which doesn't seem to work well.
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:http_extensions_cache/http_extensions_cache.dart';
import 'package:http_extensions/http_extensions.dart';
/// Builds a widget when the connectionState is none and waiting
typedef LoadingBuilder = Widget Function(BuildContext context);
/// Builds a if some error occurs
typedef ErrorBuilder = Widget Function(BuildContext context, Object error);
class MeetNetworkImage extends StatelessWidget {
/// Image url that you want to show in your app.
final String imageUrl;
/// When image data loading from the [imageUrl],
/// you can build specific widgets with [loadingBuilder]
final LoadingBuilder loadingBuilder;
/// When some error occurs,
/// you can build specific error widget with [errorBuilder]
final ErrorBuilder errorBuilder;
final double scale;
final double width;
final double height;
final Color color;
final FilterQuality filterQuality;
final BlendMode colorBlendMode;
final BoxFit fit;
final AlignmentGeometry alignment;
final ImageRepeat repeat;
final Rect centerSlice;
final bool matchTextDirection;
/// Whether to continue showing the old image (true), or briefly show nothing
/// (false), when the image provider changes.
final bool gaplessPlayback;
final String semanticLabel;
final bool excludeFromSemantics;
MeetNetworkImage({
#required this.imageUrl,
this.loadingBuilder = null,
this.errorBuilder = null,
this.scale = 1.0,
this.height,
this.width,
this.color = const Color(0xFDFFFF),
this.fit = BoxFit.fill,
this.alignment = Alignment.center,
this.repeat = ImageRepeat.noRepeat,
this.semanticLabel,
this.centerSlice,
this.colorBlendMode,
this.excludeFromSemantics = false,
this.filterQuality = FilterQuality.low,
this.matchTextDirection = false,
this.gaplessPlayback = false,
}) : assert(imageUrl != null),
assert(alignment != null),
assert(repeat != null),
assert(matchTextDirection != null);
Future<Response> getUrlResponse() {
/*
//The caching part I tried, does not seems working
final client = ExtendedClient(
inner: Client(),
extensions: [
CacheExtension(
//logger: Logger("Cache"),
defaultOptions: CacheOptions(
expiry: const Duration(hours: 168),
// The duration after the cached result of the request will be expired.
//forceUpdate: false, // Forces to request a new value, even if an valid cache is available
//forceCache: false, // Forces to return the cached value if available (even if expired).
//ignoreCache: true, //Indicates whether the request should bypass all caching logic
//returnCacheOnError: true, //If [true], on error, if a value is available in the store if is returned as a successful response (even if expired).
keyBuilder: (request) => "${request.method}_${imageUrl.toString()}",
// Builds the unqie key used for indexing a request in cache.
store: MemoryCacheStore(),
// The store used for caching data.
shouldBeSaved: (response) =>
response.statusCode >= 200 && response.statusCode < 300,
),
)
],
);
return client.get(imageUrl);
*/
return get(imageUrl);
}
Widget getLoadingWidget(BuildContext context) {
if (loadingBuilder != null) {
return loadingBuilder(context);
} else
return Container(
height: height, width: width,
child: Center(
child: CircularProgressIndicator()
)
/*Image.asset(
'assets/img/loading4.gif',
height: height,
width: width,
fit: BoxFit.contain,
),*/
);
}
Widget getErrorWidget(BuildContext context, String error) {
if (errorBuilder != null) {
return errorBuilder(context, error);
} else
return Center(child: Icon(Icons.error));
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getUrlResponse(),
builder: (BuildContext context, AsyncSnapshot<Response> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return getLoadingWidget(context);
case ConnectionState.active:
case ConnectionState.done:
if (snapshot.hasError)
return getErrorWidget(context, snapshot.error);
if (!snapshot.hasData)
return getErrorWidget(context, snapshot.error);
//return getLoadingWidget(context);
return Image.memory(
snapshot.data.bodyBytes,
scale: scale,
height: height,
width: width,
color: color,
fit: fit,
alignment: alignment,
repeat: repeat,
centerSlice: centerSlice,
colorBlendMode: colorBlendMode,
excludeFromSemantics: excludeFromSemantics,
filterQuality: filterQuality,
gaplessPlayback: gaplessPlayback,
matchTextDirection: matchTextDirection,
semanticLabel: semanticLabel,
);
}
return Container();
},
);
}
}
What do you suggest?
I am using FadeInImage.memoryNetwork. It is working fine. The browser handles the cache part.
No. This is an issue with Google Chrome which prefers "cache-control" over E-Tags or Last Modified Headers. In my case I am using Firefox which caches on the basis of E-Tags. So here is the thing.
If you are using setState((){}) or the flutter engine calls the setState due to some reason, the images are rebuilt and if the images are not cached, it is again fetched. To prevent it, use the header cache-control: max-age=<any value greater than 0> and it will work fine in Chrome.
Or just build the app using web renderer canvaskit - flutter build web --web-renderer canvaskit.
I have come to this conclusion based on my experience and I could not find it anywhere, Hope it helps :)
Try to update your flutter version if you are using old one, and use from image providers interface.
And the browser will take care of the rest
Example:
Create a container add a decoration in the decoration add an image decoration
Then add from memory or network image.
Minimal Code:
File _file;
Future<void> _pickImage() async {
final image = await ImagePicker.pickImage(source: ImageSource.camera);
if (image != null) {
final file = File("${(await getApplicationDocumentsDirectory()).path}/image.png");
await file.writeAsBytes(await image.readAsBytes());
setState(() => _file = file); // `_file = image` works though
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(child: Icon(Icons.camera_alt), onPressed: _pickImage),
body: _file == null ? Container() : Image.file(_file),
);
}
Watch video
As you can see, once I pick the image, it works, but on picking it second time, it doesn't work and I also don't run into any error. Can anyone please help?
you need 3 things:
first you have to use ImageProvider and its evict() method:
var image = FileImage(File('someImage.jpg'));
then you need Image widget that uses above ImageProvider and also assigns a unique key in order to be "different" each time build() method is called:
child: Image(
image: image,
key: UniqueKey(),
),
and finally after you overwrite someImage.jpg you have to call evict() method:
// part of your _pickImage() method
// here someImage.jpg contains updated content
image.evict();
setState(() {});
UPDATE: actually you dont need var image = FileImage(File('someImage.jpg')); - you can use it directly inside Image widget as image: FileImage(File('someImage.jpg')) and call FileImage(File('someImage.jpg')).evict() after your image is ovewritten
I am using list view + pagination in Flutter to show my response data.
I face the problem when I selected the first list item the details such as id is the other list item id. I have used print() to check the id, it always shows the wrong id.
I want to show the details about my image, but it gives me wrong id. So it will show other image.
How can I solve the problem?
There is no need to define id and title as variables of the State object.
You can pass them as a parameter to the selectedItem method instead, the problem is you always set the id and title to the last item built so it will always navigate with its details instead of the actually selected item.
class _HomePage State extends State<HomePage > {
GlobalKey<PaginatorState> paginatorGlobalKey = GlobalKey();
#override
Widget build(BuildContext context) {
body: return Paginator.listView(
key: paginatorGlobalKey,
pageLoadFuture: sendPagesDataRequest,
pageItemsGetter: listItemsGetterPages,
listItemBuilder: listItemBuilder,
loadingWidgetBuilder: loadingWidgetMaker,
errorWidgetBuilder: errorWidgetMaker,
emptyListWidgetBuilder: emptyListWidgetMaker,
totalItemsGetter: totalPagesGetter,
pageErrorChecker: pageErrorChecker,
scrollPhysics: BouncingScrollPhysics(),
);
}
Future<PagesData> sendPagesDataRequest(int page) async {
String url = Uri.encodeFull("https://API_URL?page=$page");
http.Response response = await http.get(url);
PagesData pagesData = pagesDataFromJson(response.body);
return pagesData;
List<dynamic> listItemsGetterPages(PagesData pagesData) {
List<Project> list = [];
pagesData.data.forEach((value) {
list.add(value);
});
return list;
}
Widget listItemBuilder(dynamic item, int index) {
return InkWell(
onTap: () => selectedItem(item,context), // pass the item iteself in the selectedItem function
child: new CachedNetworkImage(
imageUrl:= item.image,
placeholder: (context, url) => new CircularProgressIndicator(),
errorWidget: (context, url, error) => new Icon(Icons.error),
fit:BoxFit.fill,
),
);
}
Widget loadingWidgetMaker() {
return Container(
alignment: Alignment.center,
height: 160.0,
child: CircularProgressIndicator(),
);
}
void selectedItem(dynamic item,BuildContext context) { // add a parameter for item
Navigator.of(context).pushNamed(
DetailsPage.routeName,
arguments: {
'id': item.id, // Move the item.id here
'title': item.title // Move the item.title here
});
}
}