<?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: Overpush</title>
    <description>The latest articles on DEV Community by Overpush (@overpush_dev).</description>
    <link>https://dev.to/overpush_dev</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%2F4229775%2F4f3631fa-085d-4e7a-be15-3a76c1d94bd3.png</url>
      <title>DEV Community: Overpush</title>
      <link>https://dev.to/overpush_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/overpush_dev"/>
    <language>en</language>
    <item>
      <title>Show an "update ready" banner in your Flutter app (OTA updates)</title>
      <dc:creator>Overpush</dc:creator>
      <pubDate>Fri, 06 Nov 2026 06:49:01 +0000</pubDate>
      <link>https://dev.to/overpush_dev/show-an-update-ready-banner-in-your-flutter-app-ota-updates-310h</link>
      <guid>https://dev.to/overpush_dev/show-an-update-ready-banner-in-your-flutter-app-ota-updates-310h</guid>
      <description>&lt;p&gt;When you ship over-the-air (OTA) updates to a Flutter app, the update downloads in the background and runs at the next launch. By default users see nothing, which is usually what you want.&lt;/p&gt;

&lt;p&gt;But sometimes you &lt;em&gt;do&lt;/em&gt; want to tell them, for example: "New update downloaded. Restart the app to apply it."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://overpush.in" rel="noopener noreferrer"&gt;OverPush&lt;/a&gt; doesn't draw any UI itself. It sends events, and your app shows them in its own design. Here's a small banner you can drop into any app.&lt;/p&gt;

&lt;h2&gt;
  
  
  The events
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;OverPush.events&lt;/code&gt; is a stream. The ones you'll care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;OverPushPatchFound&lt;/code&gt;: a new update exists&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OverPushDownloading&lt;/code&gt;: download progress (&lt;code&gt;percent&lt;/code&gt;, &lt;code&gt;progress&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OverPushPatchReady&lt;/code&gt;: downloaded, will run at next launch&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OverPushFailed&lt;/code&gt;: something went wrong; nothing changed on the device&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The banner
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'dart:async'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/material.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:overpush/overpush.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UpdateBanner&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;UpdateBanner&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;UpdateBanner&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;_UpdateBannerState&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;_UpdateBannerState&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;UpdateBanner&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;StreamSubscription&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OverPushEvent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;_subscription&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;Timer&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;_hideTimer&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;_message&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;initState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;_subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OverPush&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_onEvent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;_onEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OverPushEvent&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_hideTimer&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;OverPushPatchFound&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;_show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'New update found…'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;OverPushDownloading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;percent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;_show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;percent&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="s"&gt;'Downloading update…'&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'Downloading update · &lt;/span&gt;&lt;span class="si"&gt;$percent&lt;/span&gt;&lt;span class="s"&gt;%'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;OverPushPatchReady&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;_show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Update downloaded. Restart the app to apply it.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_hideTimer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Timer&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;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;seconds:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;OverPushFailed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;_show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// nothing changed; stay quiet&lt;/span&gt;
      &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;_show&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;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;setState&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;_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&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;void&lt;/span&gt; &lt;span class="n"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_subscription&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;_hideTimer&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;cancel&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;dispose&lt;/span&gt;&lt;span class="p"&gt;();&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;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&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;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_message&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;Positioned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;left:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;right:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;bottom:&lt;/span&gt; &lt;span class="n"&gt;MediaQuery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;paddingOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bottom&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;IgnorePointer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;AnimatedSwitcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;duration:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;milliseconds:&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;message&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shrink&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
              &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Material&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ValueKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'update-banner'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                  &lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF1F2430&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                  &lt;span class="nl"&gt;borderRadius:&lt;/span&gt; &lt;span class="n"&gt;BorderRadius&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;circular&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                  &lt;span class="nl"&gt;elevation:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                      &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;TextStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;white&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;fontWeight:&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;w600&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="p"&gt;),&lt;/span&gt;
                  &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Put it above your app
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;MaterialApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;builder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;child&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;Stack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="o"&gt;!&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;UpdateBanner&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;home:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;HomePage&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;That's it. It's your own widget, so restyle it however you like: a snackbar, a dialog with a "Restart now" button, or a small dot in the settings screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Getting started: &lt;a href="https://docs.overpush.in/getting-started.html" rel="noopener noreferrer"&gt;https://docs.overpush.in/getting-started.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Full banner with a progress bar: &lt;a href="https://docs.overpush.in/update-ui.html" rel="noopener noreferrer"&gt;https://docs.overpush.in/update-ui.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://overpush.in" rel="noopener noreferrer"&gt;https://overpush.in&lt;/a&gt; (free for 5,000 patch installs a month)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>tutorial</category>
      <category>ui</category>
    </item>
    <item>
      <title>Ship Flutter fixes safely: staged rollouts and one-command rollback</title>
      <dc:creator>Overpush</dc:creator>
      <pubDate>Fri, 06 Nov 2026 06:48:12 +0000</pubDate>
      <link>https://dev.to/overpush_dev/ship-flutter-fixes-safely-staged-rollouts-and-one-command-rollback-404m</link>
      <guid>https://dev.to/overpush_dev/ship-flutter-fixes-safely-staged-rollouts-and-one-command-rollback-404m</guid>
      <description>&lt;p&gt;Pushing a fix to production is scary. Pushing a fix to &lt;em&gt;every&lt;/em&gt; user at once is scarier. If the fix has its own bug, you've just made things worse for everyone.&lt;/p&gt;

