<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yang</title>
    <description>The latest articles on DEV Community by Yang (@ychescale9).</description>
    <link>https://dev.to/ychescale9</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F260615%2F0c3390ff-6344-45a9-87ce-c31db01fd91e.png</url>
      <title>DEV Community: Yang</title>
      <link>https://dev.to/ychescale9</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ychescale9"/>
    <language>en</language>
    <item>
      <title>FlowBinding 1.0</title>
      <dc:creator>Yang</dc:creator>
      <pubDate>Thu, 24 Dec 2020 07:48:18 +0000</pubDate>
      <link>https://dev.to/ychescale9/flowbinding-1-0-44h</link>
      <guid>https://dev.to/ychescale9/flowbinding-1-0-44h</guid>
      <description>&lt;p&gt;It's been over a year since the initial release of &lt;a href="https://github.com/ReactiveCircus/FlowBinding/releases/tag/0.11.0" rel="noopener noreferrer"&gt;FlowBinding&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Over the last year we've added 4 new artifacts for AndroidX libraries, dropped our minimum SDK all the way back to API 14, introduced a new &lt;code&gt;InitialValueFlow&lt;/code&gt; API for widgets that emit state changes, and &lt;a href="https://reactivecircus.github.io/FlowBinding/api/-modules.html" rel="noopener noreferrer"&gt;new API docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Today I'm excited share the &lt;a href="https://github.com/ReactiveCircus/FlowBinding/releases/tag/1.0.0" rel="noopener noreferrer"&gt;1.0 stable release&lt;/a&gt; of FlowBinding!&lt;/p&gt;

&lt;h3&gt;
  
  
  What's new
&lt;/h3&gt;

&lt;p&gt;While the majority of the APIs have remained the same since the first public release (&lt;a href="https://dev.to/ychescale9/binding-android-ui-with-kotlin-flow-22ok"&gt;original blogpost&lt;/a&gt;) last year, we've made a decent amount of improvements to the library over the last 14 months.&lt;/p&gt;

&lt;h4&gt;
  
  
  New artifacts
&lt;/h4&gt;

&lt;p&gt;Our focus has been on providing bindings for &lt;code&gt;flowbinding-material&lt;/code&gt; which is where most of the new UI widgets are being developed, but we also added 4 new artifacts for AndroidX libraries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/ReactiveCircus/FlowBinding/tree/main/flowbinding-activity" rel="noopener noreferrer"&gt;flowbinding-activity&lt;/a&gt; - provides binding on &lt;code&gt;OnBackPressedDispatcher&lt;/code&gt; from &lt;a href="https://developer.android.com/guide/components/activities/intro-activities" rel="noopener noreferrer"&gt;AndroidX Activity&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ReactiveCircus/FlowBinding/tree/main/flowbinding-lifecycle" rel="noopener noreferrer"&gt;flowbinding-lifecycle&lt;/a&gt; - turns lifecycle change events from &lt;a href="https://developer.android.com/topic/libraries/architecture/lifecycle" rel="noopener noreferrer"&gt;AndroidX Lifecycle&lt;/a&gt; into &lt;code&gt;Flow&amp;lt;Lifecycle.Event&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ReactiveCircus/FlowBinding/tree/main/flowbinding-preference" rel="noopener noreferrer"&gt;flowbinding-preference&lt;/a&gt; - provides bindings for &lt;code&gt;Preference&lt;/code&gt; and &lt;code&gt;EditTextPreference&lt;/code&gt; from &lt;a href="https://developer.android.com/guide/topics/ui/settings" rel="noopener noreferrer"&gt;AndroidX Preference (Settings)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ReactiveCircus/FlowBinding/tree/main/flowbinding-viewpager" rel="noopener noreferrer"&gt;flowbinding-viewpager&lt;/a&gt; - backports bindings from &lt;code&gt;flowbinding-viewpager2&lt;/code&gt; for the legacy &lt;code&gt;androidx.viewpager.widget.ViewPager&lt;/code&gt; artifact.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Min SDK 14
&lt;/h4&gt;

&lt;p&gt;Originally the &lt;code&gt;minSdkVersion&lt;/code&gt; FlowBinding required was &lt;strong&gt;API 21&lt;/strong&gt;. While this is reasonable for most users, support for lower API levels remains essential for users who are developing for emerging markets (see this &lt;a href="https://twitter.com/ychescale9/status/1232472676645535744" rel="noopener noreferrer"&gt;tweet&lt;/a&gt; for more context).&lt;/p&gt;

&lt;p&gt;We've now dropped our &lt;code&gt;minSdkVersion&lt;/code&gt; to &lt;strong&gt;API 14&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Initial value
&lt;/h4&gt;

&lt;p&gt;For UI widgets that hold a state internally such as the current value of a &lt;strong&gt;Slider&lt;/strong&gt; (a recently added &lt;a href="https://github.com/material-components/material-components-android/blob/master/docs/components/Slider.md" rel="noopener noreferrer"&gt;Material Component&lt;/a&gt;), our bindings used to provide an optional &lt;code&gt;emitImmediately: Boolean&lt;/code&gt; to indicate whether the current value should be emitted &lt;strong&gt;immediately&lt;/strong&gt; when collected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@CheckResult&lt;/span&gt;
&lt;span class="nd"&gt;@UseExperimental&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ExperimentalCoroutinesApi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Slider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valueChanges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emitImmediately&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;callbackFlow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default value of &lt;code&gt;emitImmediately&lt;/code&gt; was &lt;code&gt;false&lt;/code&gt; which &lt;a href="https://github.com/ReactiveCircus/FlowBinding/issues/94" rel="noopener noreferrer"&gt;not everyone agrees&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After discussions within the community we've decided to introduce a new &lt;code&gt;InitialValueFlow&lt;/code&gt; for the bindings that emit state changes (many thanks to &lt;a href="https://github.com/svenjacobs" rel="noopener noreferrer"&gt;Sven Jacobs&lt;/a&gt; for the idea).&lt;/p&gt;

&lt;p&gt;An &lt;code&gt;InitialValueFlow&lt;/code&gt; emits the current value (state) of the widget immediately upon collection of the Flow.&lt;/p&gt;

&lt;p&gt;This effectively changed the previous default value of &lt;code&gt;emitImmediately&lt;/code&gt; from &lt;code&gt;false&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;, but communicates the behavior more clearly in the type itself.&lt;/p&gt;

&lt;p&gt;To skip the initial emission of the current value, call the &lt;code&gt;skipInitialValue()&lt;/code&gt; function on the &lt;code&gt;InitialValueFlow&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;slider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valueChanges&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;skipInitialValue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="c1"&gt;// handle value&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launchIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uiScope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// current value won't be emitted immediately&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is also consistent with RxBinding's &lt;code&gt;InitialValueObservable&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  New API Docs
&lt;/h4&gt;

&lt;p&gt;FlowBinding has many artifacts and APIs. We now have a new &lt;a href="https://reactivecircus.github.io/FlowBinding/" rel="noopener noreferrer"&gt;project website&lt;/a&gt; that documents all transitive AndroidX dependencies and the bindings available in each artifact, along with &lt;a href="https://reactivecircus.github.io/FlowBinding/api/-modules.html" rel="noopener noreferrer"&gt;new API docs&lt;/a&gt; powered by Dokka 1.4.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmy69kf34uhx4pm6kjfut.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmy69kf34uhx4pm6kjfut.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What's next?
&lt;/h3&gt;

&lt;p&gt;Jetpack Compose is clearly the &lt;strong&gt;future&lt;/strong&gt; of UI development for Android, while FlowBinding specifically targets the &lt;strong&gt;current&lt;/strong&gt; generation of UI toolkits, which isn't going away anytime soon. The &lt;a href="https://github.com/material-components/material-components-android" rel="noopener noreferrer"&gt;material-components-android&lt;/a&gt; library is still being actively developed with many new view-based components being added or planned.&lt;/p&gt;

&lt;p&gt;Therefore we're planning to continue the work on maintaining AndroidX bindings and adding bindings for the new Material Design Components (e.g. &lt;a href="https://material.io/components/time-pickers/android#code" rel="noopener noreferrer"&gt;TimePicker&lt;/a&gt;) as they become available.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thanks!
&lt;/h3&gt;

&lt;p&gt;I'd like to thank everyone who has used FlowBinding, reported bugs, made feature requests, participated in discussions or sent PRs in the last 14 months, which all contributed to what we've achieved with the library today!&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
    </item>
    <item>
      <title>Git-based Android App Versioning with AGP 4.0</title>
      <dc:creator>Yang</dc:creator>
      <pubDate>Fri, 11 Sep 2020 11:27:37 +0000</pubDate>
      <link>https://dev.to/ychescale9/git-based-android-app-versioning-with-agp-4-0-24ip</link>
      <guid>https://dev.to/ychescale9/git-based-android-app-versioning-with-agp-4-0-24ip</guid>
      <description>&lt;p&gt;An Android app has 2 pieces of &lt;a href="https://developer.android.com/studio/publish/versioning#appversioning"&gt;version information&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;versionCode&lt;/code&gt; - a positive integer used as an internal version number of the app that needs to monotonically increase with each subsequent release.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;versionName&lt;/code&gt; - a string as a version number shown to users.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To automate the process of generating the &lt;code&gt;versionCode&lt;/code&gt; and &lt;code&gt;versionName&lt;/code&gt; for an app release, a common approach is to compute these values from &lt;strong&gt;Git tags&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;android&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;defaultConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;versionCode&lt;/span&gt; &lt;span class="nf"&gt;buildVersionCode&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;versionName&lt;/span&gt; &lt;span class="nf"&gt;buildVersionName&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;buildVersionCode()&lt;/code&gt; and &lt;code&gt;buildVersionName()&lt;/code&gt; need to execute a command-line process to fetch the git information (e.g. latest tag), parse it and generate the &lt;code&gt;versionCode&lt;/code&gt; and &lt;code&gt;versionName&lt;/code&gt; required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="kt"&gt;def&lt;/span&gt; &lt;span class="n"&gt;buildVersionCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;def&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ByteArrayOutputStream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
      &lt;span class="n"&gt;exec&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;commandLine&lt;/span&gt; &lt;span class="s1"&gt;'git'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'describe'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'--tags'&lt;/span&gt;
        &lt;span class="n"&gt;standardOutput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="c1"&gt;// computeVersionCodeFromTag(tag) might use a formula such as MAJOR * 10000 + MINOR * 100 + PATCH for SemVer tags.&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;computeVersionCodeFromTag&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;trim&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ignored&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;def&lt;/span&gt; &lt;span class="n"&gt;buildVersionName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="kt"&gt;def&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ByteArrayOutputStream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
      &lt;span class="n"&gt;exec&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;commandLine&lt;/span&gt; &lt;span class="s1"&gt;'git'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'describe'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'--tags'&lt;/span&gt;
          &lt;span class="n"&gt;standardOutput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;trim&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ignored&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The main issue here is that we are doing &lt;strong&gt;computation during configuration&lt;/strong&gt; of the build which is extremely inefficient as &lt;code&gt;buildVersionCode&lt;/code&gt; and &lt;code&gt;buildVersionName&lt;/code&gt; are executed every time a project is configured (e.g. every Gradle sync from the IDE, or just running &lt;code&gt;./gradlew help&lt;/code&gt;), whereas in practice the &lt;code&gt;versionCode&lt;/code&gt; and &lt;code&gt;versionName&lt;/code&gt; are only required when assembling an APK or AAB.&lt;/p&gt;

&lt;p&gt;While Gradle has been pushing for &lt;a href="https://docs.gradle.org/current/userguide/lazy_configuration.html"&gt;Lazy Task Configuration&lt;/a&gt; and &lt;a href="https://docs.gradle.org/current/userguide/task_configuration_avoidance.html"&gt;Task Configuration Avoidance&lt;/a&gt; for a couple of years now, the &lt;strong&gt;Android Gradle Plugin (AGP)&lt;/strong&gt; had not migrated some of the most important APIs to support Gradle properties and providers until very recently. Thus there wasn't an easy way to avoid computing &lt;code&gt;versionCode&lt;/code&gt; and &lt;code&gt;versionName&lt;/code&gt; during configuration (here's &lt;a href="https://www.youtube.com/watch?v=sQC9-Rj2yLI&amp;amp;list=PLWz5rJ2EKKc9FfSQIRXEWyWpHD6TtwxMM&amp;amp;index=40"&gt;a workaround&lt;/a&gt; provided by the AGP team back at IO 19).&lt;/p&gt;

&lt;p&gt;This issue was finally addressed in &lt;strong&gt;AGP 4.0&lt;/strong&gt; with the introduction of 2 new APIs for configuring the build variants early in the configuration phase: &lt;a href="https://developer.android.com/reference/tools/gradle-api/com/android/build/api/dsl/CommonExtension#onvariants_1"&gt;onVariants&lt;/a&gt; and &lt;a href="https://developer.android.com/reference/tools/gradle-api/com/android/build/api/dsl/CommonExtension#onvariantproperties_1"&gt;onVariantsProperties&lt;/a&gt;. Here's the &lt;a href="https://github.com/android/gradle-recipes/blob/master/BuildSrc/setVersionsFromTask/buildSrc/src/main/kotlin/CustomPlugin.kt"&gt;skeleton&lt;/a&gt; for generating &lt;code&gt;versionCode&lt;/code&gt; and &lt;code&gt;versionName&lt;/code&gt; in a Gradle task from the official &lt;a href="https://github.com/android/gradle-recipes"&gt;Android Plugin for Gradle cookbook&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Android App Versioning Gradle Plugin
&lt;/h2&gt;

&lt;p&gt;A few months ago while working on automating the release process at work, I decided to take a closer look at the new &lt;code&gt;onVariantProperties&lt;/code&gt; API and move the version info computation logic to a proper Gradle task. The initial prototype was developed in &lt;a href="https://github.com/ReactiveCircus/streamlined/tree/187fa01d0b3245dcd4af30467780f3b19b3eb05d/buildSrc/src/main/kotlin/io/github/reactivecircus/streamlined/versioning"&gt;streamlined&lt;/a&gt; and had received some &lt;a href="https://twitter.com/ychescale9/status/1249557110444023809"&gt;interests from the community&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;After adding support for customizing &lt;strong&gt;versionCode&lt;/strong&gt; and &lt;strong&gt;versionName&lt;/strong&gt; with a lambda and simplifying the plugin configurations, today I’m happy to share &lt;a href="https://github.com/ReactiveCircus/app-versioning"&gt;App Versioning&lt;/a&gt; - a Gradle Plugin for lazily generating Android app's &lt;code&gt;versionCode&lt;/code&gt; &amp;amp; &lt;code&gt;versionName&lt;/code&gt; from Git tags.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;Let's start by applying the plugin to the &lt;code&gt;app&lt;/code&gt; module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.android.application"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"io.github.reactivecircus.app-versioning"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="s"&gt;"x.y.z"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note that the samples in the post are written with &lt;a href="https://docs.gradle.org/current/userguide/kotlin_dsl.html"&gt;Gradle Kotlin DSL&lt;/a&gt; but traditional Groovy DSL is also fully supported.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At this point if your repository has a Git tag that follows &lt;a href="https://semver.org/"&gt;Semantic Versioning&lt;/a&gt;, &lt;strong&gt;app-versioning&lt;/strong&gt; will start generating &lt;code&gt;versionCode&lt;/code&gt; and &lt;code&gt;versionName&lt;/code&gt; for your APK / AAB!&lt;/p&gt;

