<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://seadowg.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://seadowg.com/" rel="alternate" type="text/html" /><updated>2026-02-26T10:23:17+00:00</updated><id>https://seadowg.com/feed.xml</id><title type="html">Callum Stott</title><subtitle>Callum Stott is a senior software engineer and XP/Lean consultant with a focus on Android and full stack web development based in London.</subtitle><entry><title type="html">rig: A lightweight, but opinionated gaming setup for Ubuntu 24.04</title><link href="https://seadowg.com/2026/01/02/rig.html" rel="alternate" type="text/html" title="rig: A lightweight, but opinionated gaming setup for Ubuntu 24.04" /><published>2026-01-02T00:00:00+00:00</published><updated>2026-01-02T00:00:00+00:00</updated><id>https://seadowg.com/2026/01/02/rig</id><content type="html" xml:base="https://seadowg.com/2026/01/02/rig.html"><![CDATA[<p>I recently switched my gaming PC from Windows to Linux, and wanted to make sure I had a quick way of reproducing the configuration. I’ve published this <a href="https://gitlab.com/seadowg/rig">here</a> so that others can use it as a one command setup tool to get Ubuntu 24.04 set up for gaming.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Why play games when you can write bash?]]></summary></entry><entry><title type="html">How to ensure file system is updated when modifying files on a USB drive</title><link href="https://seadowg.com/2025/06/06/how-to-ensure-file-system-is-updated-when-modifying-files-on-a-usb-drive.html" rel="alternate" type="text/html" title="How to ensure file system is updated when modifying files on a USB drive" /><published>2025-06-06T00:00:00+00:00</published><updated>2025-06-06T00:00:00+00:00</updated><id>https://seadowg.com/2025/06/06/how-to-ensure-file-system-is-updated-when-modifying-files-on-a-usb-drive</id><content type="html" xml:base="https://seadowg.com/2025/06/06/how-to-ensure-file-system-is-updated-when-modifying-files-on-a-usb-drive.html"><![CDATA[]]></content><author><name></name></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Configuring Gradle Memory Usage</title><link href="https://seadowg.com/2025/05/27/configuring-gradle-memory-usage.html" rel="alternate" type="text/html" title="Configuring Gradle Memory Usage" /><published>2025-05-27T00:00:00+00:00</published><updated>2025-05-27T00:00:00+00:00</updated><id>https://seadowg.com/2025/05/27/configuring-gradle-memory-usage</id><content type="html" xml:base="https://seadowg.com/2025/05/27/configuring-gradle-memory-usage.html"><![CDATA[<h2 id="heap-configuration">Heap configuration</h2>

<p>The “simplest” way to configure how much memory Gradle will use is to configure the heap for the JVM process it spawns by passing args with <code class="language-plaintext highlighter-rouge">org.gradle.jvmargs</code> in <code class="language-plaintext highlighter-rouge">gradle.properties</code> (either in the project root or in your user’s <code class="language-plaintext highlighter-rouge">.gradle</code> directory which overrides the former):</p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Set a maximum heap size of 4 gigabytes
</span><span class="py">org.gradle.jvmargs</span><span class="p">=</span><span class="s">-Xmx4g</span>
</code></pre></div></div>

<p>There are caveats to this though. If you want to configure Gradle for machines with tighter memory constraints (like a container based CI environment), or to take advantage of a beefier dev machine there a few more possible tweaks you’ll want to be aware of:</p>

<ul>
  <li>By default, Kotlin spawns an extra daemon for compilation using the <code class="language-plaintext highlighter-rouge">org.gradle.jvmargs</code> JVM args. It’s good to keep in mind that if you configure Gradle to use a specific heap size, then you could actually end up using double that during compilation of a mixed Java/Kotlin project.</li>
  <li>The extra Kotlin daemon can be disabled by setting <code class="language-plaintext highlighter-rouge">kotlin.compiler.execution.strategy=in-process</code> as part of <code class="language-plaintext highlighter-rouge">gradle.properties</code> (useful for CI configuration) and <a href="https://kotlinlang.org/docs/gradle-compilation-and-caches.html#kotlin-daemon-jvm-options-system-property">its heap can also be configured</a>.</li>
  <li>The test heap can be adjusted with the <code class="language-plaintext highlighter-rouge">maxHeapSize</code> property in the <code class="language-plaintext highlighter-rouge">test</code> block of a <code class="language-plaintext highlighter-rouge">build.gradle</code> or in <code class="language-plaintext highlighter-rouge">unitTests.all</code> in <code class="language-plaintext highlighter-rouge">android</code> &gt; <code class="language-plaintext highlighter-rouge">testOptions</code>. Remember that this heap size is additional (Gradle spawns a specific worker process for tests) to the already spawned Gradle processes handling compilation and other tasks. If you configure a heap size of <strong>T</strong> for tests and <strong>D</strong> for your standard Gradle daemon, then your peak heap memory will be <strong>T + D</strong> (or <strong>T + D * 2</strong> if you haven’t disabled the extra Kotlin daemon).</li>
</ul>

<h2 id="parallelism">Parallelism</h2>

<p>Gradle can also handle tasks in parallel. This is useful for multi-module projects where modules are able to be built/tested in parallel as long they don’t have dependencies on each other. You can enable this feature by adding <code class="language-plaintext highlighter-rouge">org.gradle.parallel=true</code> to <code class="language-plaintext highlighter-rouge">gradle.properties</code>. There is again, a few things you should keep in mind when doing this:</p>

