<?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: Alamin Karno</title>
    <description>The latest articles on Forem by Alamin Karno (@alamin_karno_b096f2ace0bb).</description>
    <link>https://forem.com/alamin_karno_b096f2ace0bb</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%2F3667658%2Fa21268a7-d483-440a-8bc6-9e14ac8e2207.png</url>
      <title>Forem: Alamin Karno</title>
      <link>https://forem.com/alamin_karno_b096f2ace0bb</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/alamin_karno_b096f2ace0bb"/>
    <language>en</language>
    <item>
      <title>How a Bug Report Led Me to Give Flutter Developers Full Control of Crisp Chat Modals on iOS</title>
      <dc:creator>Alamin Karno</dc:creator>
      <pubDate>Sat, 14 Mar 2026 06:31:38 +0000</pubDate>
      <link>https://forem.com/alamin_karno_b096f2ace0bb/how-a-bug-report-led-me-to-give-flutter-developers-full-control-of-crisp-chat-modals-on-ios-3c8j</link>
      <guid>https://forem.com/alamin_karno_b096f2ace0bb/how-a-bug-report-led-me-to-give-flutter-developers-full-control-of-crisp-chat-modals-on-ios-3c8j</guid>
      <description>&lt;p&gt;One of the things I enjoy most about maintaining open-source software is that sometimes the best improvements start with something very small.&lt;/p&gt;

&lt;p&gt;A simple bug report.&lt;br&gt;
A curious investigation.&lt;br&gt;
And sometimes… a new feature idea.&lt;/p&gt;

&lt;p&gt;This story started exactly like that.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Bug Report That Started Everything
&lt;/h2&gt;

&lt;p&gt;While reviewing issues for the Flutter Crisp Chat plugin, I noticed a &lt;a href="https://github.com/alamin-karno/flutter-crisp-chat/issues/123" rel="noopener noreferrer"&gt;report&lt;/a&gt; opened by &lt;strong&gt;Nikita Alexeev&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The title immediately caught my attention:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Taps pass through chat modal to underlying UI on iOS 13+.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sounded strange.&lt;/p&gt;

&lt;p&gt;A modal should block interactions with the UI behind it. So how could taps reach the Flutter app underneath?&lt;/p&gt;

&lt;p&gt;I opened the issue and read the details carefully.&lt;/p&gt;

&lt;p&gt;The behavior was simple but frustrating:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the Crisp chat on an iPhone&lt;/li&gt;
&lt;li&gt;Tap near the top area of the chat&lt;/li&gt;
&lt;li&gt;Suddenly the Flutter UI behind it reacts&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Buttons could be pressed. Navigation could trigger. All while the chat was still visible.&lt;/p&gt;

&lt;p&gt;That’s definitely not the experience anyone wants when they open a support chat.&lt;/p&gt;

&lt;p&gt;So I started digging.&lt;/p&gt;


&lt;h2&gt;
  
  
  Learning Something Interesting About iOS
&lt;/h2&gt;

&lt;p&gt;While reading the issue discussion and the explanation from Nikita Alexeev, I discovered something interesting about iOS.&lt;/p&gt;

&lt;p&gt;Starting with &lt;strong&gt;iOS 13&lt;/strong&gt;, Apple changed how modal presentations behave by default.&lt;/p&gt;

&lt;p&gt;Before iOS 13, most modals were presented using:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.fullScreen&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Which means the modal covers the entire screen. Nothing behind it is visible, and more importantly, nothing behind it can receive touches.&lt;/p&gt;

&lt;p&gt;But iOS 13 introduced a new default style:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.pageSheet&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This creates the modern sheet-style UI where the modal slides up from the bottom and leaves part of the background visible.&lt;/p&gt;

&lt;p&gt;It looks elegant and more native to modern iOS design.&lt;/p&gt;

&lt;p&gt;But there was a catch.&lt;/p&gt;

&lt;p&gt;In this case, the dimmed background area was still allowing touch events to reach the Flutter UI underneath.&lt;/p&gt;

&lt;p&gt;That explained exactly why users were accidentally triggering buttons behind the chat window.&lt;/p&gt;

&lt;p&gt;Suddenly the bug made perfect sense.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Pull Request That Fixed It
&lt;/h2&gt;

&lt;p&gt;Soon after reporting the issue, Nikita Alexeev submitted a &lt;a href="https://github.com/alamin-karno/flutter-crisp-chat/pull/124" rel="noopener noreferrer"&gt;pull request&lt;/a&gt; with a very straightforward fix.&lt;/p&gt;