&lt;p&gt;Let's imagine the latest tag in your current branch is &lt;code&gt;1.3.1&lt;/code&gt;. Assembling the APK with &lt;code&gt;./gradlew assembleRelease&lt;/code&gt; will trigger a &lt;code&gt;generateAppVersionInfoForRelease&lt;/code&gt; task which generates both the &lt;code&gt;versionCode&lt;/code&gt; and &lt;code&gt;versionName&lt;/code&gt; for the assembled APK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; Task :app:generateAppVersionInfoForRelease
Generated app version code: 10301.
Generated app version name: "1.3.1".
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The generated &lt;code&gt;versionCode&lt;/code&gt; and &lt;code&gt;versionName&lt;/code&gt; are injected into the merged manifest at &lt;code&gt;app/build/intermediates/merged_manifests/release/AndroidManifest.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;manifest&lt;/span&gt; &lt;span class="na"&gt;xmlns:android=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.android.com/apk/res/android"&lt;/span&gt;
    &lt;span class="na"&gt;package=&lt;/span&gt;&lt;span class="s"&gt;"io.github.reactivecircus.streamlined"&lt;/span&gt;
    &lt;span class="na"&gt;android:versionCode=&lt;/span&gt;&lt;span class="s"&gt;"10301"&lt;/span&gt;
    &lt;span class="na"&gt;android:versionName=&lt;/span&gt;&lt;span class="s"&gt;"1.3.1"&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;/manifest&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Default rules
&lt;/h3&gt;

&lt;p&gt;Without providing any configurations, &lt;strong&gt;app-versioning&lt;/strong&gt; will fetch the latest Git tag in the repository, attempt to parse it into a &lt;strong&gt;SemVer&lt;/strong&gt; string, and compute the &lt;code&gt;versionCode&lt;/code&gt; following &lt;a href="https://en.wikipedia.org/wiki/Positional_notation"&gt;positional notation&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;versionCode = MAJOR * 10000 + MINOR * 100 + PATCH
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So for the tag &lt;code&gt;1.3.1&lt;/code&gt;, the generated &lt;code&gt;versionCode&lt;/code&gt; is &lt;code&gt;1 * 10000 + 3 * 100 + 1 = 10301&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;versionName&lt;/code&gt; generated will just be the name of the latest Git tag (&lt;code&gt;1.3.1&lt;/code&gt; in the example above).&lt;/p&gt;

&lt;p&gt;If the default rules described above work for you, you are now ready to enjoy automated version info generation without further configurations! But chances are you might want to tweak the formula used for generating &lt;code&gt;versionCode&lt;/code&gt;, or inject additional metadata into the &lt;code&gt;versionCode&lt;/code&gt; and/or &lt;code&gt;versionName&lt;/code&gt; to fit your existing process and workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instead of reserving &lt;strong&gt;2&lt;/strong&gt; digits for each of the &lt;strong&gt;MAJOR&lt;/strong&gt;, &lt;strong&gt;MINOR&lt;/strong&gt;, and &lt;strong&gt;PATCH&lt;/strong&gt; components, you might want to use a smaller or bigger range.&lt;/li&gt;
&lt;li&gt;You might want to add an additional &lt;code&gt;BUILD_NUMBER&lt;/code&gt; from CI to the &lt;code&gt;versionCode&lt;/code&gt; or &lt;code&gt;versionName&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You might need to include the &lt;strong&gt;commit hash&lt;/strong&gt; in the &lt;code&gt;versionName&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that in mind, let's now take a look at how these custom rules can be specified with a couple of plugin configurations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom rules
&lt;/h3&gt;

&lt;p&gt;To maximize the supported use cases without introducing too many configurations, &lt;strong&gt;app-versioning&lt;/strong&gt; lets you define how you want to compute the &lt;code&gt;versionCode&lt;/code&gt; and &lt;code&gt;versionName&lt;/code&gt; by implementing lambdas which are evaluated lazily during execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;appVersioning&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nf"&gt;overrideVersionCode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;gitTag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;providers&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;// TODO generate an Int from the given gitTag and/or providers&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;overrideVersionName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;gitTag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;providers&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;// TODO generate a String from the given gitTag and/or providers&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;code&gt;GitTag&lt;/code&gt; is a type-safe representation of a tag encapsulating the &lt;code&gt;rawTagName&lt;/code&gt;, &lt;code&gt;commitsSinceLatestTag&lt;/code&gt; and &lt;code&gt;commitHash&lt;/code&gt;, provided by the plugin.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;providers&lt;/code&gt; is a &lt;code&gt;ProviderFactory&lt;/code&gt; instance which is a Gradle API that can be useful for &lt;a href="https://docs.gradle.org/current/javadoc/org/gradle/api/provider/ProviderFactory.html"&gt;reading environment variables and system properties lazily&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  SemVer-based version code
&lt;/h3&gt;

&lt;p&gt;As mentioned earlier the plugin by default reserves 2 digits for each of the &lt;strong&gt;MAJOR&lt;/strong&gt;, &lt;strong&gt;MINOR&lt;/strong&gt; and &lt;strong&gt;PATCH&lt;/strong&gt; components in a SemVer tag.&lt;/p&gt;

&lt;p&gt;To allocate 3 digits per component instead (i.e. each version component can go up to 999):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.github.reactivecircus.appversioning.toSemVer&lt;/span&gt;
&lt;span class="nf"&gt;appVersioning&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;overrideVersionCode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;gitTag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;providers&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;semVer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gitTag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toSemVer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;semVer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;major&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000000&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;semVer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minor&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;semVer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patch&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;code&gt;toSemVer()&lt;/code&gt; is an extension function (or &lt;code&gt;SemVer.fromGitTag(gitTag)&lt;/code&gt; if you use Groovy) provided by the plugin to help create a type-safe &lt;code&gt;SemVer&lt;/code&gt; object from the &lt;code&gt;GitTag&lt;/code&gt; by parsing its &lt;code&gt;rawTagName&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;If a Git tag is not fully &lt;a href="https://semver.org/#semantic-versioning-specification-semver"&gt;SemVer compliant&lt;/a&gt; (e.g. &lt;code&gt;1.2&lt;/code&gt;), calling &lt;code&gt;gitTag.toSemVer()&lt;/code&gt; will throw an exception. In that case we'll need to find another way to compute the &lt;code&gt;versionCode&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using timestamp for version code
&lt;/h3&gt;

&lt;p&gt;Since the key characteristic for &lt;code&gt;versionCode&lt;/code&gt; is that it must monotonically increase with each app release, a common approach is to use the Epoch / Unix timestamp for &lt;code&gt;versionCode&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.time.Instant&lt;/span&gt;
&lt;span class="nf"&gt;appVersioning&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;overrideVersionCode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;epochSecond&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toInt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This will generate a monotonically increasing version code every time the &lt;code&gt;generateAppVersionInfoFor&amp;lt;BuildVariant&amp;gt;&lt;/code&gt; task is run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generated app version code: 1599750437.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Using environment variable
&lt;/h3&gt;

&lt;p&gt;Another common practice when computing version code is to add a &lt;code&gt;BUILD_NUMBER&lt;/code&gt; environment variable provided by CI to the formula. To do this, we can use the &lt;code&gt;providers&lt;/code&gt; lambda parameter to create a provider that's only queried during execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.github.reactivecircus.appversioning.toSemVer&lt;/span&gt;
&lt;span class="nf"&gt;appVersioning&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;overrideVersionCode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;gitTag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;providers&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;buildNumber&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;providers&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;environmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BUILD_NUMBER"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrElse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toInt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;semVer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gitTag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toSemVer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;semVer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;major&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;semVer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minor&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;semVer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patch&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;buildNumber&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;code&gt;versionName&lt;/code&gt; can be customized with the same approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.github.reactivecircus.appversioning.toSemVer&lt;/span&gt;
&lt;span class="nf"&gt;appVersioning&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;overrideVersionName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;gitTag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;providers&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;// a custom versionName combining the tag name, commitHash and an environment variable&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;buildNumber&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;providers&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;environmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BUILD_NUMBER"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrElse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toInt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="s"&gt;"${gitTag.rawTagName} - #$buildNumber (${gitTag.commitHash})"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Custom version name generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generated app version name: "1.3.1 - #345 (f0b6056)".
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  App versioning on CI
&lt;/h2&gt;

&lt;p&gt;Release APKs are usually built on CI, where the build configurations and host environment are likely to be different from a local dev environment. There are a couple of things worth noting when using the &lt;strong&gt;app-versioning&lt;/strong&gt; plugin on CI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enable the plugin only when necessary
&lt;/h3&gt;

&lt;p&gt;For performance reason many CI providers only fetch a single commit by default when checking out the repository. For &lt;strong&gt;app-versioning&lt;/strong&gt; to work we need to make sure Git tags are also fetched. Here's an example for doing this with &lt;a href="https://github.com/actions/checkout"&gt;GutHub Actions&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- uses: actions/checkout@v2
  with:
    fetch-depth: 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;CI jobs such as unit tests and Lint usually do not require a &lt;code&gt;versionCode&lt;/code&gt; to be present in the merged &lt;code&gt;AndroidManifest&lt;/code&gt; to complete. If these CI jobs are sharded such that they run in separate VMs in parallel, we can disable the &lt;strong&gt;app-versioning&lt;/strong&gt; plugin and not worry about telling the CI to fetch those additional Git tags when checking out.&lt;/p&gt;

&lt;p&gt;To do this we can define an environment variable &lt;code&gt;ENABLE_APP_VERSIONING&lt;/code&gt; and set it to &lt;code&gt;false&lt;/code&gt; to indicate that no &lt;code&gt;versionCode&lt;/code&gt; or &lt;code&gt;versionName&lt;/code&gt; need to be generated by the &lt;strong&gt;app-versioning&lt;/strong&gt; plugin for this job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unit-tests:
  name: Unit tests
  runs-on: ubuntu-latest
  env:
    ENABLE_APP_VERSIONING: false
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We can then enable / disable the plugin based on the value of the environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;enableAppVersioning&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;providers&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;environmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ENABLE_APP_VERSIONING"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forUseAtConfigurationTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrElse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toBoolean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;appVersioning&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enableAppVersioning&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note that we need to use the experimental &lt;a href="https://docs.gradle.org/current/javadoc/org/gradle/api/provider/Provider.html#forUseAtConfigurationTime--"&gt;forUseAtConfigurationTime()&lt;/a&gt; API to create a configuration time provider.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Retrieving the generated version code and version name
&lt;/h3&gt;