<ul>
  <li>Gradle will attempt to do as much in parallel as it can, and each of these parallel pieces of work will result in another thread being spawned in one of your Gradle daemons. This means that this parallel work will share the memory heap defined by <code class="language-plaintext highlighter-rouge">org.gradle.jvmargs</code>.</li>
  <li>The maximum number of parallel operations can be controlled by <code class="language-plaintext highlighter-rouge">org.gradle.workers.max</code>, but this should default to the number of CPUs.</li>
  <li>Test parallelism is configured separately from other task parallelism. To enable more than one test task to run in parallel, you can set <code class="language-plaintext highlighter-rouge">maxParallelForks</code> (again in <code class="language-plaintext highlighter-rouge">test</code> or <code class="language-plaintext highlighter-rouge">unitTests.all</code> For Android) to a number greater than 1.  The caveat here is that this will spawn new processes instead of threads, so you need to have the capacity to handle <code class="language-plaintext highlighter-rouge">maxHeapSize</code> multiplied by <code class="language-plaintext highlighter-rouge">maxParallelForks</code> in addition to the Gradle daemon’s heap.</li>
</ul>

<h2 id="thoughts">Thoughts</h2>

<p>I’ve personally found that build tasks often benefits from a boost in heap size, but that most test runs (unless there is some underlying memory leak) are pretty happy with the default 512mb heap size. You can also think about using different <code class="language-plaintext highlighter-rouge">gradle.properties</code> for different tasks (subbing them in and out with a script) or <a href="https://docs.gradle.org/current/userguide/command_line_interface.html">passing configuration as args on the command line</a>. This isn’t as useful on dev machines, but can work well on CI where you often end up with a pipelined set of tasks (pull down dependencies, then build, then test for instance). With this approach, you could have different memory configurations for different tasks - you can prioritize compilation memory for compilation tasks and prioritize test memory for test tasks.</p>

<p>It’s also important to point out that the heap does not make up the total memory footprint of a JVM process: there’s the “Metaspace” for storing loaded classes, each thread has its own “Thread Stack” etc. This means that depending on your project, your Gradle tasks might consume noticeably more memory than you’d expect from your heap configurations. Ultimately, playing with the values and seeing what gives you the best results for your project is something you’ll need to spend time on. Using tools like <a href="https://visualvm.github.io/">VisualVM</a> to analyze appropriate heap sizes for the different heaps, as well as tools like Activity Monitor on macOS (or the equivalents in other OSs) to check how large the actual processes get in memory will be really important here. Hopefully there’s enough info in this post to make that process a little less mysterious!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Heap configuration]]></summary></entry><entry><title type="html">Thoughts on Compose</title><link href="https://seadowg.com/2025/02/04/thoughts-on-compose.html" rel="alternate" type="text/html" title="Thoughts on Compose" /><published>2025-02-04T00:00:00+00:00</published><updated>2025-02-04T00:00:00+00:00</updated><id>https://seadowg.com/2025/02/04/thoughts-on-compose</id><content type="html" xml:base="https://seadowg.com/2025/02/04/thoughts-on-compose.html"><![CDATA[<p>I’ve been working on Android apps professionally since 2013, and with that I’ve experienced the windy road of different trends in Android app development: the classic “multi Activity” structure, CursorLoaders, Fragments, the switch in focus to Kotlin, AndroidX introducing lifecycle aware reactivity and a formalization of MVVM with LiveData and ViewModel, and now <a href="https://developer.android.com/compose">Compose</a>.</p>

<p>Since Compose was announced in 2019, I’ve maintained a “healthy” (I’d argue) skepticism - we all know how <a href="https://killedbygoogle.com/">willing to kill its darlings Google can be</a>, and I’d seen the pain caused by Angular 1 to 2 rewrites. I was, and I still am working on <a href="https://github.com/getodk/collect/">a very mature and very large app</a>, so any migration would need to wait until we saw Compose mature anyway.</p>

<p>This January, I took some time to play around with some toy apps for myself and decided to finally give it a go. What follows is what made me happy, and what made me sad.</p>

