<?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: Mr. Androidshin</title>
    <description>The latest articles on DEV Community by Mr. Androidshin (@mr_w_9b5b6145a065f4e5605).</description>
    <link>https://dev.to/mr_w_9b5b6145a065f4e5605</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4050208%2Fd4f05cd6-1728-4485-8733-af0a2a8db38a.png</url>
      <title>DEV Community: Mr. Androidshin</title>
      <link>https://dev.to/mr_w_9b5b6145a065f4e5605</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mr_w_9b5b6145a065f4e5605"/>
    <language>en</language>
    <item>
      <title>Migrating 10 Android apps to target API 36 in one weekend</title>
      <dc:creator>Mr. Androidshin</dc:creator>
      <pubDate>Mon, 27 Jul 2026 21:55:06 +0000</pubDate>
      <link>https://dev.to/mr_w_9b5b6145a065f4e5605/migrating-10-android-apps-to-target-api-36-in-one-weekend-1ain</link>
      <guid>https://dev.to/mr_w_9b5b6145a065f4e5605/migrating-10-android-apps-to-target-api-36-in-one-weekend-1ain</guid>
      <description>&lt;p&gt;This post was originally published on &lt;a href="https://androidshin.dev/field-log-target-api-36-ten-apps.html" rel="noopener noreferrer"&gt;androidshin.dev&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When the Play Console notice landed, it landed on ten apps at once. Each one said the same thing: I couldn't submit an update unless it targeted a recent enough Android version, with a hard date of &lt;strong&gt;August 31, 2026&lt;/strong&gt;. Seven of the ten also carried a second notice — they had to move to Google Play Billing Library 8. I had a weekend, a to-do list of ten projects built at different times with different tooling, and a strong suspicion that "just bump one number" was a lie. It was.&lt;/p&gt;

&lt;p&gt;This is the field log. Not a tidy tutorial — the actual order I did things in, the errors in the order they appeared, and where I burned time. If you're staring at the same notice, you can skip the traps I walked into.&lt;/p&gt;

&lt;h2&gt;
  
  
  The version chain nobody mentions up front
&lt;/h2&gt;

&lt;p&gt;The requirement reads as "set &lt;code&gt;targetSdk = 36&lt;/code&gt;." What it actually means, once you follow the errors, is closer to a small tooling upgrade per project. Here's the chain I ended up applying to every app, because each link forces the next:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;targetSdk = 36&lt;/code&gt; requires &lt;code&gt;compileSdk = 36&lt;/code&gt; — you can't target an API you don't compile against.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;compileSdk = 36&lt;/code&gt; requires &lt;strong&gt;Android Gradle Plugin 8.9.0+&lt;/strong&gt;. Anything older simply doesn't know Android 16 exists. My projects ranged from AGP 8.2.2 to 8.7.3; all of them had to move to 8.9.1.&lt;/li&gt;
&lt;li&gt;AGP 8.9 requires &lt;strong&gt;Gradle 8.11.1+&lt;/strong&gt;. Every wrapper that was on 8.2, 8.7, or 8.9 had to be bumped, or the build stopped with a plain version-mismatch message.&lt;/li&gt;
&lt;li&gt;The oldest projects were on &lt;strong&gt;Kotlin 1.9.x&lt;/strong&gt;, which the newer AGP complains about. Moving those to Kotlin 2.0.21 cleared it.&lt;/li&gt;
&lt;li&gt;And of course the machine needs the &lt;strong&gt;Android 16 SDK platform and Build-Tools 36&lt;/strong&gt; installed, plus &lt;strong&gt;JDK 17&lt;/strong&gt; to run AGP 8.x.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The thing that made this tedious rather than hard: my ten projects used three different build styles. Some used Groovy &lt;code&gt;build.gradle&lt;/code&gt; with a &lt;code&gt;buildscript&lt;/code&gt; classpath. Some used Groovy with the &lt;code&gt;plugins {}&lt;/code&gt; block. Two used Kotlin DSL &lt;code&gt;build.gradle.kts&lt;/code&gt;, and one of those drove everything through a &lt;code&gt;libs.versions.toml&lt;/code&gt; version catalog. The &lt;em&gt;edit&lt;/em&gt; was the same idea each time, but &lt;em&gt;where&lt;/em&gt; the version string lived moved around constantly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;My rule of thumb after ten of these: do the bumps in dependency order (SDK → AGP → Gradle → Kotlin), sync once after each, and let each error message tell you the next step. Trying to change everything at once just merges five error causes into one confusing stack trace.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The billing change was smaller than I feared
&lt;/h2&gt;

&lt;p&gt;Seven apps needed Play Billing Library 8. I'd blocked out hours for this and it took minutes each, because my integrations were already on the modern &lt;code&gt;ProductDetails&lt;/code&gt; API. In v8, exactly one thing broke in each app: the &lt;code&gt;queryProductDetailsAsync&lt;/code&gt; callback used to hand back a &lt;code&gt;List&amp;lt;ProductDetails&amp;gt;&lt;/code&gt;; now it hands back a &lt;code&gt;QueryProductDetailsResult&lt;/code&gt;, and you pull the list off it.&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="c1"&gt;// v7&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queryProductDetailsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&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="n"&gt;list&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&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;// v8&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queryProductDetailsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&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="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;productDetailsList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&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;Everything else I use — &lt;code&gt;enablePendingPurchases(PendingPurchasesParams…)&lt;/code&gt;, &lt;code&gt;queryPurchasesAsync(QueryPurchasesParams…)&lt;/code&gt;, acknowledge, consume, subscription replacement — was untouched. If your billing code predates &lt;code&gt;ProductDetails&lt;/code&gt; and still calls &lt;code&gt;querySkuDetailsAsync&lt;/code&gt; or the no-argument &lt;code&gt;enablePendingPurchases()&lt;/code&gt;, you have real removal work first — those are gone in v8.&lt;/p&gt;

