blogs
Unlocking Flutter's Performance with Concurrency & Isolates
Hello Flutter devs,
In the dynamic world of Flutter development, understanding how concurrency and isolates operate can be a game-changer for optimizing app performance. In this newsletter, we delve into the realm of isolates in Dart, exploring their significance, use cases, and how they can address performance bottlenecks in your Flutter applications.
Concurrency and Isolates in Flutter
All Dart code runs in isolates, which are similar to threads, but differ in that isolates have their own isolated memory. They do not share state in any way, and can only communicate by messaging. By default, Flutter apps do all of their work on a single isolate – the main isolate. In most cases, this model allows for simpler programming and is fast enough that the application’s UI doesn’t become unresponsive.
Sometimes though, applications need to perform exceptionally large computations that can cause “UI jank” (jerky motion). If your app is experiencing jank for this reason, you can move these computations to a helper isolate. This allows the underlying runtime environment to run the computation concurrently with the main UI isolate’s work and takes advantage of multi-core devices.
Each isolate has its own memory and its own event loop. The event loop processes events in the order that they’re added to an event queue. On the main isolate, these events can be anything from handling a user tapping in the UI, to executing a function, to painting a frame on the screen. The following figure shows an example event queue with 3 events waiting to be processed. For smooth rendering, Flutter adds a “paint frame” event to the event queue 60 times per second(for a 60Hz device). If these events aren’t processed on time, the application experiences UI jank, or worse, become unresponsive altogether.
Common use cases for isolates
There is only one hard rule for when you should use isolates, and that’s when large computations are causing your Flutter application to experience UI jank. This jank happens when there is any computation that takes longer than Flutter’s frame gap.
Any process could take longer to complete, depending on the implementation and the input data, making it impossible to create an exhaustive list of when you need to consider using isolates.
That said, isolates are commonly used for the following:
- Reading data from a local database
- Sending push notifications
- Parsing and decoding large data files
- Processing or compressing photos, audio files, and video files
- Converting audio and video files
- When you need asynchronous support while using FFI
- Applying filtering to complex lists or filesystems
Short-lived isolates
The easiest way to move a process to an isolate in Flutter is with the Isolate.run method. This method spawns an isolate, passes a callback to the spawned isolate to start some computation, returns a value from the computation, and then shuts the isolate down when the computation is complete. This all happens concurrently with the main isolate, and doesn’t block it.
Stateful, longer-lived isolates
Short-lived isolates are convenient to use, but there is performance overhead required to spawn new isolates, and to copy objects from one isolate to another. If you’re doing the same computation using Isolate.run repeatedly, you might have better performance by creating isolates that don’t exit immediately.
To do this, you can use a handful of lower-level isolate-related APIs that Isolate.run abstracts:
- Isolate.spawn() and Isolate.exit()
- ReceivePort and SendPort
- send() method
When you use the Isolate.run method, the new isolate immediately shuts down after it returns a single message to the main isolate. Sometimes, you’ll need isolates that are long lived, and can pass multiple messages to each other over time. In Dart, you can accomplish this with the Isolate API and Ports. These long-lived isolates are colloquially known as background workers.
In conclusion, understanding the intricacies of concurrency and isolates in Flutter empowers developers to optimize their applications for enhanced performance. Leveraging isolates effectively, whether for short-lived or longer-lived tasks, opens the door to a smoother and more responsive user experience. As Flutter continues to evolve, mastering these concepts becomes increasingly vital for staying at the forefront of mobile app development.