<h3 id="happy">Happy</h3>
<ul>
  <li>It feels really quick to get going and achieves its goal of letting you build a “declarative” UI layer that can be easily tested.</li>
  <li>The <a href="https://developer.android.com/develop/ui/compose/tooling/previews"><code class="language-plaintext highlighter-rouge">@Preview</code></a> feature is amazing, although it’s not quite clear why it couldn’t have worked for standard Android views, and I’m salty that they haven’t ported it over.</li>
  <li>The focus of the docs is using Material Components, but you can create your own design system using <code class="language-plaintext highlighter-rouge">foundation</code> package Composables (like <code class="language-plaintext highlighter-rouge">BasicText</code> etc.) and <a href="https://developer.android.com/develop/ui/compose/compositionlocal"><code class="language-plaintext highlighter-rouge">CompositionLocal</code></a> for theming.</li>
  <li>You can mix Compose and standard Android views in an app <strong>and</strong> still test it at an instrumentation level using <a href="https://developer.android.com/reference/kotlin/androidx/compose/ui/test/junit4/AndroidComposeTestRule"><code class="language-plaintext highlighter-rouge">AndroidComposeTestRule</code></a>. That said…
    <h3 id="sad">Sad</h3>
  </li>
  <li>…you always need to use some form of <code class="language-plaintext highlighter-rouge">ComposeTestRule</code> to interact with Composables in tests (you can’t use Espresso) which means that the tests are locked in to the implementation somewhat, and you’re always going to have to change tests when migrating to Compose. The only alternative to avoiding these costs/risks would be to use <a href="https://developer.android.com/training/testing/other-components/ui-automator">UIAutomator</a>.</li>
  <li>It’s still not “production ready” for everything - <a href="https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#AlertDialog(kotlin.Function0,androidx.compose.ui.Modifier,androidx.compose.ui.window.DialogProperties,kotlin.Function0)">dialogs are still experimental for example</a>.</li>
  <li>The level of <a href="https://giphy.com/gifs/shia-labeouf-12NUbkX6p4xOO4">magic</a> is high. For example, the fact that Composables return <code class="language-plaintext highlighter-rouge">Unit</code> and “emit” views (as opposed to returning some kind of Composable super type) feels uncomfortable to me.</li>
  <li>Integrating with lifecycle (through the <a href="https://developer.android.com/topic/libraries/architecture/compose#run-code">“effects” mechanism</a>) feels pretty awkward, and I’d be worried about this for apps that need to be lifecycle aware (for managing peripherals or background services for instance).</li>
  <li>The story around migration for an app with a custom Material theme is <a href="https://developer.android.com/develop/ui/compose/designsystems/views-to-compose">not great</a>: you end up needing to either generate a theme (using your existing colors) or create a Compose <a href="https://developer.android.com/develop/ui/compose/designsystems/material3#material-theming">Material Theme</a> from scratch to match your existing one. Whichever route you take, you’ll need to migrate shape and typography manually and will have to make any changes to your theme in both places while both exist.</li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[Fashionably late]]></summary></entry><entry><title type="html">A quick note on exceptions and WorkManager</title><link href="https://seadowg.com/2024/10/28/a-quick-note-on-exceptions-and-workmanager.html" rel="alternate" type="text/html" title="A quick note on exceptions and WorkManager" /><published>2024-10-28T00:00:00+00:00</published><updated>2024-10-28T00:00:00+00:00</updated><id>https://seadowg.com/2024/10/28/a-quick-note-on-exceptions-and-workmanager</id><content type="html" xml:base="https://seadowg.com/2024/10/28/a-quick-note-on-exceptions-and-workmanager.html"><![CDATA[<p>I was recently asked a question about what happens if an error occurs (an unexpected <code class="language-plaintext highlighter-rouge">Exception</code> is thrown) while running a task in the background using <a href="https://developer.android.com/topic/libraries/architecture/workmanager">WorkManager</a>. I realised I had no idea, so decided to investigate.</p>

<p>After playing around with the debugger and a few choice placements of <code class="language-plaintext highlighter-rouge">throw RuntimeException()</code>, I’ve managed to conclude a couple of things:</p>

<ul>
  <li>Exceptions that happen as part of <code class="language-plaintext highlighter-rouge">Worker#doWork</code> are caught by WorkManager and then logged as errors, but the exception does not cause your app to crash. This is possible to see from testing, but is also clear from the <a href="https://android.googlesource.com/platform/frameworks/support.git/+/refs/heads/androidx-work-release/work/work-runtime/src/main/java/androidx/work/Worker.kt#102">underlying code</a>.</li>
  <li>WorkManager treats this work in the same way as one that returns a <code class="language-plaintext highlighter-rouge">Result.failure</code>: any work that depend on this work as part of a chain will not run, and periodic work won’t run again until the next scheduled time. For example, if the work is scheduled to run every 15 minutes, it will try again in 15 minutes after an exception.</li>
</ul>

<p>This is good news for the stability statistics of your app, but does mean that it’s easy to completely miss that your background tasks are failing due to unexpected exceptions - you wouldn’t see them in development unless you’re paying close attention the logs and they wouldn’t appear automatically in crash logs (like Crashlytics for example).</p>

<p>Surprisingly, I couldn’t find any documentation on this, but it seems to me like it’d be good practice to wrap your <code class="language-plaintext highlighter-rouge">Worker#doWork</code> implementations in a <code class="language-plaintext highlighter-rouge">try-catch</code> so you can handle exceptions like so:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MyWorker</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nc">Context</span><span class="p">,</span> <span class="n">workerParams</span><span class="p">:</span> <span class="nc">WorkerParameters</span><span class="p">)</span> <span class="p">:</span>
    <span class="nc">Worker</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">workerParams</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">override</span> <span class="k">fun</span> <span class="nf">doWork</span><span class="p">():</span> <span class="nc">Result</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">try</span> <span class="p">{</span>
            <span class="c1">// do your thing</span>
            <span class="nc">Result</span><span class="p">.</span><span class="nf">success</span><span class="p">()</span>
        <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">t</span><span class="p">:</span> <span class="nc">Throwable</span><span class="p">)</span> <span class="p">{</span>
            <span class="c1">// handle or log exception</span>
            <span class="nc">Result</span><span class="p">.</span><span class="nf">failure</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>You could also return <code class="language-plaintext highlighter-rouge">Result.retry()</code> from the <code class="language-plaintext highlighter-rouge">catch</code> branch if you want work that “explodes” like this to be retried with whatever backoff policy you have configured for it rather than not run again (or wait till the next scheduled time for periodic work).</p>]]></content><author><name></name></author><summary type="html"><![CDATA["Silent Crashing"]]></summary></entry><entry><title type="html">Google I/O 2024</title><link href="https://seadowg.com/2024/05/21/google-io-2024.html" rel="alternate" type="text/html" title="Google I/O 2024" /><published>2024-05-21T00:00:00+00:00</published><updated>2024-05-21T00:00:00+00:00</updated><id>https://seadowg.com/2024/05/21/google-io-2024</id><content type="html" xml:base="https://seadowg.com/2024/05/21/google-io-2024.html"><![CDATA[<p>Here’s my (very biased) takeaways from several talks at Google I/O 2024:</p>