&lt;p&gt;Instead of relying on the default modal style, he forced the chat view controller to use full screen presentation.&lt;/p&gt;

&lt;p&gt;The change looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;chatVC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ChatViewController&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;chatVC&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modalPresentationStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fullScreen&lt;/span&gt;
&lt;span class="n"&gt;viewController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatVC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;animated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was a very small change, but it solved the problem completely.&lt;/p&gt;

&lt;p&gt;By forcing &lt;code&gt;.fullScreen&lt;/code&gt;, the chat now covered the entire screen again, which meant no touches could leak through to the Flutter UI behind it.&lt;/p&gt;

&lt;p&gt;The issue was fixed.&lt;/p&gt;

&lt;p&gt;But while reviewing the pull request, a new idea came to my mind.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Moment the Idea Appeared
&lt;/h2&gt;

&lt;p&gt;Yes, &lt;code&gt;.fullScreen&lt;/code&gt; solved the issue.&lt;/p&gt;

&lt;p&gt;But iOS actually supports several modal presentation styles.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.fullScreen&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.pageSheet&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.formSheet&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.overFullScreen&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.overCurrentContext&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.popover&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Different apps use different modal styles depending on their design.&lt;/p&gt;

&lt;p&gt;Some apps prefer full screen.&lt;/p&gt;

&lt;p&gt;Some prefer sheet-style modals.&lt;/p&gt;

&lt;p&gt;Some prefer overlays.&lt;/p&gt;

&lt;p&gt;So instead of hardcoding &lt;code&gt;.fullScreen&lt;/code&gt;, I thought:&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;What if Flutter developers could choose the modal style themselves?&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
That way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The touch-through bug stays fixed&lt;/li&gt;
&lt;li&gt;Developers get flexibility&lt;/li&gt;
&lt;li&gt;The plugin becomes more powerful&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s when the idea shifted from a &lt;strong&gt;bug fix&lt;/strong&gt; to a &lt;strong&gt;developer-focused feature&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  Turning the Idea into a Feature
&lt;/h2&gt;

&lt;p&gt;To make this possible, I needed to expose the modal presentation style all the way from Flutter to the native iOS implementation.&lt;/p&gt;

&lt;p&gt;So I started with the Flutter configuration.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 1 — Extending the Flutter Configuration
&lt;/h2&gt;

&lt;p&gt;First, I added a new enum to represent the iOS modal styles.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;ModalPresentationStyle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;fullScreen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;pageSheet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;formSheet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;overFullScreen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;overCurrentContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;popover&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;Then I added it to the &lt;code&gt;CrispConfig&lt;/code&gt; class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CrispConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;websiteID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;tokenId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;ModalPresentationStyle&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;modalPresentationStyle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;CrispConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;websiteID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tokenId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;modalPresentationStyle&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;Now Flutter developers could specify how the chat modal should appear.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 — Bridging the Setting to iOS
&lt;/h2&gt;

&lt;p&gt;Next, I needed to convert that Flutter value into a native iOS presentation style.&lt;/p&gt;

&lt;p&gt;Inside the Swift plugin code, I mapped the configuration to &lt;code&gt;UIModalPresentationStyle&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;modalPresentationStyleString&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"fullScreen"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;modalPresentationStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fullScreen&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"pageSheet"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;modalPresentationStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pageSheet&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"formSheet"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;modalPresentationStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;formSheet&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"overFullScreen"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;modalPresentationStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;overFullScreen&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"overCurrentContext"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;modalPresentationStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;overCurrentContext&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"popover"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;modalPresentationStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;popover&lt;/span&gt;
&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;modalPresentationStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fullScreen&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensured a safe default while still allowing flexibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — Applying the Configuration
&lt;/h2&gt;

&lt;p&gt;Finally, instead of hardcoding &lt;code&gt;.fullScreen&lt;/code&gt;, the plugin now uses the configured style.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;chatVC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ChatViewController&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;chatVC&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modalPresentationStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;crispConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modalPresentationStyle&lt;/span&gt;

&lt;span class="n"&gt;viewController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatVC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;animated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now Flutter developers have full control over the presentation style.&lt;/p&gt;




&lt;h2&gt;
  
  
  Making the Feature Easy to Discover
&lt;/h2&gt;

&lt;p&gt;After implementing the improvements, I wanted to make sure developers could easily explore and understand them.&lt;/p&gt;

&lt;p&gt;So I also improved the developer experience around the plugin.&lt;/p&gt;

