<?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: dhaibanf-hue</title>
    <description>The latest articles on DEV Community by dhaibanf-hue (@dhaibanfhue).</description>
    <link>https://dev.to/dhaibanfhue</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%2F4074922%2F6cabdcd8-4503-4aa9-9799-d1770c892022.png</url>
      <title>DEV Community: dhaibanf-hue</title>
      <link>https://dev.to/dhaibanfhue</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dhaibanfhue"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>dhaibanf-hue</dc:creator>
      <pubDate>Wed, 12 Aug 2026 14:13:11 +0000</pubDate>
      <link>https://dev.to/dhaibanfhue/-489k</link>
      <guid>https://dev.to/dhaibanfhue/-489k</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/dhaibanfhue/a-small-offline-outbox-for-flutter-fifo-ordering-disk-persistence-and-retries-3an3" class="crayons-story__hidden-navigation-link"&gt;Building a small offline outbox for Flutter&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/dhaibanfhue" class="crayons-avatar  crayons-avatar--l  "&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4074922%2F6cabdcd8-4503-4aa9-9799-d1770c892022.png" alt="dhaibanfhue profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/dhaibanfhue" class="crayons-story__secondary fw-medium m:hidden"&gt;
              dhaibanf-hue
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                dhaibanf-hue
                
              
              &lt;div id="story-author-preview-content-4378583" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/dhaibanfhue" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4074922%2F6cabdcd8-4503-4aa9-9799-d1770c892022.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;dhaibanf-hue&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/dhaibanfhue/a-small-offline-outbox-for-flutter-fifo-ordering-disk-persistence-and-retries-3an3" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 12&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/dhaibanfhue/a-small-offline-outbox-for-flutter-fifo-ordering-disk-persistence-and-retries-3an3" id="article-link-4378583"&gt;
          Building a small offline outbox for Flutter
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/dart"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;dart&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/flutter"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;flutter&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/architecture"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;architecture&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/dhaibanfhue/a-small-offline-outbox-for-flutter-fifo-ordering-disk-persistence-and-retries-3an3" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt;&amp;nbsp;reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/dhaibanfhue/a-small-offline-outbox-for-flutter-fifo-ordering-disk-persistence-and-retries-3an3#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Building a small offline outbox for Flutter</title>
      <dc:creator>dhaibanf-hue</dc:creator>
      <pubDate>Wed, 12 Aug 2026 13:53:16 +0000</pubDate>
      <link>https://dev.to/dhaibanfhue/a-small-offline-outbox-for-flutter-fifo-ordering-disk-persistence-and-retries-3an3</link>
      <guid>https://dev.to/dhaibanfhue/a-small-offline-outbox-for-flutter-fifo-ordering-disk-persistence-and-retries-3an3</guid>
      <description>&lt;p&gt;I kept running into the same awkward bit of application code: a user makes a&lt;br&gt;
change while the connection is down, the request fails, and every screen ends up&lt;br&gt;
inventing its own retry logic.&lt;/p&gt;

&lt;p&gt;Caching reads does not solve this. Writes have ordering rules. If an app queues&lt;br&gt;
&lt;code&gt;create&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, and &lt;code&gt;delete&lt;/code&gt; for the same record, sending them in the wrong&lt;br&gt;
order can leave the server in a state the user never intended.&lt;/p&gt;

&lt;p&gt;I pulled that responsibility into a small Dart package called&lt;br&gt;
&lt;a href="https://pub.dev/packages/offline_sync_outbox" rel="noopener noreferrer"&gt;&lt;code&gt;offline_sync_outbox&lt;/code&gt;&lt;/a&gt;. It stores&lt;br&gt;
pending operations, processes them in FIFO order, and leaves the actual network&lt;br&gt;
call in the application.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkv84b6e1k48sgx3domp1.gif" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkv84b6e1k48sgx3domp1.gif" alt="A request being queued offline and sent after reconnecting" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The smallest useful setup
&lt;/h2&gt;

&lt;p&gt;Install it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;flutter pub add offline_sync_outbox
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The manager needs a store and a processor. The processor is just the function&lt;br&gt;
that knows how to send one operation:&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;outbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OfflineSyncManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;store:&lt;/span&gt; &lt;span class="n"&gt;JsonFileSyncStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'app-data/outbox.json'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;processor:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;payload&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;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isSuccessful&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;SyncResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;()&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;SyncResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;retry&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;outbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initialize&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;outbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;action:&lt;/span&gt; &lt;span class="s"&gt;'create_order'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;payload:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'orderId'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&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;I deliberately did not build an HTTP abstraction into the package. The callback&lt;br&gt;
can use Dio, &lt;code&gt;http&lt;/code&gt;, Supabase, a repository class, or anything else. The package&lt;br&gt;
only needs to know whether it should remove, retry, or discard the operation.&lt;/p&gt;

&lt;p&gt;Those three outcomes are important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;success()&lt;/code&gt; removes the operation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;retry()&lt;/code&gt; keeps it at the front of the queue.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;discard()&lt;/code&gt; removes a request that will never succeed, such as invalid input.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An exception from the processor is treated as a temporary failure.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why I kept strict FIFO ordering
&lt;/h2&gt;

&lt;p&gt;The first version could have processed several requests at once. I decided&lt;br&gt;
against it because concurrency makes dependent mutations difficult to reason&lt;br&gt;
about.&lt;/p&gt;