<ul>
  <li>AICore &amp; Gemini seem like an actually exciting use of LLMs. I can see a bunch of light touch and cool generative features popping up in Android apps once the APIs become accessible.</li>
  <li><a href="https://developer.android.com/about/versions/15/behavior-changes-15#edge-to-edge">“Edge to edge” (filling the screen) will be the default display mode for Activities in Android 15 (for apps targeting it)</a>. It looks like you can opt out with a flag in the manifest, but I imagine this is the moment to deal with that.</li>
  <li>Predictive Back will finally be enabled (without a developer setting) in Android 15, but it still looks like it’ll be opt-in (via the <code class="language-plaintext highlighter-rouge">android:enableOnBackInvokedCallback</code> manifest flag). They’ve flip-flopped on this before, so I wouldn’t be surprised if it ends up behind a developer setting again in Android 15’s final release.</li>
  <li>The minimum installable target SDK version for Android 15 will be 24. This is most likely only a problem for unmaintained apps (and their users).</li>
  <li>Compose is still a big deal! The compiler is now part of Kotlin’s (rather than Android) repos and release cycle, which is… interesting? It’s not clear if this makes Compose more or less likely to get dumped or replaced with “Compose 2” (see Angular). I can’t explain it, but it seems like a good thing to me. However, it still feels to me like there isn’t a huge need to rush in given that it’s mostly just a big chunk of sugar on top of the “native” components. I’d probably want to start a new app (or section of an app) in it rather than bothering with rewrites.</li>
  <li>Android will now auto lock using “theft detection”, presumably if the gyro thinks it’s been snatched by some asshole on a scooter.</li>
  <li>Not a single talk on testing! You can however generate tests for code with Gemini which feels like an absolute nightmare.</li>
  <li>The new <a href="https://developer.android.com/about/versions/15/behavior-changes-all#benefits">16 KB memory page size</a> might affect any apps using NDK code.</li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[This is the way the world ends]]></summary></entry><entry><title type="html">Simulating Activity recreation with Robolectric</title><link href="https://seadowg.com/2023/08/14/simulating-activity-recreation-with-robolectric.html" rel="alternate" type="text/html" title="Simulating Activity recreation with Robolectric" /><published>2023-08-14T00:00:00+00:00</published><updated>2023-08-14T00:00:00+00:00</updated><id>https://seadowg.com/2023/08/14/simulating-activity-recreation-with-robolectric</id><content type="html" xml:base="https://seadowg.com/2023/08/14/simulating-activity-recreation-with-robolectric.html"><![CDATA[<p>A while ago, I submitted the issue <a href="https://github.com/android/android-test/issues/1825" target="_blank">“No way to test ‘System needs resources’ situation using ActivityScenario”</a> in which I detailed a few ways to simulate various Activity recreation scenarios in <a href="https://robolectric.org" target="_blank">Robolectric</a> (using <code class="language-plaintext highlighter-rouge">ActivityController</code>) that weren’t possible using AndroidX’s <code class="language-plaintext highlighter-rouge">ActivityScenario</code>. For my own benefit as much as anyone else’s, I wanted to go a bit deeper into exploring the different scenarios in which an Activity is recreated in Android, and how to simulate those using Robolectric so that you can test your Activity’s behaviour during them. I’m not going to be concerning myself with scenarios where the app is restarted fresh like from a “Force Stop” or a power cycle.</p>

<p>It’s worth noting that you can hopefully avoid needing to care about these scenarios by doing good things like avoiding static/application state, adhering to the tenants of unidirectional data flow and never making any mistakes. If you’re a mere human however, understanding these scenarios and protecting against bugs that they might cause is probably important. It’s also worth noting that, with some noted exceptions, I haven’t found a lot of solid documentation on any of this, so feel free to <a href="mailto:callum@seadowg.com">shout at/correct me</a> about anything you read here that is incorrect.</p>

<h2 id="activity-recreation-scenarios">Activity recreation scenarios</h2>

<p>As far as I’m aware, there are 2 distinct Activity recreation scenarios:</p>

<ol>
  <li><strong>Configuration changes</strong>: The Activity is recreated with a new configuration.</li>
  <li><strong>System recreates process</strong>: An app process in the background is destroyed by the system to reclaim resources and then recreated when the user returns to it (including the back stack). Alternatively, the app process can be restarted due to <a href="/2022/10/24/crash-test.html" target="_blank">a crash</a> or app permissions changing in system setting.</li>
</ol>

<p>In this post, I’ll attempt to document these scenarios (along with a nasty twist on them) and give examples of how they can be simulated using Robolectric.</p>

<p>A third scenario we can refer to as “System recreated Activity” (without recreating the process) is possible to simulate with the “Don’t keep activities” developer setting. This setting destroys every Activity after it is no longer visible (like when another is created on top). This is potentially a useful way to test how robust your Activities are when put through recreation, but it’s important to point out that this scenario <strong>should</strong> never happen outside of when the setting is enabled. This was answered pretty definitively in <a href="https://stackoverflow.com/questions/68778910/when-does-android-destroy-activity-without-destroying-the-entire-process">this Stack Overflow question</a> (with links to answers from the Android team themselves).</p>

<h3 id="configuration-changes">Configuration changes</h3>