&lt;p&gt;There are cases where you may need to retrieve the &lt;code&gt;versionCode&lt;/code&gt; and &lt;code&gt;versionName&lt;/code&gt; of a generated APK / AAB:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;including the version code and version name in a Slack message sent from CI after a successful build&lt;/li&gt;
&lt;li&gt;communicating with a 3rd party service where version code and version name need to be included in the request (e.g. uploading R8 mapping file to a crashing reporting service)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both the &lt;code&gt;versionCode&lt;/code&gt; and &lt;code&gt;versionName&lt;/code&gt; generated by &lt;strong&gt;app-versioning&lt;/strong&gt; are in the build output directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/build/outputs/app_versioning/&amp;lt;buildVariant&amp;gt;/version_code.txt
app/build/outputs/app_versioning/&amp;lt;buildVariant&amp;gt;/version_name.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We can &lt;code&gt;cat&lt;/code&gt; the output of these files into variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VERSION_CODE=$(cat app/build/outputs/app_versioning/&amp;lt;buildVariant&amp;gt;/version_code.txt)
VERSION_NAME=$(cat app/build/outputs/app_versioning/&amp;lt;buildVariant&amp;gt;/version_name.txt)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Note that if you need to query these files in a different VM than where the APK (and its version info) was originally generated, you need to make sure these files are "carried over" from the original VM. Otherwise you'll need to run the &lt;code&gt;generateAppVersionInfoFor&amp;lt;BuildVariant&amp;gt;&lt;/code&gt; task again to generate these files, but the generated version info might not be the same as what's actually used for the APK (e.g. if you use the Epoch timestamp for &lt;code&gt;versionCode&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Here's an example with GitHub Actions that does the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;in the &lt;a href="https://github.com/ReactiveCircus/streamlined/blob/f0b605627ffaa2a51b37cdec7c2dd846ad3a7dbf/.github/workflows/ci.yml#L33-L72"&gt;Assemble job&lt;/a&gt;, build the App Bundle and archive / upload the build outputs directory which include the AAB and its R8 mapping file, along with the &lt;code&gt;version_code.txt&lt;/code&gt; and &lt;code&gt;version_name.txt&lt;/code&gt; files generated by &lt;strong&gt;app-versioning&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;later in the &lt;a href="https://github.com/ReactiveCircus/streamlined/blob/f0b605627ffaa2a51b37cdec7c2dd846ad3a7dbf/.github/workflows/ci.yml#L202-L247"&gt;Publish to Play Store job&lt;/a&gt;, download the previously archived build outputs directory, &lt;code&gt;cat&lt;/code&gt; the content of &lt;code&gt;version_code.txt&lt;/code&gt; and &lt;code&gt;version_name.txt&lt;/code&gt; into variables, upload the R8 mapping file to Bugsnag API with curl and passing the retrieved &lt;code&gt;$VERSION_CODE&lt;/code&gt; and &lt;code&gt;$VERSION_NAME&lt;/code&gt; as parameters, and finally upload the AAB to Play Store (without building the AAB or generating the app version info again).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lazy, incremental, cacheable
&lt;/h2&gt;

&lt;p&gt;By moving version info generation to a Gradle task, we're finally able to avoid doing computation during Gradle's task configuration phase.&lt;/p&gt;

&lt;p&gt;We can take this further by making the &lt;code&gt;generateAppInfoFor&amp;lt;BuildVariant&amp;gt;&lt;/code&gt; task &lt;strong&gt;incremental&lt;/strong&gt; and &lt;strong&gt;cacheable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To do this the plugin tracks the &lt;code&gt;.git/refs&lt;/code&gt; directory as a task input, so the task only becomes "dirty" and re-runs when there are changes to the Git references in the repository e.g. adding new commits or tags.&lt;/p&gt;

&lt;p&gt;Task is executed with the first run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./gradlew generateAppVersionInfoForProdRelease
...
BUILD SUCCESSFUL in 3s
1 actionable task: 1 executed
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Task is &lt;code&gt;up-to-date&lt;/code&gt; with the subsequent run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./gradlew generateAppVersionInfoForProdRelease
...
BUILD SUCCESSFUL in 1s
1 actionable task: 1 up-to-date
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Task output is loaded from build cache (&lt;a href="https://docs.gradle.org/current/userguide/build_cache.html"&gt;Gradle build cache&lt;/a&gt; needs to be enabled) after cleaning the project level build directory and running the task again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./gradlew clean
./gradlew generateAppVersionInfoForProdRelease
...
BUILD SUCCESSFUL in 1s
1 actionable task: 1 from cache
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Customizing version code and version name has become easier and faster thanks to the &lt;a href="https://medium.com/androiddevelopers/new-apis-in-the-android-gradle-plugin-f5325742e614"&gt;new APIs introduced in Android Gradle Plugin 4.x&lt;/a&gt;. The &lt;a href="https://github.com/ReactiveCircus/app-versioning"&gt;Android App Versioning Gradle Plugin&lt;/a&gt; builds on top of these APIs to specifically improve the experience for app versioning use cases based on Git.&lt;/p&gt;

&lt;p&gt;Instructions for installing, configuring and using the plugin are available on &lt;a href="https://github.com/ReactiveCircus/app-versioning"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Please give it a try and &lt;a href="https://github.com/ReactiveCircus/app-versioning/issues"&gt;share your feedback&lt;/a&gt; as we work towards the stable release!&lt;/p&gt;




&lt;h3&gt;
  
  
  References:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://youtu.be/sQC9-Rj2yLI"&gt;Build Bigger, Better: Gradle for Large Projects (Google I/O'19)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/android/gradle-recipes/"&gt;Android Plugin for Gradle cookbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/androiddevelopers/new-apis-in-the-android-gradle-plugin-f5325742e614"&gt;New APIs in the Android Gradle Plugin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>git</category>
      <category>gradle</category>
    </item>
    <item>
      <title>Testing Kotlin Lambda Invocations without Mocking</title>
      <dc:creator>Yang</dc:creator>
      <pubDate>Fri, 07 Feb 2020 09:38:49 +0000</pubDate>
      <link>https://dev.to/ychescale9/testing-kotlin-lambda-invocations-without-mocking-3n32</link>
      <guid>https://dev.to/ychescale9/testing-kotlin-lambda-invocations-without-mocking-3n32</guid>
      <description>&lt;p&gt;In this post I want to share a simple technique for verifying the invocation of a Kotlin lambda function without mocking it.&lt;/p&gt;

&lt;p&gt;Let's imagine we have a class for loading data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DataLoader&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;....&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;findCachedData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="nf"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;also&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;fetchedData&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="nf"&gt;cacheData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fetchedData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;findCachedData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;....&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;cacheData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;....&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When consumer calls the &lt;code&gt;load&lt;/code&gt; function, &lt;code&gt;Data&lt;/code&gt; from the cache will be returned immediately if cached entry can be found for the given &lt;code&gt;query&lt;/code&gt;, otherwise the &lt;code&gt;fetcher&lt;/code&gt; function provided by the user will be invoked with its result being cached.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;data1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dataLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;// this should be invoked the first time&lt;/span&gt;
    &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchDataByQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;data2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dataLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;// this should NOT be invoked the second time as cached Data is available&lt;/span&gt;
    &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchDataByQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Since the execution of the &lt;code&gt;fetcher&lt;/code&gt; might be expensive, we want to make sure it's &lt;strong&gt;only&lt;/strong&gt; invoked when no &lt;code&gt;Data&lt;/code&gt; is available in the cache for the given &lt;code&gt;query&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;How do we test this behavior?&lt;/p&gt;

&lt;h2&gt;
  
  
  Mocks
&lt;/h2&gt;

&lt;p&gt;Mocking is a common way to help verify that the code under test has (or has not) interacted with a dependency.&lt;/p&gt;

&lt;p&gt;We can mock the &lt;code&gt;(String) -&amp;gt; Data&lt;/code&gt; lambda expression as if it's an interface, and &lt;code&gt;verify&lt;/code&gt; whether the &lt;code&gt;invoke(...)&lt;/code&gt; function has been invoked.&lt;/p&gt;

&lt;p&gt;Here's how our test might look like with &lt;strong&gt;mockito-kotlin&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;dataLoader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DataLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.)&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;testData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;`fetcher&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;executed&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="n"&gt;associated&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;cache`&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;mockFetcher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;on&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;doReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;dataLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mockFetcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// fetcher should be invoked the first time&lt;/span&gt;
    &lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mockFetcher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="nf"&gt;clearInvocations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mockFetcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;dataLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mockFetcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// fetcher should NOT be invoked for the same query the second time&lt;/span&gt;
    &lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mockFetcher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Here's what we've done to test the behavior mentioned earlier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a &lt;code&gt;mock&lt;/code&gt; of the type &lt;code&gt;(String) -&amp;gt; Data&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Stub the &lt;code&gt;invoke(query: String): Data&lt;/code&gt; function and return a &lt;code&gt;testData&lt;/code&gt; (the actual value is not important in this test).&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;dataLoader.load("query", mockFetcher)&lt;/code&gt; for the first time.&lt;/li&gt;
&lt;li&gt;Verify that the &lt;code&gt;invoke(query: String): Data&lt;/code&gt; function on the &lt;code&gt;mockFetcher&lt;/code&gt; was invoked exactly 1 time, as no existing data associated with &lt;code&gt;query&lt;/code&gt; exists in the cache yet.&lt;/li&gt;
&lt;li&gt;Clear any previous recorded invocations on the &lt;code&gt;mockFetcher&lt;/code&gt; to avoid stubbing again.&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;dataLoader.load("query", mockFetcher)&lt;/code&gt; for the second time.&lt;/li&gt;
&lt;li&gt;Verify that the &lt;code&gt;invoke(query: String): Data&lt;/code&gt; function on the &lt;code&gt;mockFetcher&lt;/code&gt; was NOT invoked this time as cached &lt;code&gt;Data&lt;/code&gt; for the &lt;code&gt;query&lt;/code&gt; exists.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same test looks similar with &lt;strong&gt;MockK&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;dataLoader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DataLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.)&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;testData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;`fetcher&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;executed&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="n"&gt;associated&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;cache`&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;mockFetcher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mockk&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;every&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;mockFetcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;testData&lt;/span&gt;

    &lt;span class="n"&gt;dataLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mockFetcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// fetcher should be invoked the first time&lt;/span&gt;
    &lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exactly&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;mockFetcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;clearMocks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mockFetcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;dataLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mockFetcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// fetcher should NOT be invoked for the same query the second time&lt;/span&gt;
    &lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exactly&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;mockFetcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Fakes
&lt;/h2&gt;

&lt;p&gt;The approach above seems reasonable, but having to use mocks in unit tests is usually a code smell.&lt;/p&gt;

&lt;p&gt;The necessity to use mocks in unit tests is often caused by tight coupling between the unit under test and its dependencies. For example, when a class directly depends on a type from a third-party library or something that does IO operation, unit testing the class without mocking means the tests will likely be indeterministic and slow - the opposite of what we want with unit tests.&lt;/p&gt;

&lt;p&gt;However, mocking comes with costs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mocking adds noise and cognitive load, especially when there are multiple / nested mocks that need to be stubbed in subtle ways to setup the specific test scenarios.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mocking can make refactoring harder as tests with mocks verify both the external behavior and the internal workings of the class under test, coupling test code and production code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Relying on a mocking framework for testing discourages developers from following good design principles such as dependency inversion.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As such we generally want to avoid using mocks in unit tests and only use them sparingly in higher-level integration tests when necessary.&lt;/p&gt;

&lt;p&gt;Now if we look at the &lt;code&gt;fetcher&lt;/code&gt; lambda again, there's nothing complex or indeterministic about its implementation that requires mocking. In fact, the implementation is always provided by the call-side as a trailing lambda:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;dataLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// perform any side-effects...&lt;/span&gt;
    &lt;span class="o"&gt;....&lt;/span&gt;
    &lt;span class="c1"&gt;// return data&lt;/span&gt;
    &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Mocking the fetcher here provides no benefit aside from being able to &lt;code&gt;verify&lt;/code&gt; the invocation using the mocking framework.&lt;/p&gt;

&lt;p&gt;We can easily achieve the same with a &lt;strong&gt;Fake&lt;/strong&gt; implementation of the lambda:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;dataLoader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DataLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.)&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;testData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;`fetcher&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;executed&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="n"&gt;associated&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;cache`&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;invokeCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;testFetcher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;invokeCount&lt;/span&gt;&lt;span class="p"&gt;++&lt;/span&gt;
        &lt;span class="n"&gt;testData&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;dataLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testFetcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invokeCount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;dataLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testFetcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// invokeCount should NOT increment&lt;/span&gt;
    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invokeCount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Instead of mocking the &lt;code&gt;fetcher: (String) -&amp;gt; Data&lt;/code&gt; lambda, we track the number of invocations with a &lt;code&gt;invokeCount&lt;/code&gt; variable that gets incremented whenever the &lt;code&gt;fetcher&lt;/code&gt; lambda is invoked. Now we're able to verify the lambda invocations with regular assertions.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;testFetcher&lt;/code&gt; implementation records the number of invocations on the lambda and returns a &lt;code&gt;Data&lt;/code&gt;. This looks like something we want to reuse in multiple tests.&lt;/p&gt;

&lt;p&gt;We need a &lt;code&gt;TestFetcher&lt;/code&gt; class with a &lt;code&gt;var&lt;/code&gt; for tracking the invocation count, but since the &lt;code&gt;fetcher&lt;/code&gt; parameter is a Kotlin function type rather than a regular interface with a &lt;code&gt;fun fetch(query: String): Data&lt;/code&gt;, what super type should this class implement?&lt;/p&gt;

&lt;p&gt;It turns out that a class can implement a function type just like a regular interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestFetcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;expectedData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;invokeCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;invokeCount&lt;/span&gt;&lt;span class="p"&gt;++&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;expectedData&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With this fake &lt;code&gt;fetcher&lt;/code&gt; implementation in place, our test has now become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;dataLoader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DataLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.)&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;testData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;`fetcher&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;executed&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="n"&gt;associated&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;cache`&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;testFetcher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TestFetcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expectedData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;testData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;dataLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testFetcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testFetcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invokeCount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;dataLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testFetcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// invokeCount should NOT increment&lt;/span&gt;
    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testFetcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invokeCount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We can also implement a different &lt;code&gt;Fetcher&lt;/code&gt; that throws an exception:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FailedTestFetcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;expectedException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;invokeCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;invokeCount&lt;/span&gt;&lt;span class="p"&gt;++&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;expectedException&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we can test that an exception thrown by the &lt;code&gt;fetcher&lt;/code&gt; is propagated to the call-side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;dataLoader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DataLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.)&lt;/span&gt;

&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;`fetcher&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nf"&gt;propagated`&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;testFetcher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FailedTestFetcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expectedException&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="nf"&gt;assertThrows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IOException&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;dataLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testFetcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testFetcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invokeCount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Note that currently &lt;a href="https://youtrack.jetbrains.com/issue/KT-18707"&gt;&lt;code&gt;suspend&lt;/code&gt; function is not allowed as super types&lt;/a&gt; but this will be supported in the upcoming Kotlin 1.4.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// doesn't compile&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Fetcher&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;....&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  A real-world example
&lt;/h2&gt;

&lt;p&gt;I recently wrote a new &lt;a href="https://github.com/dropbox/Store/tree/master/cache"&gt;in-memory caching library&lt;/a&gt; for &lt;a href="https://github.com/dropbox/Store"&gt;dropbox/store&lt;/a&gt;. One of the supported features is &lt;a href="https://github.com/dropbox/Store/tree/master/cache#cache-loader"&gt;cache loader&lt;/a&gt; which is an API for getting cached value by key and using the provided &lt;code&gt;loader: () -&amp;gt; Value&lt;/code&gt; lambda to compute and cache the value automatically if none exists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nc"&gt;Key&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;....&lt;/span&gt;
    &lt;span class="cm"&gt;/**&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="cm"&gt;     * Returns the value associated with [key] in this cache if exists,&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="cm"&gt;     * otherwise gets the value by invoking [loader], associates the value with [key] in the cache,&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="cm"&gt;     * and returns the cached value.&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="cm"&gt;     *&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="cm"&gt;     * Note that [loader] is executed on the caller's thread. When called from multiple threads&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="cm"&gt;     * concurrently, if an unexpired value for the [key] is present by the time the [loader] returns&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="cm"&gt;     * the new value, the existing value won't be replaced by the new value.&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="cm"&gt;     * Instead the existing value will be returned.&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="cm"&gt;     *&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="cm"&gt;     * Any exceptions thrown by the [loader] will be propagated to the caller of this function.&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="cm"&gt;     */&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Value&lt;/span&gt;
&lt;span class="o"&gt;....&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is similar to our &lt;code&gt;DataLoader&lt;/code&gt; example above. We can verify the &lt;code&gt;loader&lt;/code&gt; lambda invocations with the same approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;`get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;an&lt;/span&gt; &lt;span class="n"&gt;unexpired&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;associated&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="n"&gt;executing&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="nf"&gt;loader`&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;cache&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expireAfterAccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expiryDuration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NANOSECONDS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;

    &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"dog"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// create a new `() -&amp;gt; Value` lambda&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;loader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cat"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// loader should not have been invoked as "dog" is already in the cache.&lt;/span&gt;
    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invokeCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dog"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To support adding different variants of fake &lt;code&gt;Loader&lt;/code&gt; implementation for some of the more complex concurrency tests, we have a &lt;code&gt;TestLoader&lt;/code&gt; class that takes &lt;code&gt;val block: () -&amp;gt; Value&lt;/code&gt; as a constructor parameter, and a bunch of top-level functions for creating different &lt;code&gt;Loader&lt;/code&gt; implementations for different needs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestLoader&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;invokeCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;operator&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;invokeCount&lt;/span&gt;&lt;span class="p"&gt;++&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;block&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// a regular loader that returns computed value immediately&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;createLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;computedValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="nc"&gt;TestLoader&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;computedValue&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// a loader that throws an exception immediately&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createFailingLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="nc"&gt;TestLoader&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// a loader that takes certain duration (virtual) to execute and return the computed value&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;createSlowLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;computedValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;executionTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TestLoader&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;runBlocking&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;executionTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;computedValue&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The complete test suite can be found &lt;a href="https://github.com/dropbox/Store/blob/master/cache/src/test/kotlin/com/dropbox/android/external/cache4/CacheLoaderTest.kt"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;I hope you find this technique useful! Here's the TLDR:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You don't need help from a mocking framework for testing Kotlin lambda invocations - it's easy to track the invocation count in the lambda implementation provided&lt;/li&gt;
&lt;li&gt;You don't need to wrap a function inside an interface to be able to provide a fake implementation in tests - a class can implement a function type directly just like implementing a regular interface.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Thanks to &lt;a href="https://github.com/eyalgu"&gt;Eyal Guthmann&lt;/a&gt; for suggesting the use of invocation counter in my &lt;a href="https://github.com/dropbox/Store/pull/49"&gt;cache rewrite PR&lt;/a&gt; which inspired me to write this article.&lt;/p&gt;




