<?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>Forem: Davis | UNTRAPD</title>
    <description>The latest articles on Forem by Davis | UNTRAPD (@untrapd).</description>
    <link>https://forem.com/untrapd</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%2F3850067%2F00977daf-4c7a-40ee-a726-fde8f312d5a6.png</url>
      <title>Forem: Davis | UNTRAPD</title>
      <link>https://forem.com/untrapd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/untrapd"/>
    <language>en</language>
    <item>
      <title>A Flutter Plugin Secretly Injected Permissions Into My App — Google Rejected It 4 Times Before I Found It</title>
      <dc:creator>Davis | UNTRAPD</dc:creator>
      <pubDate>Thu, 09 Apr 2026 22:03:27 +0000</pubDate>
      <link>https://forem.com/untrapd/a-flutter-plugin-secretly-injected-permissions-into-my-app-google-rejected-it-4-times-before-i-4jc3</link>
      <guid>https://forem.com/untrapd/a-flutter-plugin-secretly-injected-permissions-into-my-app-google-rejected-it-4-times-before-i-4jc3</guid>
      <description>&lt;p&gt;Google Play rejected my Flutter app 4 times. Same reason each time: sensitive permissions I never asked for.&lt;/p&gt;

&lt;p&gt;I grepped every file. &lt;code&gt;READ_SMS&lt;/code&gt;, &lt;code&gt;SEND_SMS&lt;/code&gt;, &lt;code&gt;READ_CALL_LOG&lt;/code&gt; — none of them in my AndroidManifest.xml.&lt;/p&gt;

&lt;p&gt;Because they weren't in my manifest. They were in my compiled bytecode.&lt;/p&gt;

&lt;h2&gt;
  
  
  The culprit
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;permission_handler&lt;/code&gt;. One of the most popular Flutter plugins. 8,000+ likes on pub.dev.&lt;/p&gt;

&lt;p&gt;Here's what nobody tells you: it compiles &lt;strong&gt;string constants for every Android permission it supports&lt;/strong&gt; into your DEX bytecode. Not because you request them. Not because you use them. Because the plugin's internal mapping references them all.&lt;/p&gt;

&lt;p&gt;Google's automated review doesn't just read your manifest. It scans compiled bytecode for permission-related strings. &lt;code&gt;permission_handler&lt;/code&gt; puts them there. Every single one.&lt;/p&gt;

&lt;p&gt;You didn't request them. You don't use them. Google still flags them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap nobody warns you about
&lt;/h2&gt;

&lt;p&gt;Here's what made this worse: &lt;strong&gt;Google Play testing tracks don't enforce the same permission policies as Production.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I uploaded builds with SMS permissions to Internal Testing. Approved. Closed Testing. Approved. Pre-registration. Approved. 172 countries. Everything green.&lt;/p&gt;

&lt;p&gt;Then I submitted to Production. Rejected.&lt;/p&gt;

&lt;p&gt;The testing tracks let everything through. You build confidence. You think your permissions are fine. Then Production applies a completely different set of rules — and you're back to zero with no warning.&lt;/p&gt;

&lt;p&gt;If you're testing sensitive permissions on Internal or Closed Testing and it "works" — that tells you nothing about Production approval.&lt;/p&gt;

&lt;h2&gt;
  
  
  What didn't work
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Removing permissions from AndroidManifest.xml — scanner reads DEX, not just manifest&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tools:node="remove"&lt;/code&gt; — removes from merged manifest, strings survive in bytecode&lt;/li&gt;
&lt;li&gt;ProGuard/R8 rules — some references survive optimization&lt;/li&gt;
&lt;li&gt;Permission declaration form — Google rejected it because I wasn't &lt;em&gt;using&lt;/em&gt; those permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What worked
&lt;/h2&gt;

&lt;p&gt;Removed &lt;code&gt;permission_handler&lt;/code&gt; entirely. Replaced with a native Kotlin method channel — one file, exactly the permissions I need, zero baggage.&lt;/p&gt;

&lt;p&gt;Next submission: zero phantom permissions. Approved.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson
&lt;/h2&gt;