<p>As far as I’ve seen, this is the only Activity recreation scenario that’s been <a href="https://developer.android.com/guide/topics/resources/runtime-changes">well documented</a> and can be easily tested using AndroidX Test with instrumentation or local tests. The basic premise here is that when something Android thinks of as “configuration” (like screen size, orientation, dark/light mode etc) changes at the system level, Activity object will be recreated with that new configuration. Before this happens, the system calls <a href="https://developer.android.com/reference/android/app/Activity#onSaveInstanceState(android.os.Bundle)" target="_blank"><code class="language-plaintext highlighter-rouge">Activity#onSaveInstanceState</code></a> which creates a <code class="language-plaintext highlighter-rouge">Bundle</code> that will eventually be passed to <a href="https://developer.android.com/reference/android/app/Activity#onCreate(android.os.Bundle)" target="_blank"><code class="language-plaintext highlighter-rouge">Activity#onCreate</code></a> for the new instance of the Activity. That “saved instances state” <code class="language-plaintext highlighter-rouge">Bundle</code> can therefore be used to persist state between configurations. On top of that there’s two pretty big things to keep in mind:</p>

<ol>
  <li>Jetpack’s <a href="https://developer.android.com/topic/libraries/architecture/viewmodel#lifecycle" target="_blank">ViewModels</a> survive configuration changes (assuming they’re being created using a <code class="language-plaintext highlighter-rouge">ViewModelProvider</code>) making them a good place to keep state that should stick around between rotations etc.</li>
  <li>Fragments hosted in the Activity (including <code class="language-plaintext highlighter-rouge">DialogFragment</code> instances) will be recreated as part of your <code class="language-plaintext highlighter-rouge">super.onCreate</code> call. This is useful for doing things like keeping dialogs on screen between configuration changes, but it can cause problems if your code doesn’t take into account setting things up for these recreated Fragments. A prime example of this that you’ll need to have any custom <code class="language-plaintext highlighter-rouge">FragmentFactory</code> setup taken care of before <code class="language-plaintext highlighter-rouge">super.onCreate</code> so that Fragments can be recreated.</li>
</ol>

<p>Here’s how to simulate this scenario using Robolectric:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">activityController</span> <span class="p">=</span> <span class="nc">Robolectric</span><span class="p">.</span><span class="nf">buildActivity</span><span class="p">(</span><span class="nc">MyActivity</span><span class="o">::</span><span class="k">class</span><span class="p">.</span><span class="n">java</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">setup</span><span class="p">()</span>
<span class="n">activityController</span><span class="p">.</span><span class="nf">recreate</span><span class="p">()</span>
</code></pre></div></div>

<p>As I said before, you can also use AndroidX Test for this:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">activityScenario</span> <span class="p">=</span> <span class="nc">ActivityScenario</span><span class="p">.</span><span class="nf">launch</span><span class="p">(</span><span class="nc">MyActivity</span><span class="o">::</span><span class="k">class</span><span class="p">.</span><span class="n">java</span><span class="p">)</span>
<span class="n">activityScenario</span><span class="p">.</span><span class="nf">recreate</span><span class="p">()</span>
</code></pre></div></div>

<h3 id="system-recreates-process">System recreates process</h3>