&lt;p&gt;Featured in &lt;a href="https://mailchi.mp/kotlinweekly/kotlin-weekly-185"&gt;Kotlin Weekly #185&lt;/a&gt; and &lt;a href="https://androidweekly.net/issues/issue-401"&gt;Android Weekly #401&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>testing</category>
    </item>
    <item>
      <title>Exploring Cirrus CI for Android</title>
      <dc:creator>Yang</dc:creator>
      <pubDate>Mon, 23 Dec 2019 13:05:06 +0000</pubDate>
      <link>https://dev.to/ychescale9/exploring-cirrus-ci-for-android-434</link>
      <guid>https://dev.to/ychescale9/exploring-cirrus-ci-for-android-434</guid>
      <description>&lt;p&gt;I recently wrote about my experience using various cloud-based CI services for &lt;a href="https://dev.to/ychescale9/running-android-emulators-on-ci-from-bitrise-io-to-github-actions-3j76"&gt;running Android instrumented tests on CI&lt;/a&gt; for opensource projects, and how I landed on a solution using a custom &lt;a href="https://github.com/ReactiveCircus/android-emulator-runner" rel="noopener noreferrer"&gt;GitHub Action&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As mentioned in that &lt;a href="https://dev.to/ychescale9/running-android-emulators-on-ci-from-bitrise-io-to-github-actions-3j76"&gt;article&lt;/a&gt;, &lt;strong&gt;most&lt;/strong&gt; cloud-based CI providers today don't have &lt;strong&gt;KVM&lt;/strong&gt; enabled in the host VMs which is required for running hardware-accelerated x86 / x86_64 emulators in a docker environment.&lt;/p&gt;

&lt;p&gt;I recently came across &lt;a href="https://cirrus-ci.org/" rel="noopener noreferrer"&gt;Cirrus CI&lt;/a&gt;, a less-known cloud-based CI provider with native KVM support and it's completely free for opensource projects. Despite having a positive experience with GitHub Workflow using my custom &lt;a href="https://github.com/ReactiveCircus/android-emulator-runner" rel="noopener noreferrer"&gt;android-emulator-runner&lt;/a&gt; action that runs on macOS VMs, I was keen to try out KVM-enabled containers provided by Cirrus CI as I'd still be more comfortable with a solution based on a standard Linux/docker environment which &lt;a href="https://github.com/google/android-emulator-container-scripts" rel="noopener noreferrer"&gt;Google has been putting more effort into recently&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this article I'm going to share some of my experiences with &lt;strong&gt;Cirrus CI&lt;/strong&gt; - specifically, how I migrated &lt;strong&gt;&lt;a href="https://github.com/ReactiveCircus/FlowBinding" rel="noopener noreferrer"&gt;FlowBinding&lt;/a&gt;&lt;/strong&gt;'s instrumented tests from GitHub Action to Cirrus CI task, along with some of the features provided by Cirrus CI you might find useful for Android in general.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cirrus CI Overview
&lt;/h2&gt;

&lt;p&gt;Cirrus CI doesn't have a fancy product / marketing website like some of the other providers such as &lt;a href="https://circleci.com/" rel="noopener noreferrer"&gt;CircleCI&lt;/a&gt;. The main entry point is  &lt;a href="https://cirrus-ci.org/" rel="noopener noreferrer"&gt;cirrus-ci.org&lt;/a&gt; which covers feature descriptions, pricing information, examples, and comprehensive documentations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Features
&lt;/h3&gt;

&lt;p&gt;A list of features can be found &lt;a href="https://cirrus-ci.org/features/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. For Android (and opensource projects in particular) some of the more interesting ones are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free for opensource projects (public repositories on GitHub)&lt;/li&gt;
&lt;li&gt;Provides Linux, Windows, macOS and FreeBSD containers&lt;/li&gt;
&lt;li&gt;Provides KVM-enabled containers&lt;/li&gt;
&lt;li&gt;Containers on community clusters (free plans) can use a maximum of 8.0 CPUs and up to 24 GB of memory&lt;/li&gt;
&lt;li&gt;Supports local and remote build cache&lt;/li&gt;
&lt;li&gt;Supports build matrix&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a comparison with other CI services found on Cirrus CI's website:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F5rxy5bpca3u60ogc9cnu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F5rxy5bpca3u60ogc9cnu.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pricing
&lt;/h3&gt;

&lt;p&gt;Cirrus CI's pricing models can be found &lt;a href="https://cirrus-ci.org/pricing/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As mentioned before, Cirrus CI is free for public repositories. There is a &lt;strong&gt;per-user&lt;/strong&gt; &lt;a href="https://cirrus-ci.org/faq/#are-there-any-limits" rel="noopener noreferrer"&gt;concurrency limit&lt;/a&gt; of 8 Linux VMs, but this is quite generous for a free plan and should be more than enough for most projects.&lt;/p&gt;

&lt;p&gt;Commercial plans for private repositories are available at $10/seat/month which is again quite affordable compared to some of the other more popular services, but for my projects I haven't needed to upgrade.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;Cirrus CI currently only supports repositories hosted on GitHub. To get started, go to the Cirrus CI Application on &lt;a href="https://github.com/marketplace/cirrus-ci" rel="noopener noreferrer"&gt;GitHub Marketplace&lt;/a&gt; and setup a plan for your account or organization.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fn4nno7ey9k92dqetssnn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fn4nno7ey9k92dqetssnn.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's basically all you need to do to setup Cirrus CI for a project. Everything else (except a couple of security options) is configured in a &lt;code&gt;.cirrus.yml&lt;/code&gt; file in your project's root directory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing CI Tasks
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Task(s)&lt;/strong&gt; are the building block of a &lt;code&gt;.cirrus.yml&lt;/code&gt; configuration file. A task defines a sequence of &lt;strong&gt;instructions&lt;/strong&gt; to run and the &lt;strong&gt;environment&lt;/strong&gt; to execute these instructions in. The equivalent concepts of &lt;strong&gt;tasks&lt;/strong&gt; and &lt;strong&gt;instructions&lt;/strong&gt; in GitHub Actions are &lt;strong&gt;jobs&lt;/strong&gt; and &lt;strong&gt;steps&lt;/strong&gt; respectively. Here's a simple task that assembles a debug APK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assemble_task:
  container:
    image: reactivecircus/android-sdk:latest
  assemble_script:
    ./gradlew assembleDebug
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tasks in &lt;code&gt;.cirrus.yml&lt;/code&gt; have the &lt;code&gt;task&lt;/code&gt; suffix. In this case &lt;code&gt;assemble_task&lt;/code&gt; is the only task we define in the config.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Script&lt;/strong&gt; is one of the &lt;a href="https://cirrus-ci.org/guide/writing-tasks/#supported-instructions" rel="noopener noreferrer"&gt;supported instructions&lt;/a&gt;. Similarly scripts need to have the &lt;code&gt;script&lt;/code&gt; suffix e.g. &lt;code&gt;assemble_script&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;container&lt;/code&gt; is a field where we can define the docker image used for the task. &lt;strong&gt;&lt;a href="https://github.com/ReactiveCircus/docker-android-images#android-sdk-image" rel="noopener noreferrer"&gt;reactivecircus/android-sdk&lt;/a&gt;&lt;/strong&gt; is a docker image with the minimum Android SDK components required for building Android projects.&lt;/p&gt;

&lt;p&gt;A comprehensive guide for writing tasks can be found &lt;a href="https://cirrus-ci.org/guide/writing-tasks/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's worth noting that &lt;strong&gt;code completion&lt;/strong&gt; support from IntelliJ IDEA / Android Studio is available when writing &lt;code&gt;.cirrus.yml&lt;/code&gt;, thanks to the YMAL plugin bundled in the IDE and the supported &lt;a href="http://schemastore.azurewebsites.net/json/" rel="noopener noreferrer"&gt;schema store&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fsn75k8urivvja5rlaiz2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fsn75k8urivvja5rlaiz2.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dashboard
&lt;/h3&gt;

&lt;p&gt;Cirrus CI's build dashboard is hosted at &lt;a href="https://cirrus-ci.com/" rel="noopener noreferrer"&gt;cirrus-ci.com&lt;/a&gt;. Once logged in with your GitHub account you'll see a list of builds for all your projects using Cirrus CI. Individual tasks and build results are available for each build.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Frq36v5d9cusdwwxv549j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Frq36v5d9cusdwwxv549j.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While the UIs don't look as pretty as some of the more popular products, overall I found them effective and responsive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running Instrumented Tests
&lt;/h2&gt;

&lt;p&gt;Now let's look at how we can write a task that runs Android instrumented tests on hardware-accelerated emulators.&lt;/p&gt;

&lt;p&gt;We'll start by converting the following GitHub workflow that uses the &lt;strong&gt;android-emulator-runner&lt;/strong&gt; action for running instrumented tests on macOS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs:
  test:
    runs-on: macOS-latest
    steps:
    - name: checkout
      uses: actions/checkout@v2

    - name: run tests
      uses: reactivecircus/android-emulator-runner@v2
      with:
        api-level: 28
        arch: x86_64
        target: default
        emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim
        script: ./gradlew connectedDebugAndroidTest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Converted task in &lt;code&gt;.cirrus.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;connected_check_task:
  name: Run Android instrumented tests
  only_if: $CIRRUS_BRANCH == "master" || CIRRUS_PR != ""
  env:
    API_LEVEL: 28
    TARGET: default
    ARCH: x86_64
  container:
    image: reactivecircus/android-emulator-28:latest
    kvm: true
    cpu: 8
    memory: 24G
  create_device_script:
    echo no | avdmanager create avd --force --name "api-${API_LEVEL}" --abi "${TARGET}/${ARCH}" --package "system-images;android-${API_LEVEL};${TARGET};${ARCH}"
  start_emulator_background_script:
    $ANDROID_HOME/emulator/emulator -avd "api-${API_LEVEL}" -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none
  wait_for_emulator_script:
    adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 3; done; input keyevent 82'
  disable_animations_script: |
    adb shell settings put global window_animation_scale 0.0
    adb shell settings put global transition_animation_scale 0.0
    adb shell settings put global animator_duration_scale 0.0
  run_instrumented_tests_script:
    ./gradlew connectedDebugAndroidTest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;name&lt;/code&gt; is a field where we can define a custom name for the task.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;only_if&lt;/code&gt; is a keyword that controls whether the task should be executed. &lt;code&gt;$CIRRUS_BRANCH == "master" || CIRRUS_PR != ""&lt;/code&gt; here means we only want to run this task on commits on the master branch or any pull request.&lt;/p&gt;

&lt;p&gt;Environment variables can be specified in the &lt;code&gt;env&lt;/code&gt; block. Here we are only specifying the configurations of the system image used for creating the emulator. Other common environment variables such as &lt;code&gt;JAVA_TOOL_OPTIONS&lt;/code&gt; or &lt;code&gt;GRADLE_OPTS&lt;/code&gt; can also be specified here.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;container&lt;/code&gt; defines the host VM used for running the task and its configurations. We use &lt;a href="https://github.com/ReactiveCircus/docker-android-images#android-emulator-images" rel="noopener noreferrer"&gt;reactivecircus/android-emulator-28&lt;/a&gt; as the docker image which has the minimum SDK components for running hardware-accelerated emulator on API 28.&lt;/p&gt;

&lt;p&gt;The most interesting bit here is &lt;code&gt;kvm: true&lt;/code&gt; where we &lt;a href="https://cirrus-ci.org/guide/linux/#kvm-enabled-privileged-containers" rel="noopener noreferrer"&gt;enable KVM&lt;/a&gt; for the container to take advantage of native virtualization when running the modern x86 / x86_64 emulators.&lt;/p&gt;

&lt;p&gt;We've also given the container the maximum amount of CPU (8) and memory (24G) as the container will be running an Android Emulator instance as well as Gradle commands, both of which are known to be processor and memory intensive.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;create_device_script&lt;/code&gt; and &lt;code&gt;wait_for_emulator_script&lt;/code&gt; create a new AVD instance, launch an Emulator, and wait until it's fully booted and ready for use. These are not needed in the GitHub workflow as they are taken care of by the &lt;a href="https://github.com/ReactiveCircus/android-emulator-runner" rel="noopener noreferrer"&gt;android-emulator-runner Github Action&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We then add the &lt;code&gt;disable_animations_script&lt;/code&gt; to disable the global animations on the emulator. This is again run by default when using the &lt;a href="https://github.com/ReactiveCircus/android-emulator-runner" rel="noopener noreferrer"&gt;android-emulator-runner GitHub Action&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally in the &lt;code&gt;run_instrumented_script&lt;/code&gt; we define the Gradle task for running all Android tests in all modules.&lt;/p&gt;

&lt;p&gt;Here's the build summary for &lt;strong&gt;FlowBinding&lt;/strong&gt; which has 160 tests across 10 modules:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fdo9i0tttfj1cty49fzis.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fdo9i0tttfj1cty49fzis.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The build took &lt;strong&gt;~20 mins&lt;/strong&gt; which is almost identical to the GitHub Actions equivalence. Note that there's an extra &lt;strong&gt;1-2 mins&lt;/strong&gt; of scheduling period for KVM-enabled containers due to the additional virtualization layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build matrix
&lt;/h3&gt;

&lt;p&gt;With GitHub Actions we can leverage &lt;strong&gt;build matrix&lt;/strong&gt; to run the tests across multiple API levels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs:
  test:
    runs-on: macOS-latest
    strategy:
      matrix:
        api-level: [21, 22, 23, 24, 25, 26, 27, 28]
    steps:
    - name: checkout
      uses: actions/checkout@v2

    - name: run tests
      uses: reactivecircus/android-emulator-runner@v2
      with:
        api-level: ${{ matrix.api-level }}
        script: ./gradlew connectedDebugAndroidTest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can do the same with Cirrus CI using &lt;a href="https://cirrus-ci.org/guide/writing-tasks/#matrix-modification" rel="noopener noreferrer"&gt;matrix modification&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;connected_check_task:
  name: Run Android instrumented tests (API $API_LEVEL)
  only_if: $CIRRUS_BRANCH == "master" || CIRRUS_PR != ""
  env:
    matrix:
      - API_LEVEL: 21
      - API_LEVEL: 22
      - API_LEVEL: 23
      - API_LEVEL: 24
      - API_LEVEL: 25
      - API_LEVEL: 26
      - API_LEVEL: 27
      - API_LEVEL: 28
  container:
    image: reactivecircus/android-emulator-${API_LEVEL}:latest
    kvm: true
    cpu: 8
    memory: 24G
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've defined a matrix of &lt;code&gt;API_LEVEL&lt;/code&gt; values so the same &lt;code&gt;connected_check_task&lt;/code&gt; will be executed for each of these values. Note that we are able to specify the name of the docker image dynamically because these images are named in the same &lt;code&gt;reactivecircus/android-emulator-${API_LEVEL}&lt;/code&gt; pattern. The repository for these images can be found &lt;a href="https://github.com/ReactiveCircus/docker-android-images" rel="noopener noreferrer"&gt;here&lt;/a&gt; (all x86 images for API 21 and above are available).&lt;/p&gt;