&lt;h2&gt;
  
  
  The error that wasn't a code error #1: a lint wall on release
&lt;/h2&gt;

&lt;p&gt;This is where the weekend stopped being smooth. One app built fine in debug and then died assembling the release with a fatal lint error I hadn't seen in years:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: "pro_active" is translated here but not found in
default locale [ExtraTranslation]
    values-en/strings.xml
2 errors, 0 warnings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two strings existed in the English translation file but not in the default &lt;code&gt;values/strings.xml&lt;/code&gt;. Lint treats that as a crash risk, and release builds run &lt;code&gt;lintVital&lt;/code&gt;, which is fatal by default. The strings turned out to be dead leftovers from a "Pro" tier I'd removed long ago. The correct fix wasn't to suppress the check with a baseline, it was to delete the two orphaned strings. I then wrote a quick script to compare every locale's keys against the default across all my apps, so this couldn't ambush me again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The error that wasn't a code error #2: a file lock on Windows
&lt;/h2&gt;

&lt;p&gt;The one that genuinely made me question my changes turned out to have nothing to do with them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:app:lintVitalAnalyzeRelease
...androidx.compose.runtime.lint.RuntimeIssueRegistry-....jar:
The process cannot access the file because it is being used
by another process
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks like a Compose lint failure. It isn't. It's Windows telling you another process is holding a lint-cache &lt;code&gt;.jar&lt;/code&gt; — usually a stray Gradle daemon, an antivirus scan, or a file-sync client watching the &lt;code&gt;build&lt;/code&gt; folder. The fix: &lt;code&gt;gradlew --stop&lt;/code&gt;, clean, and rebuild; if it persists, close the IDE and delete the &lt;code&gt;app/build&lt;/code&gt; folder, or reboot. I lost twenty minutes reading Compose lint docs before I actually read the second half of the error message. Read the whole line first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edge-to-edge: the visual surprise
&lt;/h2&gt;

&lt;p&gt;Targeting recent Android enforces edge-to-edge — the system draws your content behind the status and navigation bars. On my View-based game screens, toolbars slid under the clock and buttons sat under the navigation pill. The fix is to enable it explicitly and consume the insets as padding:&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="c1"&gt;// once, ideally in a shared BaseActivity&lt;/span&gt;
&lt;span class="nc"&gt;WindowCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDecorFitsSystemWindows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&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;ViewCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOnApplyWindowInsetsListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;insets&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;bars&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;insets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInsets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;WindowInsetsCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;systemBars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="nc"&gt;WindowInsetsCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;displayCutout&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPadding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;insets&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Compose screens were mostly fine — &lt;code&gt;Scaffold&lt;/code&gt; already applies its insets once you call &lt;code&gt;enableEdgeToEdge()&lt;/code&gt;. While I was in the themes, I also deleted &lt;code&gt;android:statusBarColor&lt;/code&gt; and &lt;code&gt;android:navigationBarColor&lt;/code&gt;, which are deprecated and ignored in edge-to-edge.&lt;/p&gt;

&lt;h2&gt;
  
  
  The odd one out: a transitive dependency I didn't declare
&lt;/h2&gt;

&lt;p&gt;One app got an "outdated SDK version (androidx.fragment 1.1.0)" notice. I don't use Fragments in that app — it's Compose. The old version was pulled in transitively by a Google library. The fix was to declare a modern version explicitly so it wins resolution:&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="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.fragment:fragment:1.8.5"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Play Console reports on your &lt;em&gt;resolved&lt;/em&gt; dependency graph, not just what you wrote in your build file. Sometimes the thing it flags is three libraries deep.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;Two lessons stuck. First, &lt;strong&gt;the tooling upgrade is the real work, not the target number&lt;/strong&gt; — if I'd bumped AGP/Gradle/Kotlin across all ten apps as a batch &lt;em&gt;first&lt;/em&gt;, then flipped &lt;code&gt;targetSdk&lt;/code&gt;, I'd have hit the version-matrix errors once instead of ten times. Second, &lt;strong&gt;release lint surfaces years of small debt at once&lt;/strong&gt; — orphaned translations, deprecated theme attributes, outdated transitive deps. Budget time for that, not just for the migration itself.&lt;/p&gt;

&lt;p&gt;Net result: ten apps compiling against API 36, seven on Billing 8, all with bumped version codes and ready to upload. The mandatory part was maybe 30% of the effort. The other 70% was pre-existing debt that the deadline finally forced me to pay down.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Versions, Play requirements, and deadlines change over time — treat the specifics here as a snapshot from my own migration and confirm the current numbers in the Play Console and official docs before you ship. Originally published on &lt;a href="https://androidshin.dev/field-log-target-api-36-ten-apps.html" rel="noopener noreferrer"&gt;androidshin.dev&lt;/a&gt;, where I keep a running log of solo Android dev war stories and small tools.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>googleplay</category>
      <category>gradle</category>
    </item>
  </channel>
</rss>