&lt;p&gt;I added an &lt;strong&gt;interactive demo inside the example app&lt;/strong&gt; where developers can test different iOS modal styles.&lt;/p&gt;

&lt;p&gt;There’s a button called “&lt;strong&gt;iOS Modal Style Demo&lt;/strong&gt;” that opens the chat using different presentation styles so developers can see how each one behaves.&lt;/p&gt;

&lt;p&gt;I also updated the documentation to clearly explain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How iOS modal presentation works&lt;/li&gt;
&lt;li&gt;Why .fullScreen fixes the touch-through issue&lt;/li&gt;
&lt;li&gt;How to configure the modal style in Flutter&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What Developers Gain From This
&lt;/h2&gt;

&lt;p&gt;What started as a small bug report now gives Flutter developers several benefits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better User Experience&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No more accidental taps on the UI behind the chat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flexible Modal Presentation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Developers can choose the modal style that fits their app design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Platform Awareness&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These changes only affect iOS behavior, while Android remains unchanged.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Love About Open Source
&lt;/h2&gt;

&lt;p&gt;This experience reminded me why I enjoy maintaining open-source projects.&lt;/p&gt;

&lt;p&gt;Sometimes improvements don’t start with big feature ideas.&lt;/p&gt;

&lt;p&gt;They start with a simple issue report.&lt;/p&gt;

&lt;p&gt;A developer noticing something strange.&lt;/p&gt;

&lt;p&gt;A pull request proposing a fix.&lt;/p&gt;

&lt;p&gt;And a moment of curiosity that turns a bug fix into something better.&lt;/p&gt;

&lt;p&gt;So huge thanks to &lt;a href="https://github.com/aspid168" rel="noopener noreferrer"&gt;Nikita Alexeev&lt;/a&gt; for reporting the issue and submitting the original fix that inspired this improvement.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;If you’re using the &lt;a href="https://pub.dev/packages/crisp_chat" rel="noopener noreferrer"&gt;Flutter Crisp Chat&lt;/a&gt; plugin, you can try the new configuration like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CrispConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;websiteID:&lt;/span&gt; &lt;span class="s"&gt;'YOUR_WEBSITE_ID'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;modalPresentationStyle:&lt;/span&gt; &lt;span class="n"&gt;ModalPresentationStyle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pageSheet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;enableNotifications:&lt;/span&gt; &lt;span class="kc"&gt;false&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;And if you’re curious to see how each modal style behaves, check out the &lt;a href="https://github.com/alamin-karno/flutter-crisp-chat" rel="noopener noreferrer"&gt;example&lt;/a&gt; app in the repository.&lt;/p&gt;




&lt;p&gt;Sometimes the best features don’t start with a roadmap.&lt;/p&gt;

&lt;p&gt;They start with a bug report…&lt;br&gt;
and a little curiosity.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>ios</category>
    </item>
    <item>
      <title>The Hidden Magic Behind Android’s Share Button</title>
      <dc:creator>Alamin Karno</dc:creator>
      <pubDate>Tue, 27 Jan 2026 06:16:09 +0000</pubDate>
      <link>https://forem.com/alamin_karno_b096f2ace0bb/the-hidden-magic-behind-androids-share-button-3hkj</link>
      <guid>https://forem.com/alamin_karno_b096f2ace0bb/the-hidden-magic-behind-androids-share-button-3hkj</guid>
      <description>&lt;p&gt;Have you ever hit the &lt;strong&gt;Share&lt;/strong&gt; button in your app — maybe to send a funny meme, a link, or a photo — and seen that neat list of apps pop up?&lt;/p&gt;

&lt;p&gt;As a mobile developer, I always thought it was just… magic. Call an Intent, Android shows a list, job done.&lt;/p&gt;

&lt;p&gt;But one day, while debugging a crash on &lt;code&gt;ActivityNotFoundException&lt;/code&gt;, I paused and asked myself:&lt;/p&gt;

&lt;p&gt;“Wait… how does Android actually know which apps to show? How does it know WhatsApp, Gmail, or Drive can handle my data?”&lt;/p&gt;

&lt;p&gt;That question sent me down a rabbit hole that changed how I think about app interactions forever.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Understanding the Intent
&lt;/h2&gt;

&lt;p&gt;I started with the basics. An Intent is like a messenger in Android. It carries a message from one part of the system — or even one app — to another.&lt;/p&gt;