&lt;p&gt;With GitHub Actions you can &lt;a href="https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#example-excluding-configurations-from-a-matrix" rel="noopener noreferrer"&gt;exclude&lt;/a&gt; some of the configurations generated by the build matrix. This is not currently supported by Cirrus CI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build cache (local)
&lt;/h3&gt;

&lt;p&gt;Many Gradle tasks are &lt;a href="https://docs.gradle.org/current/userguide/more_about_tasks.html#sec:up_to_date_checks" rel="noopener noreferrer"&gt;incremental&lt;/a&gt; and &lt;a href="https://docs.gradle.org/current/userguide/build_cache.html" rel="noopener noreferrer"&gt;cacheable&lt;/a&gt;, which can significantly improve build times.&lt;/p&gt;

&lt;p&gt;The local Gradle build cache is located at &lt;code&gt;~/.gradle/cache&lt;/code&gt; and we are able to get fast incremental builds on CI when tasks outputs are available in this directory.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;CircleCI&lt;/strong&gt; saving and restoring the local Gradle cache directory is easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs:
  build:
    executor: android
    steps:
      - checkout
      - restore_cache:
          key: gradle-{{ checksum "buildSrc/dependencies.gradle" }}-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}
      - run:
          name: Assemble
          command: ./gradlew assemble
      - save_cache:
          key: gradle-{{ checksum "buildSrc/dependencies.gradle" }}-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}
          paths:
            - ~/.gradle/cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cache will only be invalidated when &lt;code&gt;buildSrc/dependencies.gradle&lt;/code&gt; or &lt;code&gt;gradle/wrapper/gradle-wrapper.properties&lt;/code&gt; changes which is usually when we update our library dependencies or Gradle version. By doing this we can avoid re-downloading all the dependencies on each build on CI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cirrus CI&lt;/strong&gt; also provides a similar &lt;a href="https://cirrus-ci.org/guide/writing-tasks/#cache-instruction" rel="noopener noreferrer"&gt;&lt;code&gt;cache&lt;/code&gt; instruction&lt;/a&gt; that works similarly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;connected_check_task:
  name: Run Android instrumented tests
  only_if: $CIRRUS_BRANCH == "master" || CIRRUS_PR != ""
  env:
    API_LEVEL: 28
    TARGET: default
    ARCH: x86_64
  container:
    image: reactivecircus/android-emulator-28:latest
    kvm: true
    cpu: 8
    memory: 24G
  gradle_cache:
    folder: ~/.gradle/caches
    fingerprint_script: cat buildSrc/dependencies.gradle &amp;amp;&amp;amp; cat gradle/wrapper/gradle-wrapper.properties
  ...
  cleanup_script:
    - rm -rf ~/.gradle/caches/[0-9].*
    - rm -rf ~/.gradle/caches/transforms-1
    - rm -rf ~/.gradle/caches/journal-1
    - rm -rf ~/.gradle/caches/jars-3/*/buildSrc.jar
    - find ~/.gradle/caches/ -name "*.lock" -type f -delete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Cirrus CI doc recommends using &lt;a href="https://cirrus-ci.org/examples/#caching" rel="noopener noreferrer"&gt;a technique&lt;/a&gt; for avoiding re-upload of the cache when the build outputs are unchanged - adding a &lt;code&gt;cleanup_script&lt;/code&gt; that removes the non-deterministic files generated on each build at the end of the task. This improvement is based on the expectation that re-generating these non-deterministic files and always executing a few tasks on every build has much less impact than uploading hundred of Mb of the cache files on every build.&lt;/p&gt;

&lt;p&gt;But there's one area where &lt;strong&gt;CircleCI&lt;/strong&gt; performs significantly better - the time taken for downloading and uploading these cache archives.&lt;/p&gt;

&lt;p&gt;For roughly &lt;strong&gt;500Mb&lt;/strong&gt; of cache archive, with &lt;strong&gt;CircleCI&lt;/strong&gt; it only takes about &lt;strong&gt;15 seconds&lt;/strong&gt; to &lt;a href="https://app.circleci.com/jobs/github/ReactiveCircus/FlowBinding/497" rel="noopener noreferrer"&gt;download&lt;/a&gt; (restore) it, and &lt;strong&gt;25 seconds&lt;/strong&gt; to &lt;a href="https://app.circleci.com/jobs/github/ReactiveCircus/FlowBinding/492" rel="noopener noreferrer"&gt;upload&lt;/a&gt; it.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;Cirrus CI&lt;/strong&gt;, &lt;a href="https://cirrus-ci.com/task/5982829204209664" rel="noopener noreferrer"&gt;downloading&lt;/a&gt; &lt;strong&gt;~500Mb&lt;/strong&gt; of cache archive takes nearly &lt;strong&gt;1 minute&lt;/strong&gt; while &lt;a href="https://cirrus-ci.com/task/4999028994998272" rel="noopener noreferrer"&gt;uploading&lt;/a&gt; takes over &lt;strong&gt;1 minute&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build cache (remote)
&lt;/h3&gt;

&lt;p&gt;Gradle also has built-in support for &lt;a href="https://docs.gradle.org/current/userguide/build_cache.html#sec:build_cache_configure_remote" rel="noopener noreferrer"&gt;HTTP remote build cache&lt;/a&gt; which works similarly as a local build cache except the build outputs come from a remote server rather than the local &lt;code&gt;~/.gradle/cache&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cirrus CI&lt;/strong&gt; is the first CI service I've used that provides native support for Gradle's &lt;a href="https://cirrus-ci.org/examples/#build-cache" rel="noopener noreferrer"&gt;remote HTTP build cache&lt;/a&gt;. This can be configured by adding the following to your &lt;code&gt;settings.gradle&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ext.isCiServer = System.getenv().containsKey("CIRRUS_CI")
ext.isMasterBranch = System.getenv()["CIRRUS_BRANCH"] == "master"
ext.buildCacheHost = System.getenv().getOrDefault("CIRRUS_HTTP_CACHE_HOST", "localhost:12321")

buildCache {
  local {
    enabled = !isCiServer
  }
  remote(HttpBuildCache) {
    url = "http://${buildCacheHost}/"
    enabled = isCiServer
    push = isMasterBranch
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that if your project uses the &lt;code&gt;buildSrc&lt;/code&gt; directory, the build cache configuration should also be applied to &lt;code&gt;buildSrc/settings.gradle&lt;/code&gt;. This can be done by putting the build cache configuration above into a separate &lt;code&gt;gradle/buildCacheSettings.gradle&lt;/code&gt; file and applying it to both &lt;code&gt;settings.gradle&lt;/code&gt; and &lt;code&gt;buildSrc/settings.gradle&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optimizing script execution order
&lt;/h3&gt;

&lt;p&gt;Before looking at the final build time improvement, let's take a look at a technique for optimizing your CI pipeline when running instrumented tests on an Emulator.&lt;/p&gt;

&lt;p&gt;Here's a visualization of our current &lt;code&gt;connected_check_task&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fy8hzmtn5t91jajrz4h33.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fy8hzmtn5t91jajrz4h33.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We create a new AVD instance, launch an Emulator in the &lt;strong&gt;background&lt;/strong&gt;, &lt;strong&gt;wait until the Emulator is fully booted&lt;/strong&gt;, and finally run the &lt;code&gt;./gradlew connectedDebugAndroidTest&lt;/code&gt; task.&lt;/p&gt;

&lt;p&gt;Is there anything else we could be doing while waiting for the Emulator to come online (usually takes about &lt;strong&gt;1 minute&lt;/strong&gt;)?&lt;/p&gt;

&lt;p&gt;As mentioned earlier many Gradle tasks are &lt;strong&gt;incremental&lt;/strong&gt;, this means that once a task has been executed, its &lt;code&gt;outputs&lt;/code&gt; will be stored in the &lt;code&gt;build&lt;/code&gt; directory within the sub-project (module) and running the same task again (without changing its &lt;code&gt;inputs&lt;/code&gt;) won't actually execute the task itself. Instead the &lt;code&gt;outputs&lt;/code&gt; of the task will be immediately loaded from the &lt;code&gt;build&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;assemble&amp;lt;BuildVariant&amp;gt;AndroidTest&lt;/code&gt; task that generates the test APKs is incremental.&lt;/p&gt;

&lt;p&gt;Let's first run it locally with no build cache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./gradlew clean assembleDebugAndroidTest --no-build-cache
...
BUILD SUCCESSFUL in 59s
936 actionable tasks: 934 executed, 2 up-to-date
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The build took about 1 minute with 934 out of 936 tasks executed.&lt;/p&gt;

&lt;p&gt;If we run it again without cleaning the build directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./gradlew assembleDebugAndroidTest
...
BUILD SUCCESSFUL in 3s
914 actionable tasks: 914 up-to-date
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The build completes in 3 seconds and no tasks were executed as they are all &lt;code&gt;up-to-date&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now since the &lt;code&gt;connectedDebugAndroidTest&lt;/code&gt; Gradle task we run at the end effectively &lt;strong&gt;depends on&lt;/strong&gt; the &lt;code&gt;assembleDebugAndroidTest&lt;/code&gt; task for generating the test APKs, by first running the &lt;code&gt;assembleDebugAndroidTest&lt;/code&gt; task explicitly while waiting for the Emulator to boot, the &lt;code&gt;connectedDebugAndroidTest&lt;/code&gt; task will be significantly faster as all of its sub-tasks would be &lt;code&gt;up-to-date&lt;/code&gt; at that point.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fl8h5sliwbixubn7peiuk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fl8h5sliwbixubn7peiuk.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Final result
&lt;/h3&gt;

&lt;p&gt;Here's the final &lt;code&gt;.cirrus.yml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;connected_check_task:
  name: Run Android instrumented tests (API $API_LEVEL)
  timeout_in: 30m
  only_if: $CIRRUS_BRANCH == "master" || CIRRUS_PR != ""
  env:
    matrix:
      - API_LEVEL: 21
      - API_LEVEL: 22
      - API_LEVEL: 23
      - API_LEVEL: 24
      - API_LEVEL: 25
      - API_LEVEL: 26
      - API_LEVEL: 27
      - API_LEVEL: 28
    JAVA_TOOL_OPTIONS: -Xmx6g
    GRADLE_OPTS: -Dorg.gradle.daemon=false -Dkotlin.incremental=false -Dkotlin.compiler.execution.strategy=in-process
  container:
    image: reactivecircus/android-emulator-${API_LEVEL}:latest
    kvm: true
    cpu: 8
    memory: 24G
  create_device_script:
    echo no | avdmanager create avd --force --name "api-${API_LEVEL}" --abi "${TARGET}/${ARCH}" --package "system-images;android-${API_LEVEL};${TARGET};${ARCH}"
  start_emulator_background_script:
    $ANDROID_HOME/emulator/emulator -avd "api-${API_LEVEL}" -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none
  assemble_instrumented_tests_script:
    ./gradlew assembleDebugAndroidTest
  wait_for_emulator_script:
    adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 3; done; input keyevent 82'
  disable_animations_script: |
    adb shell settings put global window_animation_scale 0.0
    adb shell settings put global transition_animation_scale 0.0
    adb shell settings put global animator_duration_scale 0.0
  run_instrumented_tests_script:
    ./gradlew connectedDebugAndroidTest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With remote build cache enabled, I managed to cut the build time to &lt;strong&gt;~15 mins&lt;/strong&gt; for &lt;strong&gt;FlowBinding&lt;/strong&gt;. This is a significant improvement compared to the &lt;strong&gt;~20 mins&lt;/strong&gt; build time with &lt;strong&gt;GitHub Actions&lt;/strong&gt; (mostly due to the unlimited build cache provided by &lt;strong&gt;Cirrus CI&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fw91e46x7uztwrsawrkz3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fw91e46x7uztwrsawrkz3.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Features
&lt;/h2&gt;

&lt;p&gt;After having a positive experience migrating the instrumented tests to run on &lt;strong&gt;Cirrus CI&lt;/strong&gt;, I decided to explore some of the other features provided by the service and see how they work in broader Android CI use cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Task dependency
&lt;/h3&gt;

&lt;p&gt;By default all tasks defined in &lt;code&gt;.cirrus.yml&lt;/code&gt; run in parallel. &lt;a href="https://cirrus-ci.org/guide/writing-tasks/#task-execution-dependencies" rel="noopener noreferrer"&gt;Task execution dependencies&lt;/a&gt; can be configured by using the &lt;code&gt;depends_on&lt;/code&gt; keyword. In the following example, the &lt;code&gt;publish_artifacts_task&lt;/code&gt; will only be executed after both the &lt;code&gt;assemble_task&lt;/code&gt; and &lt;code&gt;connected_check_task&lt;/code&gt; have completed successfully.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assemble_task:
  ...

connected_check_task:
  ...

publish_artifacts_task:
  depends_on:
    - assemble
    - connected_check
  deploy_snapshot_script:
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Encrypted secrets
&lt;/h3&gt;

&lt;p&gt;Sometimes a CI task might need access to some secrets. For example, building a release APK may require the encryption key for the release keystore, while publishing a library to Maven Central requires the Sonatype Nexus credentials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cirrus CI&lt;/strong&gt; supports &lt;a href="https://cirrus-ci.org/guide/writing-tasks/#encrypted-variables" rel="noopener noreferrer"&gt;encrypted variables&lt;/a&gt; which can be safely added to the &lt;code&gt;.cirrus.yml&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;To use a secret in a CI task, go to the Cirrus CI settings page and follow the instruction to generate the encrypted variable:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fb3gjhq0syrlwgp8dlseu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fb3gjhq0syrlwgp8dlseu.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then add the generated &lt;code&gt;ENCRYPTED[xxx]&lt;/code&gt; as a regular environment variable in the &lt;code&gt;env&lt;/code&gt; block of a task:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;publish_artifacts_task:
  depends_on:
    - assemble_and_check
    - connected_check
  env:
    SONATYPE_NEXUS_USERNAME: ENCRYPTED[abcd]
    SONATYPE_NEXUS_PASSWORD: ENCRYPTED[1234]
  container:
    image: reactivecircus/android-sdk:latest
  deploy_snapshot_script:
    ./gradlew clean androidSourcesJar androidJavadocsJar uploadArchives --no-daemon --no-parallel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Artifacts
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cirrus CI&lt;/strong&gt; supports storing build artifacts and making them available for download from the dashboard via the &lt;a href="https://cirrus-ci.org/guide/writing-tasks/#artifacts-instruction" rel="noopener noreferrer"&gt;&lt;code&gt;artifacts&lt;/code&gt; instruction&lt;/a&gt;. The following example produces the &lt;strong&gt;JUnit XML artifacts&lt;/strong&gt; for the &lt;code&gt;unit_tests_task&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unit_tests_task:
  container:
    image: reactivecircus/android-sdk:latest
    cpu: 8
    memory: 24G
  unit_test_script:
    ./gradlew test
  always:
    junit_artifacts:
      path: "**/test-results/**/*.xml"
      type: text/xml
      format: junit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conditionally skipping builds