<p>As mentioned earlier, Android will occasionally destroy app processes in the background to reclaim memory. This probably happens more than you realize according to <a href="https://dontkillmyapp.com" target="_blank">dontkillmyapp.com</a>. When this happens, the app’s process and current back stack is destroyed (with corresponding <code class="language-plaintext highlighter-rouge">Activity#onSaveInstanceState</code> calls) and then both are recreated when the user navigates back to the app. Because the back stack is recreated, we do again get to keep our saved instance state <code class="language-plaintext highlighter-rouge">Bundle</code> and our Fragments, but we’ll lose ViewModels and any “process” level state (Java <code class="language-plaintext highlighter-rouge">static</code> or state we’ve attached to the Android <code class="language-plaintext highlighter-rouge">Application</code>). You can force this behaviour to happen whenever you switch between apps (the one now in the background will be destroyed) using the <a href="https://developer.android.com/studio/debug/dev-options#apps" target="_blank">“Background process limit” setting in Developer settings</a>.</p>

<p>I’m unable to provide a one-size-fits-all solution to simulating this scenario as what state needs to be reset or initializers that need to be run to simulate the process restart will be different for every app. Here’s an example that you can bring your own <code class="language-plaintext highlighter-rouge">resetProcess</code> implementation along to however:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">initial</span> <span class="p">=</span> <span class="nc">Robolectric</span><span class="p">.</span><span class="nf">buildActivity</span><span class="p">(</span><span class="nc">MyActivity</span><span class="o">::</span><span class="k">class</span><span class="p">.</span><span class="n">java</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">setup</span><span class="p">()</span>
<span class="kd">val</span> <span class="py">outState</span> <span class="p">=</span> <span class="nc">Bundle</span><span class="p">()</span>
<span class="n">initial</span><span class="p">.</span><span class="nf">saveInstanceState</span><span class="p">(</span><span class="n">outState</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">pause</span><span class="p">()</span>
    <span class="p">.</span><span class="nf">stop</span><span class="p">()</span>
    <span class="p">.</span><span class="nf">destroy</span><span class="p">()</span>

<span class="nf">resetProcess</span><span class="p">()</span>
        
<span class="kd">val</span> <span class="py">recreated</span> <span class="p">=</span> <span class="nc">Robolectric</span><span class="p">.</span><span class="nf">buildActivity</span><span class="p">(</span><span class="nc">MyActivity</span><span class="o">::</span><span class="k">class</span><span class="p">.</span><span class="n">java</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">setup</span><span class="p">(</span><span class="n">outState</span><span class="p">)</span>
</code></pre></div></div>

<p>It’s probably obvious, but still worth pointing out that implementing a realistic version of <code class="language-plaintext highlighter-rouge">resetProcess</code> is always going to be challenging as you might not be aware of every piece of static state in your app. I’d definitely suggest putting your app through this scenario manually in an emulator or a test device to discover any problematic state you might have.</p>

<h2 id="activity-results">Activity results</h2>

<p>You forgot about <a href="https://developer.android.com/reference/android/app/Activity#startActivityForResult(android.content.Intent,%20int)" target="_blank"><code class="language-plaintext highlighter-rouge">Activity#startActivityForResult</code></a> right? Although it shouldn’t cause you any problems during <a href="#configuration-changes">configuration changes</a>, it can add some real headaches to the <a href="#system-recreates-process">system recreates process</a> scenario. Imagine that <code class="language-plaintext highlighter-rouge">MyActivity</code> from our examples starts another Activity <code class="language-plaintext highlighter-rouge">ResultActivity</code> for result. Android could destroy the whole process if the user navigates away. When <code class="language-plaintext highlighter-rouge">ResultActivity</code> returns the result after this, <code class="language-plaintext highlighter-rouge">onActivityResult</code> will be called after <code class="language-plaintext highlighter-rouge">MyActivity#onCreate</code> is called during recreation which might get you in trouble if you’re loading state you expected to have ready already or if you have something important in <code class="language-plaintext highlighter-rouge">Activity#onResume</code>. Again, we can fortunately simulate this with some (arguably far nastier) Robolectric:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">initial</span> <span class="p">=</span> <span class="nc">Robolectric</span><span class="p">.</span><span class="nf">buildActivity</span><span class="p">(</span><span class="nc">MyActivity</span><span class="o">::</span><span class="k">class</span><span class="p">.</span><span class="n">java</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">setup</span><span class="p">()</span>

<span class="c1">// Action to start `ResultActivity` for result</span>

<span class="kd">val</span> <span class="py">outState</span> <span class="p">=</span> <span class="nc">Bundle</span><span class="p">()</span>
<span class="n">initial</span><span class="p">.</span><span class="nf">saveInstanceState</span><span class="p">(</span><span class="n">outState</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">pause</span><span class="p">()</span>
    <span class="p">.</span><span class="nf">stop</span><span class="p">()</span>
    <span class="p">.</span><span class="nf">destroy</span><span class="p">()</span>

<span class="nf">resetProcess</span><span class="p">()</span>
        
<span class="kd">val</span> <span class="py">recreated</span> <span class="p">=</span> <span class="nc">Robolectric</span><span class="p">.</span><span class="nf">buildActivity</span><span class="p">(</span><span class="nc">MyActivity</span><span class="o">::</span><span class="k">class</span><span class="p">.</span><span class="n">java</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">intent</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="n">outState</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">start</span><span class="p">()</span>
    <span class="p">.</span><span class="nf">restoreInstanceState</span><span class="p">(</span><span class="n">outState</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">postCreate</span><span class="p">(</span><span class="n">outState</span><span class="p">)</span>

<span class="kd">val</span> <span class="py">startedActivityForResult</span> <span class="p">=</span> <span class="nf">shadowOf</span><span class="p">(</span><span class="n">initial</span><span class="p">.</span><span class="k">get</span><span class="p">())</span>
    <span class="p">.</span><span class="n">nextStartedActivityForResult</span>
<span class="nf">shadowOf</span><span class="p">(</span><span class="n">recreated</span><span class="p">.</span><span class="k">get</span><span class="p">()).</span><span class="nf">receiveResult</span><span class="p">(</span>
    <span class="n">startedActivityForResult</span><span class="p">.</span><span class="n">intent</span><span class="p">,</span> 
    <span class="n">resultCode</span><span class="p">,</span> 
    <span class="n">result</span>
<span class="p">)</span>

<span class="n">recreated</span><span class="p">.</span><span class="nf">resume</span><span class="p">()</span>
        <span class="p">.</span><span class="nf">visible</span><span class="p">()</span>
        <span class="p">.</span><span class="nf">topActivityResumed</span><span class="p">(</span><span class="k">true</span><span class="p">)</span>
</code></pre></div></div>

<p>Here we have to manually execute the lifecycle steps that <code class="language-plaintext highlighter-rouge">ActivityController#setup</code> was handling for us so that we can simulate the result being received (via <code class="language-plaintext highlighter-rouge">ShadowActivity#receiveResult</code>) between <code class="language-plaintext highlighter-rouge">postCreate</code> and <code class="language-plaintext highlighter-rouge">onResume</code>.</p>

<p>As far as I’m aware, the lifecycle steps and their ordering would be the same here if you opted to use the new <a href="https://developer.android.com/training/basics/intents/result#launch" target="_blank">Activity Results API</a> instead of using the now deprecated <code class="language-plaintext highlighter-rouge">startActivityForResult</code>/<code class="language-plaintext highlighter-rouge">onActivityResult</code> directly (which you should if you can).</p>

<h2 id="extensions-to-the-rescue">Extensions to the rescue</h2>

<p>We’re at a point where we need a fairly noisy amount of code to simulate these scenarios, and in practice these tests would be very hard to read. I’ve wrapped all this up in an extension for <code class="language-plaintext highlighter-rouge">ActivityController</code> to make my (and hopefully your) life a little easier:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">inline</span> <span class="k">fun</span> <span class="p">&lt;</span><span class="k">reified</span> <span class="nc">A</span> <span class="p">:</span> <span class="nc">Activity</span><span class="p">&gt;</span> <span class="nf">ActivityController</span><span class="p">&lt;</span><span class="nc">A</span><span class="p">&gt;.</span><span class="nf">recreateWithProcessRestore</span><span class="p">(</span>
    <span class="n">resultCode</span><span class="p">:</span> <span class="nc">Int</span><span class="p">?</span> <span class="p">=</span> <span class="k">null</span><span class="p">,</span>
    <span class="n">result</span><span class="p">:</span> <span class="nc">Intent</span><span class="p">?</span> <span class="p">=</span> <span class="k">null</span><span class="p">,</span>
    <span class="k">noinline</span> <span class="n">resetProcess</span><span class="p">:</span> <span class="p">(()</span> <span class="p">-&gt;</span> <span class="nc">Unit</span><span class="p">)?</span> <span class="p">=</span> <span class="k">null</span>
<span class="p">):</span> <span class="nc">ActivityController</span><span class="p">&lt;</span><span class="nc">A</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="c1">// Destroy activity with saved instance state</span>
    <span class="kd">val</span> <span class="py">outState</span> <span class="p">=</span> <span class="nc">Bundle</span><span class="p">()</span>
    <span class="k">this</span><span class="p">.</span><span class="nf">saveInstanceState</span><span class="p">(</span><span class="n">outState</span><span class="p">).</span><span class="nf">pause</span><span class="p">().</span><span class="nf">stop</span><span class="p">().</span><span class="nf">destroy</span><span class="p">()</span>

    <span class="c1">// Reset process if needed</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">resetProcess</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
        <span class="nf">resetProcess</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="c1">// Recreate with saved instance state</span>
    <span class="kd">val</span> <span class="py">recreated</span> <span class="p">=</span> <span class="nc">Robolectric</span><span class="p">.</span><span class="nf">buildActivity</span><span class="p">(</span><span class="nc">A</span><span class="o">::</span><span class="k">class</span><span class="p">.</span><span class="n">java</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">intent</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="n">outState</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">start</span><span class="p">()</span>
        <span class="p">.</span><span class="nf">restoreInstanceState</span><span class="p">(</span><span class="n">outState</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">postCreate</span><span class="p">(</span><span class="n">outState</span><span class="p">)</span>

    <span class="c1">// Return result</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">resultCode</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">val</span> <span class="py">startedActivityForResult</span> <span class="p">=</span> <span class="nf">shadowOf</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="k">get</span><span class="p">())</span>
            <span class="p">.</span><span class="n">nextStartedActivityForResult</span>
        <span class="nf">shadowOf</span><span class="p">(</span><span class="n">recreated</span><span class="p">.</span><span class="k">get</span><span class="p">()).</span><span class="nf">receiveResult</span><span class="p">(</span>
            <span class="n">startedActivityForResult</span><span class="p">.</span><span class="n">intent</span><span class="p">,</span>
            <span class="n">resultCode</span><span class="p">,</span>
            <span class="n">result</span>
        <span class="p">)</span>
    <span class="p">}</span>

    <span class="c1">// Resume activity</span>
    <span class="k">return</span> <span class="n">recreated</span><span class="p">.</span><span class="nf">resume</span><span class="p">()</span>
        <span class="p">.</span><span class="nf">visible</span><span class="p">()</span>
        <span class="p">.</span><span class="nf">topActivityResumed</span><span class="p">(</span><span class="k">true</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>There was probably a way to do this without <code class="language-plaintext highlighter-rouge">reified</code>, but what fun would that be?</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Ways to test Android killing your stuff]]></summary></entry><entry><title type="html">Biometric linkage of longitudinally collected electronic case report forms and confirmation of subject identity</title><link href="https://seadowg.com/2023/08/05/biometric-linkage.html" rel="alternate" type="text/html" title="Biometric linkage of longitudinally collected electronic case report forms and confirmation of subject identity" /><published>2023-08-05T00:00:00+00:00</published><updated>2023-08-05T00:00:00+00:00</updated><id>https://seadowg.com/2023/08/05/biometric-linkage</id><content type="html" xml:base="https://seadowg.com/2023/08/05/biometric-linkage.html"><![CDATA[]]></content><author><name></name></author><summary type="html"><![CDATA[An open framework for ODK and related tools]]></summary></entry><entry><title type="html">Google I/O 2023</title><link href="https://seadowg.com/2023/06/05/google-io-2020.html" rel="alternate" type="text/html" title="Google I/O 2023" /><published>2023-06-05T00:00:00+00:00</published><updated>2023-06-05T00:00:00+00:00</updated><id>https://seadowg.com/2023/06/05/google-io-2020</id><content type="html" xml:base="https://seadowg.com/2023/06/05/google-io-2020.html"><![CDATA[<p>Here’s my (very biased) takeaways from several talks at Google I/O 2023:</p>

<ul>
  <li>Kotlin build scripts are now the default for Android projects.</li>
  <li>Google seems to be trying to add features to Android to try and placate Samsung’s zest for killing apps (see <a href="https://dontkillmyapp.com" target="_blank">dontkillmyapp.com</a>). One such change is that Foreground Services will now require a type.</li>
  <li>Like <a href="/2022/05/11/whats-new-in-android-2022.html" target="_blank">last year</a> there was a lot of emphasis on better supporting larger screens and config changes (which makes a lot of sense alongside the new Pixel Fold). There’s some new tooling that looks pretty helpful for making this easier such as a new resizeable emulator and new “Device API” (undocumented but discussed <a href="https://io.google/2023/program/6d34f5ed-60c1-4a92-9ff9-5aa4fd561af4/" target="_blank">here</a>) for Espresso which they claim gives synchronous test device rotations.</li>
</ul>

<p>And to finish, a poem:</p>

<p><em>This is the way the world ends</em><br />
<em>This is the way the world ends</em><br />
<em>This is the way the world ends</em><br />
<em>Not with a bang but a cheer for dark theme.</em></p>]]></content><author><name></name></author><summary type="html"><![CDATA[This is the way the world ends]]></summary></entry><entry><title type="html">Monthly roundup: March 2023</title><link href="https://seadowg.com/2023/05/08/monthly-roundup-march-2023.html" rel="alternate" type="text/html" title="Monthly roundup: March 2023" /><published>2023-05-08T00:00:00+00:00</published><updated>2023-05-08T00:00:00+00:00</updated><id>https://seadowg.com/2023/05/08/monthly-roundup-march-2023</id><content type="html" xml:base="https://seadowg.com/2023/05/08/monthly-roundup-march-2023.html"><![CDATA[<h1>🎸</h1>

<p>I finally got “Bow, East” onto streaming services. I originally put the release up on Bandcamp last year and then discovered that Spotify, Apple Music etc will not except a single track release where the track and release title don’t match. I did what anyone would do - huffed.</p>

<p>I then spent almost a year spinning my wheels about if I should give in and rename the track, or add a silent track, or split the single track or any other of a thousand alternatives. I ended up biting the bullet and splitting the single track into three which does fit pretty well given the composition has three “movements”. Anyway, links to the release on different streaming services can be found <a href="https://distrokid.com/hyperfollow/lapetitemort/bow-east-2" target="_blank">here</a>.</p>

<h1 id="-1">🎧</h1>

<p>I ended up getting very into Saint-Petersburg’s Black Metal act Somn after loving their half of the <a href="https://somnband.bandcamp.com/album/split" target="_blank">split they did with Wowod</a>. Their first album is definitely worth a spin:</p>

<iframe style="border: 0; width: 100%; height: 120px;" src="https://bandcamp.com/EmbeddedPlayer/album=2999388345/size=large/bgcol=ffffff/linkcol=0687f5/tracklist=false/artwork=small/transparent=true/" seamless=""><a href="https://somnband.bandcamp.com/album/the-all-devouring">The All-devouring by Somn</a></iframe>

<h1 id="-2">👨‍💻</h1>

<p><a href="https://forum.getodk.org/t/odk-collect-v2023-1/40930" target="_blank">Collect v2023.1</a> launched at the end of the month. I spent a chunk of the release cycle adding <a href="https://docs.getodk.org/form-question-types/#select-one-from-map-widget" target="_blank">selectable geo shapes</a>. This will likely be hugely useful for folks, but building out these kinds of features can be a little mind-numbing as Collect supports three different mapping engines (Google, OSM and Mapbox). See <a href="/2022/07/04/mapbox.html" target="_blank">previous posts</a> for shenanigans around that. I’m currently “on strike” from maps work until I feel less burnt by it.</p>

<p>I also ended up working on a couple of other projects this month:</p>

<ul>
  <li>I added support for dynamic colors (or “Material You”) to my absolutely essential <a href="https://github.com/seadowg/cluck/releases/tag/v1.1.0.21" target="_blank">roast chicken time calculator app “Cluck”</a>. I’ve not been able to update the app in the Play Store yet as I messed my developer account up when I detangled my email from Google and still need to fix that with Google’s support. As a side note, Jen is still mad at me for “zuckerberging” her on this app.</li>
  <li>I worked on a short side gig helping some folks deploy their own instance of Postfacto, which coincidently then <a href="https://github.com/pivotal/postfacto" target="_blank">disappeared</a> from the internet before <a href="https://github.com/vmware-archive/postfacto" target="_blank">reappearing</a> a few weeks later as an archived repo. Postfacto has stagnated for a while now and, although it’s always sad to see things get retired, it’s nice to see a decision get made about its future.</li>
</ul>

<h1 id="-3">🍷</h1>

<p>Before leaving the US, I spent a great night out being a pretend somm for my bestie and all round good guy <a href="http://www.colindeeb.com/" target="_blank">Colin Deeb</a> in Oakland’s <a href="https://slugbaroakland.com/" target="_blank">Slug Bar</a> (which I’d heavily recommend). My choices for the night were:</p>

<ul>
  <li>Costadilla - O-X 2021</li>
  <li>Gut Oggau - Maskerade Rosa 2020</li>
  <li>Le Coste - Litrozzo Rosso 2021</li>
  <li>Costadilla - 280 slm 2021</li>
  <li>Vini Viti Vinci - Irancy</li>
</ul>

<p>I guess there was only so long that I was going to stick to my “only local wines” rule.</p>

<p>We spent the end of March (and a good portion of April) in Lyon, so I ended up drinking a ton of Gamay. More on that next time.</p>

<h1 id="-4">🎲</h1>

<p>I’m still board game free given we’re on the move, but I did spend way too much time on a replay of <a href="https://en.wikipedia.org/wiki/Death_Stranding" target="_blank">Death Stranding</a> on my Steam Deck. It’s still absolutely baffling (in a good way) that this game exists. The Director’s Cut is well worth picking up if you’re in the mood for a first or second go around.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Drop the "The". Just "Cluck". It's cleaner.]]></summary></entry></feed>