&lt;p&gt;There are &lt;strong&gt;two types of Intents&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Explicit Intents&lt;/strong&gt;: You know exactly which component should handle your request. Example: opening your own &lt;code&gt;ProfileActivity&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implicit Intents&lt;/strong&gt;: You only describe &lt;strong&gt;what you want done&lt;/strong&gt;, not who should do it. Example: sharing an image, sending an email, or opening a PDF. Android figures out the rest.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 2: Breaking Down the Implicit Intent
&lt;/h2&gt;

&lt;p&gt;I wrote a tiny snippet in my app:&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="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;shareIntent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ACTION_SEND&lt;/span&gt;
    &lt;span class="nf"&gt;putExtra&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EXTRA_TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Check out this cool blog!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;startActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createChooser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shareIntent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Share via"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, it seems simple. But internally, it’s a beautifully orchestrated dance. Here’s what I realized:&lt;/p&gt;

&lt;p&gt;An Implicit Intent has &lt;strong&gt;three main parts&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Action&lt;/strong&gt;: What you want to do (&lt;code&gt;ACTION_SEND&lt;/code&gt; = send something).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Category&lt;/strong&gt;: The general type of action (&lt;code&gt;CATEGORY_DEFAULT&lt;/code&gt; = standard).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MIME Type&lt;/strong&gt;: What type of data you’re sending (&lt;code&gt;text/plain&lt;/code&gt;, &lt;code&gt;image/jpeg&lt;/code&gt;, etc.).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These three pieces of info act like a search query to find the right apps.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: The Journey of an Intent
&lt;/h2&gt;