&lt;p&gt;Web teams solved this years ago with canary releases and instant rollbacks. Mobile apps usually can't: once a build is in the store, it's out. With over-the-air (OTA) updates for Flutter, you can work the same way web teams do.&lt;/p&gt;

&lt;p&gt;Here's the workflow I use with &lt;a href="https://overpush.in" rel="noopener noreferrer"&gt;OverPush&lt;/a&gt;, a code push service for Flutter.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start small: release to 10%
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;overpush patch android &lt;span class="nt"&gt;--rollout-percent&lt;/span&gt; 10
overpush patch ios &lt;span class="nt"&gt;--rollout-percent&lt;/span&gt; 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only 10% of devices get the update. The same devices stay in the 10% over time, so when you go to 50%, the first group keeps the update and new devices join.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Watch the numbers
&lt;/h2&gt;

&lt;p&gt;The dashboard shows, for each patch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how many devices downloaded it&lt;/li&gt;
&lt;li&gt;how many are running it&lt;/li&gt;
&lt;li&gt;how many failed, and why&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Give it an hour or a day, depending on how many users you have.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Go wider
&lt;/h2&gt;

&lt;p&gt;Patch numbers count up per release, so if this is patch 4:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;overpush patches rollout 4 &lt;span class="nt"&gt;--percent&lt;/span&gt; 50
overpush patches rollout 4 &lt;span class="nt"&gt;--percent&lt;/span&gt; 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Devices see the new percentage the next time they open the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Something's wrong? Pull it back
&lt;/h2&gt;

&lt;p&gt;If the patch is bad and the original release code was fine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;overpush patches revoke 4 &lt;span class="nt"&gt;--reason&lt;/span&gt; &lt;span class="s2"&gt;"checkout total wrong"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On their next launch, devices delete the patch and run the release's own code again.&lt;/p&gt;

&lt;p&gt;If patch 4 is bad but patch 3 was fine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;overpush patches rollback &lt;span class="nt"&gt;--to&lt;/span&gt; 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This serves patch 3's code again, so devices that already took patch 4 move off it.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. What if the patch crashes the app on startup?
&lt;/h2&gt;