&lt;/h3&gt;

&lt;p&gt;Sometimes you may want to skip a CI task if the there's no change in the source code (e.g. changing the README.md file). For example we might want to &lt;strong&gt;only&lt;/strong&gt; run the &lt;code&gt;connected_check_task&lt;/code&gt; if there are changes to the Kotlin, XML or Gradle sources in the commit or pull request. This can be implemented with the &lt;a href="https://cirrus-ci.org/guide/writing-tasks/#conditional-task-execution" rel="noopener noreferrer"&gt;&lt;code&gt;skip&lt;/code&gt; keyword&lt;/a&gt; and the &lt;a href="https://cirrus-ci.org/guide/writing-tasks/#supported-functions" rel="noopener noreferrer"&gt;&lt;code&gt;changesInclude&lt;/code&gt; function&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;connected_check_task:
  only_if: $CIRRUS_BRANCH == "master" || CIRRUS_PR != ""
  skip: "!changesInclude('.cirrus.yml', '*.gradle', '*.gradle.kts', '**/*.gradle', '**/*.gradle.kts', '*.properties', '**/*.properties', '**/*.kt', '**/*.xml')"
  ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F2hz82h65lt279ye47u8k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F2hz82h65lt279ye47u8k.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that this is also supported by GitHub Actions via the &lt;a href="https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#onpushpull_requestpaths" rel="noopener noreferrer"&gt;path filters&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Manual trigger
&lt;/h3&gt;

&lt;p&gt;A task can also be triggered &lt;strong&gt;manually&lt;/strong&gt; from the Cirrus CI dashboard or the GitHub Checks page. This is often useful in deployment (e.g. publishing a new APK) where we need to be able to control when to trigger the task.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cirrus-ci.org/guide/writing-tasks/#manual-tasks" rel="noopener noreferrer"&gt;Manual task&lt;/a&gt; can be configured by adding &lt;code&gt;trigger_type: manual&lt;/code&gt; to the task:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;publish_artifacts_task:
  trigger_type: manual
  depends_on:
    - assemble_and_check
    - connected_check
  ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fuaolcs0voeh63lf29aks.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fuaolcs0voeh63lf29aks.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ft1m9gvdulchu2iztcas6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ft1m9gvdulchu2iztcas6.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Delayed execution - Required PR Labels
&lt;/h3&gt;

&lt;p&gt;Tasks such as instrumented tests require special execution environment and have a longer feedback cycle, which make them more expensive to run. Therefore when a new PR is created we might want to run the fast checks (assemble and unit tests) immediately, and only trigger the expensive checks after an initial review.&lt;/p&gt;

&lt;p&gt;Cirrus CI provides a feature called &lt;a href="https://cirrus-ci.org/guide/writing-tasks/#required-pr-labels" rel="noopener noreferrer"&gt;Required PR Labels&lt;/a&gt;. Tasks can specify a list of &lt;code&gt;required_pr_labels&lt;/code&gt; to only get triggered once these labels have been added to the pull request.&lt;/p&gt;

&lt;p&gt;In the following example the &lt;code&gt;connected_check_task&lt;/code&gt; will only be run after the &lt;code&gt;initial-review&lt;/code&gt; label has been added to the pull request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;connected_check_task:
  required_pr_labels: initial-review
  only_if: $CIRRUS_BRANCH == "master" || CIRRUS_PR != ""
  ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the GitHub Checks page, tasks which are waiting for PR labels are considered "neutral":&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fexmioeeku26lo5twp1zp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fexmioeeku26lo5twp1zp.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To trigger these tasks, apply the required &lt;code&gt;initial-review&lt;/code&gt; label to the PR:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fe63jaqfr1rvu5ipq4y0a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fe63jaqfr1rvu5ipq4y0a.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;connected_check_task&lt;/code&gt; will now be triggered.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build notification
&lt;/h3&gt;

&lt;p&gt;A major feature missing from Cirrus CI is notification. When a build fails, the status will be reflected on the GitHub Checks page, but Cirrus CI doesn't have built-in mechanism for sending an Email notification.&lt;/p&gt;

&lt;p&gt;This can be worked around by adding a &lt;a href="https://github.com/cirrus-actions/email" rel="noopener noreferrer"&gt;GitHub Action&lt;/a&gt; that sends emails when a GitHub Check Suite completes, but you'll need to provide your own SMTP server information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cirrus CI Android Templates
&lt;/h2&gt;

&lt;p&gt;If you've made it this far, chances are you want to try out Cirrus CI for your own projects. To help you get started, I've created some &lt;a href="https://github.com/ReactiveCircus/cirrusci-android-templates" rel="noopener noreferrer"&gt;cirrusci-android-templates&lt;/a&gt; for common use cases such as building APKs, running instrumented tests, and publishing library artifacts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Overall I have been really impressed with &lt;strong&gt;Cirrus CI&lt;/strong&gt;. I was initially attracted by the KVM-enabled containers for running instrumented tests. Not only was I able to migrate FlowBinding to use &lt;a href="https://github.com/ReactiveCircus/FlowBinding/pull/59" rel="noopener noreferrer"&gt;Cirrus CI for running instrumented tests&lt;/a&gt;, it was also a lot of fun discovering and trying a few other cool features along the way.&lt;/p&gt;

&lt;p&gt;It's clear that the Cirrus CI Team understands the unique challenges when it comes to doing CI for Android. Having used quite a few CI solutions for Android including Jenkins, Buddybuild, Buildkite, CircleCI, Bitrise, and GitHub Actions, I'd like to highlight some of the key competitive advantages Cirrus CI brings to the table and why you might want to consider using it for Android:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Excellent documentations and examples&lt;/li&gt;
&lt;li&gt;Completely free for opensource projects with very generous concurrency limit&lt;/li&gt;
&lt;li&gt;KVM-enabled containers for running hardware-accelerated Emulators&lt;/li&gt;
&lt;li&gt;Powerful VMs - up to 8.0 CPUs and 24 GB of RAM for Linux containers&lt;/li&gt;
&lt;li&gt;Built-in support for Gradle build cache (both local and remote)&lt;/li&gt;
&lt;li&gt;First-class  GitHub integration with features such as &lt;a href="https://cirrus-ci.org/guide/writing-tasks/#required-pr-labels" rel="noopener noreferrer"&gt;Required PR Labels&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are also a few things that could use some improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Being a smaller player in the market means less efforts have been put into marketing, branding, and UX of the product especially the build dashboard&lt;/li&gt;
&lt;li&gt;Downloading and uploading cache archives takes noticeably longer than some of the other services&lt;/li&gt;
&lt;li&gt;Currently only GitHub is supported, although &lt;a href="https://github.com/cirruslabs/cirrus-ci-docs/issues/9" rel="noopener noreferrer"&gt;BitBucket support is planed&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;There's no built-in support for Email notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these was a deal breaker for running instrumented tests. But due to the slower download and upload speeds for build cache, I have yet to migrate the rest my CI pipelines from &lt;strong&gt;CircleCI&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cirrus Lab&lt;/strong&gt; (the team behind Cirrus CI) recently announced &lt;a href="https://www.cirrus-emulators.com/" rel="noopener noreferrer"&gt;Cirrus Emulators&lt;/a&gt;, a cloud service that provides hardware-accelerated Android Emulators which can be integrated with any CI services using a CLI. I'll share my experience with it once the product is publicly available.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;




&lt;p&gt;Thanks to &lt;a href="https://twitter.com/fedor" rel="noopener noreferrer"&gt;Fedor Korotkov&lt;/a&gt; for the review.&lt;/p&gt;

</description>
      <category>android</category>
      <category>ci</category>
      <category>testing</category>
    </item>
    <item>
      <title>Running Android Instrumented Tests on CI - from Bitrise.io to GitHub Actions</title>
      <dc:creator>Yang</dc:creator>
      <pubDate>Sun, 24 Nov 2019 09:36:00 +0000</pubDate>
      <link>https://dev.to/ychescale9/running-android-emulators-on-ci-from-bitrise-io-to-github-actions-3j76</link>
      <guid>https://dev.to/ychescale9/running-android-emulators-on-ci-from-bitrise-io-to-github-actions-3j76</guid>
      <description>&lt;p&gt;It's almost 2020 and it remains a challenge to run Android Instrumented tests on CI, especially for &lt;strong&gt;opensource&lt;/strong&gt; projects and small teams:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setting up and managing an in-house device farm is expensive and requires long-term investment and on-going infrastructure support - not a viable option for most teams.&lt;/li&gt;