&lt;p&gt;I wanted to visualize the flow. When I tap &lt;strong&gt;Share&lt;/strong&gt;, this is what happens behind the scenes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Intent hits the Activity Manager Service (AMS)&lt;/strong&gt;: Think of AMS as Android’s traffic controller. It decides: “Where should this message go?”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AMS asks the Package Manager Service (PMS) for matches&lt;/strong&gt;: PMS is a database of all installed apps and their capabilities (declared via &lt;code&gt;&amp;lt;intent-filter&amp;gt;&lt;/code&gt; in &lt;code&gt;AndroidManifest.xml&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PMS matches the Intent to apps&lt;/strong&gt;: It checks the action, category, and MIME type against every app’s intent filters. All matching apps go into a &lt;strong&gt;resolve list&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AMS decides what to do next&lt;/strong&gt;: &lt;strong&gt;No matches&lt;/strong&gt;? Throws &lt;code&gt;ActivityNotFoundException&lt;/code&gt;. &lt;strong&gt;One match&lt;/strong&gt;? Launches it directly. &lt;strong&gt;Multiple matches&lt;/strong&gt;? Shows a chooser dialog (the familiar share sheet).&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;At this moment, it hit me: the share sheet is not a UI trick. It’s AMS + PMS doing intelligent matching under the hood. Mind blown.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp4i3erwdd9170vqnkkpf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp4i3erwdd9170vqnkkpf.png" alt="FlowChat — Implicit Intents" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: A Real Example — Sharing an Image
&lt;/h2&gt;

&lt;p&gt;I tested sharing a photo:&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="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;imageUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Uri&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getImageUri&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;shareIntent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ACTION_SEND&lt;/span&gt;
    &lt;span class="nf"&gt;putExtra&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EXTRA_STREAM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;imageUri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"image/jpeg"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;startActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createChooser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shareIntent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Share Image via"&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 happens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ACTION_SEND&lt;/code&gt; → I want to send something.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EXTRA_STREAM&lt;/code&gt; → My image URI.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;type = "image/jpeg"&lt;/code&gt; → Only apps that can handle JPEG images appear.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The resolve list might include WhatsApp, Gmail, Drive, etc. Android doesn’t hardcode these — it &lt;strong&gt;calculates dynamically&lt;/strong&gt; based on my device’s installed apps and their intent-filters.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: How Receiving Apps Register
&lt;/h2&gt;

&lt;p&gt;For apps to show in the share sheet, they declare intent-filters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;intent-filter&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;action&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.intent.action.SEND"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;category&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.intent.category.DEFAULT"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;data&lt;/span&gt; &lt;span class="na"&gt;android:mimeType=&lt;/span&gt;&lt;span class="s"&gt;"image/*"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/intent-filter&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Action → Can handle &lt;code&gt;SEND&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Category → Default&lt;/li&gt;
&lt;li&gt;Data → Any image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Android matches my Intent against this, adds it to the resolve list, and the app appears as an option. Simple in theory, elegant in execution.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Why It Matters for Developers
&lt;/h2&gt;

&lt;p&gt;This little deep dive changed how I design apps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Better UX&lt;/strong&gt;: I can control which apps appear by tweaking MIME types.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graceful error handling&lt;/strong&gt;: I now check for ActivityNotFoundException.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Powerful cross-app features&lt;/strong&gt;: I can build apps that communicate seamlessly without hardcoding app dependencies.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It also taught me to think like Android. Every tap, every Intent, every share sheet is a tiny example of Android’s architecture at work.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: The Takeaway
&lt;/h2&gt;

&lt;p&gt;Next time you tap Share, imagine the journey:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your Intent → AMS → PMS → Resolve List → Share Sheet → Chosen App&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Understanding this makes you not just a developer who “makes things work,” but a developer who &lt;strong&gt;understands why things work&lt;/strong&gt;. And that perspective is invaluable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Developer Challenge
&lt;/h2&gt;

&lt;p&gt;Open your favorite app and tap &lt;strong&gt;Share&lt;/strong&gt;. Ask yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How is Android deciding which apps to show?&lt;/li&gt;
&lt;li&gt;What happens if no app can handle my Intent?&lt;/li&gt;
&lt;li&gt;How could I design my app to only show relevant options?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Curiosity leads to mastery. Start small, like I did with &lt;code&gt;ACTION_SEND&lt;/code&gt;, and you’ll soon be exploring more complex flows like &lt;code&gt;ACTION_VIEW&lt;/code&gt;, &lt;code&gt;ACTION_PICK&lt;/code&gt;, and custom deep links.&lt;/p&gt;

</description>
      <category>android</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Your App Worked Perfectly… Until the OS Killed It</title>
      <dc:creator>Alamin Karno</dc:creator>
      <pubDate>Fri, 23 Jan 2026 20:12:16 +0000</pubDate>
      <link>https://forem.com/alamin_karno_b096f2ace0bb/your-app-worked-perfectly-until-the-os-killed-it-38e9</link>
      <guid>https://forem.com/alamin_karno_b096f2ace0bb/your-app-worked-perfectly-until-the-os-killed-it-38e9</guid>
      <description>&lt;p&gt;A production story from real apps. For a long time, I thought process death was an edge case. Something you read about in docs. Something that might happen, but not in real life. After all, my apps worked fine. QA was happy. Users were using them.&lt;/p&gt;

&lt;p&gt;Then production taught me otherwise.&lt;/p&gt;




&lt;h2&gt;
  
  
  It Started With Autonemo
&lt;/h2&gt;

&lt;p&gt;While working on &lt;a href="https://play.google.com/store/apps/details?id=com.autonemovtsgpswox.track&amp;amp;hl=en-US" rel="noopener noreferrer"&gt;&lt;strong&gt;Autonemo&lt;/strong&gt;&lt;/a&gt;, a vehicle tracking app, everything looked solid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Live vehicle tracking&lt;/li&gt;
&lt;li&gt;Trip status&lt;/li&gt;
&lt;li&gt;Ongoing routes&lt;/li&gt;
&lt;li&gt;Driver activity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You open the app → tracking works.&lt;br&gt;
You background it → come back → still works.&lt;/p&gt;

&lt;p&gt;Until users started reporting something strange:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Sometimes when I come back to the app, tracking is gone.”&lt;br&gt;
“The trip looks like it never started.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No crashes.&lt;br&gt;
No ANRs.&lt;br&gt;
No error logs.&lt;/p&gt;

&lt;p&gt;Just… wrong state.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Bug I Couldn’t Reproduce
&lt;/h2&gt;

&lt;p&gt;On my phone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High RAM&lt;/li&gt;
&lt;li&gt;Latest Android&lt;/li&gt;
&lt;li&gt;No aggressive background killing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I could switch apps, come back, and everything was fine.&lt;/p&gt;

&lt;p&gt;But real users?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Budget devices&lt;/li&gt;
&lt;li&gt;Old OS versions&lt;/li&gt;
&lt;li&gt;Heavy multitasking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s when I realized:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The OS was killing the app in the background.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not pausing it. Not stopping it nicely.&lt;br&gt;
&lt;strong&gt;Killing the process&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Mistake (Flutter)
&lt;/h2&gt;

&lt;p&gt;At that time, critical tracking state lived only in memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TrackingState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isTripActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;vehicleId&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;As long as the app was alive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI was correct&lt;/li&gt;
&lt;li&gt;Tracking looked accurate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But once the OS killed the process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Memory was wiped&lt;/li&gt;
&lt;li&gt;State reset&lt;/li&gt;
&lt;li&gt;App restarted as if nothing was happening&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No crash = no alert.&lt;br&gt;
Just broken logic.&lt;/p&gt;


&lt;h2&gt;
  
  
  “Okay, Fixed” — Then VivaUtilities Happened
&lt;/h2&gt;

&lt;p&gt;Later, while working on &lt;strong&gt;VivaUtilities&lt;/strong&gt;, a Flutter app for &lt;strong&gt;meal and leave management&lt;/strong&gt;, I saw the same issue — but this time in a different form.&lt;/p&gt;

&lt;p&gt;Users would:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start applying for leave&lt;/li&gt;
&lt;li&gt;Fill half the form&lt;/li&gt;
&lt;li&gt;Switch apps (email, calendar, messages)&lt;/li&gt;
&lt;li&gt;Come back…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the form was empty.&lt;/p&gt;

&lt;p&gt;Again:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No crash&lt;/li&gt;
&lt;li&gt;No error&lt;/li&gt;
&lt;li&gt;Just lost progress&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And again, QA couldn’t reproduce it consistently.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Common Pattern I Missed
&lt;/h2&gt;

&lt;p&gt;I finally stopped looking at features and started looking at &lt;strong&gt;lifecycle&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Both apps had the same assumption:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“If the app goes to background, my state is still there.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That assumption is wrong.&lt;/p&gt;

&lt;p&gt;On Flutter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Widgets rebuild&lt;/li&gt;
&lt;li&gt;Controllers recreate&lt;/li&gt;
&lt;li&gt;Providers restart&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the process is dead, &lt;strong&gt;everything in memory is gone&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Flutter Fix: Persist What Matters
&lt;/h2&gt;

&lt;p&gt;I changed how I decided what to persist.&lt;/p&gt;

&lt;p&gt;Not everything. Only what would hurt to lose.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setBool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'is_trip_active'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'vehicle_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vehicleId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then restored it &lt;strong&gt;on app start&lt;/strong&gt;, not later.&lt;/p&gt;

&lt;p&gt;For UI-related state, I also started using &lt;strong&gt;state restoration&lt;/strong&gt; (which I had ignored before):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LeaveFormScreen&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;LeaveFormScreen&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LeaveFormScreen&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_LeaveFormScreenState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_LeaveFormScreenState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LeaveFormScreen&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;RestorationMixin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;RestorableString&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RestorableString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;restorationId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;'leave_form'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;restoreState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RestorationBucket&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;oldBucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;initialRestore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;registerForRestoration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'leave_reason'&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 alone prevented a whole class of “random reset” complaints.&lt;/p&gt;




&lt;h2&gt;
  
  
  Then Native Android Taught Me the Same Lesson (VivaCash)
&lt;/h2&gt;

&lt;p&gt;I thought this was a Flutter-specific problem.&lt;/p&gt;

&lt;p&gt;Then &lt;strong&gt;VivaCash&lt;/strong&gt; proved me wrong.&lt;/p&gt;

&lt;p&gt;VivaCash is a &lt;strong&gt;native Android app written in Java&lt;/strong&gt;, handling sensitive MFS flows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amount input&lt;/li&gt;
&lt;li&gt;Transaction steps&lt;/li&gt;
&lt;li&gt;OTP flows&lt;/li&gt;
&lt;li&gt;App switching (SMS, banking apps)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users reported:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I went to check OTP and came back — transaction restarted.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Again:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No crash&lt;/li&gt;
&lt;li&gt;No exception&lt;/li&gt;
&lt;li&gt;Just lost state&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Java Trap: Trusting Memory Too Much
&lt;/h2&gt;

&lt;p&gt;The code looked innocent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaymentViewModel&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&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;I assumed:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“ViewModel survives rotation, so I’m safe.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But ViewModel does &lt;strong&gt;not&lt;/strong&gt; survive process death.&lt;/p&gt;

&lt;p&gt;When the OS killed the app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ViewModel was recreated&lt;/li&gt;
&lt;li&gt;Amount reset&lt;/li&gt;
&lt;li&gt;Flow broken&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Real Fix on Android: &lt;code&gt;SavedStateHandle&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Once we moved critical state into &lt;code&gt;SavedStateHandle&lt;/code&gt;, the issue disappeared:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaymentViewModel&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;SavedStateHandle&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;PaymentViewModel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SavedStateHandle&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;handle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setAmount&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"amount"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;getAmount&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Double&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"amount"&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;value&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&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;Now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rotation → safe&lt;/li&gt;
&lt;li&gt;Background kill → safe&lt;/li&gt;
&lt;li&gt;Process recreation → safe&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Rule I Ship Apps With Now
&lt;/h2&gt;

&lt;p&gt;After &lt;a href="https://apps.apple.com/us/app/autonemo-vehicle-tracking/id6453320203" rel="noopener noreferrer"&gt;Autonemo&lt;/a&gt;. After VivaUtilities. After VivaCash.&lt;/p&gt;

&lt;p&gt;This is the rule I never break:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If losing it would confuse or frustrate the user, it does not belong only in memory&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Memory is temporary. The OS is ruthless. Users don’t care why it happened.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Bug Is So Dangerous
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It passes QA&lt;/li&gt;
&lt;li&gt;It doesn’t crash&lt;/li&gt;
&lt;li&gt;It only happens on real devices&lt;/li&gt;
&lt;li&gt;It slowly destroys trust&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And users don’t complain loudly. They just stop using your app.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;Your app doesn’t fail when it crashes.&lt;/p&gt;

&lt;p&gt;It fails when the OS kills it — and your app pretends nothing important was happening.&lt;/p&gt;

&lt;p&gt;That’s a silent failure. And those are the worst ones.&lt;/p&gt;

</description>
      <category>dart</category>
      <category>flutter</category>
    </item>
    <item>
      <title>That One Share Button That Broke on iOS 26 and How I Fixed It</title>
      <dc:creator>Alamin Karno</dc:creator>
      <pubDate>Wed, 17 Dec 2025 20:53:25 +0000</pubDate>
      <link>https://forem.com/alamin_karno_b096f2ace0bb/that-one-share-button-that-broke-on-ios-26-and-how-i-fixed-it-17f8</link>
      <guid>https://forem.com/alamin_karno_b096f2ace0bb/that-one-share-button-that-broke-on-ios-26-and-how-i-fixed-it-17f8</guid>
      <description>&lt;p&gt;A few weeks ago, during my holiday break, I decided to revisit one of my old pet projects &lt;a href="https://github.com/alamin-karno/wallpaper_hub" rel="noopener noreferrer"&gt;WallpaperHub&lt;/a&gt;. It’s a simple Flutter app where users can browse wallpapers and share them with friends.&lt;/p&gt;

&lt;p&gt;Nothing fancy.&lt;br&gt;
Nothing experimental.&lt;/p&gt;

&lt;p&gt;Or at least… that’s what I thought.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Calm Before the Crash
&lt;/h2&gt;

&lt;p&gt;The app had been running perfectly for a long time.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flutter app ✅&lt;/li&gt;
&lt;li&gt;share_plus plugin ✅&lt;/li&gt;
&lt;li&gt;Tested on iOS 17 ✅&lt;/li&gt;
&lt;li&gt;Tested on Android ✅&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I recently updated my system and got access to an &lt;strong&gt;iOS 26 device&lt;/strong&gt;.&lt;br&gt;
Naturally, I wanted to run the app and see how it behaves on the latest iOS.&lt;/p&gt;

&lt;p&gt;The app launched.&lt;br&gt;
The UI looked fine.&lt;br&gt;
Scrolling was smooth.&lt;/p&gt;

&lt;p&gt;Then I tapped the Share button.&lt;/p&gt;

&lt;p&gt;💥 &lt;strong&gt;Boom. Runtime exception.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  “Wait… This Worked Yesterday”
&lt;/h2&gt;

&lt;p&gt;The crash happened &lt;strong&gt;only when triggering the share action&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What confused me even more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same code&lt;/li&gt;
&lt;li&gt;Same plugin version&lt;/li&gt;
&lt;li&gt;Same app&lt;/li&gt;
&lt;li&gt;Worked perfectly on iOS 17&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But on &lt;strong&gt;iOS 26&lt;/strong&gt;, the app crashed immediately.&lt;/p&gt;

&lt;p&gt;At first, I assumed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maybe Xcode issue&lt;/li&gt;
&lt;li&gt;Maybe simulator bug&lt;/li&gt;
&lt;li&gt;Maybe iOS beta instability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But no, this was a &lt;strong&gt;real breaking change&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Error That Gave It Away
&lt;/h2&gt;

&lt;p&gt;After carefully reading the logs, the issue pointed toward the &lt;strong&gt;share sheet presentation&lt;/strong&gt; on iOS.&lt;/p&gt;

&lt;p&gt;That’s when I remembered something important:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;On iOS, sharing content requires a valid &lt;strong&gt;source rectangle&lt;/strong&gt; for popover presentation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Older iOS versions were more forgiving.&lt;br&gt;
iOS 26? &lt;strong&gt;Not anymore&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Root Cause (Why This Happens)
&lt;/h2&gt;

&lt;p&gt;If you’re using &lt;strong&gt;&lt;code&gt;share_plus&lt;/code&gt; version 11 or below&lt;/strong&gt;, the plugin internally relied on deprecated behavior when presenting the iOS share sheet.&lt;/p&gt;

&lt;p&gt;Specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;iOS now &lt;strong&gt;strictly requires&lt;/strong&gt; a valid &lt;code&gt;sharePositionOrigin&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Without it, the app crashes at runtime&lt;/li&gt;
&lt;li&gt;Older iOS versions silently ignored this&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, upgrading iOS exposed an &lt;strong&gt;incompatibility in older plugin versions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This wasn’t a Flutter issue.&lt;br&gt;
This wasn’t my code logic issue.&lt;br&gt;
It was an &lt;strong&gt;outdated dependency&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Quick Fix (Two Ways)
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Recommended Solution: Upgrade &lt;code&gt;share_plus&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The easiest and safest solution is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;share_plus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The newer versions properly handle iOS share sheet requirements.&lt;/p&gt;




&lt;h3&gt;
  
  
  Still Using Deprecated Code? It Won’t Break (Yet)
&lt;/h3&gt;

&lt;p&gt;Interestingly, even in the latest versions, &lt;strong&gt;deprecated APIs still work&lt;/strong&gt;, as long as you explicitly provide the required positioning.&lt;/p&gt;

&lt;p&gt;Here’s the &lt;strong&gt;updated code&lt;/strong&gt; I used in my app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findRenderObject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;RenderBox&lt;/span&gt;&lt;span class="o"&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;SharePlus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;share&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;ShareParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;text:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;subject:&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;sharePositionOrigin:&lt;/span&gt;
        &lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;localToGlobal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Offset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// REQUIRED for iOS&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 single line is the key&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;localToGlobal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Offset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It tells iOS:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Here’s where the share sheet should originate from.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Without it, 💥 crash.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real Example From My Project
&lt;/h2&gt;

&lt;p&gt;This wasn’t a demo fix.&lt;br&gt;
This is exactly what I did in my real project &lt;strong&gt;WallpaperHub&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub repo:
👉 &lt;a href="https://github.com/alamin-karno/wallpaper_hub" rel="noopener noreferrer"&gt;https://github.com/alamin-karno/wallpaper_hub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Exact file where the fix lives:
👉 &lt;a href="https://raw.githubusercontent.com/alamin-karno/wallpaper_hub/refs/heads/master/lib/core/helper/app_helper.dart" rel="noopener noreferrer"&gt;https://raw.githubusercontent.com/alamin-karno/wallpaper_hub/refs/heads/master/lib/core/helper/app_helper.dart&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you open that file, you’ll see how the helper method safely handles sharing across platforms including iOS 26.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why iOS 26 Made This Visible
&lt;/h2&gt;

&lt;p&gt;This is the important takeaway&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;iOS 26 didn’t break your app.&lt;br&gt;
It stopped tolerating unsafe behavior.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Older iOS versions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allowed missing share origin&lt;/li&gt;
&lt;li&gt;Failed silently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;iOS 26:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enforces correct popover positioning&lt;/li&gt;
&lt;li&gt;Crashes if you don’t provide it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is actually a good thing but only if your dependencies are up to date.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned (So You Don’t Suffer Like I Did)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Always test your Flutter apps on the latest iOS&lt;/li&gt;
&lt;li&gt;Old plugins can break even if your code hasn’t changed&lt;/li&gt;
&lt;li&gt;iOS is less forgiving with UI presentation rules&lt;/li&gt;
&lt;li&gt;Reading logs carefully saves hours&lt;/li&gt;
&lt;li&gt;Sometimes… AI really does help&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This bug cost me a few hours of digging, log reading, and head scratching all from tapping a single Share button.&lt;/p&gt;

&lt;p&gt;But that’s Flutter life:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One OS update&lt;/li&gt;
&lt;li&gt;One outdated plugin&lt;/li&gt;
&lt;li&gt;One runtime crash&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hopefully, this story saves you that time.&lt;/p&gt;

&lt;p&gt;If your Flutter app crashes on &lt;strong&gt;iOS 26 when sharing&lt;/strong&gt;, now you know exactly &lt;strong&gt;why&lt;/strong&gt; and &lt;strong&gt;how to fix it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Happy coding 🚀 And keep your dependencies fresh.&lt;/p&gt;

</description>
      <category>dart</category>
      <category>flutter</category>
    </item>
  </channel>
</rss>
