How to run Flutter in the background?

Do you want to know how you can run Dart code — even when your app is not in the foreground?

Tim Rijckaert
Aug 1, 2019 · 4 min read

We are in the process of porting the VRT NWS app to Flutter.
Our app consists of multiple news feeds with various contents:

  • Headlines
  • Recent news
  • Popular news

When a user opens the app, the user quickly wants to get the latest news.
Besides performing a network call when the feed becomes visible, we also setup a background job which periodically fetches the latest articles.
This is a setting that is exposed to the user.

The Settings section about background fetching

However, in the new Flutter app it is Dart’s responsibility to perform network calls and parse the fetched data.

So we had to find answers to the following questions:

  • How to schedule a background job in Dart?
  • How to run a Dart function in the background?
  • How to run a Dart function without an initialised Flutter engine ?

There is an official blogpost, but it goes in way too much detail about geofencing instead of talking about the Dart/Flutter part.

TL;DR: We made a workmanager package to facilitate the boilerplate of setting this up. It is still in alpha stage as we are currently testing it ourselves.
You can find it on pub.dev.

In this section, we’ll highlight the most important information about background execution on Flutter.

For more information about how the Flutter WorkManager package works internally, please keep reading.

If you just want to get started with the package you can scroll to the bottom of the page.

How to schedule a background job in Dart?

Let’s first take a look at how we would do this without Flutter.
Android and iOS have very different approaches:

On Android, there are multiple ways to schedule a background job. It seems like every year there is a new and better way :

  • AlarmManager
  • JobService
  • Firebase Job Dispatcher
  • and the latest one, WorkManager.

The consensus these days seems to be on WorkManager.
Long story short: WorkManager provides an easy-to-use API where you can define:

  • If a background job should run once, or periodically
  • Apply multiple constraints like for example if a task needs internet connection, or if the battery should be fully charged, and so much more.
  • An interval of how many times a job should run.

The job is guaranteed to run some time in the future.
Android takes full ownership of when is the best time to run background jobs, and batches these jobs (our app’s and others’) together for minimum battery drain.

If you want to learn more about WorkManager, have a look the excellent documentation.

iOS

On iOS, options are much more limited. The systems decides when to give an app the chance to perform a background fetch, so that the app appears to remain “alive”.

The only control we have is the minimum background fetch interval, but iOS may decide to never start our app to perform a background fetch (for example if we take too long before returning a UIBackgroundFetchResult to the completionHandler).

Starting Flutter Engine in the background

When a background job is started by native the Flutter engine is not active. So we are unable to run Dart.

Can Android/iOS starts the Flutter engine in the background?

Yes! We’ll first need to register a Dart callback function which will only be invoked whenever a background job is started by the native code.

This callback function is referred to as a callbackDispatcher.

The process is the following on Dart:

📝 Note: The callbackDispatcher must be a top level function or a static function.

The PluginUtilities#getCallbackHandle is probably something new.
This is the function responsible for taking your callbackDispatcher and registering it with the Flutter engine.
It can be seen as a simple key-value map inside the Flutter engine.

The key is the hashcode of the callbackDispatcher.
The value is a handle to the entry point itself.

We only send this hashcode to native over a MethodChannel.
Android/iOS will then save it for later use.

⚠️ Careful: Renaming or moving an entry point to a different file in between registrations may cause the map to be out of sync.

Once a job is actually triggered in the background on either iOS or Android we need to do the opposite.

We need to retrieve the hashcode and lookup its associated callbackDispatcher function.
Flutter provides some convenience methods to start a Dart process from arguments.

For example on Android it looks something like this:

Flutter WorkManager Plugin

To make our life (and yours 😉) easier, we developed a Flutter plugin which already handles all the above mentioned boilerplate code.
It makes use of the WorkManager API’s on Android and performFetch on iOS.

📝 Note: that this plugin will work on both iOS and Android if you follow the setup steps first found in the README. The following example assumes you have.

Like we explained the plugin takes care for you to register the callbackDispatcher. Just call WorkManager.initialize(callbackDispatcher)

With the help of the registerOneOffTask we tell Flutter explicitly to start an Android job.
The value of myTask variable will be returned to the registered callbackDispatcher so you can differentiate if you have multiple tasks.

iOS’ performFetch will always return a fixed value of Workmanager.iOSBackgroundTask .

This is the simplest of example, but more options can be configured on Android. Take a look at the README

Special shoutout to Jérémie Vincke for implementing the iOS part of the plugin and for co-writing this Medium post.

Thanks for reading.

VRT Digital Studio

Behind the scenes of VRT Digital Studio

Tim Rijckaert

Written by

Kotlin Developer@VRT.

VRT Digital Studio

Behind the scenes of VRT Digital Studio

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade