<?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: Viet, Nguyen Tuan</title>
    <description>The latest articles on Forem by Viet, Nguyen Tuan (@brewkits).</description>
    <link>https://forem.com/brewkits</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%2F3714499%2Fd0d3ec2d-de53-4aa6-89ab-dc3350b5184b.png</url>
      <title>Forem: Viet, Nguyen Tuan</title>
      <link>https://forem.com/brewkits</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/brewkits"/>
    <language>en</language>
    <item>
      <title>The Most Comprehensive Event Rate Limiting Solution for Flutter &amp; Dart Ecosystem</title>
      <dc:creator>Viet, Nguyen Tuan</dc:creator>
      <pubDate>Wed, 04 Feb 2026 03:12:08 +0000</pubDate>
      <link>https://forem.com/brewkits/giai-phap-event-rate-limiting-toan-dien-nhat-cho-flutter-dart-ecosystem-2edj</link>
      <guid>https://forem.com/brewkits/giai-phap-event-rate-limiting-toan-dien-nhat-cho-flutter-dart-ecosystem-2edj</guid>
      <description>&lt;h2&gt;
  
  
  The Problem You're Facing
&lt;/h2&gt;

&lt;p&gt;Are you implementing debounce/throttle manually with &lt;code&gt;Timer&lt;/code&gt;?&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;_SearchState&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;SearchPage&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;Timer&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;_debounceTimer&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;onSearchChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_debounceTimer&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;_debounceTimer&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="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;300&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="n"&gt;_callSearchAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&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="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;_debounceTimer&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="c1"&gt;// Forget this = memory leak!&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3 critical issues:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;❌ &lt;strong&gt;Memory leak&lt;/strong&gt; if you forget to dispose&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Race condition&lt;/strong&gt; - old requests return after new ones&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Code duplication&lt;/strong&gt; every time you need debounce&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Solution: flutter_debounce_throttle
&lt;/h2&gt;