&lt;p&gt;Consider this sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create note -&amp;gt; update note -&amp;gt; delete note
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;create note&lt;/code&gt; fails temporarily, sending the other two requests is usually&lt;br&gt;
wrong. The manager therefore stops at the first retryable failure and leaves&lt;br&gt;
newer work behind it.&lt;/p&gt;

&lt;p&gt;This can cause head-of-line blocking. That is a real limitation, not a hidden&lt;br&gt;
implementation detail. For unrelated work, I would use separate managers and&lt;br&gt;
queue files—for example, one for orders and another for analytics.&lt;/p&gt;
&lt;h2&gt;
  
  
  Saving the queue
&lt;/h2&gt;

&lt;p&gt;The included &lt;code&gt;JsonFileSyncStore&lt;/code&gt; writes the queue to disk and restores it on the&lt;br&gt;
next launch:&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;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonFileSyncStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/app-data/offline-queue.json'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application owns the path. In Flutter that path normally comes from the&lt;br&gt;
storage service already used by the app.&lt;/p&gt;

&lt;p&gt;For tests there is an in-memory store:&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;outbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OfflineSyncManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;store:&lt;/span&gt; &lt;span class="n"&gt;MemorySyncStore&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nl"&gt;autoSync:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;processor:&lt;/span&gt; &lt;span class="n"&gt;sendOperation&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;&lt;code&gt;SyncStore&lt;/code&gt; is public, so an app can provide SQLite, Hive, Isar, IndexedDB, or&lt;br&gt;
encrypted storage. The file implementation uses &lt;code&gt;dart:io&lt;/code&gt;, which means Flutter&lt;br&gt;
Web needs its own store.&lt;/p&gt;
&lt;h2&gt;
  
  
  Connectivity stays outside the package
&lt;/h2&gt;

&lt;p&gt;Connection state is another area where applications already have opinions. The&lt;br&gt;
package accepts a small adapter instead of depending on a connectivity plugin:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppConnectivity&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;SyncConnectivity&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;AppConnectivity&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;network&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;NetworkService&lt;/span&gt; &lt;span class="n"&gt;network&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;Stream&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;changes&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;network&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;statusChanges&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;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;isOnline&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;network&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isOnline&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;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&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;When the stream reports that the app is online, automatic synchronization can&lt;br&gt;
start. There is also an explicit &lt;code&gt;synchronize()&lt;/code&gt; method for app lifecycle events,&lt;br&gt;
pull-to-refresh, and tests.&lt;/p&gt;
&lt;h2&gt;
  
  
  Retry decisions
&lt;/h2&gt;

&lt;p&gt;Temporary failures use bounded exponential backoff:&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;policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SyncRetryPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;maxAttempts:&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;initialDelay:&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;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;maxDelay:&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;minutes:&lt;/span&gt; &lt;span class="mi"&gt;2&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;For a response such as HTTP 429, the processor can use a server-provided delay:&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;return&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SyncResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;reason:&lt;/span&gt; &lt;span class="s"&gt;'rate limited'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;retryAfter:&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;30&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;The application still decides which failures are temporary. A validation error&lt;br&gt;
should usually be discarded; a timeout probably deserves another attempt. I do&lt;br&gt;
not think a general-purpose package can make that decision reliably without&lt;br&gt;
knowing the API.&lt;/p&gt;
&lt;h2&gt;
  
  
  Looking at a sync pass
&lt;/h2&gt;

&lt;p&gt;The result reports what happened:&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;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;outbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;synchronize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'sent: &lt;/span&gt;&lt;span class="si"&gt;${report.succeeded}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'pending: &lt;/span&gt;&lt;span class="si"&gt;${report.pending}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'offline: &lt;/span&gt;&lt;span class="si"&gt;${report.skippedOffline}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is also an event stream for enqueue, processing, retry, success, discard,&lt;br&gt;
and permanent failure. That is enough to connect logs or a small “waiting to&lt;br&gt;
sync” indicator without putting UI concerns in the package.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is still application work
&lt;/h2&gt;

&lt;p&gt;This package is not a complete offline data layer. It does not resolve conflicts,&lt;br&gt;
merge local and remote models, upload binary files, or guarantee execution after&lt;br&gt;
the operating system terminates the app.&lt;/p&gt;

&lt;p&gt;Authentication also needs care. A queued request should not be replayed under a&lt;br&gt;
different user after sign-out. In a real app I would include the user identity in&lt;br&gt;
the operation data or clear the relevant queue during the account transition.&lt;/p&gt;

&lt;p&gt;The first production integration I would choose is reading progress or another&lt;br&gt;
idempotent upsert. Payments and file uploads need more specific recovery rules.&lt;/p&gt;
&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;

&lt;p&gt;The repository has a small example that queues an operation offline and sends it&lt;br&gt;
after the connectivity source changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;dart run example/offline_sync_outbox_example.dart
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are currently 11 tests covering ordering, concurrent enqueue calls, retry&lt;br&gt;
exhaustion, malformed storage, and restoring a saved queue.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/dhaibanf-hue/offline_sync_outbox" rel="noopener noreferrer"&gt;Source on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pub.dev/packages/offline_sync_outbox" rel="noopener noreferrer"&gt;Package on pub.dev&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have shipped a similar queue, I would be interested in where you drew the&lt;br&gt;
storage and connectivity boundaries. Small bug reports and focused pull requests&lt;br&gt;
are welcome.&lt;/p&gt;

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