Future getImage() async {
var image = await ImagePicker.pickImage(source: ImageSource.camera);
setState(() {
_image = image;
print("IMG:" + _image.toString());
});
setPrefs() ;
}
Future setPrefs() async {
_base64 = base64.encode(_image.readAsBytesSync());
print(_base64);
final prefs = await SharedPreferences.getInstance();
prefs.setString(IMAGE_KEY, _base64);
}
The readAsBytesSync()
method works fine on Android but too slow in iOS. So how can I move this code to a new background thread?
You can use the android_alarm_manager flutter plugin which lets you run Dart code in the background when an alarm fires. Another way with more control would be to write a native Android service (using Java or Kotlin) for your app that communicates with the flutter frontend via device storage or shared prefs.
Dart/Flutter is single threaded and not possible to share global variable. As each isolate has its own memory,space and everything. To make it work like multi threaded you have to use isolates and the communication will be used through ports by sending message to one another.
By design, Dart is a single-threaded programming language. That's mean we have asynchronous code across application. When a program starts, it creates something that is called Isolate. When isolated created, the microtask manager executes all events asynchronously.
1. Use Future
You can use the async version of the readAsBytes
.
So instead of:
final imageData = _image.readAsBytesSync();
_base64 = base64.encode(imageData);
You could have:
final imageData = await _image.readAsBytes();
_base64 = base64.encode(imageData);
2. Use Isolate
In your code, it may not necessarily be the readAsBytes
line that is slow. It might be the base 64 encodings of the image. If that's the case, you could put the entire computation into a separate isolate. There is a convenient method compute that you can use.
// This needs to be a global function
encodeImage(File imageFile) {
return base64.encodeimageFile.readAsBytesSync());
}
Future setPrefs() async {
_base64 = await compute(encodeImage, _image);
print(_base64);
final prefs = await SharedPreferences.getInstance();
prefs.setString(IMAGE_KEY, _base64);
}
In addition, it is worth mentioning that SharedPreferences
(for Android, aka, NSUserDefaults
on iOS) is designed to store small user settings. It is not designed for storing the images, which could be megabytes big. It is better to copy the image file into the app's document folder, and only store the filename in the SharedPreferences
.
Since Flutter is single-threaded and runs an event loop (like Node.js), you don’t have to worry about thread management or spawning background threads. If you’re doing I/O-bound work, such as disk access or a network call, then you can safely use async/await
and you’re done. If, on the other hand, you need to do computationally intensive work that keeps the CPU busy, you want to move it to an Isolate
to avoid blocking the event loop.
For I/O-bound work, declare the function as an async
function, and await
on long-running tasks inside the function:
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = jsonDecode(response.body);
});
}
This is how you typically do network or database calls, which are both I/O operations.
Isolates are separate execution threads that do not share any memory with the main execution memory heap. This means you can’t access variables from the main thread, or update your UI by calling setState()
. Isolates are true to their name, and cannot share memory (in the form of static fields, for example).
Here, dataLoader()
is the Isolate
that runs in its own separate execution thread. In the isolate you can perform more CPU intensive processing (parsing a big JSON, for example), or perform computationally intensive math, such as encryption or signal processing.
You can run the full example below:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:isolate';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
}
showLoadingDialog() {
if (widgets.length == 0) {
return true;
}
return false;
}
getBody() {
if (showLoadingDialog()) {
return getProgressDialog();
} else {
return getListView();
}
}
getProgressDialog() {
return Center(child: CircularProgressIndicator());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: getBody());
}
ListView getListView() => ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
});
Widget getRow(int i) {
return Padding(
padding: EdgeInsets.all(10.0),
child: Text("Row ${widgets[i]["title"]}"),
);
}
loadData() async {
ReceivePort receivePort = ReceivePort();
await Isolate.spawn(dataLoader, receivePort.sendPort);
// The 'echo' isolate sends its SendPort as the first message
SendPort sendPort = await receivePort.first;
List msg = await sendReceive(
sendPort,
"https://jsonplaceholder.typicode.com/posts",
);
setState(() {
widgets = msg;
});
}
// the entry point for the isolate
static dataLoader(SendPort sendPort) async {
// Open the ReceivePort for incoming messages.
ReceivePort port = ReceivePort();
// Notify any other isolates what port this isolate listens to.
sendPort.send(port.sendPort);
await for (var msg in port) {
String data = msg[0];
SendPort replyTo = msg[1];
String dataURL = data;
http.Response response = await http.get(dataURL);
// Lots of JSON to parse
replyTo.send(jsonDecode(response.body));
}
}
Future sendReceive(SendPort port, msg) {
ReceivePort response = ReceivePort();
port.send([msg, response.sendPort]);
return response.first;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With