&lt;p&gt;An &lt;strong&gt;all-in-one&lt;/strong&gt; library with 360+ tests and 95% coverage that solves everything:&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ Automatic lifecycle management
&lt;/h3&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;_NewWayState&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;NewWay&lt;/span&gt;&lt;span class="p"&gt;&amp;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;debouncer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Debouncer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;duration:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ms&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;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;debouncer&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;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// That's it!&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// No need to dispose anything!&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ✅ Proper async handling
&lt;/h3&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;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConcurrentAsyncThrottler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;mode:&lt;/span&gt; &lt;span class="n"&gt;ConcurrencyMode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Auto cancel old requests&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;onSearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;controller&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;results&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;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&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;searchResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&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;h3&gt;
  
  
  ✅ Ready-to-use widgets
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Anti-spam button (1 line!)&lt;/span&gt;
&lt;span class="n"&gt;ThrottledInkWell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;onTap:&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;processPayment&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="s"&gt;'Pay &lt;/span&gt;&lt;span class="err"&gt;\$&lt;/span&gt;&lt;span class="s"&gt;99'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Search with loading state&lt;/span&gt;
&lt;span class="n"&gt;DebouncedQueryBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;onQuery:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;searchUsers&lt;/span&gt;&lt;span class="p"&gt;(&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;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;search&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isLoading&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;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;onChanged:&lt;/span&gt; &lt;span class="n"&gt;search&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;
  
  
  Quick Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;This lib&lt;/th&gt;
&lt;th&gt;easy_debounce&lt;/th&gt;
&lt;th&gt;rxdart&lt;/th&gt;
&lt;th&gt;Manual Timer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Auto-dispose&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;⚠️&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Async support&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Race condition handling&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;⚠️&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate Limiter&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flutter Widgets&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server-side (Pure Dart)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependencies&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Many&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  📱 Prevent double-click
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;ThrottledInkWell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;duration:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;onTap:&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;processPayment&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="s"&gt;'Buy &lt;/span&gt;&lt;span class="err"&gt;\$&lt;/span&gt;&lt;span class="s"&gt;99'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// User taps 10 times → only 1 is processed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔍 Search autocomplete
&lt;/h3&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;debouncer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Debouncer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;duration:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;onChanged:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&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;debouncer&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;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="c1"&gt;// Only call API when user stops typing for 300ms&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🖥️ Batch DB writes (Backend)
&lt;/h3&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;batcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BatchThrottler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;duration:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;maxBatchSize:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;onBatchExecute:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logs&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;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;insertBatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;batcher&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;logEntry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 1000 calls → 10 batches → 90% less DB load&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  💰 Rate limit API
&lt;/h3&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;limiter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RateLimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;maxTokens:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;refillRate:&lt;/span&gt; &lt;span class="mi"&gt;100&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;limiter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tryAcquire&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;'Rate limit exceeded'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;callOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advanced Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🏢 Distributed Rate Limiting (v2.4+)
&lt;/h3&gt;

&lt;p&gt;Rate limiting across multiple server instances with Redis:&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;limiter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DistributedRateLimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="s"&gt;'user:&lt;/span&gt;&lt;span class="si"&gt;$userId&lt;/span&gt;&lt;span class="s"&gt;'&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;RedisRateLimiterStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;redis:&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;maxTokens:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;refillRate:&lt;/span&gt; &lt;span class="mi"&gt;10&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;limiter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tryAcquire&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="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;statusCode:&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🧹 Auto-cleanup (v2.3+)
&lt;/h3&gt;

&lt;p&gt;Automatically cleanup unused limiters to prevent memory leaks with dynamic IDs:&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;PostController&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;EventLimiterMixin&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;onLike&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;debounce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'like_&lt;/span&gt;&lt;span class="si"&gt;$postId&lt;/span&gt;&lt;span class="s"&gt;'&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;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;like&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;postId&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="c1"&gt;// Auto-remove unused limiters after 10 minutes&lt;/span&gt;
&lt;span class="c1"&gt;// User scrolls through 1000 posts - no OOM crash!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🎮 ThrottledGestureDetector
&lt;/h3&gt;

&lt;p&gt;Drop-in replacement for &lt;code&gt;GestureDetector&lt;/code&gt; with automatic throttling:&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;ThrottledGestureDetector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;onTap:&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;handleTap&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nl"&gt;onPanUpdate:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;details&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;updatePosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;delta&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;MyWidget&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// Supports 40+ gesture callbacks with auto throttling!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Throttle vs Debounce?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Throttle:&lt;/strong&gt; Execute &lt;strong&gt;immediately&lt;/strong&gt;, lock for 300ms, ignore events during lock&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use: Button clicks, scroll events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Debounce:&lt;/strong&gt; Wait for user to &lt;strong&gt;stop&lt;/strong&gt; for 300ms before executing&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use: Search input, form validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4 Concurrency Modes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;drop&lt;/code&gt;: Ignore new task if busy&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;replace&lt;/code&gt;: Cancel old task, run new one (perfect for search)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;enqueue&lt;/code&gt;: Queue tasks&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;keepLatest&lt;/code&gt;: Keep only running task + latest task&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Mixin for State Management
&lt;/h2&gt;

&lt;p&gt;Works with Provider, GetX, Bloc, Riverpod...&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;SearchController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;GetxController&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;EventLimiterMixin&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;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;debounceAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'search'&lt;/span&gt;&lt;span class="p"&gt;,&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;results&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;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nl"&gt;mode:&lt;/span&gt; &lt;span class="n"&gt;ConcurrencyMode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&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;onClose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cancelAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Auto cleanup&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;onClose&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;
  
  
  Production Quality
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;360+ tests&lt;/strong&gt; with 95% coverage&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Zero dependencies&lt;/strong&gt; (only &lt;code&gt;meta&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Type-safe&lt;/strong&gt; (full generic support)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Memory-safe&lt;/strong&gt; (verified with LeakTracker)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Cross-platform&lt;/strong&gt; (Mobile, Web, Desktop, Server)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Flutter App&lt;/span&gt;
&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;flutter_debounce_throttle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^2.4.2&lt;/span&gt;

&lt;span class="c1"&gt;# Flutter + Hooks&lt;/span&gt;
&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;flutter_debounce_throttle_hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^2.4.2&lt;/span&gt;

&lt;span class="c1"&gt;# Pure Dart (Server/CLI)&lt;/span&gt;
&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dart_debounce_throttle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^2.4.2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If you're using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Manual &lt;code&gt;Timer&lt;/code&gt; → Memory leak risk&lt;/li&gt;
&lt;li&gt;❌ &lt;code&gt;easy_debounce&lt;/code&gt; → Missing async support&lt;/li&gt;
&lt;li&gt;❌ &lt;code&gt;rxdart&lt;/code&gt; only for debounce → Overkill dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then &lt;strong&gt;flutter_debounce_throttle&lt;/strong&gt; is the solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Memory-safe by default&lt;/li&gt;
&lt;li&gt;✅ Production-tested (360+ tests)&lt;/li&gt;
&lt;li&gt;✅ Universal (Flutter + Dart backend)&lt;/li&gt;
&lt;li&gt;✅ Zero dependencies&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📦 Pub.dev: &lt;a href="https://pub.dev/packages/flutter_debounce_throttle" rel="noopener noreferrer"&gt;https://pub.dev/packages/flutter_debounce_throttle&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 GitHub: &lt;a href="https://github.com/brewkits/flutter_debounce_throttle" rel="noopener noreferrer"&gt;https://github.com/brewkits/flutter_debounce_throttle&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐛 Issues: &lt;a href="https://github.com/brewkits/flutter_debounce_throttle/issues" rel="noopener noreferrer"&gt;https://github.com/brewkits/flutter_debounce_throttle/issues&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Technical review of flutter_debounce_throttle v2.4.2&lt;/em&gt;&lt;/p&gt;

</description>
      <category>debounce</category>
      <category>throttle</category>
      <category>eventlimited</category>
      <category>ratelimited</category>
    </item>
    <item>
      <title>Mastering Native Interop in KMP: Why KRelay is the “Ultimate Bridge” Multiplatform</title>
      <dc:creator>Viet, Nguyen Tuan</dc:creator>
      <pubDate>Wed, 04 Feb 2026 02:32:54 +0000</pubDate>
      <link>https://forem.com/brewkits/mastering-native-interop-in-kmp-why-krelay-is-the-ultimate-bridge-multiplatform-1g9d</link>
      <guid>https://forem.com/brewkits/mastering-native-interop-in-kmp-why-krelay-is-the-ultimate-bridge-multiplatform-1g9d</guid>
      <description>&lt;h1&gt;
  
  
  KRelay: The Native Interop Bridge for Kotlin Multiplatform
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;KMP isn't just about sharing code; it's about managing Native UI safely. KRelay is the tool that makes it possible.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;In my 15 years of mobile engineering — from J2ME to Flutter and now Kotlin Multiplatform (KMP) — one "final boss" always remains: &lt;strong&gt;Native Interop&lt;/strong&gt;. How do you trigger native UI components like Toasts, Camera, or Navigation from shared code without causing memory leaks, threading crashes, or losing events during screen rotation?&lt;/p&gt;

&lt;p&gt;Enter &lt;strong&gt;KRelay&lt;/strong&gt; — a library designed with the Unix Philosophy: "Do one thing and do it well."&lt;/p&gt;




&lt;h2&gt;
  
  
  The Core Value: Why KRelay?
&lt;/h2&gt;

&lt;p&gt;Traditionally, calling platform-specific code from shared ViewModels requires complex patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Boilerplate Overload&lt;/strong&gt;: You often need &lt;code&gt;MutableSharedFlow&lt;/code&gt; + &lt;code&gt;emit&lt;/code&gt; + &lt;code&gt;collectAsState&lt;/code&gt; + &lt;code&gt;LaunchedEffect&lt;/code&gt; just for a simple notification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory Leaks&lt;/strong&gt;: Strong references to an Activity or ViewController in shared code prevent garbage collection, leading to &lt;code&gt;OutOfMemoryError&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event Loss&lt;/strong&gt;: If you dispatch a command while the UI is recreating (e.g., during rotation), that event is often lost forever.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;KRelay solves these issues through three fundamental pillars:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Weak Registry
&lt;/h3&gt;

&lt;p&gt;KRelay automatically manages &lt;code&gt;WeakReferences&lt;/code&gt; to your platform implementations. When a ViewController or Activity is destroyed, the reference is cleared automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Sticky Queue
&lt;/h3&gt;

&lt;p&gt;If your ViewModel dispatches an action while the UI is not ready (e.g., during a cold start or rotation), KRelay queues the action and replays it as soon as the UI registers.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Safe Dispatch
&lt;/h3&gt;

&lt;p&gt;All commands are automatically executed on the platform's Main/UI thread (using Looper on Android and GCD on iOS), preventing threading-related crashes.&lt;/p&gt;




&lt;h2&gt;
  
  
  The "Ultimate Combo": KRelay + Adapter Pattern
&lt;/h2&gt;

&lt;p&gt;One of the biggest hurdles in 2026 is that many modern Swift libraries (like Lottie or Alamofire) are "Pure Swift" and are not visible to Kotlin's cinterop.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Adapter Pattern&lt;/strong&gt; combined with KRelay is the most powerful solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Strategy&lt;/strong&gt;: Define an interface in &lt;code&gt;commonMain&lt;/code&gt; (e.g., &lt;code&gt;NavFeature&lt;/code&gt;). Create a Swift Adapter that implements this interface and calls your 3rd-party Swift library.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Benefit&lt;/strong&gt;: Your ViewModel stays pure and testable. KRelay acts as the safe messenger, handling the threading and lifecycle complexities of the native implementation.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Technical Deep-Dive
&lt;/h2&gt;

&lt;p&gt;KRelay is a high-performance &lt;strong&gt;Runtime bridge&lt;/strong&gt;, avoiding the complexity and build-time overhead of KSP or reflection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;Add the library to your shared module's dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// shared/build.gradle.kts&lt;/span&gt;
&lt;span class="n"&gt;commonMain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dev.brewkits:krelay:1.0.0"&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;h3&gt;
  
  
  Core API Usage
&lt;/h3&gt;

&lt;p&gt;Defining and using a feature is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. Define (commonMain)&lt;/span&gt;
&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ToastFeature&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;RelayFeature&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;show&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="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Dispatch (Shared Logic)&lt;/span&gt;
&lt;span class="nc"&gt;KRelay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ToastFeature&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;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Register (Android/iOS Native)&lt;/span&gt;
&lt;span class="nc"&gt;KRelay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ToastFeature&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nc"&gt;AndroidToastImpl&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Advanced Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Priority System&lt;/strong&gt;: Use &lt;code&gt;ActionPriority&lt;/code&gt; (LOW to CRITICAL) to ensure important events like error dialogs are replayed first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance Metrics&lt;/strong&gt;: Monitor dispatch counts and queue statistics per feature to optimize your app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safe Clean-up&lt;/strong&gt;: Use &lt;code&gt;clearQueue&amp;lt;T&amp;gt;()&lt;/code&gt; in your ViewModel's &lt;code&gt;onCleared()&lt;/code&gt; to prevent lambda capture leaks.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Project Architecture
&lt;/h2&gt;

&lt;p&gt;KRelay's internal structure is optimized for platform-specific efficiency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;KRelay/
├── commonMain/        # Core registry and queue logic
│   └── KRelay.kt, Priority.kt, Metrics.kt
├── androidMain/       # Android Looper and ReentrantLock
│   └── Lock.android.kt, MainThreadExecutor.android.kt
└── iosMain/           # iOS GCD and NSRecursiveLock
    └── Lock.ios.kt, MainThreadExecutor.ios.kt, KRelay+Extensions.swift
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Important Limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Process Death&lt;/strong&gt;: KRelay's queue is in-memory and does not survive process death. Never use it for critical transactions like payments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Singleton Scope&lt;/strong&gt;: While simple for most apps, large Super Apps should use Feature Namespacing to avoid registry conflicts.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;KRelay isn't trying to be a state manager or a background worker. It is a &lt;strong&gt;focused, reliable messenger&lt;/strong&gt; for one-way UI commands. By combining KRelay with the Adapter Pattern, you gain the flexibility to use any native library while maintaining the cleanest shared code architecture possible.&lt;/p&gt;

&lt;p&gt;Start building safer KMP apps today.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Repository&lt;/strong&gt;: &lt;a href="https://github.com/brewkits/krelay" rel="noopener noreferrer"&gt;github.com/brewkits/krelay&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags&lt;/strong&gt;: &lt;code&gt;#KotlinMultiplatform&lt;/code&gt; &lt;code&gt;#KMP&lt;/code&gt; &lt;code&gt;#NativeInterop&lt;/code&gt; &lt;code&gt;#AndroidDev&lt;/code&gt; &lt;code&gt;#iOSDev&lt;/code&gt; &lt;code&gt;#CleanArchitecture&lt;/code&gt;&lt;/p&gt;

</description>
      <category>kmp</category>
      <category>mobilearchitecture</category>
      <category>mobile</category>
      <category>nativeinterop</category>
    </item>
    <item>
      <title>Grant: The “No-Nonsense” Permission Manager for Kotlin Multiplatform</title>
      <dc:creator>Viet, Nguyen Tuan</dc:creator>
      <pubDate>Wed, 04 Feb 2026 02:26:24 +0000</pubDate>
      <link>https://forem.com/brewkits/grant-the-no-nonsense-permission-manager-for-kotlin-multiplatform-4n1a</link>
      <guid>https://forem.com/brewkits/grant-the-no-nonsense-permission-manager-for-kotlin-multiplatform-4n1a</guid>
      <description>&lt;p&gt;&lt;strong&gt;Stop Fighting with Android Manifests and Info.plist. Request permissions in shared code like a boss.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After solving the &lt;strong&gt;UI Command&lt;/strong&gt; problem, I ran into the next biggest wall in Kotlin Multiplatform (KMP) development: &lt;strong&gt;Runtime Permissions&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Current Chaos
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Android:&lt;/strong&gt; You need &lt;code&gt;ActivityResultLauncher&lt;/code&gt;, &lt;code&gt;ContextCompat.checkSelfPermission&lt;/code&gt;, manifest entries, and complex logic for &lt;code&gt;shouldShowRequestPermissionRationale&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;iOS:&lt;/strong&gt; You need &lt;code&gt;AVCaptureDevice.requestAccess&lt;/code&gt;, &lt;code&gt;PHPhotoLibrary&lt;/code&gt;, delegates, and async completion blocks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Shared Problem:&lt;/strong&gt; How do you unite these two chaotic worlds into a clean, linear flow in your ViewModel?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enter Grant.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 What is Grant?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Grant&lt;/strong&gt; is a permission manager built specifically for &lt;strong&gt;Kotlin Multiplatform&lt;/strong&gt;, designed with a &lt;strong&gt;"Headless &amp;amp; Coroutines-First"&lt;/strong&gt; philosophy.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ It is &lt;strong&gt;NOT&lt;/strong&gt; a UI library (it won't force ugly popups on you)&lt;/li&gt;
&lt;li&gt;✅ It is a &lt;strong&gt;System Engine&lt;/strong&gt; that abstracts the operating system, giving you full control over the UI/UX while it handles the dirty work&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🏆 Why Grant is the "De-Facto Standard" for 2026
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1️⃣ Unified API (One Enum to Rule Them All)
&lt;/h3&gt;

&lt;p&gt;Forget about checking Android SDK versions or iOS authorization statuses manually. Grant abstracts everything into a simple, &lt;strong&gt;type-safe Enum&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You don't need to know the difference between &lt;strong&gt;Android API 33&lt;/strong&gt; and &lt;strong&gt;iOS 16&lt;/strong&gt; under the hood. &lt;strong&gt;Grant handles it.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2️⃣ Linear Logic (Suspend Functions)
&lt;/h3&gt;

&lt;p&gt;Permissions are inherently transactional: &lt;strong&gt;Ask → Wait → Result&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of callbacks (which lead to &lt;strong&gt;"Callback Hell"&lt;/strong&gt;) or complex Delegates, Grant uses &lt;strong&gt;suspend functions&lt;/strong&gt; natively. Your logic runs &lt;strong&gt;top-to-bottom&lt;/strong&gt;, making it incredibly readable and maintainable.&lt;/p&gt;

&lt;h3&gt;
  
  
  3️⃣ Smart Status Handling
&lt;/h3&gt;

&lt;p&gt;The hardest part of permissions isn't asking; it's handling when the user says &lt;strong&gt;"No, and don't ask again."&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Android:&lt;/strong&gt; The system dialog stops appearing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iOS:&lt;/strong&gt; The status becomes &lt;code&gt;.denied&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Grant &lt;strong&gt;detects this state automatically&lt;/strong&gt; across platforms, allowing you to guide the user to &lt;strong&gt;System Settings&lt;/strong&gt; instantly.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ The "Clean" Architecture Way
&lt;/h2&gt;

&lt;p&gt;Let's look at the difference between the traditional way and the Grant way.&lt;/p&gt;

&lt;h3&gt;
  
  
  ❌ The Old Way (The Pain)
&lt;/h3&gt;

&lt;p&gt;You have to inject an &lt;code&gt;Activity&lt;/code&gt; into your ViewModel (causing leaks), or create complex interfaces in &lt;code&gt;commonMain&lt;/code&gt; and implement them separately for each platform. Your code becomes &lt;strong&gt;fragmented&lt;/strong&gt; and &lt;strong&gt;hard to test&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ The Grant Way (The Solution)
&lt;/h3&gt;

&lt;p&gt;You handle permissions directly in your &lt;strong&gt;Shared Code&lt;/strong&gt; (ViewModel/UseCase), with &lt;strong&gt;zero&lt;/strong&gt; Android &lt;code&gt;Context&lt;/code&gt; or iOS Delegates required.&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;class&lt;/span&gt; &lt;span class="nc"&gt;CameraViewModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCaptureClicked&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// 1. Check current status&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Grant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CAMERA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;PermissionStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GRANTED&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;openCamera&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

                &lt;span class="nc"&gt;PermissionStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DENIED&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="c1"&gt;// 2. Request (Suspends until user decides)&lt;/span&gt;
                    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Grant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CAMERA&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isGranted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nf"&gt;openCamera&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nf"&gt;showErrorUI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"We need camera access to take photos."&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="nc"&gt;PermissionStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PERMANENTLY_DENIED&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="c1"&gt;// 3. Handle "Don't ask again" scenario&lt;/span&gt;
                    &lt;span class="nf"&gt;showSettingsDialog&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;
  
  
  🚀 The Killer Feature: Handling "Permanently Denied"
&lt;/h2&gt;

&lt;p&gt;A major UX pain point occurs when a user accidentally clicks &lt;strong&gt;"Don't ask again"&lt;/strong&gt; (Android) or denies permission (iOS). Subsequent requests fail silently, making your app look &lt;strong&gt;broken&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Grant solves this with a &lt;strong&gt;single line of code:&lt;/strong&gt;&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Grant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CAMERA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;PermissionStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PERMANENTLY_DENIED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Opens the specific System Settings page for your app&lt;/span&gt;
    &lt;span class="nc"&gt;Grant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openSettings&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;blockquote&gt;
&lt;p&gt;This is the polish of a library built by &lt;strong&gt;product veterans&lt;/strong&gt;: It solves the &lt;strong&gt;UX problem&lt;/strong&gt;, not just the technical one.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📊 Technical Face-Off: Grant vs. The World
&lt;/h2&gt;

&lt;p&gt;Let's break down exactly why Grant outperforms traditional methods (like &lt;strong&gt;Moko-Permissions&lt;/strong&gt; or &lt;strong&gt;Accompanist&lt;/strong&gt;):&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Configuration &amp;amp; Setup
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Experience&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Traditional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ High friction. Often requires binding to an Activity or wrapping your Compose UI in a special Provider.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Grant&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;Zero-Config&lt;/strong&gt;. Just add the dependency. It automatically hooks into the lifecycle context-aware components.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  2. Control Flow
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Experience&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Traditional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Relies on Callbacks or reactive Streams/Flows, which can complicate simple transactional logic.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Grant&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Uses &lt;strong&gt;Suspend Functions&lt;/strong&gt;. This keeps your business logic linear and easy to reason about.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  3. Architecture Compatibility
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Experience&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Traditional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Tightly coupled to the UI Layer. Hard to use inside a ViewModel or Domain UseCase.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Grant&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;Headless&lt;/strong&gt;. It works perfectly inside ViewModels, keeping your UI code dumb and your logic code smart.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  4. Boilerplate
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Experience&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Traditional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Requires passing Context or Activity references manually.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Grant&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;Context-Aware&lt;/strong&gt;. It handles the platform context injection under the hood, keeping your shared code pure.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  👨‍💻 Final Verdict
&lt;/h2&gt;

&lt;p&gt;In modern software architecture, specifically KMP, we strive for &lt;strong&gt;"Platform Transparency."&lt;/strong&gt; Your business logic shouldn't care if it's running on an &lt;strong&gt;iPhone 15&lt;/strong&gt; or a &lt;strong&gt;Pixel 9&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grant&lt;/strong&gt; is the perfect abstraction layer to achieve this transparency for permissions. It hides the ugly, fragmented platform APIs and gives you a &lt;strong&gt;clean, powerful, and safe&lt;/strong&gt; toolset.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are looking for a &lt;strong&gt;"Complete &amp;amp; Elite"&lt;/strong&gt; permission solution for your next KMP project, &lt;strong&gt;Grant is the way to go.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🔗 Links
&lt;/h2&gt;

&lt;p&gt;👉 &lt;strong&gt;GitHub Repo:&lt;/strong&gt; &lt;a href="https://www.github.com/brewkits/Grant" rel="noopener noreferrer"&gt;https://www.github.com/brewkits/Grant&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;#KotlinMultiplatform&lt;/code&gt; &lt;code&gt;#KMP&lt;/code&gt; &lt;code&gt;#AndroidDev&lt;/code&gt; &lt;code&gt;#iOSDev&lt;/code&gt; &lt;code&gt;#Permissions&lt;/code&gt; &lt;code&gt;#CleanArchitecture&lt;/code&gt;&lt;/p&gt;

</description>
      <category>kotlinmultiplatform</category>
      <category>kmp</category>
      <category>permissions</category>
      <category>permission</category>
    </item>
    <item>
      <title>KMP WorkManager: Enterprise-Grade Background Tasks for Kotlin Multiplatform</title>
      <dc:creator>Viet, Nguyen Tuan</dc:creator>
      <pubDate>Thu, 22 Jan 2026 01:57:51 +0000</pubDate>
      <link>https://forem.com/brewkits/kmp-workmanager-enterprise-grade-background-tasks-for-kotlin-multiplatform-3cl2</link>
      <guid>https://forem.com/brewkits/kmp-workmanager-enterprise-grade-background-tasks-for-kotlin-multiplatform-3cl2</guid>
      <description>&lt;p&gt;We all love Kotlin Multiplatform (KMP). Sharing business logic, networking, and data layers between Android and iOS is a dream come true. But let's be honest: there is one specific area where the "write once, run everywhere" dream often turns into a &lt;strong&gt;fragmented nightmare&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Background Processing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you are coming from Android, you are used to &lt;strong&gt;WorkManager&lt;/strong&gt;. It's robust, persistent, and handles constraints like "only run when charging" beautifully.&lt;/p&gt;

&lt;p&gt;Then you port your logic to iOS. You encounter &lt;strong&gt;BGTaskScheduler&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Suddenly, you realize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Android tasks are &lt;strong&gt;persistent&lt;/strong&gt;; iOS tasks are &lt;strong&gt;opportunistic&lt;/strong&gt; and can be killed at any moment.&lt;/li&gt;
&lt;li&gt;Android supports &lt;strong&gt;complex constraints&lt;/strong&gt;; iOS is extremely strict (mostly just network).&lt;/li&gt;
&lt;li&gt;The worst part: If an iOS user &lt;strong&gt;force-quits&lt;/strong&gt; the app, your background task logic might be dead in the water, potentially &lt;strong&gt;corrupting data&lt;/strong&gt; if you were halfway through a multi-step chain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I spent months battling these discrepancies in enterprise projects. I wanted the reliability of Android's WorkManager, but &lt;strong&gt;on both platforms&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The existing libraries were mostly thin wrappers. They let you schedule a task, but they didn't solve the hard engineering problems of &lt;strong&gt;state restoration&lt;/strong&gt; and &lt;strong&gt;OS resource limits&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So, I built &lt;strong&gt;KMP WorkManager&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The "Gap" in the Ecosystem
&lt;/h2&gt;

&lt;p&gt;When I analyzed the existing solutions, I found they were missing critical "Enterprise-grade" features required for production apps:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chain State Restoration&lt;/strong&gt;: What happens if a multi-step workflow (Download → Process → Upload) gets killed by iOS at step 2? Most libs just restart from step 1 next time, wasting battery and data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reliability vs. Speed&lt;/strong&gt;: Writing to UserDefaults or flat files on iOS is fine for small apps, but what if you have a queue of 500 offline analytics events?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer Experience&lt;/strong&gt;: I didn't want to write Swift callbacks. I wanted a &lt;strong&gt;pure Kotlin API&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I decided to treat iOS constraints not as a bug, but as a &lt;strong&gt;feature to be managed gracefully&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Introducing KMP WorkManager
&lt;/h2&gt;

&lt;p&gt;This isn't just a wrapper. It's a &lt;strong&gt;full-fledged task orchestration engine&lt;/strong&gt; built for reliability.&lt;/p&gt;

&lt;p&gt;Here is how you schedule a periodic sync task. Look familiar?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Shared Code (CommonMain)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"data-sync"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;trigger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TaskTrigger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Periodic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intervalMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inWholeMilliseconds&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;workerClassName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"SyncWorker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;constraints&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Constraints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;requiresNetwork&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// On Android, this uses Foreground Service&lt;/span&gt;
        &lt;span class="c1"&gt;// On iOS, this requests BGProcessingTask&lt;/span&gt;
        &lt;span class="n"&gt;isHeavyTask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&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;strong&gt;One API. Both platforms. Zero platform-specific boilerplate in your ViewModels.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Under the Hood: Engineering for Stability
&lt;/h2&gt;

&lt;p&gt;I want to highlight three specific technical decisions that make this library production-ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The O(1) Append-Only Queue
&lt;/h3&gt;

&lt;p&gt;On iOS, file I/O can become a bottleneck when managing persistent queues. In early versions, we saw performance hits when queueing hundreds of tasks because of O(N) serialization.&lt;/p&gt;

&lt;p&gt;In v2.1.0, we rewrote the iOS persistence layer to use an &lt;strong&gt;Append-Only Queue&lt;/strong&gt; architecture with atomic operations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Enqueue&lt;/strong&gt;: O(1) complexity (appending to file).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dequeue&lt;/strong&gt;: O(1) complexity (pointer arithmetic).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Result&lt;/strong&gt;: Benchmarks showed a &lt;strong&gt;13x to 40x performance improvement&lt;/strong&gt; over standard serialization methods. Enqueueing 100 chains dropped from ~1000ms to just &lt;strong&gt;24ms&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Graceful Shutdown &amp;amp; State Restoration
&lt;/h3&gt;

&lt;p&gt;This is my favorite feature. iOS gives background tasks about 30 seconds (or slightly more for processing tasks). If you run out of time, the system kills you.&lt;/p&gt;

&lt;p&gt;We implemented a &lt;strong&gt;Graceful Shutdown&lt;/strong&gt; mechanism. When iOS signals expiration:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The library intercepts the signal.&lt;/li&gt;
&lt;li&gt;It creates a "savepoint" for your current Task Chain.&lt;/li&gt;
&lt;li&gt;It halts execution cleanly.&lt;/li&gt;
&lt;li&gt;Next time the app runs, it &lt;strong&gt;resumes exactly from the last successful step&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;No data loss. No restarted downloads.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Transparent Exact Alarms
&lt;/h3&gt;

&lt;p&gt;Android has &lt;code&gt;AlarmManager&lt;/code&gt; for exact timing. iOS doesn't allow background code execution at exact times (only notifications).&lt;/p&gt;

&lt;p&gt;Instead of faking it or failing silently, the library introduces &lt;strong&gt;&lt;code&gt;ExactAlarmIOSBehavior&lt;/code&gt;&lt;/strong&gt;. You decide explicitly how to handle this platform discrepancy:&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="n"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"morning-reminder"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;trigger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TaskTrigger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Exact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;workerClassName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ReminderWorker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;constraints&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Constraints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="c1"&gt;// Explicitly handle iOS limitations&lt;/span&gt;
        &lt;span class="n"&gt;exactAlarmIOSBehavior&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ExactAlarmIOSBehavior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SHOW_NOTIFICATION&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;ul&gt;
&lt;li&gt;
&lt;strong&gt;SHOW_NOTIFICATION&lt;/strong&gt;: Just show a UI alert (Apple approved).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ATTEMPT_BACKGROUND_RUN&lt;/strong&gt;: Try to run code (Best effort, not guaranteed).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;THROW_ERROR&lt;/strong&gt;: Fail fast during dev to catch logic errors.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  My Commitment to Maintenance
&lt;/h2&gt;

&lt;p&gt;This project is not a weekend hobby. It was born out of frustration in production environments and is built to stay.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tests&lt;/strong&gt;: The library currently has &lt;strong&gt;236+ Unit &amp;amp; Integration Tests&lt;/strong&gt; running on CI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt;: We have extensive docs on migration, best practices, and architecture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Roadmap&lt;/strong&gt;: We are actively working on v2.2.0 (File Coordination Strategy) and v2.3.0 (SQLDelight integration).&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;The library is available on &lt;strong&gt;Maven Central&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dev.brewkits:kmpworkmanager:2.3.0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've included a comprehensive &lt;strong&gt;Demo App&lt;/strong&gt; in the repo that simulates real-world scenarios like Chains, Failures, and Push Notifications so you can see it in action before integrating.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/brewkits/kmpworkmanager" rel="noopener noreferrer"&gt;Check out the GitHub Repository&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;I'd love to hear your feedback. If you are struggling with background tasks in KMP, give this a spin and let me know if it eases your pain!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Cover image credit: Generated with DALL-E, edited by author.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>android</category>
      <category>ios</category>
      <category>kmp</category>
    </item>
  </channel>
</rss>
