Reputation: 1
I am developing an application in Flutter where I need to implement an image selection function like in instagram. But there is an issue, my app UI is freezing when trying to get and compress files from user phone gallery.
This is my first experience with flutter isolates, but as far as i know it should work without freezes.
Here is an image for a better understanding of what i want to do.
This is a function that calls getFiles function in isolation. Here i get paths of user phone gallery files and pass them to another function in order to compress and get files for rendering.
Future fetchImages({ bool fetchMore = false, bool force = false }) async {
if (!fetchMore) {
setState(() => fetched = false);
}
if (force) {
assetsCount = await assetPathEntity!.assetCountAsync;
page = 0;
files.clear();
}
if (assetsCount == 0 || page >= (assetsCount / pageSize)) {
return setState(() => fetched = true);
}
final assetEntities = await assetPathEntity!.getAssetListPaged(page: page++, size: pageSize);
lastCompletedIndex = files.length;
final receivePort = ReceivePort();
final completer = Completer();
getFiles(receivePort.sendPort, assetEntities);
try {
receivePort.listen((filesPaths) {
for (final filePath in filesPaths) {
files.add({
"path": filePath,
"compressedFile": null,
});
}
if (scaledFile == null && files.isNotEmpty) {
scaledFile = File(files.first["path"]);
}
compressAlbumImages();
completer.complete();
setState(() => fetched = true);
}).onError((_) {
compressAlbumImages();
completer.complete();
setState(() => fetched = true);
});
await Future.wait([completer.future]);
} catch (_) { }
finally {
receivePort.close();
}
}
This is getFiles function that runs in isolation
void getFiles(SendPort sendPort, List<AssetEntity> assetEntities) async {
final List<String> filesPaths = [];
for (final assetEntity in assetEntities) {
try {
final file = await assetEntity.file;
if (file != null) {
filesPaths.add(file.path);
}
} catch (_) { }
}
sendPort.send(filesPaths);
}
This is a function that calls compressImages function and adds any value to refresh the list of images Here i pass the paths and get compressed files for rendering.
void compressAlbumImages() async {
final receivePort = ReceivePort();
final completer = Completer();
compressImages(receivePort.sendPort, files, lastCompletedIndex);
try {
receivePort.listen((compressedFilesWithPath) {
files = compressedFilesWithPath;
fileStreamCt.sink.add(1);
completer.complete();
}).onError((_) {
completer.complete();
});
await Future.wait([completer.future]);
} catch (_) {}
finally {
receivePort.close();
}
return;
}
This is an image compression function that runs in isolation
void compressImages(SendPort sendPort, List<Map<String, dynamic>> files, int startFromIndex) async {
final List<String> filesToBeRemoved = [];
for (int idx = startFromIndex; idx < files.length; idx++) {
final file = files[idx];
try {
final compressedFile = await FlutterNativeImage.compressImage(
file["path"],
quality: 20,
percentage: 20,
targetHeight: 300,
targetWidth: 300,
);
file["compressedFile"] = compressedFile;
} catch (_) {
filesToBeRemoved.add(file["path"]);
}
}
if (filesToBeRemoved.isEmpty) {
return sendPort.send(files);
}
final compressedFilesWithPaths = files
.whereNot((element) => filesToBeRemoved.contains(element["path"]))
.toList();
sendPort.send(compressedFilesWithPaths);
}
And finally i render compressed images
return StreamBuilder(
stream: fileStreamCt.stream,
builder: (ctx, AsyncSnapshot<int> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("LOADING...");
}
if (snapshot.hasError) {
return Text("AN ERROR OCCURRED");
}
return buildAlbumImages();
}
);
Render like this
Image.file(compressedImage);
If in short - When i'm getting user phone gallery files && compress them, my app UI starts freezing. I don't know why it's freezing.
I tried the same process but with file.readAsBytes() and to render like Image.memory(compressedFileBytes), but it was useless.
I would be very grateful for any help. Thanks in advance.
Upvotes: 0
Views: 1314
Reputation: 680
In your code you never actually create an isolate. The 'isolate code' in getFile
, compressImages
etc simply runs on the main isolate and indeed will block the UI.
Per documentation, you create an isolate with Isolate.spawn
and pass only the sendPort. The isolate then must send back a receivePort, and the main thread uses that port to send the data you want to isolate to process (like assetEntities
), processes it and sends the results back to the main thread. It's a bit complicated, and requires different function signatures than you have here.
Fortunately, a much easier way to accomplish what you want (still using Isolates that won't block the UI) is to use the compute
function from the dart:async
package:
Change the signature of your getFiles
function to Future<List<String>> getFiles(List<AssetEntity> assetEntities) async
and do in it what you need to do, returning the list of filesPaths
as you do now. Importantly, getFiles
must be a top level or a static function, it cannot be a regular class method. Then, where you need the calculation done you use something like var filesPaths = await compute(getFiles, assetEntities)
. Now, the getFiles
function is called in an isolate, and the return value is given back to you on the main isolate. The nice thing is that now this looks a lot like regular await
call, no need for sendPorts etc. You can do the same thing for your other heavy calculation methods.
One (big) constraint with isolates is the type of argument you can pass to and from an isolate, see here. Those same constraints apply here, because under the hood the compute
function also uses sendPorts etc.
Upvotes: 2