<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Ben Konyi on Medium]]></title>
        <description><![CDATA[Stories by Ben Konyi on Medium]]></description>
        <link>https://medium.com/@bkonyi?source=rss-d4e32e866239------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*d1hf9rmd3uJL8yb1sPMIDA.png</url>
            <title>Stories by Ben Konyi on Medium</title>
            <link>https://medium.com/@bkonyi?source=rss-d4e32e866239------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 15:09:54 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@bkonyi/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Dart DevTools: Analyzing application performance with the CPU Profiler]]></title>
            <link>https://medium.com/dartlang/dart-devtools-analyzing-application-performance-with-the-cpu-profiler-3e94a0ec06ae?source=rss-d4e32e866239------2</link>
            <guid isPermaLink="false">https://medium.com/p/3e94a0ec06ae</guid>
            <category><![CDATA[dart]]></category>
            <category><![CDATA[flutter]]></category>
            <category><![CDATA[dart-devtools]]></category>
            <category><![CDATA[flutterdevtools]]></category>
            <category><![CDATA[cpu-profiling]]></category>
            <dc:creator><![CDATA[Ben Konyi]]></dc:creator>
            <pubDate>Mon, 12 Jun 2023 21:38:43 GMT</pubDate>
            <atom:updated>2023-06-13T14:44:08.831Z</atom:updated>
            <content:encoded><![CDATA[<p>Whether you’re a backend developer writing a command line tool using Dart, or a UX engineer building applications with Flutter, program performance is critical to the success of your project. Command line tools should minimize latency and apps should be responsive and snappy with no dropped frames. As developers, we try our best to write performant code but sometimes it’s not clear why our code isn’t performing as well as we would expect.</p><p>Tracking down performance issues in a code base can be a daunting task. There are multiple ways to write Flutter code such that it runs slower than it should. Some are glaringly obvious and others nefariously subtle. For example, perhaps you used the wrong API or data structure for a particular situation.</p><p>This article works through a simple case study examining the performance of a slow Dart command-line interface (CLI) application. You’ll learn about:</p><ul><li>General CPU profilers and their importance.</li><li>The sampling CPU profiler that ships with Dart and Flutter.</li></ul><p>With this new understanding of CPU profiling, we’ll debug the performance of our program. We’ll do so using the Dart DevTools CPU Profiler and dart:developer’s UserTag functionality to pinpoint the inefficient code. We’ve got lots to cover, so let’s get started!</p><p><em>Note: Dart DevTools is also referred to as Flutter DevTools, but shouldn’t be confused with Chrome DevTools.</em></p><h3>Case Study: Implementing grep in Dart</h3><p>Consider the following simple Dart CLI application:</p><pre><br>// filename: grep.dart<br>import &#39;dart:io&#39;;<br><br>/// Finds and prints all instances of [pattern] in the [file].<br>void grep(File file, String pattern) {<br>  // Open the file and read its contents.<br>  final lines = file.readAsLinesSync();<br><br>  String output = &#39;&#39;;<br>  int lineNum = 1;<br>  // Check each line to see if it matches `pattern`.<br>  for (final line in lines) {<br>    final matcher = RegExp(pattern);<br>    if (matcher.hasMatch(line)) {<br>      final foundMessage = &#39;$lineNum:$line&#39;;<br>      output += foundMessage;<br>    }<br>    lineNum++;<br>  }<br>  stdout.writeln(output);<br>}<br><br>void main(List&lt;String&gt; arguments) { <br>  if (arguments.length != 2) {<br>    print(&#39;Usage: dart grep.dart &lt;path&gt; &lt;pattern&gt;&#39;);<br>    exitCode = 64;<br>    return;<br>  }<br>  final path = arguments[0];<br>  final pattern = arguments[1];<br><br>  final file = File(path);<br>  if (!file.existsSync()) {<br>    stderr.writeln(&quot;Error: unable to open file &#39;$path&#39;&quot;);<br>    exitCode = 64;<br>    return;<br>  }<br><br>  grep(file, pattern);<br>}</pre><p>The grep.dart program implements a version of the common Unix tool. It allows users to search files for the occurrence of a string of characters that matches a specified pattern. For example, given a file names.txt containing:</p><pre>$ cat names.txt<br>Frank<br>Bob<br>Franny<br>June<br>Ben<br>Francis</pre><p>Let’s find all lines containing the string &#39;Fran&#39;:</p><pre>$ dart grep.dart names.txt &#39;Fran&#39;<br>1:Frank<br>3:Franny<br>6:Francis</pre><p>Excellent! We should expect this output from grep. That said, names.txt is a <em>small</em> file. Let’s try and find all the instances of &#39;Hummingbird&#39; in a larger text document. What if we ran the same program on a 437,000 line, 147 MiB file about hummingbirds?</p><pre>$ dart grep.dart hummingbird_encyclopedia.txt &#39;Hummingbird&#39;</pre><p>Huh… it doesn’t complete, even after <strong>two minutes</strong>. How does Unix grep perform?</p><pre>$ grep -n &#39;Hummingbird&#39; hummingbird_encyclopedia.txt<br>16:&#39;&#39;&#39;Hummingbirds&#39;&#39;&#39; are [[bird]]s native to the…<br>22:Hummingbirds have the highest…<br>24:Hummingbirds split from their [[Sister taxon|sister group]]…<br>// Output continues</pre><p>Unix’s grep searched the entire file and returned all lines containing Hummingbird in about 45 seconds. Clearly there’s something strange in our code that we should investigate. But how can we identify the cause of our performance issues? The <strong>CPU Profiler</strong> shipped with Dart DevTools is a great place to start!</p><h3>What is a CPU profiler?</h3><p>CPU profiling tools track where a program spends its time when executing. To provide the most computationally efficient CPU profiling experience, the Dart virtual machine (VM), powering Dart CLI and Flutter applications, uses a <strong>sampling</strong> CPU profiler. When used with tools like Dart DevTools, you can identify performance bottlenecks in Dart programs.</p><p>A sampling CPU profiler takes a statistical approach to collecting application performance data. It collects samples by interrupting threads at regular intervals and taking a snapshot of the current call stack and other relevant execution state. These samples can be processed to gain insights into approximately how much time is spent executing specific functions, and how frequently functions appear on different call stacks.</p><p>The frequency that samples are collected is known as the <strong>sampling rate</strong> and is measured in samples per second (otherwise known as hertz or Hz). Most sampling profilers have sampling rates of 1000 Hz or higher. Higher sampling rates result in more detailed CPU profiles, at the expense of higher sampling overhead in the target process. At reasonable sampling rates, sampling CPU profilers are efficient and have little to no impact on the performance characteristics of the profiled application. As an added bonus, the collected data is typically less computationally expensive to process for analysis when compared to tracing profilers.</p><h3>Deep Dive: How does a sampling profiler work?</h3><p><em>This section goes into detail about how the Dart VM’s sampling CPU profiler works. You don’t need to know these details when analyzing CPU profiles. If you don’t care about the nitty gritty details of sampling CPU profilers, skip this section.</em></p><p>The Dart VM’s sampling CPU profiler has three important components: the thread interrupter, the sample collector, and the sample processor.</p><h4>Thread Interrupter</h4><p>The <a href="https://github.com/dart-lang/sdk/blob/main/runtime/vm/thread_interrupter.cc"><strong>thread interrupter</strong></a> runs on a dedicated thread and triggers the CPU sample collection on each thread that the VM manages. The thread interrupter is typically inactive, only waking up once per sampling interval. After each sampling interval, the interrupter iterates over the list of threads, informing each thread that it should pause and collect a sample. The thread interrupter has slightly different behavior on different platforms due to operating system specific details.</p><p>On most platforms which support <a href="https://en.wikipedia.org/wiki/Signal_(IPC)">signal-based control flow</a> (Android and Linux), the <a href="https://www.gnu.org/software/libc/manual/html_node/Alarm-Signals.html">SIGPROF</a> signal is sent to each thread. This triggers an <a href="https://en.wikipedia.org/wiki/Interrupt">interrupt</a> that invokes a signal handler registered by the CPU profiler on the target thread that then collects the CPU sample before resuming its work.</p><p>On other platforms which don’t support signals (Windows and Fuchsia) or have poor performance in some situations when using SIGPROF (MacOS and iOS), the thread interrupter uses system calls to explicitly pause and resume each thread after collecting the CPU sample. In this case, sample collection is done on the thread interrupter thread rather than on the thread being sampled.</p><h4>Sample collection</h4><p>Once a thread has been interrupted, the CPU profiler <a href="https://github.com/dart-lang/sdk/blob/bbacf39e9c1085650b5f2a180285792ad0dd76d9/runtime/vm/profiler.cc#L1311">collects a sample</a> of the thread’s current execution state. Each <a href="https://github.com/dart-lang/sdk/blob/bbacf39e9c1085650b5f2a180285792ad0dd76d9/runtime/vm/profiler.h#L195">sample</a> includes metadata such as:</p><ul><li>thread and isolate identifiers</li><li>active <a href="https://api.dart.dev/stable/dart-developer/UserTag/UserTag.html">user tag</a> for the thread</li><li>collection timestamps</li><li>current stack trace of the sampled thread</li></ul><p>The collected stack trace consists of a list of program counters (PCs), which correspond to the return addresses of each Dart and native function found on the stack. These PCs are collected through a process known as “walking the stack”. While performing a <a href="https://github.com/dart-lang/sdk/blob/bbacf39e9c1085650b5f2a180285792ad0dd76d9/runtime/vm/profiler.cc#L208">stack walk</a>, the stack walker uses the top frame’s frame pointer (FP) and the known layout of each stack frame to find and record the PC associated with the function as well as the FP of the previous stack frame. The stack walker repeats this process, using the previous frame’s FP as a starting point, until it reaches the end of the stack as shown in Figure 1.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*manGRq62JBgJER8ufYlAfA.png" /><figcaption><strong><em>Figure 1: An example of stack trace collection on an ARM64 system. The stack walker starts at </em></strong><strong><em>FP(N+1), retrieves the program counter (PC) from address FP(N+1) + 0x10, and adds it to the stack trace as Frame 0. The stack walker then looks up FP(N) from address FP(N+1) + 0x8, repeating the same procedure using FP(N) to look up details for Frame 1.</em></strong></figcaption></figure><p>Each collected sample is stored in the VM’s sample buffer, a <a href="https://en.wikipedia.org/wiki/Circular_buffer">circular buffer</a> that can store a limited number of CPU samples. This allows for the VM to avoid the need for additional allocations at runtime, which can negatively impact performance or <a href="https://en.wikipedia.org/wiki/Signal_(IPC)#Risks">cause bad things to happen if done in the signal handler</a>.</p><p>The size of the sample buffer is fixed at runtime and, once full, old samples will be overwritten with new samples by the profiler. The rate at which the sample buffer fills up depends on the sampling rate and average stack depth of each sample. For example, higher sampling rates and deeper stacks cause the buffer to wrap around more quickly. In DevTools, developers can control how quickly the sample buffer wraps around by <a href="https://docs.flutter.dev/tools/devtools/cpu-profiler#cpu-sampling-rate">choosing</a> between low (1000 Hz), medium (4000 Hz), and high (20,000 Hz) sampling rates.</p><h4>Sample processing</h4><p>When a client sends a request for a CPU sample profile via the <a href="https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md#getcpusamples">VM service protocol</a>, the CPU profiler needs to process the collected samples before sending them to the client. The profiler:</p><ol><li>Iterates over the sample buffer, using filters to only retrieve samples for the isolate and time period specified by the client.</li><li><em>Symbolizes</em>, or maps PCs to function names, each stack frame in the set of samples.</li><li>Serializes the entire processed sample buffer to JSON.</li><li>Sends the JSON back to the client.</li></ol><p>Even after processing done by the profiler, the <a href="https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md#cpusamples">CPU samples response</a> is low-level and requires additional processing by developer tooling to be useful. For example, Dart DevTools can convert the list of CPU samples into various structural representations that allow for identifying expensive functions (<a href="https://docs.flutter.dev/tools/devtools/cpu-profiler#bottom-up">Bottom Up</a>) and costly call paths (<a href="https://docs.flutter.dev/tools/devtools/cpu-profiler#call-tree">Call Tree</a> and <a href="https://docs.flutter.dev/tools/devtools/cpu-profiler#flame-chart">CPU Flame Chart</a>), and inspecting caller and callee statistics (<a href="https://docs.flutter.dev/tools/devtools/cpu-profiler#method-table">Method Table</a>) for individual methods.</p><h3>Using Dart DevTools to Profile Dart and Flutter Applications</h3><p>Now that you’re familiar with what sampling CPU profilers are and how they work, let’s debug the performance of our grep implementation. Let’s run the code again with --observe and open up the Dart DevTools CPU Profiler tab:</p><p><strong><em>Note:</em></strong><em> You don’t need to use the </em><em>--observe flag when testing a Flutter app in DevTools.</em></p><pre>$ dart — observe grep.dart hummingbird_encyclopedia.txt &#39;Hummingbird&#39;<br>The Dart VM service is listening on http://127.0.0.1:8181/omxEtsCtW9k=/<br>The Dart DevTools debugger and profiler is available at: http://127.0.0.1:8181/omxEtsCtW9k=/devtools?uri=ws://127.0.0.1:8181/omxEtsCtW9k=/ws<br>// At this point, the terminal hangs with no output.</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*4btaxCBW8Btx71RM" /></figure><p>Yikes! Of all the samples collected in a period of 11.6 seconds, the isolate spent more than 90% of its CPU time executing _StringBase.+. It’s likely that this is related to our performance issues, but it might not be obvious what block of code in our grep function is the source of the slowdown. Luckily, we can further narrow down the location of the expensive calls to _StringBase.+ using <strong>user tags.</strong></p><h3>Categorizing CPU samples with User Tags</h3><p>When the Dart CPU profiler interrupts a thread to collect a new sample, it records the current user tag set for the isolate. The <a href="https://api.dart.dev/stable/dart-developer/dart-developer-library.html">dart:developer</a> library provides the <a href="https://api.dart.dev/stable/dart-developer/dart-developer-library.html">UserTag</a> class that allows you to specify and set custom tags for sections of code that you’re interested in profiling.</p><p>To better understand where we’re spending time in our grep implementation, we can instrument the function with user tags:</p><pre>// filename: grep.dart<br>import &#39;dart:developer&#39;;<br>import &#39;dart:io&#39;;<br><br>/// Finds and prints all instances of [pattern] in the [file].<br>void grep(File file, String pattern) {<br>  final defaultTag = getCurrentTag();<br>  final fileReadTag = UserTag(&#39;File Read&#39;);<br>  final textMatchTag = UserTag(&#39;Text Matching&#39;);<br>  final printTag = UserTag(&#39;Print Output&#39;);<br><br>  // Set the &#39;File Read&#39; tag as the current user tag.<br>  fileReadTag.makeCurrent();<br><br>  // Open the file and read its contents.<br>  final lines = file.readAsLinesSync();<br><br>  // Set the &#39;Text Matching&#39; tag as the current user tag.<br>  textMatchTag.makeCurrent();<br>  String output = &#39;&#39;;<br>  int lineNum = 1;<br>  // Check each line to see if it matches `pattern`.<br>  for (final line in lines) {<br>    final matcher = RegExp(pattern);<br>    if (matcher.hasMatch(line)) {<br>      final foundMessage = &#39;$lineNum:$line&#39;;<br>      output += foundMessage;<br>    }<br>    lineNum++;<br>  }<br><br>  // Set the &#39;Print Output&#39; tag as the current user tag.<br>  printTag.makeCurrent();<br>  stdout.writeln(output);<br><br>  // Reset the user tag to the tag set when grep was invoked.<br>  defaultTag.makeCurrent();<br>}</pre><p>Now, let’s re-run our program and open up the CPU Profiler. To see our categorized profile, select the <strong>Group by: User Tag</strong> option from the dropdown:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*zxcsj31nCh14rLYa" /></figure><p>When we expand the <strong>Text Matching</strong> tag, we confirm that the _StringBase.+ method is called in our text-matching loop:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ai9S8-Y8c_94rr6p" /></figure><p>With this information, we should be able to take a closer look at our code and identify the issue:</p><pre>  // Set the ‘Text Matching’ tag as the current user tag.<br>  textMatchTag.makeCurrent();<br>  String output = &#39;&#39;;<br>  int lineNum = 1;<br>  // Check each line to see if it matches `pattern`.<br>  for (final line in lines) {<br>    final matcher = RegExp(pattern);<br>    if (matcher.hasMatch(line)) {<br>      final foundMessage = &#39;$lineNum:$line&#39;;<br>      // Ahah! This is our call to _StringBase.+!<br>      output += foundMessage;<br>    }<br>    lineNum++;<br>  }</pre><p>There it is! We’ve made the classic mistake of appending content to a <a href="https://api.dart.dev/stable/dart-core/String-class.html">String</a> multiple times rather than using a <a href="https://api.dart.dev/stable/dart-core/StringBuffer-class.html">StringBuffer</a>. Appending content to a String creates a new string to store the result of the _StringBase.+ method. As a result, we copy output + foundMessage into a new string each time we find a match.</p><p>As output gets longer, appending data to it becomes more expensive, taking O(m*n) to perform the copies, where m is the average number of characters in a match and n is the total number of characters in the final string. If we use a StringBuffer, we don’t copy output each time we append to it, instead concatenating the matches in a single O(n) operation at the end of the function.</p><p>Now that our app uses <a href="https://api.dart.dev/stable/dart-core/StringBuffer/writeln.html">StringBuffer.writeln</a> instead of appending to a String, let’s look at our function:</p><pre>// filename: grep.dart<br>import ‘dart:developer’;<br>import &#39;dart:io&#39;;<br><br>/// Finds and prints all instances of [pattern] in the [file].<br>void grep(File file, String pattern) {<br>  final defaultTag = getCurrentTag();<br>  final fileReadTag = UserTag(‘File Read’);<br>  final textMatchTag = UserTag(‘Text Matching’);<br>  final printTag = UserTag(‘Print Output’);<br><br>  // Set the ‘File Read’ tag as the current user tag.<br>  fileReadTag.makeCurrent();<br><br>  // Open the file and read its contents.<br>  final lines = file.readAsLinesSync();<br><br>  // Set the ‘Text Matching’ tag as the current user tag.<br>  textMatchTag.makeCurrent();<br>  final output = StringBuffer();<br>  int lineNum = 1;<br>  // Check each line to see if it matches `pattern`.<br>  for (final line in lines) {<br>    final matcher = RegExp(pattern);<br>    if (matcher.hasMatch(line)) {<br>      final foundMessage = &#39;$lineNum:$line&#39;;<br>      // Add the match to the buffer without creating a copy.<br>      output.writeln(foundMessage);<br>    }<br>    lineNum++;<br>  }<br><br>  // Set the ‘Print Output’ tag as the current user tag.<br>  printTag.makeCurrent();<br>  // output.toString() concatenates each entry in the buffer<br>  // into a new String, only performing a single allocation of<br>  // size `output.length`. <br>  stdout.writeln(output);<br><br>  // Reset the user tag to the tag set when grep was invoked.<br>  defaultTag.makeCurrent();<br>}</pre><p>Let’s run our test again to see if there’s any improvement:</p><pre>$ dart grep.dart hummingbird_encyclopedia.txt &#39;Hummingbird&#39;<br>16:‘’’Hummingbirds’’’ are [[bird]]s native to the…<br>22:Hummingbirds have the highest…<br>24:Hummingbirds split from their [[Sister taxon|sister group]]…<br>// Output continues</pre><p>Using StringBuffer, we can find all the instances of &#39;Hummingbird&#39; in about <strong>45 seconds</strong>. That’s much better and almost the same as the Unix grep implementation! Let’s take another look at the CPU profiler to see if we can improve performance further:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*dL9EpPsSbaU-Bem-" /></figure><p>A quick glance at the profile tells us that we spend most of our time printing matches and that the actual matching only takes about 200ms. We should focus on the code that falls under the &#39;Print Output&#39; tag:</p><pre>  // Set the ‘Print Output’ tag as the current user tag.<br>  printTag.makeCurrent();<br>  // output.toString() concatenates each entry in the buffer<br>  // into a new String, only performing a single allocation of<br>  // size `output.length`. <br>  stdout.writeln(output);</pre><p>There doesn’t seem much we can do as we are only making a single call to <a href="https://api.dart.dev/stable/dart-io/stdout.html">stdout.writeln</a>, a member of the core <a href="https://api.dart.dev/stable/dart-io/dart-io-library.html">dart:io</a> library. Looking at the <a href="https://docs.flutter.dev/tools/devtools/cpu-profiler#flame-chart">CPU flame chart in the profiler</a>, we see that this code is in the SDK and that we can make no more performance gains in our code.</p><p>With that, we’re done!</p><h3>Summary</h3><p>We’ve accomplished a lot since the beginning of this article. We:</p><ul><li>Wrote a simple grep tool in Dart.</li><li>Identified our program had poor performance.</li><li>Learned about CPU profilers and explored the Dart VM’s sampling CPU profiler.</li><li>Used the Dart DevTools’ <a href="https://docs.flutter.dev/tools/devtools/cpu-profiler">CPU Profiler</a> to identify and fix our program’s performance issues.</li></ul><p>Having a good understanding of performance tooling is an important skill and, as we’ve shown in this article, can help you identify potentially subtle performance issues in your code. The CPU Profiler is only one of many tools shipped with <a href="https://docs.flutter.dev/tools/devtools/overview">Dart DevTools</a> that can help you better understand the behavior and performance of your Dart CLI and Flutter applications.</p><p>In future articles, we’ll explore other ways to use Dart DevTools to debug and optimize your apps, including:</p><ul><li>Analyzing memory usage through the <a href="https://docs.flutter.dev/tools/devtools/memory">Memory</a> screen</li><li>Examining execution timelines through the <a href="https://docs.flutter.dev/tools/devtools/performance">Performance</a> screen</li><li>Inspecting HTTP traffic through the <a href="https://docs.flutter.dev/tools/devtools/network">Network</a> screen</li></ul><p>Until then, happy hacking!</p><p><em>If you feel so inclined, </em><a href="https://github.com/bkonyi"><em>follow me on GitHub</em></a><em> to keep up with my work on Flutter and the Dart virtual machine, as well as my other pet projects.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3e94a0ec06ae" width="1" height="1" alt=""><hr><p><a href="https://medium.com/dartlang/dart-devtools-analyzing-application-performance-with-the-cpu-profiler-3e94a0ec06ae">Dart DevTools: Analyzing application performance with the CPU Profiler</a> was originally published in <a href="https://medium.com/dartlang">Dart</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Executing Dart in the Background with Flutter Plugins and Geofencing]]></title>
            <link>https://blog.flutter.dev/executing-dart-in-the-background-with-flutter-plugins-and-geofencing-2b3e40a1a124?source=rss-d4e32e866239------2</link>
            <guid isPermaLink="false">https://medium.com/p/2b3e40a1a124</guid>
            <category><![CDATA[dart]]></category>
            <category><![CDATA[raspberry-pi]]></category>
            <category><![CDATA[tutorial]]></category>
            <category><![CDATA[flutter]]></category>
            <dc:creator><![CDATA[Ben Konyi]]></dc:creator>
            <pubDate>Thu, 20 Sep 2018 02:32:20 GMT</pubDate>
            <atom:updated>2022-04-11T19:48:41.569Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ae_YppUn96m93qkT" /><figcaption>No garage door remote? Not a problem with Flutter and a Raspberry Pi!</figcaption></figure><p><strong><em>04/11/2022</em></strong>: <em>This article has been updated to replace broken links due to docs and plugins changing names or locations.</em></p><p><strong><em>09/10/2020</em></strong>: <em>This article has been updated to reflect the move to the v2 Android embedding API. Migration instructions for applications created pre-1.12 can be found </em><a href="https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects"><em>here</em></a><em>, and </em><a href="https://flutter.dev/docs/development/packages-and-plugins/plugin-api-migration"><em>here</em></a><em> for plugins.</em></p><p>Whether handling push notifications, location updates, or sensor events, many useful features require that an application has the ability to handle events without user interaction, even when not running in the foreground. Up until this point, applications written using Flutter could only handle background events using platform code, and plugins had no way of allowing for plugin users to provide a callback to handle background events in Dart. Basically, if a Flutter user wants to handle background events in an application, they were required to create a platform-specific implementation for each of their target platforms.</p><p>Luckily, this is no longer the situation, thanks to the arrival of Flutter support for background execution of Dart code. Having designed much of the Flutter background execution flow, I’m excited to share my experiences developing plugins which take advantage of this functionality, such as the <a href="https://pub.dartlang.org/packages/android_alarm_manager#-readme-tab-">android_alarm_manager</a> and <a href="https://github.com/flutter/plugins/tree/master/packages/location_background">an iOS location event handler</a>, to help you get started creating your own plugins.</p><p>Throughout the rest of this article, I’ll explore (in detail) the process of building a Flutter plugin that handles background geofencing events on both Android and iOS. Finally, just for fun, I’ll showcase how I used this <a href="https://github.com/bkonyi/FlutterGeofencing">geofencing plugin</a> to create a simple application that can be used to open my garage door automatically when I get close to home.</p><h4>Table of Contents</h4><ul><li><a href="https://medium.com/p/2b3e40a1a124#56b7">Geofencing: defining the Dart API</a></li><li><a href="https://medium.com/p/2b3e40a1a124#155c">Dart background execution</a></li><li><a href="https://medium.com/p/2b3e40a1a124#1b99">Background execution: Android (Kotlin)</a></li><li><a href="https://medium.com/p/2b3e40a1a124#5979">Background execution: iOS (Objective-C)</a></li><li><a href="https://medium.com/p/2b3e40a1a124#6db8">Usage example: operating a garage door with geofencing</a></li><li><a href="https://medium.com/p/2b3e40a1a124#3d77">Conclusion</a></li><li><a href="https://medium.com/p/2b3e40a1a124#2bc9">Resources</a></li></ul><h3>Geofencing: defining the Dart API</h3><p>Before writing any platform-specific code, I’ll first need to define the Dart API for the geofencing plugin. Since <a href="https://developer.android.com/training/location/geofencing">Android</a> and <a href="https://developer.apple.com/documentation/corelocation/monitoring_the_user_s_proximity_to_geographic_regions?language=objc">iOS</a> each have their own APIs for registering and handling geofencing events, I want the Dart interface to provide a reasonable geofencing abstraction that is compatible with both platforms. Without going into too much detail about Android and iOS APIs, the following roughly describes the Dart interface that I’ll use for my plugin:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cdee0571140d05833e69545da4bf8228/href">https://medium.com/media/cdee0571140d05833e69545da4bf8228/href</a></iframe><p>This interface provides the following functionality to users of the plugin:</p><ul><li>The ability to create instances of GeofenceRegion, which contain the coordinates and radius of a geofence, a unique ID, and a list of geofencing events to listen for. Since Android provides a richer set of options for defining geofences than iOS, Android-specific options are made available through the optional androidSettings property.</li><li>GeofencingPlugin.registerGeofence allows for the registration of a GeofenceRegion instance with a callback that is invoked when a geofence event for that region is received.</li><li>GeofencingPlugin.removeGeofence and GeofencingPlugin.removeGeofenceById unregister a GeofenceRegion from triggering additional events.</li></ul><p>Overall, this interface is rather simple and (mostly) platform agnostic, making the plugin easy to use on both Android and iOS.</p><h3>Dart background execution</h3><p>This section covers how to set up your isolate for background execution. You will learn how to reference callbacks, and how to use the callback dispatcher.</p><h4>Referencing Callbacks</h4><p>Now that the Dart interface defined, start adding plumbing to communicate with the platform-specific portions of the plugin. For example, the following code initializes the geofencing plugin and registers the geofences:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fcc3bb095d95709053591bd7bcd657c7/href">https://medium.com/media/fcc3bb095d95709053591bd7bcd657c7/href</a></iframe><p>If you’ve previously developed Flutter plugins and are familiar with MethodChannel, this should look as expected, for the most part. (If you’re new to plugin development, check out <a href="https://flutter.io/platform-channels/">the platform channels article</a> for an introduction). However, the two calls to PluginUtilities.getCallbackHandle might stand out.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/819/0*ilMs7WWcpN3q9UxO" /><figcaption><em>Callback handles are managed by the Flutter engine and can be used to reference and lookup callbacks across isolates.</em></figcaption></figure><p>In order to invoke a Dart callback as a result of a background event, you must retrieve a handle that is passed between Dart and platform code while also allowing for lookup of the callback across platform threads and Dart <a href="https://api.dartlang.org/stable/2.0.0/dart-isolate/dart-isolate-library.html">isolates</a>.</p><p><strong>Aside: </strong>Retrieving a CallbackHandle for a method from PluginUtilities.getCallbackHandle has the side effect of populating a callback cache within the Flutter engine, as seen in the diagram above. This cache maps information required to retrieve callbacks to raw integer handles, which are simply hashes calculated based on the properties of the callback. This cache persists across launches, but be aware that <strong><em>callback lookups may fail if the callback is renamed or moved and </em></strong><strong><em>PluginUtilities.getCallbackHandle is not called for the updated callback</em></strong><em>.</em></p><p>In the code above, two instances of CallbackHandle are obtained: one for the callback, which is associated with a GeofenceRegion, and another for a method of the name callbackDispatcher. The callbackDispatcher method, the entrypoint of the background isolate, is responsible for preprocessing raw geofence event data, looking up callbacks via PluginUtilities.getCallbackFromHandle, and invoking them for registered geofences.</p><h4>The Callback Dispatcher</h4><p>As mentioned at the end of the previous section, I’ll use a pattern that I refer to as the<em> callback dispatcher</em> to create the entrypoint for the geofencing plugin’s background isolate. This pattern allows for performing the initialization required to establish communication channels with platform code while also allowing for the creation of non-trivial interfaces for callback methods. For this geofencing plugin, the callback dispatcher implementation is as follows:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cf0090dea8ee1931d2c4a51cb46bc5a4/href">https://medium.com/media/cf0090dea8ee1931d2c4a51cb46bc5a4/href</a></iframe><p>As you can see, on the invocation of callbackDispatcher (upon the creation of the geofencing plugin’s background isolate), only four operations are performed. First, a MethodChannel is created for listening to events from the plugin. Next, WidgetsFlutterBinding.ensureInitialized() is called to initialize state needed to communicate with the Flutter engine. At this point, the MethodCall handler is set to process plugin events before finally alerting the platform portion of the plugin that the background isolate is initialized and ready to start handling events.</p><p>Once the plugin starts sending events to the callback dispatcher, the callback provided by the plugin user can be invoked. First, PluginUtilities.getCallbackFromHandle is called to retrieve an instance of the callback associated with the triggered geofencing event using the raw callback handle. Next, the raw arguments from the MethodCall are refined into:</p><ul><li>An instance of List&lt;String&gt; for the IDs of the geofences that were triggered</li><li>An instance of Location describing the current location of the device</li><li>An instance of the GeofenceEvent enum that represents whether the device has entered, exited, or dwelled within the triggered geofences.</li></ul><p>Then provide this info as arguments to our callback.</p><p><strong>Important Note:</strong> You may have noticed that no state is kept within the callback handler. This is because <strong><em>there is no guarantee that the background isolate will stay alive while the application itself is backgrounded</em></strong>. Both Android and iOS have lifecycle policies that can result in background services or execution being killed, meaning that the background isolate may be destroyed and then recreated the next time the application is woken up. As a result, <strong><em>best practice avoids storing volatile state in either the callback handler or user-provided callbacks</em></strong>.</p><p>At this point, we now have all of the Dart code needed for the plugin! Now, onto the platform-specific portion of the geofencing plugin.</p><h3>Background execution: Android (Kotlin)</h3><p>For the Android implementation of the plugin, I’ll need to implement the following classes:</p><ul><li>The GeofencingPlugin class, which is registered with the Flutter engine in order to receive and handle method calls made from Dart code</li><li>A GeofencingBroadcastReceiver, which is invoked by the system on a geofence event</li><li>The GeofencingService, which creates the background isolate, initializes the callback dispatcher described earlier, and processes geofence events before invoking the callback dispatcher.</li></ul><p>This trinity of 1) plugin, 2) broadcast receiver, and 3) service classes is a common pattern for plugins on Android, so it is worth becoming familiar with it. Although I’ve decided to use Kotlin for this plugin, everything here can also be implemented using Java.</p><h4>GeofencingPlugin</h4><p>As previously mentioned, the main purpose of the GeofencingPlugin is to process requests from Dart code and then register or remove geofences based on the contents of said request. An instance of this class is automatically created and added to the plugin registry at application startup.</p><p>This class implements two interfaces that are required for basic functionality of the plugin:</p><ul><li><a href="https://api.flutter.dev/javadoc/io/flutter/embedding/engine/plugins/FlutterPlugin.html">FlutterPlugin</a>: declares onAttachedToEngine and onDetachedFromEngine, used to notify the plugin of its connection status to a Flutter engine instance.</li><li><a href="https://api.flutter.dev/javadoc/io/flutter/plugin/common/MethodChannel.MethodCallHandler.html">MethodCallHandler</a>: declares onMethodCall, the method used to process messages sent to the plugin over a MethodChannel.</li></ul><p>Most plugins need to implement both FlutterPlugin and MethodCallHandler, but some plugins may also require information about the current Activity or other application component.</p><p>To get access to application components currently attached to the plugin instance, a plugin should implement one or more of the “awareness” interfaces for <a href="https://api.flutter.dev/javadoc/io/flutter/embedding/engine/plugins/activity/ActivityAware.html">Activity</a>, <a href="https://api.flutter.dev/javadoc/io/flutter/embedding/engine/plugins/broadcastreceiver/package-summary.html">BroadcastReceiver</a>, <a href="https://api.flutter.dev/javadoc/io/flutter/embedding/engine/plugins/contentprovider/package-summary.html">ContentProvider</a>, or <a href="https://api.flutter.dev/javadoc/io/flutter/embedding/engine/plugins/service/package-summary.html">Service</a>. These interfaces declare callbacks that can be invoked by the Flutter engine to notify the plugin when a component is attached or detached. For example, a plugin that requires access to an Activity would implement the ActivityAware interface and would be notified when the plugin gains or loses access to an Activity due to the application being minimized.</p><h4>Creating Geofences</h4><p>In order to handle requests, create an instance of MethodChannel on the same channel from earlier, and then register the GeofencingPlugin instance with this new channel in the implementation of onAttachedToEngine:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a264cfd805f8e586c233746e1bdbd3be/href">https://medium.com/media/a264cfd805f8e586c233746e1bdbd3be/href</a></iframe><p>In order to manage these requests, onMethodCall needs to be implemented:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2036728966a4396a8ca5d0885de53758/href">https://medium.com/media/2036728966a4396a8ca5d0885de53758/href</a></iframe><p>Finally, I’ll add the ability to register geofences (removing geofences is relatively trivial, so I’ll focus on adding geofences in this article):</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/baafbecc153c8e1c7d5397d382e57fb5/href">https://medium.com/media/baafbecc153c8e1c7d5397d382e57fb5/href</a></iframe><p>There’s a lot going on here, so let’s break this down:</p><ol><li>Pull the relevant arguments out of the ArrayList sent over the MethodChannel</li><li>Create an instance of Geofence that describes the location and size of the geofence as well as its various trigger parameters</li><li>Before registering the Geofence instance, do another check to ensure that the application still has the correct device permissions for geofencing</li><li>Finally, a GeofencingRequest as well as a PendingIntent are created and used to register the geofence. The PendingIntent is used to invoke the GeofencingBroadcastReceiver when the geofence is triggered; it contains the callback handle associated with that geofence.</li></ol><p>That’s it! At this point the plugin can create and register a geofence. However, the plugin is not yet ready to handle actual geofence events. For that, the plugin needs to be able to be woken up by the system when there is a geofence event to be handled.</p><h4>Scheduling the geofencing service</h4><p>Now that the plugin can register geofence events, it also needs to be able to handle the events themselves. When a geofence registered by the plugin is triggered, Android starts the Flutter application in the background, creates an instance of GeofencingBroadcastReceiver, and invokes the overridden onReceive method:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6ac1783a4eb09bbb7697070a6f814a3f/href">https://medium.com/media/6ac1783a4eb09bbb7697070a6f814a3f/href</a></iframe><p>The onReceive implementation is simple: it ensures that the Flutter framework is initialized and then adds the Intent for the geofencing event to the GeofencingService’s work queue. Since GeofencingService is an implementation of a JobIntentService, GeofencingService.enqueueWork is simply a wrapper around the enqueueWork method in JobIntentService, which handles scheduling the work for the service.</p><h4>Handling Geofence Events</h4><p>At some point after a geofence event is added to the work queue, Android wakes up the application to invoke GeofencingService’s onHandleWork method. However, before onHandleWork can be called, an instance of GeofencingService must be created and initialized by invoking startGeofencingService from GeofencingService’s onCreate method.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f901b60ce91c382504847cbb39f12146/href">https://medium.com/media/f901b60ce91c382504847cbb39f12146/href</a></iframe><p>startGeofencingService is responsible for ensuring that the plugin has an associated <a href="https://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngine.html">FlutterEngine</a> instance. Each FlutterEngine instance provides access to a <a href="https://api.flutter.dev/javadoc/io/flutter/embedding/engine/dart/DartExecutor.html">DartExecutor</a> which can be used to execute Dart code in a new isolate. In this case, the FlutterEngine instance has the important task of initializing the callback dispatcher, and executing the callbacks registered with the plugin.</p><p>After startGeofencingService is done executing, onHandleWork is called by the system with the Intent that was queued up earlier:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/045d3882ec8c503a04ada32ca95ae1c7/href">https://medium.com/media/045d3882ec8c503a04ada32ca95ae1c7/href</a></iframe><p>Most of the above code builds the argument list which is sent to the callback dispatcher. However, before passing the processed geofence event arguments to the callback dispatcher, the plugin must ensure that the callback dispatcher has started listening on its MethodChannel. To achieve this behavior, the GeofencingService listens for a message from the callback dispatcher, which is sent after the MethodCall handler for the dispatcher is set:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2675ff79b228c5072135026dda3e68eb/href">https://medium.com/media/2675ff79b228c5072135026dda3e68eb/href</a></iframe><p>At this point, the GeofencingService is completely initialized and any geofencing events that have queued up are sent to the callback dispatcher.</p><h3>Background execution: iOS (Objective-C)</h3><p>Now that the geofencing plugin implementation for Android is finished, the same geofencing functionality needs to be implemented for iOS.</p><h4>Initializing the plugin</h4><p>One of the first tasks performed by the Flutter engine at startup is registering and initializing all plugins used by the application. On iOS, this involves invoking the static registerWithRegistrar method defined for each plugin. For the geofencing plugin, initialization involves creating an instance of GeofencingPlugin and registering it as an application delegate as seen in the code snippet below. This allows for the Flutter engine to delegate handling of certain events to the plugin.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2cbead6d264767921b273a8635618a02/href">https://medium.com/media/2cbead6d264767921b273a8635618a02/href</a></iframe><p>Additional state for the plugin is set when the GeofencingPlugin instance is created during plugin registration:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/10998d73ef57419d5f141bbc89b84418/href">https://medium.com/media/10998d73ef57419d5f141bbc89b84418/href</a></iframe><h4>Starting the callback dispatcher</h4><p>Once initialization of internal state is complete the callback dispatcher needs to be started by invoking startGeofencingService. This is either done when the user calls GeofencingManager.initialize() in their application or when the application is started to handle a geofence event (more on this later).</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a903431ee722594431db6a9931b0fe59/href">https://medium.com/media/a903431ee722594431db6a9931b0fe59/href</a></iframe><p><strong>Note</strong>: the FlutterMethodChannel for the callback dispatcher is only registered <em>after</em> the headless runner has been started. If an attempt to register the callback dispatcher’s method channel is made before this is done, the application will likely crash.</p><h4>Handling method calls</h4><p>Similar to how onMethodCall needed to be implemented on Android to allow for the plugin to handle requests from the Dart interface and callback dispatcher, handleMethodCall must be implemented:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8b9c017e56c0089363cb5e3411497400/href">https://medium.com/media/8b9c017e56c0089363cb5e3411497400/href</a></iframe><h4>Registering geofences</h4><p>With initialization completed, the plugin is ready to register for geofence events. When the plugin user requests for a geofence to be set, registerGeofence is called:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9bdb8b434eb982b9e0ac5f772a7b7e80/href">https://medium.com/media/9bdb8b434eb982b9e0ac5f772a7b7e80/href</a></iframe><p>This method creates the geofence region and uses the CLLocationManager’s startMonitoringForRegion method to register the geofence. In order to keep track of which callback is associated with the newly registered geofence, the callback handle is mapped to the region’s user provided identifier which is stored to disk using NSUserDefaults. Doing this allows for the plugin to lookup the callback handle when a geofence event is received, even if the application had been closed since the geofence was registered.</p><h4>Handling geofence events</h4><p>Once the system determines that a geofence has been entered or exited, the CLLocationManager invokes one of didEnterRegion or didExitRegion. At this point, the callback handle for the geofence which was triggered is retrieved from storage and the callback dispatcher is invoked:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bd82276fe87c0707d1ec5981194ae810/href">https://medium.com/media/bd82276fe87c0707d1ec5981194ae810/href</a></iframe><h4>Geofence events in a suspended state</h4><p>If you are familiar with developing applications for both Android and iOS, you’ll probably know that iOS is much more restrictive than Android when it comes to executing code in the background. Instead of spawning potentially long-running services to handle background events, iOS allows for applications to register for specific types of events which, when received, wake up the application and invoke any relevant delegates. Since the <a href="https://docs.flutter.io/objcdoc/Protocols/FlutterPlugin.html">FlutterPlugin</a> protocol allows for plugins to be registered as delegates, it’s relatively simple to handle any background event provided by the system.</p><p>For geofencing, the plugin needs to implement didFinishLaunchingWithOptions which is invoked when the application has just been started and is ready to run. The dictionary parameter of this method will contain UIApplicationLaunchOptionsLocationKey if the application was launched due to a geofence event.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/622df2edac7daa3214add493467e2e0a/href">https://medium.com/media/622df2edac7daa3214add493467e2e0a/href</a></iframe><p>If the application is launched as the result of a geofence being triggered, the callback dispatcher for the plugin will still need to be initialized by calling startGeofencingService with the cached callback dispatcher handle. After returning from this method, the location manager will invoke the appropriate handler described in the previous section for the geofence event.</p><h3>Usage example: operating a garage door with geofencing</h3><p>Now that the geofencing plugin is fully implemented for both Android and iOS, I can finally put it to good use: automatically opening my garage door as I pedal towards my house!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ae_YppUn96m93qkT" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*at7FCzjM5lAd8NC_" /><figcaption>The relay is triggered by the Raspberry Pi, which opens and closes the door (left). A proximity sensor allows for the garage door service to know whether or not the door is currently open (right).</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/778/0*74sQ7Fl7ueommzJy" /><figcaption><em>My garage door remote, built using Flutter. Can you tell that I’m a backend engineer?</em></figcaption></figure><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fgfycat.com%2Fifr%2FWickedHospitableEidolonhelvum&amp;url=https%3A%2F%2Fgfycat.com%2Fwickedhospitableeidolonhelvum&amp;image=https%3A%2F%2Fthumbs.gfycat.com%2FWickedHospitableEidolonhelvum-size_restricted.gif&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=gfycat" width="1920" height="1080" frameborder="0" scrolling="no"><a href="https://medium.com/media/0ed6fbe779dbc88356994ed5e1c78e9e/href">https://medium.com/media/0ed6fbe779dbc88356994ed5e1c78e9e/href</a></iframe><p>Over the past couple of months I’ve been tinkering with a Raspberry Pi and the Dart <a href="https://pub.dartlang.org/packages/rpi_gpio/versions/0.4.0-dev.1#-readme-tab-">rpi_gpio package</a> to get back into working with circuits. I had been toying around with the idea of making a Flutter application that would open my garage door for quite awhile, so it made sense for my first hardware project to use Dart and Flutter. Over a couple of weekends, I wrote a <a href="https://github.com/bkonyi/GarageDoorController/blob/master/bin/garage_server.dart">service to control the garage door</a>, performed minor surgery to wire a relay across the opener button, installed a proximity sensor to query the state of the door, and finally wrote a <a href="https://github.com/bkonyi/FlutterGarageDoorOpener">simple application using Flutter that functions as a remote control</a>.</p><p>After using my solution for a couple of weeks, I was becoming annoyed. Although it was great that I could open the garage with a mobile application, I still had to pull out my phone, open the app, and then manually trigger the door. This is particularly painful for me since fingerprint readers apparently don’t like to work when covered in sweat, making it difficult to unlock my phone (I don’t have a car and I commute 15km each way by bike, so fingerprint reader struggles are a daily occurrence).</p><p>Luckily for me, Flutter now has a geofencing plugin that can perform tasks even while the application isn’t open, including opening my garage door!</p><h4>Setting the right permissions</h4><p>The first thing I’ll do is ensure that my application has the right permissions to use the geofencing plugin.</p><h4>Permissions: Android</h4><p>In order for the geofencing plugin to run in the background, GeofencingBroadcastReceiver and GeofencingService need to be registered in AndroidManifest.xml:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/659ce39c05baf67a257321c44679bb37/href">https://medium.com/media/659ce39c05baf67a257321c44679bb37/href</a></iframe><p>Geofencing on Android also requires the ACCESS_FINE_LOCATION and ACCESS_BACKGROUND_LOCATION (Android 10+) permissions to be requested in AndroidManifest.xml:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1a152ee12fa9a9ddacb823a90b5a2d58/href">https://medium.com/media/1a152ee12fa9a9ddacb823a90b5a2d58/href</a></iframe><h4>Permissions: iOS</h4><p>Requesting the relevant permissions on iOS requires some simple modifications to Info.plist in the ios directory of the application. First, add the following lines to request background location updates:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ba37362a2d45c8b45b14e252923a70f8/href">https://medium.com/media/ba37362a2d45c8b45b14e252923a70f8/href</a></iframe><p>Then set the NSLocation description messages:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5b24b820c034efdc85c5bfb4ef42c597/href">https://medium.com/media/5b24b820c034efdc85c5bfb4ef42c597/href</a></iframe><p>These descriptions are shown to the user when the application requests access to their location. <strong>If they’re not provided, geofencing registration will fail silently!</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b52f1a56c42ef25425574e2e7d8d9816/href">https://medium.com/media/b52f1a56c42ef25425574e2e7d8d9816/href</a></iframe><p>Finally, similarly to what was done for Android, set a reference to the application’s plugin registrant within GeofencingPlugin from the application’s AppDelegate. This is needed to register the application’s plugins with the geofencing plugin’s background isolate, which makes it possible to use other plugins in the context of that isolate.</p><h4>Bringing it all together</h4><p>Now that the permissions required for geofencing have been set correctly, all that’s left to do is wiring the plugin into the garage door remote application.</p><p>First of all, the plugin needs to be initialized. This is done in a method named initialize, which is invoked when the application starts:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6c63d1fb426eacf27eaf562d17fe95cf/href">https://medium.com/media/6c63d1fb426eacf27eaf562d17fe95cf/href</a></iframe><p>Next, there should be some way to toggle whether or not the garage door should open when the geofence is entered. This can be accomplished with a simple Switch displayed at the bottom of the application:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a02a06bfb8a3d568c956322aca674663/href">https://medium.com/media/a02a06bfb8a3d568c956322aca674663/href</a></iframe><p>Depending on the state of this Switch, the application either registers a geofence with the callback used to open the garage door, or removes the geofence.</p><p>Finally, the geofence region around my home and its corresponding callback are defined in GeofenceTrigger:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ff62591c7a1a5104b6cd0185d554d261/href">https://medium.com/media/ff62591c7a1a5104b6cd0185d554d261/href</a></iframe><p>For my initial tests I created a GeofenceRegion with a radius of 300m around my home that will trigger at some point after I enter the area. Once the geofence is entered the homeGeofenceCallback is invoked, checks are performed to ensure the application can communicate with the garage door server, and then a request to open the door is sent. Once I confirmed that the logic within the callback actually triggered the garage door to open using a third-party application to mock my location and movements, it was time to do some real world testing on my bike!</p><p>After a few trips up and down the street it became apparent that, although geofences are triggered almost immediately when using a mocked location, Android provides no guarantees as to when a geofence event is delivered. Unfortunately, this means that it can potentially take minutes before my garage door remote is notified that I’ve entered the geofence region around my home. With a radius as small as 300m, I often found myself waiting a minute or two for the door to open on its own.</p><p>The temporary fix for this was to increase the radius of the geofence region to 1km, which seems to work well enough for now. Obviously, there’s some issues with this approach, but I plan on further refining the proximity triggering logic to use a larger geofence that will start more frequent location updates. These location updates will then be used to manually determine whether I’m within a certain radius of my house, at which point the request to open the door will be sent.</p><h3>Conclusion</h3><p>If you’ve made it this far you should now have all the knowledge you need to create a Flutter plugin that executes Dart code in the background! Of course, I was only able to scratch the surface of plugin development for Flutter in this article but I would highly recommend the documentation for <a href="https://flutter.io/developing-packages/">developing packages and plugins</a> as well as the walkthrough for <a href="https://flutter.io/platform-channels/">writing custom platform-specific code with platform channels</a>. If you want a deeper look into how the geofencing plugin is implemented, the full source can be found in the <a href="https://github.com/bkonyi/FlutterGeofencing">FlutterGeofencing</a> repo on GitHub. I’ve also linked to all the additional documentation, sample plugins, and projects mentioned throughout this article in the Resources section below.</p><p>I’ve had a lot of fun implementing background execution support for Flutter, and even more fun creating the geofencing plugin for this article. (It was a wonderful excuse to work on a personal project as part of my job!) If you feel so inclined, <a href="https://github.com/bkonyi">follow me on GitHub</a> to keep up with my work on Flutter and the Dart virtual machine, as well as my other pet projects.</p><p>Thanks for reading and happy Fluttering!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*D6jZA_ksvRN5EJi1" /><figcaption>Like many members of the Dart and Flutter teams, Dash loves cycling. However, for obvious reasons, Dash has a bit of trouble riding a bike.</figcaption></figure><h3>Resources</h3><h4>Docs for Dart:</h4><ul><li><a href="https://api.flutter.dev/flutter/dart-ui/PluginUtilities-class.html"><strong>PluginUtilities</strong></a>: contains methods for dealing with CallbackHandles</li><li><a href="https://api.flutter.dev/flutter/dart-ui/IsolateNameServer-class.html"><strong>IsolateNameServer</strong></a>: establishing communication between foreground and background isolates using named SendPorts</li></ul><h4>Docs for Android:</h4><ul><li><a href="https://api.flutter.dev/javadoc/overview-summary.html"><strong>Flutter Java APIs</strong></a></li><li><a href="https://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngine.html"><strong>FlutterEngine</strong>: allows for spawning a background isolate</a></li><li><a href="https://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngine.html"><strong>FlutterCallbackInformation</strong></a>: lookup callback information needed for starting a background isolate using callback handles</li></ul><h4>Docs for iOS:</h4><ul><li><a href="https://api.flutter.dev/objcdoc/Classes/FlutterEngine.html"><strong>FlutterEngine</strong></a>: allows for spawning a background isolate</li><li><a href="https://api.flutter.dev/objcdoc/Classes/FlutterCallbackCache.html"><strong>FlutterCallbackCache</strong></a>: lookup callback information needed for starting a background isolate using callback handles</li></ul><h4>Sample Plugins:</h4><ul><li><a href="https://github.com/bkonyi/FlutterGeofencing"><strong>FlutterGeofencing</strong></a>: the geofencing plugin created for this article</li><li><a href="https://pub.dartlang.org/packages/android_alarm_manager#-readme-tab-"><strong>android_alarm_manager</strong></a>: an AlarmManager plugin for Flutter</li><li><a href="https://pub.dev/packages/background_location"><strong>background_location</strong></a>: plugin for listening for significant location changes on Android and iOS</li></ul><h4>Projects referenced:</h4><ul><li><a href="https://github.com/danrubel/rpi_gpio.dart"><strong>rpi_gpio</strong></a>: package for accessing the Raspberry Pi GPIO pins.</li><li><a href="https://github.com/bkonyi/FlutterGarageDoorOpener"><strong>FlutterGarageDoorOpener</strong></a>: garage door remote control using Flutter</li><li><a href="https://github.com/bkonyi/GarageDoorController"><strong>GarageDoorController</strong></a>: the garage door controller library and service</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2b3e40a1a124" width="1" height="1" alt=""><hr><p><a href="https://blog.flutter.dev/executing-dart-in-the-background-with-flutter-plugins-and-geofencing-2b3e40a1a124">Executing Dart in the Background with Flutter Plugins and Geofencing</a> was originally published in <a href="https://blog.flutter.dev/">Flutter</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>