&lt;p&gt;Two things bit me at once:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Flutter plugins have compiled consequences.&lt;/strong&gt; &lt;code&gt;permission_handler&lt;/code&gt; ships references to every Android permission into your binary. Google can't tell the difference between "string exists in code" and "app uses this permission."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Testing tracks are not Production.&lt;/strong&gt; Don't assume approval on Internal/Closed Testing means anything for Production. The policy enforcement is different.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two commands that would have saved me two weeks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# What you installed&lt;/span&gt;
flutter pub deps

&lt;span class="c"&gt;# What actually shipped&lt;/span&gt;
apkanalyzer dex packages your-app.apk | &lt;span class="nb"&gt;grep &lt;/span&gt;permission
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They don't show the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The timeline
&lt;/h2&gt;

&lt;p&gt;4 rejections over 5 days. 1 plugin. Now live on Google Play in 172 countries.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Has anyone else been bitten by this? I'm curious — does Google intentionally skip permission checks on testing tracks, or is it a gap in their review system? And is &lt;code&gt;permission_handler&lt;/code&gt; the only Flutter plugin that does this?&lt;/em&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>android</category>
      <category>googleplay</category>
      <category>mobile</category>
    </item>
    <item>
      <title>I built an AI marketing army. It delivered content to nobody.</title>
      <dc:creator>Davis | UNTRAPD</dc:creator>
      <pubDate>Mon, 30 Mar 2026 10:57:38 +0000</pubDate>
      <link>https://forem.com/untrapd/i-built-an-ai-marketing-army-it-delivered-content-to-nobody-4p2a</link>
      <guid>https://forem.com/untrapd/i-built-an-ai-marketing-army-it-delivered-content-to-nobody-4p2a</guid>
      <description>&lt;p&gt;I'm a solo Android developer. I built a phone recovery app with Claude as my only teammate. 14 versions shipped in 110 days. Live on Google Play in 172 countries.&lt;/p&gt;

&lt;p&gt;Building was the fun part. Marketing was where I learned the most painful lesson of my career.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;I solved my marketing problem the way any developer would: by building more software.&lt;/p&gt;

&lt;p&gt;Custom automation system from scratch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Native Twitter API (OAuth 1.0a, no third-party tools)&lt;/li&gt;
&lt;li&gt;Native Meta API (Facebook + Instagram, OAuth 2.0)&lt;/li&gt;
&lt;li&gt;Cron-based scheduling with rate limiting&lt;/li&gt;
&lt;li&gt;Token refresh pipeline&lt;/li&gt;
&lt;li&gt;Content generation via Claude&lt;/li&gt;
&lt;li&gt;A/B tested formats and timing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;40 hours of infrastructure. Genuinely impressive engineering. I was proud of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 81-post experiment
&lt;/h2&gt;

&lt;p&gt;15-day holiday campaign. 81 posts scheduled across Twitter, Instagram, and Facebook.&lt;/p&gt;

&lt;p&gt;Results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;31 posts actually went through (rest got blocked — platforms fighting back)&lt;/li&gt;
&lt;li&gt;361 total impressions&lt;/li&gt;
&lt;li&gt;0 beta signups&lt;/li&gt;
&lt;li&gt;Followers: 26 → 21 (lost five)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Best performing tweet: "The 9-to-5 was designed in 1926" — 87 impressions, 24% of the entire campaign's reach. Had nothing to do with my app.&lt;/p&gt;

&lt;h2&gt;
  
  
  What went wrong
&lt;/h2&gt;

&lt;p&gt;Posting to 26 followers = 0-5 views per post. The algorithms have nobody to show your content to. Then the high volume + zero engagement trains the algorithm you're spam. Each post gets suppressed harder than the last.&lt;/p&gt;

&lt;p&gt;I was actively teaching Twitter to hide me. 81 posts out, zero comments on anyone else's stuff. Broadcasting to empty rooms on a schedule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI didn't solve the distribution problem. It just made it faster to confirm I had one.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson
&lt;/h2&gt;

&lt;p&gt;Content without distribution is invisible. Every marketing guide assumes you have a baseline audience. At zero, the rules are different and nobody tells you that.&lt;/p&gt;

&lt;p&gt;The failure story turned out to be more interesting than anything I posted in those 81 scheduled posts. I'm documenting the full journey with raw data — every campaign, every pivot, every failure with actual numbers.&lt;/p&gt;

&lt;p&gt;If you're at zero right now — what actually worked for getting your first handful of users?&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>ai</category>
      <category>startup</category>
      <category>marketing</category>
    </item>
  </channel>
</rss>
