Can I run "image.dart" functions in background - image

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.

Related

How can I improve the performance of Wrap Widget with multiple child?

When I move from tabs in the UI it's takes too long to load the chips under the tabs.
I've used the AutomaticKeepAliveClientMixin<> but it broke my Scrollbar and still takes time to load large Chips children from the Wrap Widget. What I would need is a Wrap.builder() but doesn't exist. Is there something I can do to optimize the code to load smoothly?
I have an interactive sample code of the current problem:
https://dartpad.dev/009e9ccae07175074cb77d7792c3692b
Try moving the tabs left to right in the sample to see the performance issue.
If it can't be fixed, I wonder if I can detect when the widget is rendering to show a circular progress indicator while switching tabs.
Any Ideas?
Thanks
Technically the idea of Wrap.builder wouldn't be a solution here because the issue persists even when all the items are in the displayed (nothing to display on demand)
You can make the swiping experience better by delaying the rendering of the tab content until after the swiping animation is done
class _ChipsContentState extends State<ChipsContent> {
bool visible = false;
final List<String> _filters = <String>[];
#override
void initState() {
super.initState();
Future.delayed(kTabScrollDuration).then((value) {
if (mounted) {
setState(() => visible = true);
}
});
}
#override
Widget build(BuildContext context) {
if (!visible) {
return Center(child: CircularProgressIndicator());
}
return ListView(
children: [
if (visible)
Wrap(
spacing: 8.0,
children: widget.chipData.map((chipName) => ChipFilter(chipName: chipName, filter: _filters)).toList(),
),
],
);
}
}
A Wrap.builder() is indeed missing from Flutter. A GitHub issue was opened about this but then closed without a satisfying solution.
I can think of a few workarounds, like using a swiper/carousel, a GridView.builder(), or a List.builder() with Rows inside it. Of course, they're far from being as convenient as a Wrap in this case.
Try Changing your filterChips and "build" methods as follows,
List<Widget> get filterChips {
List<Widget> widgets = [];
for (final chipName in widget.chipData) {
widgets.add(ChipFilter(chipName: chipName, filter: _filters));
}
return widgets;
}
#override
Widget build(BuildContext context) {
// super.build(context);
return Scrollbar(
child: SingleChildScrollView(
child: Wrap(//IS THERE A Wrap.builder()? OR ALTERNATIVE TO DESIRED LAYOUT?
spacing: 8.0,
children: filterChips,
),),
);
}

Image.network loadingBuilder's loading progress event is null

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.

How to create a picture area in Flutter?

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());
}
},
),
);
}

Textfield controller method makes performance slow down

I'm filtering my list of my GridView.builder like this:
onSearchTextChanged(String text) async {
if (text.isEmpty) {
_searchList = _productList.toList();
setState(() {
});
return;
}
_searchList = _productList.where((product) => product.name.toLowerCase().contains(text.toLowerCase()) ||
product.type.toLowerCase().contains(text.toLowerCase())).toList();
setState(() {});
}
but when im typing on the textfield the performance just go down, exactly to 2.5 fps sometimes when im deleting the text or typing to fast.
This is my Gridview builder
GridView.builder(
primary: false,
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: itemWidth / itemHeight,
),
itemCount: _searchList.length,
itemBuilder: (BuildContext context, int index) {
return _searchList[index];
}));
Always set state variables inside the setState() call.
Make your onSearchTextChanged() function save the search text and start a Future.delayed() that performs the search and update after a short delay. See: https://stackoverflow.com/a/54686588/1736338 .
You can use a Suffix Tree data structure to speed up the search operation. Unfortunately, I couldn't find one written in Dartlang. You could code one. The trie package is unsuitable since it supports only Set functionality, not Map functionality.

Flutter. High GPU load with list of high resolution images

I've started to learn Flutter Framework and high GPU load when I'm trying to render a list of images which come from the network and have high resolution.
I've created test project on github for demo https://github.com/troublediehard/flutter_imageList_high_gpu_load
Am I doing anything wrong? Is there way to optimise images before render?
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Image loading test'),
),
body: buildLists(),
),
);
}
Widget buildLists() {
final imageUrls1 = [
'https://via.placeholder.com/2000',
'https://via.placeholder.com/2001',
'https://via.placeholder.com/2002',
];
final imageUrls2 = [
'https://via.placeholder.com/2003',
'https://via.placeholder.com/2004',
'https://via.placeholder.com/2005',
];
return Column(
children: <Widget>[
buildList(imageUrls1),
buildList(imageUrls2),
],
);
}
buildList(List<String> urls) {
return Container(
height: 150,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: urls.length,
itemBuilder: (context, index) {
return Image.network(
urls[index],
height: 130,
width: 130,
);
}),
);
}
}
There is an open issue in Flutter that characterizes this problem (see this github issue, in particular the links to three issues created from it) as of mid-January 2019. Hopefully it will be resolved sooner rather than later, as more people have been running into it, but I get the impression there's quite a lot of internal discussion around how to properly handle high-res images as it needs to be a flexible and robust solution that works for many use-cases. I believe the high GPU load you're seeing is probably the result of loading up the entire set of images into the GPUs texture buffer, although as a comment states you should expect much difference performance on a real device and in release mode.
The current advice from the flutter devs is to use lower resolution images (i.e. set up your server to serve thumbnails). While this is a slightly annoying response, it is actually something you should think about doing anyways as when you download high-res photos you're 1) using bandwidth, 2) using server memory/processing power, and 3) using additional phone memory (even if images are resized before showing this would still be true). Serving thumbnails should result in a better user experience, even if it means that the both the low-res and high res photos are downloaded separately (when the user taps on the thumbnail for example).

Resources