&lt;li&gt;Cloud-based device farms such as &lt;strong&gt;Firebase Test Lab&lt;/strong&gt; are a much more cost-effective solution for most teams as they take care of managing the infrastructure while offering simple APIs for &lt;a href="https://firebase.google.com/docs/test-lab/android/continuous" rel="noopener noreferrer"&gt;integrating with existing CI pipelines&lt;/a&gt;. However, for opensource projects with a large amount of tests, free plans offered by these services likely won't cover the needs, while paying for such service is usually hard to justify for an opensource effort.&lt;/li&gt;
&lt;li&gt;For small teams who are not quite ready to invest in a fully-fledged cloud-based testing service, running tests on the &lt;strong&gt;Android Emulator&lt;/strong&gt; within a docker container has been the most viable and common approach. But today most cloud-based CI services are still lacking &lt;strong&gt;hardware acceleration&lt;/strong&gt; support from the host VM, which is the no.1 blocker for running tests on modern Android Emulators (especially on recent API levels) on CI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I recently released &lt;a href="https://github.com/ReactiveCircus/FlowBinding" rel="noopener noreferrer"&gt;FlowBinding&lt;/a&gt; which has over &lt;strong&gt;160&lt;/strong&gt; instrumented tests across 10 library modules. I wanted to be able to run all these tests on each PR / merge into master without worrying about things like usage limits and quotas. So &lt;strong&gt;Firebase Test Lab&lt;/strong&gt; is not an option as the free plan has a daily limit of &lt;strong&gt;10&lt;/strong&gt; test runs on virtual devices and &lt;strong&gt;5&lt;/strong&gt; test runs on physical devices. Instead I needed something that checks these boxes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free for opensource projects.&lt;/li&gt;
&lt;li&gt;Supports configuring the Android Emulator system images used - API level, target: (default or google_apis), arch / ABI (x86 or x86_64).&lt;/li&gt;
&lt;li&gt;Supports running emulators in &lt;strong&gt;headless&lt;/strong&gt; mode (as of &lt;a href="https://androidstudio.googleblog.com/2019/11/emulator-2927-canary.html" rel="noopener noreferrer"&gt;Emulator 29.2.7 Canary&lt;/a&gt; this is equivalent to running &lt;code&gt;emulator -no-window&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Supports running modern x86 and x86_64 based emulators with &lt;strong&gt;hardware acceleration (KVM) enabled&lt;/strong&gt; for better performance.&lt;/li&gt;
&lt;li&gt;Provides enough RAM for running both &lt;strong&gt;Gradle&lt;/strong&gt; and an instance of &lt;strong&gt;Emulator&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this post I'll share my journey on finding the best solution for running Android Emulators on CI for opensource projects.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hardware-accelerated Emulators
&lt;/h2&gt;

&lt;p&gt;Out of those requirements mentioned earlier, enabling &lt;strong&gt;hardware acceleration&lt;/strong&gt; support on the host VM has been the most difficult one to meet.&lt;/p&gt;

&lt;p&gt;The old ARM-based emulators were slow. But more importantly ARM-based system images are no longer supported by Google, as only x86 and x86_64 images are provided for recent API levels.&lt;/p&gt;

&lt;p&gt;The modern Intel Atom (x86 and x86_64) emulators intend to provide much better performance with &lt;a href="https://developer.android.com/studio/run/emulator-acceleration" rel="noopener noreferrer"&gt;GPU hardware acceleration&lt;/a&gt;. However, installation of special software (HAXM on Mac &amp;amp; Windows, QEMU on Linux) is required for enabling hardware acceleration support. This presents a challenge on CI as to be able to run hardware accelerated emulators within a docker container, &lt;a href="https://www.linux-kvm.org/page/Main_Page" rel="noopener noreferrer"&gt;KVM&lt;/a&gt; must be enabled by the host VM which isn't the case for most cloud-based CI providers due to infrastructural limits.&lt;/p&gt;

&lt;p&gt;This means currently we are not able to run hardware-accelerated emulators on some of the most popular cloud-based CI services such as &lt;strong&gt;CircleCI&lt;/strong&gt; and &lt;strong&gt;Travis&lt;/strong&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Google wants to help
&lt;/h2&gt;

&lt;p&gt;It's worth to note that recently Google has been putting more efforts into offering better support for running Emulators on &lt;strong&gt;CI&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Earlier this year the Android Emulator Team added support for running the emulator engine in &lt;a href="https://androidstudio.googleblog.com/2019/02/emulator-2818-canary.html" rel="noopener noreferrer"&gt;headless mode&lt;/a&gt; to reduce some of the implicit expectations on the host machine running the emulator in a CI environment. This feature has been unified with the &lt;code&gt;-no-window&lt;/code&gt; mode in a &lt;a href="https://androidstudio.googleblog.com/2019/11/emulator-2927-canary.html" rel="noopener noreferrer"&gt;recent release&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Google recently published a &lt;a href="https://android-developers.googleblog.com/2019/10/continuous-testing-with-new-android.html" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; highlighting some of the new tools they've been working on to improve deployability and debuggability of the Emulators on CI. They also opensourced a bunch of &lt;a href="https://github.com/google/android-emulator-container-scripts" rel="noopener noreferrer"&gt;scripts&lt;/a&gt; for running Emulators in (Docker) containers.&lt;/li&gt;
&lt;li&gt;There was a dedicated session titled &lt;a href="https://www.youtube.com/watch?v=70-FCrCKdRM" rel="noopener noreferrer"&gt;Emulator in a Continuous Integration (CI) Environment&lt;/a&gt; at Android Developer Summit 2019.&lt;/li&gt;
&lt;li&gt;There is also &lt;a href="https://www.youtube.com/watch?v=-_kZC29sWAo" rel="noopener noreferrer"&gt;Project Nitrogen&lt;/a&gt; which is a larger effort on testing introduced back at Google IO 2018, but there hasn't been any public announcement on its progress this year.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's encouraging to see Google's interest and commitment to continuously improve the experience of running Emulators on CI, but as of today the &lt;strong&gt;KVM&lt;/strong&gt; dependency is still the main hurdle that prevents most users from being able to leverage these new tools and improvements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bitrise.io
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.bitrise.io/" rel="noopener noreferrer"&gt;bitrise.io&lt;/a&gt; is a CI/CD platform dedicated for Mobile. It has its own ecosystem including an online &lt;a href="https://www.bitrise.io/features/workflow-editor" rel="noopener noreferrer"&gt;Workflow Editor&lt;/a&gt; and an extensive library of workflow &lt;a href="https://devcenter.bitrise.io/steps-and-workflows/getting-started-steps/" rel="noopener noreferrer"&gt;steps&lt;/a&gt;, which probably appeal to new teams / projects starting out with basic Mobile CI needs.&lt;/p&gt;

&lt;p&gt;But what got me excited were the provided steps for running Android UI tests (both physical and virtual devices), and &lt;strong&gt;KVM&lt;/strong&gt; support in the host environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Bitrise Steps
&lt;/h3&gt;

&lt;p&gt;Bitrise provides the &lt;a href="https://blog.bitrise.io/new-step-android-build-for-ui-testing" rel="noopener noreferrer"&gt;Android Build for UI Testing&lt;/a&gt; and &lt;a href="https://github.com/bitrise-steplib/steps-virtual-device-testing-for-android" rel="noopener noreferrer"&gt;Virtual Device Testing for Android&lt;/a&gt; steps which use &lt;strong&gt;Firebase Test Lab&lt;/strong&gt; to run tests for the chosen module / build variant with no limit on device hours / no. of test executions. But there are a couple of limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only 1 build variant from 1 module can be run for each step. This means if you have multiple library modules with instrumented tests, you'll have to manually configure a &lt;strong&gt;Android Build for UI Testing&lt;/strong&gt; or &lt;strong&gt;Virtual Device Testing for Android&lt;/strong&gt; step for each module (or setup parallel workflows for running these in parallel if you wish to pay for additional containers).&lt;/li&gt;
&lt;li&gt;Running tests in a library module doesn't work unless an app APK is also present. The &lt;a href="https://discuss.bitrise.io/t/vdt-not-able-to-run-instrumentation-tests-on-android-library-project/3197/7" rel="noopener noreferrer"&gt;workaround&lt;/a&gt; is to also run &lt;code&gt;app:assembleDebug&lt;/code&gt; for your library module tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You could also use the &lt;a href="https://github.com/bitrise-steplib/steps-avd-manager" rel="noopener noreferrer"&gt;AVD Manager&lt;/a&gt; and &lt;a href="https://github.com/bitrise-steplib/steps-wait-for-android-emulator" rel="noopener noreferrer"&gt;Wait for Android emulator&lt;/a&gt; steps to spin up an Emulator on Bitrise locally and then run all your tests with Gradle, but you don't have full control over the Emulator configs and the specific SDK components needed for your workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom workflow
&lt;/h3&gt;

&lt;p&gt;Since Bitrise's host VMs have &lt;strong&gt;KVM&lt;/strong&gt; enabled, we can easily setup a custom Workflow to control exactly what SDK components we want to download / update, how we want to configure and start an Emulator by providing with a custom shell script and running tests with regular Gradle commands.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F08w18sretir44jzo0lm8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F08w18sretir44jzo0lm8.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a &lt;a href="https://github.com/ychescale9/bitrise-ui-tests-workflow" rel="noopener noreferrer"&gt;repo&lt;/a&gt; showing how to setup such custom workflow on Bitrise.&lt;/p&gt;

&lt;p&gt;At this point I was finally able to run Android instrumented tests on CI for &lt;strong&gt;FlowBinding&lt;/strong&gt; on every PR! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fuldnev0gkmgfv75sugct.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fuldnev0gkmgfv75sugct.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting greedy
&lt;/h3&gt;

&lt;p&gt;Being able to run &lt;strong&gt;unlimited&lt;/strong&gt; instrumented tests &lt;strong&gt;for free&lt;/strong&gt; on CI is not something to be taken for granted. But as more modules and tests were being added to &lt;strong&gt;FlowBinding&lt;/strong&gt;, build time for the Bitrise workflow also significantly increased.&lt;/p&gt;

&lt;p&gt;When I first integrated the Bitrise workflow, &lt;strong&gt;FlowBinding&lt;/strong&gt; had &lt;strong&gt;2&lt;/strong&gt; library modules with &lt;strong&gt;9&lt;/strong&gt; instrumented tests; the entire Bitrise workflow took about &lt;strong&gt;10 mins&lt;/strong&gt; (this includes running a custom script to configure and spin up an Emulator, and running the tests with &lt;code&gt;./gradlew connectedCheck&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;By the time I was ready to publish the first version of &lt;strong&gt;FlowBinding&lt;/strong&gt;, there were &lt;strong&gt;10&lt;/strong&gt; library modules with a total of &lt;strong&gt;160&lt;/strong&gt; instrumented tests; the entire Bitrise workflow was taking over &lt;strong&gt;30 mins&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fudfzer83da1vf5qxdwuc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fudfzer83da1vf5qxdwuc.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While the number of modules and tests are unlikely to significantly increase for this project, an &lt;strong&gt;above 30 mins&lt;/strong&gt; build time on CI for each PR is still quite an inconvenience.&lt;/p&gt;

&lt;p&gt;Besides the long build time, I also wasn't very comfortable with the recommended way of configuring workflows with the online editor and having to rely on those built-in steps provided by Bitrise. Relying on the platform and ecosystem makes it harder to port our pipelines to potentially better alternatives in the future.&lt;/p&gt;

&lt;p&gt;Given these concerns and dissatisfactions, I was set out to continue the search for better experience for running instrumented tests on CI. &lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Actions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub Actions&lt;/strong&gt; recently added &lt;a href="https://github.blog/2019-08-08-github-actions-now-supports-ci-cd/" rel="noopener noreferrer"&gt;support for CI/CD&lt;/a&gt;, and is free for public repositories. As a result many opensource projects on GitHub are starting to migrate their CI/CD workflows to GitHub Actions which has first-class integration with GitHub itself and strong infrastructure support from &lt;strong&gt;Azure&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The first thing I tried was running a &lt;strong&gt;hardware-accelerated&lt;/strong&gt; Emulator within a docker container on a &lt;strong&gt;Linux / Ubuntu&lt;/strong&gt; VM. Unfortunately (and perhaps unsurprisingly) &lt;strong&gt;KVM&lt;/strong&gt; is not enabled on the host machines.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;GitHub Actions&lt;/strong&gt; also offer free &lt;strong&gt;macOS&lt;/strong&gt; machines for public repositories, and these macOS VMs also have &lt;a href="https://help.github.com/en/actions/automating-your-workflow-with-github-actions/software-installed-on-github-hosted-runners#android" rel="noopener noreferrer"&gt;&lt;strong&gt;HAXM&lt;/strong&gt; (Hardware Accelerated Execution Manager) installed&lt;/a&gt;. This is promising as we should be able to setup a GitHub workflow that installs the required SDK components and spins up a hardware-accelerated Emulator directly from the host machine, and finally run our tests on the Emulator!&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom action on macOS
&lt;/h3&gt;

&lt;p&gt;Setting up a workflow that runs on &lt;strong&gt;macOS&lt;/strong&gt; though is not as easy as doing the same with &lt;strong&gt;Docker&lt;/strong&gt;, as our base environment is no longer a docker image with all the tools installed and configured and we'll need to port our scripts to work in the macOS environment. The workflow would need to do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install / update the required &lt;strong&gt;Android SDK&lt;/strong&gt; components including build-tools, platform-tools, platform (for the required API level), emulator and system-images (for the required API level).&lt;/li&gt;
&lt;li&gt;Create a new instance of &lt;strong&gt;AVD&lt;/strong&gt; with various configuration options such as the API level, CPU architecture / ABI, and device profile used.&lt;/li&gt;
&lt;li&gt;Launch a new instance of Emulator from the AVD created.&lt;/li&gt;
&lt;li&gt;Wait until the Emulator is booted and ready for use.&lt;/li&gt;
&lt;li&gt;Finally execute a Gradle command to run the tests - e.g. &lt;code&gt;./gradlew connectedCheck&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can package this process into a &lt;a href="https://github.com/marketplace/actions/android-emulator-runner/" rel="noopener noreferrer"&gt;custom GitHub Action&lt;/a&gt; and provide a bunch of &lt;a href="https://github.com/ReactiveCircus/android-emulator-runner#configurations" rel="noopener noreferrer"&gt;configuration options&lt;/a&gt; for the consumer.&lt;/p&gt;

&lt;p&gt;A workflow that uses the custom action (&lt;a href="https://github.com/ReactiveCircus/android-emulator-runner" rel="noopener noreferrer"&gt;Android Emulator Runner&lt;/a&gt;) to run instrumented tests on &lt;strong&gt;API 29&lt;/strong&gt; may look something like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

jobs:
  test:
    runs-on: macOS-latest
    steps:
    - name: checkout
      uses: actions/checkout@v2

    - name: run tests
      uses: reactivecircus/android-emulator-runner@v2
      with:
        api-level: 29
        script: ./gradlew connectedCheck


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Outcome
&lt;/h3&gt;

&lt;p&gt;It was time to run the custom GitHub Action with &lt;strong&gt;FlowBinding&lt;/strong&gt;, and the &lt;a href="https://github.com/ReactiveCircus/FlowBinding/runs/296389790" rel="noopener noreferrer"&gt;result&lt;/a&gt; was promising!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fwqn9ttd2irbou5ugqtv7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fwqn9ttd2irbou5ugqtv7.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The entire GitHub workflow took around &lt;strong&gt;20 mins&lt;/strong&gt;, while the test suites can now be run against multiple API-levels in parallel (maximum concurrent macOS jobs for public repo is &lt;strong&gt;5 per user/org&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;This is more than &lt;strong&gt;30%&lt;/strong&gt; improvements over the previous solution with &lt;strong&gt;Bitrise&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On paper the specs of the build machines provided by &lt;strong&gt;GitHub Actions&lt;/strong&gt; and &lt;strong&gt;Bitrise&lt;/strong&gt; are similar - 2-core CPU / 7GB RAM. So I'm not really sure what the cause of the improvement is. One possibility is being able to run everything directly on the host VM without the &lt;strong&gt;virtualization overhead&lt;/strong&gt; in a docker-based environment, but that depends on &lt;a href="https://www.brightcomputing.com/blog/containerization-vs.-virtualization-more-on-overhead" rel="noopener noreferrer"&gt;many factors&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Leveraging build matrix
&lt;/h3&gt;

&lt;p&gt;One of my favourite features of &lt;strong&gt;GitHub Actions&lt;/strong&gt; is &lt;a href="https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix" rel="noopener noreferrer"&gt;Build Matrix&lt;/a&gt;, which provides an easy way to run the same job across multiple configurations.&lt;/p&gt;

&lt;p&gt;For example, to run instrumented tests on API 21, 23, 29 for both x86 and x86_64  ABIs, we can define the values for each configuration option under &lt;code&gt;matrix&lt;/code&gt; and reference these values in the inputs of the action:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

jobs:
  test:
    runs-on: macOS-latest
    strategy:
      matrix:
        api-level: [21, 23, 29]
        arch: [x86, x86_64]
    steps:
    - name: checkout
      uses: actions/checkout@v2

    - name: run tests
      uses: reactivecircus/android-emulator-runner@v2
      with:
        api-level: ${{ matrix.api-level }}
        arch: ${{ matrix.arch }}
        script: ./gradlew connectedCheck


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The workflow will run the job with all 6 configurations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API 21, x86&lt;/li&gt;
&lt;li&gt;API 21, x86_64&lt;/li&gt;
&lt;li&gt;API 23, x86&lt;/li&gt;
&lt;li&gt;API 23, x86_64&lt;/li&gt;
&lt;li&gt;API 29, x86&lt;/li&gt;
&lt;li&gt;API 29, x86_64&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can also filter out certain configurations generated by the build matrix. If we only want to run the job with (API 21, x86) and (API 29, x86_64), we can easily define the configurations we don't want with &lt;code&gt;exclude&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

jobs:
  test:
    runs-on: macOS-latest
    strategy:
      matrix:
        api-level: [21, 29]
        arch: [x86, x86_64]
        exclude:
          - api-level: 21
            arch: x86_64
          - api-level: 29
            arch: x86
    steps:
    - name: checkout
      uses: actions/checkout@v2

    - name: run tests
      uses: reactivecircus/android-emulator-runner@v2
      with:
        api-level: ${{ matrix.api-level }}
        arch: ${{ matrix.arch }}
        script: ./gradlew connectedCheck


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This will only generate 2 jobs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API 21, x86&lt;/li&gt;
&lt;li&gt;API 29, x86_64&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can even &lt;a href="https://twitter.com/ychescale9/status/1192051899060457474" rel="noopener noreferrer"&gt;go nuts&lt;/a&gt; (please don't) and run your tests against all combinations of the configurations available, which when executed will generate a total number of &lt;strong&gt;9&lt;/strong&gt; API levels (API 21 to 29) x &lt;strong&gt;2&lt;/strong&gt; targets (default, google_apis) x &lt;strong&gt;2&lt;/strong&gt; CPU / ABI (x86, x86_64) = &lt;strong&gt;36&lt;/strong&gt; jobs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Flh5ndw5z80ujcud6hufg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Flh5ndw5z80ujcud6hufg.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🙃&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fun fact&lt;/strong&gt; - while playing with build matrix I discovered that there is no system image available for &lt;code&gt;android-27;google_apis;x86_64&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt; - &lt;a href="https://www.reddit.com/r/androiddev/comments/e5nulf/emulator_29211_and_amd_hypervisor_12_to_stable/f9l00cz?utm_source=share&amp;amp;utm_medium=web2x" rel="noopener noreferrer"&gt;a response from Google&lt;/a&gt; re. the missing system image:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For ‘android-27;google_apis;x86_64’, there may not be a good reason for it missing. That was in a transitional period where we just moved to x86_64 kernels with x86 32-bit userspace, and we needed to manage the extra complexity from there and never got around to making the x86_64 userspace version. We should have a x86 64-bit userspace version of it at some point, though, but it's not currently on our list of stuff to do (Sorry).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Build Cache?
&lt;/h3&gt;

&lt;p&gt;One of the things I love the most about &lt;strong&gt;CircirCI&lt;/strong&gt; is its native support for &lt;strong&gt;caching&lt;/strong&gt;. Since we are able to cache the entire &lt;code&gt;~/.gradle&lt;/code&gt; directly to get incredibly fast incremental builds with &lt;strong&gt;CircleCI&lt;/strong&gt;, I wondered if I could do something similar to my &lt;strong&gt;Github workflow&lt;/strong&gt; to make it even faster.&lt;/p&gt;

&lt;p&gt;Turned out there is this &lt;a href="https://github.com/actions/cache" rel="noopener noreferrer"&gt;actions/cache&lt;/a&gt; provided by GitHub for caching dependencies and build outputs.&lt;/p&gt;

&lt;p&gt;We can cache the &lt;code&gt;~/.gradle/caches&lt;/code&gt; directory by adding the following step to the job:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

- uses: actions/cache@v1
  with:
    path: ~/.gradle/caches
    key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
    restore-keys: ${{ runner.os }}-gradle-


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;del&gt;Unfortunately this won't work right now as there's a &lt;strong&gt;200 MB&lt;/strong&gt; limit on the cache size which isn't enough for Gradle, although they are planning to &lt;a href="https://github.com/actions/cache/issues/6#issuecomment-557099690" rel="noopener noreferrer"&gt;increase the limit to &lt;strong&gt;2GB&lt;/strong&gt;&lt;/a&gt; in the future.&lt;/del&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: per-cache size limit has been increased to &lt;strong&gt;2GB&lt;/strong&gt; so the caching config above should now work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Journey never ends
&lt;/h2&gt;

&lt;p&gt;While the &lt;strong&gt;custom GitHub Action&lt;/strong&gt; has been working really well for &lt;strong&gt;FlowBinding&lt;/strong&gt; since the migration, I'm still hoping to find a more generic and future-proof solution where everything runs within a docker container rather than directly on a macOS VM. It's important for me to be able to easily try out / migrate to other CI/CD providers in the future, and using a workflow that runs things directly on a macOS VM instead of a standard Docker environment would likely make it harder to do so.&lt;/p&gt;

&lt;p&gt;I recently came across a less-known cloud-based CI provider with native KVM support, and is completely free for opensource projects. In a future article I'll explore and share my experience with this product and compare it with my current CI workflow.&lt;/p&gt;

&lt;p&gt;Stay tuned!&lt;/p&gt;




&lt;p&gt;Featured in &lt;a href="https://androidweekly.net/issues/issue-390" rel="noopener noreferrer"&gt;Android Weekly #390&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>android</category>
      <category>ci</category>
      <category>github</category>
      <category>testing</category>
    </item>
    <item>
      <title>Binding Android UI with Kotlin Flow</title>
      <dc:creator>Yang</dc:creator>
      <pubDate>Tue, 29 Oct 2019 15:23:30 +0000</pubDate>
      <link>https://dev.to/ychescale9/binding-android-ui-with-kotlin-flow-22ok</link>
      <guid>https://dev.to/ychescale9/binding-android-ui-with-kotlin-flow-22ok</guid>
      <description>&lt;p&gt;Modern Android codebases are becoming increasingly &lt;strong&gt;reactive&lt;/strong&gt;. With concepts and patterns such as &lt;a href="http://hannesdorfmann.com/android/model-view-intent"&gt;MVI&lt;/a&gt;, &lt;a href="https://redux.js.org/"&gt;Redux&lt;/a&gt;, &lt;a href="https://redux.js.org/basics/data-flow"&gt;Unidirectional Data Flow&lt;/a&gt;, many components of the system are being modelled as &lt;strong&gt;streams&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;UI events can also be modelled as streams of inputs into the system.&lt;/p&gt;

&lt;p&gt;Android’s platform and unbundled UI widgets provide listener / callback style APIs, but with &lt;a href="https://github.com/JakeWharton/RxBinding"&gt;RxBinding&lt;/a&gt; they can easily be mapped to RxJava &lt;code&gt;Observable&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;findViewById&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;clicks&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// handle button clicked&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Kotlin Flow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/Kotlin/kotlinx.coroutines"&gt;kotlinx.coroutines&lt;/a&gt; 1.3 introduced &lt;code&gt;Flow&lt;/code&gt;, which is an important addition to the library which finally has support for &lt;a href="https://medium.com/@elizarov/cold-flows-hot-channels-d74769805f9"&gt;cold streams&lt;/a&gt;. It’s (conceptually) a reactive streams implementation based on Kotlin’s suspending functions and channels API.&lt;/p&gt;

&lt;h4&gt;
  
  
  Binding Android UI with Flow
&lt;/h4&gt;

&lt;p&gt;In this post I’m not going to discuss &lt;strong&gt;why&lt;/strong&gt; you may or may not want to migrate from &lt;strong&gt;RxJava&lt;/strong&gt; to &lt;strong&gt;Kotlin Coroutines&lt;/strong&gt; / &lt;strong&gt;Flow&lt;/strong&gt;. But let’s see how we can implement the same &lt;code&gt;clicks()&lt;/code&gt; example above with &lt;code&gt;Flow&lt;/code&gt;. The API should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;findViewById&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clicks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// this returns a Flow&amp;lt;Unit&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// handle button clicked&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;kotlinx.coroutines&lt;/strong&gt; library offers many top-level builder functions for creating Flow. One such function is &lt;code&gt;callbackFlow&lt;/code&gt; which is specifically designed for converting a multi-shot callback API into a &lt;code&gt;Flow&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clicks&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;callbackFlow&lt;/span&gt; 
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;listener&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OnClickListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;offer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;setOnClickListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;awaitClose&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;setOnClickListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The block within &lt;code&gt;awaitClose&lt;/code&gt; is run when the consumer of the flow cancels the flow collection so this is the perfect place to remove the listener registered earlier.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;offer(…)&lt;/code&gt; pushes a new element into the &lt;code&gt;SendChannel&lt;/code&gt; which &lt;code&gt;Flow&lt;/code&gt; uses internally. But the function might throw an exception if the channel is closed for send. We can create an extension function that catches any cancellation exception:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;E&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;SendChannel&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;E&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeOffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;E&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isClosedForSend&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;offer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CancellationException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Here’s the complete implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@CheckResult&lt;/span&gt;
&lt;span class="nd"&gt;@UseExperimental&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ExperimentalCoroutinesApi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clicks&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;callbackFlow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;checkMainThread&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;listener&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OnClickListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;safeOffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;setOnClickListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;awaitClose&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;setOnClickListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;conflate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Some UI widgets might hold a state internally such as the current value of a &lt;strong&gt;Slider&lt;/strong&gt; (a recently added &lt;a href="https://github.com/material-components/material-components-android/blob/master/docs/components/Slider.md"&gt;Material Component&lt;/a&gt;) which you might want to observe with a &lt;code&gt;Flow&lt;/code&gt;. In this case it might also be useful if the Flow can emit the current value &lt;strong&gt;immediately&lt;/strong&gt; when collected, so that you can bind the value to some other UI element as soon as the screen is launched without the value of the slider ever being changed by the user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@CheckResult&lt;/span&gt;
&lt;span class="nd"&gt;@UseExperimental&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ExperimentalCoroutinesApi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Slider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valueChanges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emitImmediately&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;callbackFlow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;checkMainThread&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;listener&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Slider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OnChangeListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="nf"&gt;safeOffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;setOnChangeListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;awaitClose&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;setOnChangeListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startWithCurrentValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emitImmediately&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;conflate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The optional &lt;code&gt;emitImmediately&lt;/code&gt; parameter controls whether to emit the current value immediately on flow collection.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;emitImmediately&lt;/code&gt; is true we add &lt;code&gt;onStart { emit(value)}&lt;/code&gt; on the original &lt;code&gt;flow&lt;/code&gt; which is the equivalent of &lt;code&gt;startWith(value)&lt;/code&gt; in RxJava. This behaviour can again be wrapped in an extension function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="nf"&gt;startWithCurrentValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emitImmediately&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;?):&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emitImmediately&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;onStart&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;block&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As we can see it’s quite easy to implement UI event bindings for Kotlin Flow, thanks to the powerful Coroutines APIs. But there are myriad of other widgets both from the platform and the unbundled libraries (AndroidX), while new components such as &lt;strong&gt;MaterialDatePicker&lt;/strong&gt; and &lt;strong&gt;Slider&lt;/strong&gt; are being added to &lt;a href="https://github.com/material-components/material-components-android"&gt;Material Components Android&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It’d be nice if we have a library of these bindings for Kotlin Flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Introducing FlowBinding
&lt;/h3&gt;

&lt;p&gt;In the past few months I’ve been working on &lt;a href="https://github.com/ReactiveCircus/FlowBinding"&gt;FlowBinding&lt;/a&gt; which offers a comprehensive set of binding APIs for Android’s platform and unbundled UI widgets, and I’m delighted to share the first &lt;a href="https://github.com/ReactiveCircus/FlowBinding/releases/tag/0.5.0"&gt;public release&lt;/a&gt; now that the roadmap for 1.0 is complete.&lt;/p&gt;

&lt;p&gt;The library is inspired by Jake’s &lt;a href="https://github.com/JakeWharton/RxBinding"&gt;RxBinding&lt;/a&gt; and aims to cover &lt;strong&gt;most&lt;/strong&gt; of the bindings provided by RxBinding, while shifting our focus to supporting more modern &lt;strong&gt;AndroidX&lt;/strong&gt; APIs such as &lt;strong&gt;ViewPager2&lt;/strong&gt; and the new components in Material Components as they become available.&lt;/p&gt;

&lt;p&gt;Bindings are available as independent artifacts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Platform bindings&lt;/span&gt;
&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s2"&gt;"io.github.reactivecircus.flowbinding:flowbinding-android:${flowbinding_version}"&lt;/span&gt;
&lt;span class="c1"&gt;// AndroidX bindings&lt;/span&gt;
&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s2"&gt;"io.github.reactivecircus.flowbinding:flowbinding-appcompat:${flowbinding_version}"&lt;/span&gt;
&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s2"&gt;"io.github.reactivecircus.flowbinding:flowbinding-core:${flowbinding_version}"&lt;/span&gt;
&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s2"&gt;"io.github.reactivecircus.flowbinding:flowbinding-drawerlayout:${flowbinding_version}"&lt;/span&gt;
&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s2"&gt;"io.github.reactivecircus.flowbinding:flowbinding-navigation:${flowbinding_version}"&lt;/span&gt;
&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s2"&gt;"io.github.reactivecircus.flowbinding:flowbinding-recyclerview:${flowbinding_version}"&lt;/span&gt;
&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s2"&gt;"io.github.reactivecircus.flowbinding:flowbinding-swiperefreshlayout:${flowbinding_version}"&lt;/span&gt;
&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s2"&gt;"io.github.reactivecircus.flowbinding:flowbinding-viewpager2:${flowbinding_version}"&lt;/span&gt;
&lt;span class="c1"&gt;// Material Components bindings&lt;/span&gt;
&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s2"&gt;"io.github.reactivecircus.flowbinding:flowbinding-material:${flowbinding_version}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;List of specific binding APIs provided is available in &lt;a href="https://github.com/ReactiveCircus/FlowBinding#roadmap"&gt;each subproject&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tests
&lt;/h4&gt;

&lt;p&gt;Lots of efforts have been put into testing the library. &lt;strong&gt;All&lt;/strong&gt; binding APIs are covered by Android instrumented tests which are run on CI builds.&lt;/p&gt;

&lt;h4&gt;
  
  
  Usage
&lt;/h4&gt;

&lt;p&gt;To observe click events on an Android &lt;code&gt;View&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;findViewById&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clicks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// binding API available in flowbinding-android&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// handle button clicked&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launchIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uiScope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Binding Scope
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;launchIn(scope)&lt;/code&gt; is a shorthand for &lt;code&gt;scope.launch { flow.collect() }&lt;/code&gt; provided by the &lt;strong&gt;kotlinx-coroutines-core&lt;/strong&gt; library.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;uiScope&lt;/code&gt; in the example above is a &lt;code&gt;CoroutineScope&lt;/code&gt; that defines the lifecycle of this &lt;code&gt;Flow&lt;/code&gt;. The binding implementation will respect this scope by unregistering the callback / listener automatically when the scope is cancelled.&lt;/p&gt;

&lt;p&gt;In the context of Android lifecycle this means the &lt;code&gt;uiScope&lt;/code&gt; passed in here should be a scope that's bound to the &lt;code&gt;Lifecycle&lt;/code&gt; of the view the widget lives in.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;androidx.lifecycle:lifecycle-runtime-ktx:2.2.0&lt;/code&gt; introduced an extension property &lt;code&gt;LifecycleOwner.lifecycleScope: LifecycleCoroutineScope&lt;/code&gt; which will be cancelled when the &lt;code&gt;Lifecycle&lt;/code&gt; is destroyed.&lt;/p&gt;

&lt;p&gt;In an Activity it might look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleActivity&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AppCompatActivity&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;setContentView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;activity_example&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;findViewById&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clicks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// handle button clicked&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launchIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lifecycleScope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// provided by lifecycle-runtime-ktx &lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Note that with FlowBinding you no longer need to unregister / remove listeners or callbacks in &lt;code&gt;onDestroy()&lt;/code&gt; as this is done automatically for you.&lt;/p&gt;

&lt;h4&gt;
  
  
  More Examples
&lt;/h4&gt;

&lt;p&gt;All binding APIs are documented with &lt;strong&gt;Example of usage&lt;/strong&gt; which can be found in the source.&lt;/p&gt;

&lt;p&gt;You can also find usages of all bindings from the instrumented tests.&lt;/p&gt;

&lt;h4&gt;
  
  
  Roadmap
&lt;/h4&gt;

&lt;p&gt;With the initial release we’ve covered most of the bindings available RxBindings and added bindings for &lt;strong&gt;Material Components&lt;/strong&gt; including the new &lt;strong&gt;MaterialDatePicker&lt;/strong&gt; and &lt;strong&gt;Slider&lt;/strong&gt;. While the library is heavily tested with instrumented tests, the APIs are not yet stable. Our plan is to polish the library by adding any missing bindings and fixing bugs as we work towards 1.0.&lt;/p&gt;

&lt;p&gt;Your help would be much appreciated! Please feel free to &lt;a href="https://github.com/ReactiveCircus/FlowBinding/issues"&gt;create an issue&lt;/a&gt; on GitHub if you think a useful binding is missing or you want a new binding added to the library.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ReactiveCircus/FlowBinding"&gt;FlowBinding&lt;/a&gt; provides a more fluent and consistent API compared to the stock listeners / callbacks.&lt;/p&gt;

&lt;p&gt;One added benefit is that we no longer need to unregister / remove listeners in &lt;code&gt;onDestroy&lt;/code&gt; method as we can take advantage and Coroutine’s &lt;strong&gt;structured concurrency&lt;/strong&gt; and the &lt;code&gt;lifecycleScope&lt;/code&gt; provided by &lt;strong&gt;AndroidX Lifecycle&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If your project currently uses &lt;strong&gt;RxBinding&lt;/strong&gt; and you are planning to migrate over to Kotlin Coroutines / Flow, &lt;strong&gt;FlowBinding&lt;/strong&gt; should be a good replacement option.&lt;/p&gt;

&lt;p&gt;I hope this can be of use to some of you. Please feel free to leave a comment below or reach out to me on &lt;a href="https://twitter.com/ychescale9"&gt;twitter&lt;/a&gt;.&lt;br&gt;
😀&lt;/p&gt;




&lt;p&gt;Featured in &lt;a href="https://mailchi.mp/kotlinweekly/kotlin-weekly-170"&gt;Kotlin Weekly #170&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>coroutines</category>
    </item>
  </channel>
</rss>
