← Back to blog

Improve Flutter Performance with Background Isolates in Flutter 3.7

Guillaume Bernos

Lead Software Engineer

20th March, 2023

Need a custom tool? Invertase can help

Tired of investing in off-the-shelf software that doesn't quite fit your business? Invertase can build solutions tailored to your exact needs. Tell us what you're looking for here and we'll be in touch.

The future of Flutter is so exciting. The release of version 3.7 was full of announcements! Today, I want to draw your attention to something that was quietly announced before the fanfare of this release… Official support for Background Isolates with native plugins.

Let’s see how we can use this new feature with Firestore!

Before starting

Currently, Background Isolates will only allow you to communicate from Flutter to Host, and you will not be able to communicate from Host to Flutter. We hope to see this feature in a future release 🤞. Unfortunately, this rules out event listeners for changes to a collection or document.

You can track Background Isolates support for FlutterFire on GitHub.

You also need to be aware that Isolates are not supported on the web, which will limit where you can use this feature to improve performance.

Use case

Let’s imagine we need to compute something stored in Firestore locally on the user’s device. You might need it to be responsive and cannot wait for a Cloud Function to run, or maybe you need to parse the data according to the user location or any real-time data from the user’s device.

Typically, you would request the document from Firestore and start interacting with it. But if the document is big, you might encounter lag in your Flutter app. It feels weird.

Why should I have a lag in the UI when I’m getting a document from Firestore?

The response is straightforward: Flutter is mono-threaded, meaning everything runs in a single thread: the UI thread. Your performance might suffer if you begin to parse a big message from Firestore. How does one solve this problem?

That’s where Background Isolates enter the picture.

Background Isolates allow the creation of another thread in which the parsing of the Firestore document can be executed. You can only send the parsed data back to the main thread and your application. It might be only a tiny portion of the initial retrieved document.

Isolate Function

Let’s first create the Isolate function to get a list of films and compute the most liked film.

Future<void> _isolateGetTheMostLikes(
  List<Object> args,
) async {
  final rootIsolateToken = args[0] as RootIsolateToken;
  final sendPort = args[1] as SendPort;

  BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);

    final moviesRef = FirebaseFirestore.instance
    .collection('firestore-example-app')
    .withConverter<Movie>(
      fromFirestore: (snapshots, _) => Movie.fromJson(snapshots.data()!),
      toFirestore: (movie, _) => movie.toJson(),
    );
  final movies = await moviesRef.get();
  final mostLiked = movies.docs
      .map((doc) => doc.data())
      .reduce((a, b) => a.likes > b.likes ? a : b);

  final mostLikedTitle = mostLiked.title;
  print('Most liked movie in isolate: $mostLikedTitle');

  sendPort.send(mostLikedTitle);
}

As you can see, you start by defining a list of arguments. The Isolate function will need at least a RootIsolateToken and a SendPort to communicate back with the main thread.

The BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken); call ensures this new Isolate is properly initialized. Then you can start setting up your Firebase app like usual with initializeApp.

Once all the movies are fetched from Firestore, we can parse them and send back the result to the main thread using the SendPort .

Calling the Isolate Function

Now that our function is defined, we can call it from the main thread.

RootIsolateToken rootIsolateToken = RootIsolateToken.instance;
if (rootIsolateToken == null) {
    print("Cannot get the RootIsolateToken");
    return;
}
ReceivePort receivePort = ReceivePort();
await Isolate.spawn(
  _isolateGetTheMostLikes,
  [rootIsolateToken, receivePort.sendPort],
);
receivePort.listen((message) {
  print('Most liked movie in main thread: $message');
});

The first step to creating a Background Isolate is getting a RootIsolateToken from the main thread. Then you can define and listen to a ReceivePort . This port will be open between the Isolate and the main thread, so you communicate back the most liked movie.

Running this code should produce this result:

flutter: Most liked movie in isolate: Avatar
flutter: Most liked movie in main thread: Avatar

Conclusion

As you can see, thanks to Flutter 3.7, it’s now effortless to get native plugins code to run in Background Isolates.

You can now improve your Flutter apps’ performance while still benefiting from the power of native plugins.

Stay tuned for more updates and exciting news that we will share in the future. Follow us on Invertase TwitterLinkedin, and Youtube, and subscribe to our monthly newsletter to stay up-to-date. You may also join our Discord to have an instant conversation with us.

Guillaume Bernos

Lead Software Engineer

Categories