&lt;p&gt;You don't have to do anything. If an update stops the app from starting, the phone goes back to the store version on its own, even offline. Then revoke the patch so no other device tries it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cheat sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Try a fix on a few users first&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--rollout-percent 5&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fix looks good, go wider&lt;/td&gt;
&lt;td&gt;&lt;code&gt;patches rollout N --percent 100&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Patch is bad, release code is fine&lt;/td&gt;
&lt;td&gt;&lt;code&gt;patches revoke N&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Patch is bad, previous patch was fine&lt;/td&gt;
&lt;td&gt;&lt;code&gt;patches rollback --to N&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Patch crashes on startup&lt;/td&gt;
&lt;td&gt;Nothing: devices roll back by themselves&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All of these also work from the dashboard: open the release, then ⋮ on the patch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://overpush.in" rel="noopener noreferrer"&gt;https://overpush.in&lt;/a&gt; (free for 5,000 patch installs a month)&lt;/li&gt;
&lt;li&gt;Docs: &lt;a href="https://docs.overpush.in/rollouts.html" rel="noopener noreferrer"&gt;https://docs.overpush.in/rollouts.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How do you ship hotfixes today? Curious to hear what other Flutter teams do.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>mobile</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to update a Flutter app without the App Store or Play Store review</title>
      <dc:creator>Overpush</dc:creator>
      <pubDate>Fri, 06 Nov 2026 06:17:30 +0000</pubDate>
      <link>https://dev.to/overpush_dev/how-to-update-a-flutter-app-without-the-app-store-or-play-store-review-44og</link>
      <guid>https://dev.to/overpush_dev/how-to-update-a-flutter-app-without-the-app-store-or-play-store-review-44og</guid>
      <description>&lt;p&gt;You shipped a Flutter release. A day later, users hit a bug: a crash on one screen, a wrong API URL, a broken checkout. The fix is one line of Dart.&lt;/p&gt;

&lt;p&gt;The normal path is a new build, an upload, a store review that can take hours or days, and then waiting for users to actually install the update. For a production bug, that's too slow.&lt;/p&gt;

&lt;p&gt;This post shows how to send that Dart fix straight to the app already installed on users' phones, on iOS and Android, with over-the-air (OTA) updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is an OTA update for Flutter?
&lt;/h2&gt;

&lt;p&gt;An OTA update (also called code push) sends changed Dart code to the installed app. The app checks for an update when it opens, downloads it in the background, and runs the new code at the next launch. The store build stays the same.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you can change:&lt;/strong&gt; any Dart code. Business logic, API calls, widgets, layouts, text, translations, even new screens and classes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What still needs a store release:&lt;/strong&gt; native code (Swift, Kotlin, Java, Objective-C), new plugins, images and fonts, and permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step by step
&lt;/h2&gt;

&lt;p&gt;I'll use &lt;a href="https://overpush.in" rel="noopener noreferrer"&gt;OverPush&lt;/a&gt;, a code push service for Flutter that I've been building. It has a free tier, so you can try this on a real app.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Install the CLI and connect your project
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://overpush.in/install.sh | bash
overpush login
overpush init
flutter pub add overpush
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That adds one small config file and one dependency line. No changes to &lt;code&gt;main.dart&lt;/code&gt;, no native code.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Release to the store once
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;overpush release android
overpush release ios
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Submit those builds to Google Play and the App Store the way you always do. Every later fix is checked against exactly what shipped.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Fix the bug and push the update
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;overpush patch android &lt;span class="nt"&gt;--rollout-percent&lt;/span&gt; 10
overpush patch ios &lt;span class="nt"&gt;--rollout-percent&lt;/span&gt; 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;10% of devices get it first. Watch installs and failures on the dashboard, then raise the rollout to 100%.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if the fix itself is broken?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Every update is signed with your app's own key, and the app only accepts updates signed with it.&lt;/li&gt;
&lt;li&gt;If an update stops the app from starting, the phone goes back to the store version on its own, even offline.&lt;/li&gt;
&lt;li&gt;You can roll an update back for everyone with one command.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Is this allowed by Apple and Google?
&lt;/h2&gt;

&lt;p&gt;Apple's guidelines allow an app to download code that changes its behaviour as long as it doesn't change the app's main purpose. Google Play applies its policy differently to different kinds of apps, so check the current policy for yours. On both stores the safe rule is the same: use OTA updates for fixes and improvements, not to turn your app into something reviewers didn't see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://overpush.in" rel="noopener noreferrer"&gt;https://overpush.in&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Getting started guide: &lt;a href="https://docs.overpush.in/getting-started.html" rel="noopener noreferrer"&gt;https://docs.overpush.in/getting-started.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Free for 5,000 patch installs a month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you ship Flutter apps in production, I'd love to hear what would stop you from using code push, or what you'd need from it. Drop a comment!&lt;/p&gt;

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