<?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: Andrea Sunny</title>
    <description>The latest articles on DEV Community by Andrea Sunny (@andrea-sunny).</description>
    <link>https://dev.to/andrea-sunny</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%2F3289442%2F13e0e545-6caf-4460-9afa-a3959048e549.jpg</url>
      <title>DEV Community: Andrea Sunny</title>
      <link>https://dev.to/andrea-sunny</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andrea-sunny"/>
    <language>en</language>
    <item>
      <title>How To Choose and Implement Response Queues, Caching, and Logging in Alamofire on iOS</title>
      <dc:creator>Andrea Sunny</dc:creator>
      <pubDate>Fri, 12 Jun 2026 05:14:30 +0000</pubDate>
      <link>https://dev.to/andrea-sunny/how-to-choose-and-implement-response-queues-caching-and-logging-in-alamofire-on-ios-c59</link>
      <guid>https://dev.to/andrea-sunny/how-to-choose-and-implement-response-queues-caching-and-logging-in-alamofire-on-ios-c59</guid>
      <description></description>
    </item>
    <item>
      <title>How To Choose and Implement Response Queues, Caching, and Logging in Alamofire on iOS</title>
      <dc:creator>Andrea Sunny</dc:creator>
      <pubDate>Fri, 12 Jun 2026 03:30:00 +0000</pubDate>
      <link>https://dev.to/andrea-sunny/how-to-choose-and-implement-response-queues-caching-and-logging-in-alamofire-on-ios-2e9p</link>
      <guid>https://dev.to/andrea-sunny/how-to-choose-and-implement-response-queues-caching-and-logging-in-alamofire-on-ios-2e9p</guid>
      <description>&lt;p&gt;Efficient network handling can make or break the responsiveness and performance of your iOS app. While learning about Alamofire and URLSession, I discovered some important (and subtle) behaviors around response queues, especially why Alamofire defaults to processing responses on the main thread. Here’s what I learned, with practical code examples and tips for caching, prioritizing, retrying, and logging network requests efficiently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Alamofire's Default Response Queue Matter?
&lt;/h2&gt;

&lt;p&gt;When you use Alamofire for network calls, by default, it delivers completion handlers on the &lt;strong&gt;main thread&lt;/strong&gt;. This design is intentional:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI updates in iOS must happen on the main thread.&lt;/li&gt;
&lt;li&gt;Many developers handle response parsing and UI updates together in the same callback.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, this behavior can lead to subtle UI glitches or dropped frames if your response handler does any heavy work (parsing, decoding, DB writes). Understanding and managing your "response queue" is essential for performance-oriented apps.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Alamofire by default delivers on main thread&lt;/span&gt;
&lt;span class="kt"&gt;AF&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.example.com/data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;responseJSON&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="c1"&gt;// This runs on the main thread! Good for UI work, bad for heavy parsing.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To process on a background thread (off the main queue):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;backgroundQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"com.example.network"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;qos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userInitiated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kt"&gt;AF&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.example.com/data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;backgroundQueue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="c1"&gt;// Heavy parsing or computation here&lt;/span&gt;
        &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Then update UI as needed&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;
  
  
  Comparing with URLSession
&lt;/h2&gt;

&lt;p&gt;By contrast, &lt;code&gt;URLSession&lt;/code&gt; calls its completion handlers on &lt;strong&gt;background threads&lt;/strong&gt; by default. That means you'll usually need a &lt;code&gt;DispatchQueue.main.async&lt;/code&gt; jump for UI updates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"https://api.example.com/data"&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;URLSession&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="c1"&gt;// This block runs on a background thread by default&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Parse data off main&lt;/span&gt;
        &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Update UI&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;task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Caching and Data Persistence: URLSession &amp;amp; Alamofire
&lt;/h2&gt;

&lt;p&gt;Caching reduces network traffic and improves performance. Both URLSession and Alamofire support HTTP caching, but configuration is explicit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;URLSession example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;URLCache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;URLSessionConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlCache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;URLSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// Use 'session' for requests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Alamofire leverages the same NSCache mechanisms by default, since it wraps URLSession.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you want more granular caching, you can configure &lt;code&gt;URLSessionConfiguration&lt;/code&gt; passed to Alamofire's &lt;code&gt;Session&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Request Prioritization in Alamofire
&lt;/h2&gt;

&lt;p&gt;Alamofire 5+ lets you set request priority using the underlying &lt;code&gt;URLSessionTask&lt;/code&gt; priority property. This helps you control which requests the system should focus on if resources get tight (for example, prioritize user-facing data over background image downloads).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.example.com/critical"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;high&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.example.com/background"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Logging Network Activity with Alamofire
&lt;/h2&gt;

&lt;p&gt;Tracking all requests and responses is invaluable for debugging and analytics. Alamofire supports plugins like &lt;code&gt;Network Activity Logger&lt;/code&gt;, which prints full request/response details to the console.&lt;/p&gt;

&lt;p&gt;Add this handy tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;AlamofireNetworkActivityLogger&lt;/span&gt;
&lt;span class="kt"&gt;NetworkActivityLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startLogging&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kt"&gt;NetworkActivityLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;You’ll see all Alamofire calls logged.&lt;/li&gt;
&lt;li&gt;Great for debugging and performance checks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Error Handling and Retry Logic
&lt;/h2&gt;

&lt;p&gt;Transients network errors happen – timeouts, disconnects, rate limits. Alamofire makes retries easy via its &lt;code&gt;RetryPolicy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Example: Retry up to 3 times with delay if receiving 429 (Too Many Requests):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Alamofire&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;CustomRetryPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;RetryPolicy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dueTo&lt;/span&gt; &lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;@escaping&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;RetryResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&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="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="kt"&gt;HTTPURLResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Retry with delay&lt;/span&gt;
            &lt;span class="nf"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retryWithDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;5.0&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;completion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doNotRetry&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="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;interceptor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CustomRetryPolicy&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.example.com/data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Monitoring and Analytics
&lt;/h2&gt;

&lt;p&gt;To gain insights and debug network performance, you can use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NetworkActivityLogger&lt;/strong&gt; (as above)&lt;/li&gt;
&lt;li&gt;System tools (&lt;code&gt;Network Link Conditioner&lt;/code&gt;, Instruments, etc.)&lt;/li&gt;
&lt;li&gt;In-app analytics/logging (for error codes, slow/failed requests)&lt;/li&gt;
&lt;li&gt;Third-party monitors (like Appxiom)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more on real-world monitoring and debugging, the &lt;a href="https://www.appxiom.com/blogs/handling-network-calls-efficiently-in-ios-using-urlsession-and-alamofire-in-swift/" rel="noopener noreferrer"&gt;original blog post&lt;/a&gt; is a practical guide covering end-to-end network handling in Swift, including caching and retries.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Developers Often Get Wrong
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Assuming Alamofire's completion handler is always on a background thread:&lt;/strong&gt; In reality, heavy parsing in Alamofire’s default completion will block the UI!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting &lt;code&gt;DispatchQueue.main.async&lt;/code&gt; in URLSession:&lt;/strong&gt; UI code crashes or doesn’t update if you update UIKit from the background.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not configuring URLCache:&lt;/strong&gt; Missing out on free performance gains from HTTP caching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ignoring request priority:&lt;/strong&gt; Background image/data requests can starve urgent calls if priorities aren’t set correctly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Neglecting network logging:&lt;/strong&gt; Makes debugging timeouts and API errors much harder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overusing retry logic:&lt;/strong&gt; Blindly retrying can amplify server load or lead to bad UX.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Alamofire delivers responses on the main thread by default.&lt;/strong&gt; Use a custom response queue for heavy background work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URLSession delivers on a background thread by default.&lt;/strong&gt; Always call &lt;code&gt;DispatchQueue.main.async&lt;/code&gt; for UI changes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apply caching using URLCache for both URLSession and Alamofire where possible.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prioritize and manage requests&lt;/strong&gt; - assign priorities for best user experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log network traffic&lt;/strong&gt; with tools like NetworkActivityLogger to debug and optimize.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handle errors and retries thoughtfully&lt;/strong&gt; - don’t blindly retry everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor performance and failures&lt;/strong&gt; using analytics or observability tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you learned something new, check out &lt;a href="https://www.appxiom.com/blogs/handling-network-calls-efficiently-in-ios-using-urlsession-and-alamofire-in-swift/" rel="noopener noreferrer"&gt;the original guide&lt;/a&gt; for deeper technical explanations!&lt;/p&gt;

</description>
      <category>swift</category>
      <category>ios</category>
      <category>alamofire</category>
      <category>networking</category>
    </item>
    <item>
      <title>How To Choose and Implement Response Queues, Caching, and Logging in Alamofire on iOS</title>
      <dc:creator>Andrea Sunny</dc:creator>
      <pubDate>Fri, 12 Jun 2026 03:30:00 +0000</pubDate>
      <link>https://dev.to/andrea-sunny/how-to-choose-and-implement-response-queues-caching-and-logging-in-alamofire-on-ios-292l</link>
      <guid>https://dev.to/andrea-sunny/how-to-choose-and-implement-response-queues-caching-and-logging-in-alamofire-on-ios-292l</guid>
      <description>&lt;p&gt;Efficient network handling can make or break the responsiveness and performance of your iOS app. While learning about Alamofire and URLSession, I discovered some important (and subtle) behaviors around response queues, especially why Alamofire defaults to processing responses on the main thread. Here’s what I learned, with practical code examples and tips for caching, prioritizing, retrying, and logging network requests efficiently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Alamofire's Default Response Queue Matter?
&lt;/h2&gt;

&lt;p&gt;When you use Alamofire for network calls, by default, it delivers completion handlers on the &lt;strong&gt;main thread&lt;/strong&gt;. This design is intentional:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI updates in iOS must happen on the main thread.&lt;/li&gt;
&lt;li&gt;Many developers handle response parsing and UI updates together in the same callback.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, this behavior can lead to subtle UI glitches or dropped frames if your response handler does any heavy work (parsing, decoding, DB writes). Understanding and managing your "response queue" is essential for performance-oriented apps.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Alamofire by default delivers on main thread&lt;/span&gt;
&lt;span class="kt"&gt;AF&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.example.com/data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;responseJSON&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="c1"&gt;// This runs on the main thread! Good for UI work, bad for heavy parsing.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To process on a background thread (off the main queue):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;backgroundQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"com.example.network"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;qos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userInitiated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kt"&gt;AF&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.example.com/data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;backgroundQueue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="c1"&gt;// Heavy parsing or computation here&lt;/span&gt;
        &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Then update UI as needed&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;
  
  
  Comparing with URLSession
&lt;/h2&gt;

&lt;p&gt;By contrast, &lt;code&gt;URLSession&lt;/code&gt; calls its completion handlers on &lt;strong&gt;background threads&lt;/strong&gt; by default. That means you'll usually need a &lt;code&gt;DispatchQueue.main.async&lt;/code&gt; jump for UI updates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"https://api.example.com/data"&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;URLSession&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="c1"&gt;// This block runs on a background thread by default&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Parse data off main&lt;/span&gt;
        &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Update UI&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;task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Caching and Data Persistence: URLSession &amp;amp; Alamofire
&lt;/h2&gt;

&lt;p&gt;Caching reduces network traffic and improves performance. Both URLSession and Alamofire support HTTP caching, but configuration is explicit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;URLSession example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;URLCache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;URLSessionConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlCache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;URLSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// Use 'session' for requests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Alamofire leverages the same NSCache mechanisms by default, since it wraps URLSession.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you want more granular caching, you can configure &lt;code&gt;URLSessionConfiguration&lt;/code&gt; passed to Alamofire's &lt;code&gt;Session&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Request Prioritization in Alamofire
&lt;/h2&gt;

&lt;p&gt;Alamofire 5+ lets you set request priority using the underlying &lt;code&gt;URLSessionTask&lt;/code&gt; priority property. This helps you control which requests the system should focus on if resources get tight (for example, prioritize user-facing data over background image downloads).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.example.com/critical"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;high&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.example.com/background"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Logging Network Activity with Alamofire
&lt;/h2&gt;

&lt;p&gt;Tracking all requests and responses is invaluable for debugging and analytics. Alamofire supports plugins like &lt;code&gt;Network Activity Logger&lt;/code&gt;, which prints full request/response details to the console.&lt;/p&gt;

&lt;p&gt;Add this handy tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;AlamofireNetworkActivityLogger&lt;/span&gt;
&lt;span class="kt"&gt;NetworkActivityLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startLogging&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kt"&gt;NetworkActivityLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;You’ll see all Alamofire calls logged.&lt;/li&gt;
&lt;li&gt;Great for debugging and performance checks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Error Handling and Retry Logic
&lt;/h2&gt;

&lt;p&gt;Transients network errors happen – timeouts, disconnects, rate limits. Alamofire makes retries easy via its &lt;code&gt;RetryPolicy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Example: Retry up to 3 times with delay if receiving 429 (Too Many Requests):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Alamofire&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;CustomRetryPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;RetryPolicy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dueTo&lt;/span&gt; &lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;@escaping&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;RetryResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&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="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="kt"&gt;HTTPURLResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Retry with delay&lt;/span&gt;
            &lt;span class="nf"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retryWithDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;5.0&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;completion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doNotRetry&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="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;interceptor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CustomRetryPolicy&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.example.com/data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Monitoring and Analytics
&lt;/h2&gt;

&lt;p&gt;To gain insights and debug network performance, you can use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NetworkActivityLogger&lt;/strong&gt; (as above)&lt;/li&gt;
&lt;li&gt;System tools (&lt;code&gt;Network Link Conditioner&lt;/code&gt;, Instruments, etc.)&lt;/li&gt;
&lt;li&gt;In-app analytics/logging (for error codes, slow/failed requests)&lt;/li&gt;
&lt;li&gt;Third-party monitors (like Appxiom)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more on real-world monitoring and debugging, the &lt;a href="https://www.appxiom.com/blogs/handling-network-calls-efficiently-in-ios-using-urlsession-and-alamofire-in-swift/" rel="noopener noreferrer"&gt;original blog post&lt;/a&gt; is a practical guide covering end-to-end network handling in Swift, including caching and retries.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Developers Often Get Wrong
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Assuming Alamofire's completion handler is always on a background thread:&lt;/strong&gt; In reality, heavy parsing in Alamofire’s default completion will block the UI!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting &lt;code&gt;DispatchQueue.main.async&lt;/code&gt; in URLSession:&lt;/strong&gt; UI code crashes or doesn’t update if you update UIKit from the background.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not configuring URLCache:&lt;/strong&gt; Missing out on free performance gains from HTTP caching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ignoring request priority:&lt;/strong&gt; Background image/data requests can starve urgent calls if priorities aren’t set correctly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Neglecting network logging:&lt;/strong&gt; Makes debugging timeouts and API errors much harder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overusing retry logic:&lt;/strong&gt; Blindly retrying can amplify server load or lead to bad UX.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Alamofire delivers responses on the main thread by default.&lt;/strong&gt; Use a custom response queue for heavy background work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URLSession delivers on a background thread by default.&lt;/strong&gt; Always call &lt;code&gt;DispatchQueue.main.async&lt;/code&gt; for UI changes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apply caching using URLCache for both URLSession and Alamofire where possible.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prioritize and manage requests&lt;/strong&gt; - assign priorities for best user experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log network traffic&lt;/strong&gt; with tools like NetworkActivityLogger to debug and optimize.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handle errors and retries thoughtfully&lt;/strong&gt; - don’t blindly retry everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor performance and failures&lt;/strong&gt; using analytics or observability tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you learned something new, check out &lt;a href="https://www.appxiom.com/blogs/handling-network-calls-efficiently-in-ios-using-urlsession-and-alamofire-in-swift/" rel="noopener noreferrer"&gt;the original guide&lt;/a&gt; for deeper technical explanations!&lt;/p&gt;

</description>
      <category>swift</category>
      <category>ios</category>
      <category>alamofire</category>
      <category>networking</category>
    </item>
    <item>
      <title>How to Prevent Dropped Frames in SwiftUI with DispatchQueue</title>
      <dc:creator>Andrea Sunny</dc:creator>
      <pubDate>Tue, 09 Jun 2026 03:30:00 +0000</pubDate>
      <link>https://dev.to/andrea-sunny/how-to-prevent-dropped-frames-in-swiftui-with-dispatchqueue-3po6</link>
      <guid>https://dev.to/andrea-sunny/how-to-prevent-dropped-frames-in-swiftui-with-dispatchqueue-3po6</guid>
      <description>&lt;p&gt;If you’ve ever noticed your SwiftUI app stuttering, lagging animations, or random UI freezes, it may not just be heavy rendering. Sometimes, “frames get dropped at the source”: your app can’t even deliver frames for rendering because the main thread is overloaded. Here’s what that means, why it matters, and how practical use of &lt;code&gt;DispatchQueue&lt;/code&gt; can keep your app smooth and responsive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Problem Matters
&lt;/h2&gt;

&lt;p&gt;When SwiftUI apps drop frames, it’s not just an aesthetic issue. Choppy interactions and frozen screens frustrate users and hurt your ratings. For developers, fixing frame drops unlocks smoother animations and a more responsive feel - the difference between a good and a bad user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does “Frames Get Dropped at the Source” Actually Mean?
&lt;/h2&gt;

&lt;p&gt;In iOS, SwiftUI (and UIKit) tries to deliver frame updates at a constant 60 FPS. But if your code blocks the main thread with heavy tasks (data parsing, sync, image processing, etc.), new frames can’t be generated on time. When that happens, the UI won’t even get a chance to update - frames are missed before even reaching the render pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In short: If your main thread is busy, the UI can’t keep up.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A Simple Breakdown
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Main Thread&lt;/strong&gt;: Handles UI updates and event delivery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heavy Work (Computation/Networking/File IO)&lt;/strong&gt;: If done on main, it blocks everything else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result&lt;/strong&gt;: UI stops updating, animations stop, and frames are dropped &lt;em&gt;at the source&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Practical Example: Offloading with &lt;code&gt;DispatchQueue&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Let’s say you’re downloading and transforming data. Instead of blocking the main thread, run expensive tasks on a background queue, and update the UI on the main queue when ready.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;global&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;qos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userInitiated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Heavy data processing or network fetch&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;intenseCalculation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Safely update UI on the main thread&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern lets SwiftUI keep rendering uninterrupted - no dropped frames due to blocking operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where You’ll Encounter This in Real Projects
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Making slow network requests on the main thread&lt;/li&gt;
&lt;li&gt;Loading or decoding big images directly in a view&lt;/li&gt;
&lt;li&gt;Parsing large JSON files right as your view appears&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these can cause the main thread to stall, leading to dropped frames and a sluggish UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Misconceptions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;“SwiftUI is slow.”&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Often, it’s not SwiftUI’s fault: frame drops are usually due to how you schedule work, not the framework itself.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;“Using Combine or GCD is advanced.”&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;You don’t need deep reactive skills to offload tasks. &lt;code&gt;DispatchQueue&lt;/code&gt; is approachable for most use cases.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;“Small computations can’t hurt - just keep them on main.”&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Multiple “small” tasks can add up and choke the main thread, especially in data-heavy UIs.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementation: Efficient View Updates and Async Work
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;.onAppear {}&lt;/code&gt; to trigger background fetches only when needed.&lt;/li&gt;
&lt;li&gt;Always use background queues for heavy work (with &lt;code&gt;DispatchQueue.global()&lt;/code&gt; or tasks in Combine).&lt;/li&gt;
&lt;li&gt;Make sure your UI updates (like state variables or view model values) only happen back on the main queue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example using Combine for background work:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Combine&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;DataViewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ObservableObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@Published&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;cancellables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;AnyCancellable&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;URLSession&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataTaskPublisher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"https://yourapi.com/data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;decoder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;JSONDecoder&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;receiveCompletion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nv"&gt;receiveValue&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;weak&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cancellables&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;
  
  
  What Developers Often Get Wrong
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Doing heavy computation in SwiftUI view bodies.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Updating the UI from background threads&lt;/strong&gt; (causes glitches or crashes).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assuming Combine magically makes things async&lt;/strong&gt; – you still have to pay attention to which queue work runs on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not profiling on real devices&lt;/strong&gt; – performance issues are often less visible on simulators.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Neglecting image scaling or async loading&lt;/strong&gt; - loading huge images inline can freeze your app.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Looking for a Deeper Dive?
&lt;/h2&gt;

&lt;p&gt;I found &lt;a href="https://www.appxiom.com/blogs/solving-frame-rate-issues-and-app-hangs-in-swiftui-ios-apps/" rel="noopener noreferrer"&gt;this guide&lt;/a&gt; super helpful for understanding frame rate drops and app hangs at a technical level. If you’re interested in more examples, Xcode profiling tips, or background threading strategies, it’s a resource worth checking out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;If your app drops frames, it’s often because the main thread is overloaded.&lt;/li&gt;
&lt;li&gt;Offload expensive work (networking, computation, decoding) to a background queue using &lt;code&gt;DispatchQueue&lt;/code&gt; or Combine.&lt;/li&gt;
&lt;li&gt;Always update your UI back on the main thread to avoid glitches.&lt;/li&gt;
&lt;li&gt;Use tools like Instruments and real-device profiling for detecting bottlenecks.&lt;/li&gt;
&lt;li&gt;Lazy image loading (&lt;code&gt;AsyncImage&lt;/code&gt;) and efficient view hierarchies keep rendering fast and memory usage low.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Curious about what makes apps succeed (or fail). Sharing lessons from real-world performance stories.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>swiftui</category>
      <category>ios</category>
      <category>performance</category>
      <category>dispatchqueue</category>
    </item>
    <item>
      <title>Customizing SwiftUI Charts: Axis Lines, Styles, and Real-World Examples for iOS 16</title>
      <dc:creator>Andrea Sunny</dc:creator>
      <pubDate>Sat, 06 Jun 2026 04:45:00 +0000</pubDate>
      <link>https://dev.to/andrea-sunny/customizing-swiftui-charts-axis-lines-styles-and-real-world-examples-for-ios-16-i93</link>
      <guid>https://dev.to/andrea-sunny/customizing-swiftui-charts-axis-lines-styles-and-real-world-examples-for-ios-16-i93</guid>
      <description>&lt;p&gt;If you’ve ever needed to turn app data into something users can quickly understand - with no messy setup or external dependencies - Apple’s Charts framework for SwiftUI (iOS 16+) is a game changer. Whether you’re tracking user stats, showing growth, or summarizing key metrics, visually rich charts are now a native part of your iOS app’s UI toolkit.&lt;/p&gt;

&lt;p&gt;Below, I’ll break down what I learned about SwiftUI Charts after experimenting with real code and reading some excellent resources (linked below), focusing on practical usage and how you can customize things like axis lines and marks. You’ll be able to get useful visuals running fast - without leaning on third-party chart libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use SwiftUI Charts?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No extra dependencies:&lt;/strong&gt; Charts is built right into the SwiftUI ecosystem (iOS 16+).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modern, smooth UI:&lt;/strong&gt; Native SwiftUI look and feel, automatic light/dark styling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quick to implement:&lt;/strong&gt; Declarative syntax, highly customizable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First-class axes and marks:&lt;/strong&gt; Control axis lines, labels, gridlines, and bar/line/sector mark styles directly in code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short: For most in-app data viz needs, this removes a ton of friction compared to older UIKit-based (or external) solutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started: Import and Setup
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Make sure your project targets &lt;strong&gt;iOS 16+&lt;/strong&gt; and uses SwiftUI (Xcode 14+ required).&lt;/li&gt;
&lt;li&gt;Wherever you want to use charts, import the framework:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Charts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it - no SPM/CocoaPods, just native Swift.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 1: Building a Simple Bar Chart
&lt;/h2&gt;

&lt;p&gt;Bar charts are the best starting point for comparing categories (like monthly sales or feature adoption). You specify data points using &lt;code&gt;BarMark&lt;/code&gt; and SwiftUI handles the layout.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;BarChartView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;Chart&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;BarMark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Month"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Jan"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sales"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="kt"&gt;BarMark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Month"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Feb"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sales"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="kt"&gt;BarMark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Month"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Mar"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sales"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;45&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="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&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;ul&gt;
&lt;li&gt;
&lt;code&gt;Chart&lt;/code&gt; is the container for your data.&lt;/li&gt;
&lt;li&gt;Each &lt;code&gt;BarMark&lt;/code&gt; represents a bar (with x and y values).&lt;/li&gt;
&lt;li&gt;Layout, scaling, and axis rendering are handled automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can swap in real data from arrays, databases, or APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 2: Line Charts for Trends
&lt;/h2&gt;

&lt;p&gt;For visualizing time series or progress data, use &lt;code&gt;LineMark&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;TrendLineChart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="nv"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Mon"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Tue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;135&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Wed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;Chart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;id&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="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="kt"&gt;LineMark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Day"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Value"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&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="nf"&gt;foregroundStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lineStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;StrokeStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;lineWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

            &lt;span class="kt"&gt;PointMark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Day"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Value"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&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="nf"&gt;foregroundStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blue&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="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&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;ul&gt;
&lt;li&gt;Add both &lt;code&gt;LineMark&lt;/code&gt; (for the trend) and &lt;code&gt;PointMark&lt;/code&gt; (for data dots)&lt;/li&gt;
&lt;li&gt;Useful for performance, growth, or analytics dashboards&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example 3: Pie Chart (with SectorMark)
&lt;/h2&gt;

&lt;p&gt;Pie charts aren’t always the best for exact values, but are great for showing proportions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;PopulationPieChart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;Country&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;population&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="kt"&gt;Country&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"India"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;population&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1428&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="kt"&gt;Country&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"China"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;population&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1412&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="kt"&gt;Country&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"USA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;population&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;339&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;Chart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;id&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="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="kt"&gt;SectorMark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Population"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;population&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Country"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&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="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&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;
  
  
  Example 4: Scatter Plot
&lt;/h2&gt;

&lt;p&gt;Ideal for finding correlations or outliers between two continuous variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ScatterPlot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;DataPoint&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;DataPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kt"&gt;DataPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;93&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;Chart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;id&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="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;dp&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="kt"&gt;PointMark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Study Hours"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Score"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&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="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&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;
  
  
  Customizing Axes and Visual Style
&lt;/h2&gt;

&lt;p&gt;The real power of SwiftUI charts comes from customizing axes and marks to fit your app design. This is where the keywords like &lt;code&gt;swiftui charts axisline&lt;/code&gt; enter the picture. Let’s look at a bar chart with axis tweaks and better colors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;Chart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;id&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="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="kt"&gt;BarMark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Month"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sales"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&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="nf"&gt;foregroundStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;red&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clipShape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;RoundedRectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// rounded bars&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;annotation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;overlay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="kt"&gt;Rectangle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stroke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;black&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;lineWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chartXAxis&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;AxisMarks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kt"&gt;AxisGridLine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="kt"&gt;AxisValueLabel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;size&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="p"&gt;}&lt;/span&gt;
    &lt;span class="kt"&gt;AxisLine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Draws the axis line explicitly&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chartYAxis&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;AxisMarks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kt"&gt;AxisGridLine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="kt"&gt;AxisValueLabel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;size&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="p"&gt;}&lt;/span&gt;
    &lt;span class="kt"&gt;AxisLine&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="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Customizations shown above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bar color &amp;amp; shape&lt;/strong&gt;: &lt;code&gt;foregroundStyle&lt;/code&gt; and &lt;code&gt;clipShape&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bar outlines&lt;/strong&gt;: Add a black stroke for clarity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Axis labels/font&lt;/strong&gt;: Change with &lt;code&gt;.chartXAxis&lt;/code&gt;/&lt;code&gt;.chartYAxis&lt;/code&gt; and &lt;code&gt;AxisValueLabel()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Axis lines&lt;/strong&gt;: Drawn explicitly with &lt;code&gt;AxisLine()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grid lines&lt;/strong&gt;: Shown by &lt;code&gt;AxisGridLine()&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where You'll Use These Patterns
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dashboards&lt;/strong&gt;: Business analytics, admin panels&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fitness/Activity apps&lt;/strong&gt;: Progress, goals&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Personal finance/budgeting tools&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learning/tracking apps&lt;/strong&gt;: Anything that summarizes change over time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need to summarize data quickly, this API is hard to beat for speed and cleanliness.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Developers Often Get Wrong
&lt;/h2&gt;

&lt;p&gt;Even though SwiftUI charts are straightforward, a few practical mistakes crop up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting to set a frame&lt;/strong&gt;: The chart won’t show up unless you use &lt;code&gt;.frame(height: ...)&lt;/code&gt;!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mismatching data IDs:&lt;/strong&gt; Always provide a unique &lt;code&gt;id:&lt;/code&gt; if your data isn’t simple - especially for animated or dynamic charts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ignoring data type mismatches:&lt;/strong&gt; X-axis and Y-axis values must match expected types. Check string vs. numeric mismatches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skipping accessibility/labels:&lt;/strong&gt; Charts can be unreadable for assistive tech if you skip &lt;code&gt;annotation&lt;/code&gt; or neglect labels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Over-customizing early:&lt;/strong&gt; Resist the urge to over-stylize before your base chart works - debug raw visuals first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Axis line confusion:&lt;/strong&gt; If you want axis lines visible, you need to declare them directly (&lt;code&gt;AxisLine()&lt;/code&gt;). By default, they may not render as expected.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SwiftUI Charts (iOS 16+) makes native data viz fast and modern&lt;/strong&gt; - great for most in-app charts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Import the Charts library and get started immediately - no setup overhead.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can build bar, line, pie, and scatter charts easily&lt;/strong&gt;, and style them using colorful, branded, or accessible themes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Axis customization&lt;/strong&gt; (lines, grids, label styles) brings polish - use features like &lt;code&gt;AxisLine()&lt;/code&gt;, &lt;code&gt;AxisValueLabel()&lt;/code&gt;, and &lt;code&gt;.chartXAxis&lt;/code&gt;/&lt;code&gt;.chartYAxis&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid common mistakes&lt;/strong&gt; by always specifying chart frame, unique data IDs, and type-matching axis values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For deeper detail or more examples:&lt;/strong&gt; &lt;a href="https://www.appxiom.com/blogs/integrating-charts-in-swiftui-apps/" rel="noopener noreferrer"&gt;the Appxiom guide&lt;/a&gt; is a great reference that helped clarify the API for me.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;You can now visualize insights natively, with clarity, and without third-party cruft.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Happy charting!&lt;/p&gt;

</description>
      <category>swiftui</category>
      <category>ios</category>
      <category>charts</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How to Actually Test Jetpack Compose UIs with Espresso (Without Losing Your Mind)</title>
      <dc:creator>Andrea Sunny</dc:creator>
      <pubDate>Thu, 04 Jun 2026 02:30:00 +0000</pubDate>
      <link>https://dev.to/andrea-sunny/how-to-actually-test-jetpack-compose-uis-with-espresso-without-losing-your-mind-322m</link>
      <guid>https://dev.to/andrea-sunny/how-to-actually-test-jetpack-compose-uis-with-espresso-without-losing-your-mind-322m</guid>
      <description>&lt;p&gt;If you’ve just moved to Jetpack Compose for your Android UI (like me!) but still want reliable UI testing, you might wonder: &lt;strong&gt;Do I use Espresso? Compose Test APIs? Both? How do they work together?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After digging into Compose UI testing and running into a few walls, here’s what actually worked - and the practical ways to catch sneaky UI bugs before your users do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Compose UI Testing Matters (and How Espresso Fits In)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UI bugs often hide in plain sight.&lt;/strong&gt; Maybe the animation works for you but breaks on another device. Maybe that new Compose screen looks fine - until a user can’t tap a button.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated UI tests&lt;/strong&gt; help you catch these issues &lt;em&gt;before&lt;/em&gt; they hit production.&lt;/li&gt;
&lt;li&gt;With Jetpack Compose, &lt;strong&gt;Espresso isn’t replaced - it’s complemented.&lt;/strong&gt; Compose brings its own testing APIs. But you can (and often should) use both.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s walk through the setup, how it fits together, and real code examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Compose UI Testing (with Espresso) Actually Works
&lt;/h2&gt;

&lt;p&gt;Testing Compose UIs is a bit different than traditional Android Views, but the basics are still:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up your test class and environment&lt;/li&gt;
&lt;li&gt;Render your Compose UI in a controlled test&lt;/li&gt;
&lt;li&gt;Use Espresso/Compose test APIs to interact and make assertions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setup: What You Need
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Android project using Jetpack Compose&lt;/li&gt;
&lt;li&gt;UI tests enabled (&lt;code&gt;androidTest&lt;/code&gt; directory)&lt;/li&gt;
&lt;li&gt;Dependencies:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;  &lt;span class="n"&gt;androidTestImplementation&lt;/span&gt; &lt;span class="s1"&gt;'androidx.test.espresso:espresso-core:&amp;lt;latest&amp;gt;'&lt;/span&gt;
  &lt;span class="n"&gt;androidTestImplementation&lt;/span&gt; &lt;span class="s1"&gt;'androidx.test.ext:junit:&amp;lt;latest&amp;gt;'&lt;/span&gt;
  &lt;span class="n"&gt;androidTestImplementation&lt;/span&gt; &lt;span class="s1"&gt;'androidx.compose.ui:ui-test-junit4:&amp;lt;compose version&amp;gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. Create Your UI Test Class
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create a new file in &lt;code&gt;androidTest&lt;/code&gt;, e.g., &lt;code&gt;ExampleEspressoTest.kt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;This is where your UI test code lives&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Import Required Testing Tools
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;androidx.compose.ui.test.*&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;androidx.compose.ui.test.junit4.createComposeRule&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;androidx.test.espresso.Espresso.onView&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;androidx.test.espresso.matcher.ViewMatchers.*&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;androidx.test.espresso.action.ViewActions.click&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.Rule&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.Test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Set Up the Compose Test Rule
&lt;/h3&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;ExampleEspressoTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;Rule&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;composeTestRule&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createComposeRule&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&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;The rule launches your Compose UI in a test harness&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Write a Test: Compose + Espresso in Action
&lt;/h3&gt;

&lt;p&gt;Let’s write a minimal test that ensures a button is visible and clicks it:&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="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;testButtonVisibilityAndClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;composeTestRule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setContent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* no-op */&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Click Me"&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;// Check if button text exists with Espresso&lt;/span&gt;
    &lt;span class="nf"&gt;onView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;withText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Click Me"&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="nf"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isDisplayed&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
    &lt;span class="c1"&gt;// Click it as a user would&lt;/span&gt;
    &lt;span class="nf"&gt;onView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;withText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Click Me"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;click&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;code&gt;setContent { ... }&lt;/code&gt; renders your Compose UI just for the test&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onView(withText())&lt;/code&gt; lets Espresso find the button by text&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isDisplayed()&lt;/code&gt; asserts it’s there&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;perform(click())&lt;/code&gt; simulates a real tap&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Prefer Compose Matchers for Compose UIs (When Possible)
&lt;/h4&gt;

&lt;p&gt;You can interact directly with Compose:&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;composeTestRule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;hasText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Click Me"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;assertIsDisplayed&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;performClick&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;More reliable for complex UIs&lt;/li&gt;
&lt;li&gt;Prefer &lt;code&gt;onNode&lt;/code&gt;/Compose matchers unless you really need Espresso integration (e.g., hybrid View + Compose screens)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common Compose UI Test Patterns
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Matchers:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;withText("text")&lt;/code&gt; (Espresso) | &lt;code&gt;hasText("text")&lt;/code&gt; (Compose)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isDisplayed()&lt;/code&gt; (with both APIs)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Actions:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;click()&lt;/code&gt; (Espresso/Compose)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Assertions:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.check(matches(isDisplayed()))&lt;/code&gt; (Espresso)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.assertIsDisplayed()&lt;/code&gt; (Compose)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Your test code should read like: &lt;em&gt;Find this element, make sure I see it, then click - just like a user would.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where You’ll Run Into Compose UI Testing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Migrating legacy UI tests:&lt;/strong&gt; If you’re replacing classic Views with Compose, you’ll need to rethink your test logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid screens:&lt;/strong&gt; Sometimes you have both Views and Compose in the same Activity. Both Espresso and Compose test APIs work together!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom UI components:&lt;/strong&gt; Use these patterns to reliably assert and interact with custom composables.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Developers Often Get Wrong
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mixing up Espresso and Compose APIs:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Don’t use &lt;code&gt;onView()&lt;/code&gt; to interact with Compose-only components unless they’re displayed in hybrid layouts.&lt;/li&gt;
&lt;li&gt;For pure Compose UIs, prefer &lt;code&gt;composeTestRule.onNode(...)&lt;/code&gt; and related APIs.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Missing Test Rules:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Forgetting &lt;code&gt;@get:Rule val composeTestRule = createComposeRule()&lt;/code&gt; means your Compose tests won’t run properly.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Relying on UI details:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Targeting implementation details (like hardcoded text) makes tests break easily. Prefer unique &lt;code&gt;testTag&lt;/code&gt;s or stable attributes.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Ignoring synchronization:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Compose and Espresso each handle UI thread sync differently; stick with one approach per test to avoid flaky results.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Useful Learning Resource
&lt;/h2&gt;

&lt;p&gt;If you want a deeper technical guide - including more code snippets and explanations that clarified a lot for me - check out the original blog post I used as a reference: &lt;a href="https://www.appxiom.com/blogs/testing-jetpack-compose-based-android-ui-using-espresso/" rel="noopener noreferrer"&gt;Testing Jetpack Compose Based Android UI Using Espresso&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Compose UI testing is practical with the right setup - combine &lt;code&gt;composeTestRule&lt;/code&gt; and Compose matchers for clarity.&lt;/li&gt;
&lt;li&gt;Use Espresso only when mixing Compose and traditional Views, or if you need legacy integrations.&lt;/li&gt;
&lt;li&gt;Make your tests read like user stories: &lt;em&gt;find&lt;/em&gt;, &lt;em&gt;check&lt;/em&gt;, &lt;em&gt;act&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Don’t rely on fragile attributes - use &lt;code&gt;testTag&lt;/code&gt; for robust selectors.&lt;/li&gt;
&lt;li&gt;Learning resources (like the &lt;a href="https://www.appxiom.com/blogs/testing-jetpack-compose-based-android-ui-using-espresso/" rel="noopener noreferrer"&gt;Appxiom blog&lt;/a&gt;) can save you hours of confusion.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Curious about why some UI tests fail on real devices or how to avoid flaky Compose UI tests? Share your experiences or questions in the comments!&lt;/p&gt;

</description>
      <category>android</category>
      <category>jetpackcompose</category>
      <category>uittesting</category>
      <category>espresso</category>
    </item>
    <item>
      <title>How to Build Efficient Real-Time Location Features in Flutter</title>
      <dc:creator>Andrea Sunny</dc:creator>
      <pubDate>Sun, 31 May 2026 04:00:00 +0000</pubDate>
      <link>https://dev.to/andrea-sunny/how-to-build-efficient-real-time-location-features-in-flutter-4gcf</link>
      <guid>https://dev.to/andrea-sunny/how-to-build-efficient-real-time-location-features-in-flutter-4gcf</guid>
      <description>&lt;p&gt;Whether you’re building a delivery app, a fitness tracker, or just curious about how location works under the hood, chances are you’ll run into the need for reliable and efficient location tracking in Flutter at some point. I recently dug deep into real-world implementations (and mishaps) with Flutter’s location ecosystem, so I thought I’d share what helped me finally make sense of the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Location Flutter Is Tricky (and Worth Mastering)
&lt;/h2&gt;

&lt;p&gt;Location features in Flutter can seem straightforward—just grab the user’s latitude and longitude, right? But in practice, there’s a lot that can go wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apps crashing because of permission issues (especially with &lt;code&gt;geolocator&lt;/code&gt; and &lt;code&gt;permission_handler&lt;/code&gt; together).&lt;/li&gt;
&lt;li&gt;GPS draining the battery by requesting more updates than needed.&lt;/li&gt;
&lt;li&gt;Weird results running code on platforms like FlutLab, where GPS isn’t always supported.&lt;/li&gt;
&lt;li&gt;Handling accuracy, speed, altitude, and heading, all while users move unpredictably.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A solid understanding of location services can help you avoid a lot of headaches—and deliver smoother, faster apps that respect user privacy and device resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking the Right Package: &lt;code&gt;geolocator&lt;/code&gt; vs. &lt;code&gt;location&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Flutter offers a couple of battle-tested packages for GPS and location:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;geolocator&lt;/strong&gt;: My personal favorite for flexibility. Offers real-time streams, control over accuracy (&lt;code&gt;LocationAccuracy.best&lt;/code&gt;), and lots of configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;location&lt;/strong&gt;: Great for simple use cases. If you just need to grab the current position once and display it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For tracking, like fitness or delivery, &lt;strong&gt;&lt;code&gt;geolocator&lt;/code&gt;&lt;/strong&gt; shines because you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Subscribe to position changes using &lt;code&gt;getPositionStream()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Tune frequency and precision to balance battery usage and accuracy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example: Streaming Location Data
&lt;/h3&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;'package:geolocator/geolocator.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;'dart:async'&lt;/span&gt;&lt;span class="o"&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;Position&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;positionStream&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;startTracking&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;positionStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Geolocator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPositionStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;locationSettings:&lt;/span&gt; &lt;span class="n"&gt;LocationSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;accuracy:&lt;/span&gt; &lt;span class="n"&gt;LocationAccuracy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;best&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// or your preferred accuracy&lt;/span&gt;
      &lt;span class="nl"&gt;distanceFilter:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;               &lt;span class="c1"&gt;// Only update if user moves 20m&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="na"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Now you have latitude, longitude, speed, altitude, and heading&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&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="nf"&gt;stopTracking&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;positionStream&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  FlutLab Note
&lt;/h4&gt;

&lt;p&gt;If you’re testing on FlutLab, true GPS may not be available. This can lead to misleading results. Check the docs or use device emulators with GPS support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mastering Permissions: No More &lt;code&gt;geolocator&lt;/code&gt; &amp;amp; &lt;code&gt;permission_handler&lt;/code&gt; Crashes
&lt;/h2&gt;

&lt;p&gt;Most issues I found were related to improper permission handling—even causing crashes! In Flutter, location is sensitive and both Android and iOS platforms demand a clear flow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ask users with context:&lt;/strong&gt; Don’t slam a permission dialog at launch. Show a rationale first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handle denial:&lt;/strong&gt; Guide users to device settings if needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always check:&lt;/strong&gt; Users can revoke permissions at any time.
&lt;/li&gt;
&lt;/ul&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;'package:permission_handler/permission_handler.dart'&lt;/span&gt;&lt;span class="o"&gt;;&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;ensureLocationPermission&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;status&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;Permission&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;request&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;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isGranted&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;
  
  
  Pitfall:
&lt;/h3&gt;

&lt;p&gt;Mixing &lt;code&gt;geolocator&lt;/code&gt; and &lt;code&gt;permission_handler&lt;/code&gt; can create double-requests or unexpected behavior. Use just one for permission requests and check documentation for updates—these packages evolve fast (the latest geolocator flutter version for 2026 may have changes).&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoiding Battery Drain: Accuracy &amp;amp; Update Frequency
&lt;/h2&gt;

&lt;p&gt;Real-time tracking sounds great until user reviews complain about dead batteries! Here’s what helped me optimize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adjust &lt;strong&gt;accuracy&lt;/strong&gt; (&lt;code&gt;LocationAccuracy.best&lt;/code&gt;, &lt;code&gt;LocationAccuracy.high&lt;/code&gt;, etc.). Most apps don’t need meter precision.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;distance filters&lt;/strong&gt;: Only update after significant movement.&lt;/li&gt;
&lt;li&gt;Start streams only when needed (e.g., tracking screen).&lt;/li&gt;
&lt;li&gt;Stop streams as soon as tracking isn’t needed.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Geolocator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPositionStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;locationSettings:&lt;/span&gt; &lt;span class="n"&gt;LocationSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;accuracy:&lt;/span&gt; &lt;span class="n"&gt;LocationAccuracy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;medium&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;distanceFilter:&lt;/span&gt; &lt;span class="mi"&gt;50&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;
  
  
  Decoding Addresses and Rate Limiting: Geocoding in Flutter
&lt;/h2&gt;

&lt;p&gt;Turning coordinates into human-friendly addresses? Use the &lt;code&gt;flutter geocoding&lt;/code&gt; package, but be careful: it can hit API rate limits fast if overused.&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="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:geocoding/geocoding.dart'&lt;/span&gt;&lt;span class="o"&gt;;&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;getAddressFromCoords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;lng&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="k"&gt;try&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;placemarks&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;placemarkFromCoordinates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lng&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="n"&gt;placemarks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;first&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;street&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Handle geocoding errors (like limit exceeded)&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;
  
  
  Caching &amp;amp; Handling Flaky Location Data
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cache recent positions&lt;/strong&gt; to avoid repeat lookups and improve speed on re-entry.&lt;/li&gt;
&lt;li&gt;Store a timestamp with cached locations to know when to refresh.&lt;/li&gt;
&lt;li&gt;Only send updates to the backend if position changes significantly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Error Handling: Don’t Leave Users Hanging
&lt;/h2&gt;

&lt;p&gt;Sometimes, GPS or permissions fail silently—users just see a blank map. Avoid this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always check if location services are enabled before subscribing.&lt;/li&gt;
&lt;li&gt;If permissions are denied or location is disabled, display a clear, helpful message.&lt;/li&gt;
&lt;li&gt;For example: "Location is off. Please enable it in your device settings."&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Common Developer Misconceptions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Assuming permissions are always granted:&lt;/strong&gt; Users can change their mind or OS settings can revoke access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polling current location is better than streams:&lt;/strong&gt; Streams (with the right settings) are more battery friendly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High accuracy is always needed:&lt;/strong&gt; Lower accuracy modes can be accurate enough for many tasks and save major battery power.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPS always works in web/FlutLab:&lt;/strong&gt; Location support varies—review your platform requirements!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where This Comes Up in Real Apps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real-time delivery tracking&lt;/strong&gt; (Uber, DoorDash)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fitness and running apps&lt;/strong&gt; (Strava)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Geo-fenced notifications&lt;/strong&gt; (reminders or alarms based on position)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Travel guides&lt;/strong&gt; (finding attractions nearby)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Choose the right package—&lt;em&gt;&lt;code&gt;geolocator&lt;/code&gt;&lt;/em&gt; is powerful for tracking, but test on your build targets.&lt;/li&gt;
&lt;li&gt;Master permission flows; avoid double prompting (no more crash-y &lt;code&gt;geolocator&lt;/code&gt;/&lt;code&gt;permission_handler&lt;/code&gt; bugs).&lt;/li&gt;
&lt;li&gt;Balance accuracy and frequency for smooth, battery-friendly tracking.&lt;/li&gt;
&lt;li&gt;Cache location data and handle errors up front for a smoother UX.&lt;/li&gt;
&lt;li&gt;Stay updated—Flutter packages evolve, especially location-dependent ones.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're diving deeper, I highly recommend &lt;a href="https://www.appxiom.com/blogs/optimizing-the-implementation-of-location-services-in-flutter/" rel="noopener noreferrer"&gt;this blog&lt;/a&gt; for technical explanations and practical best practices that really helped me structure my implementation.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>mobile</category>
      <category>gps</category>
      <category>dart</category>
    </item>
    <item>
      <title>Data Structures in Swift: What Every iOS Developer Should Know in 2026</title>
      <dc:creator>Andrea Sunny</dc:creator>
      <pubDate>Thu, 21 May 2026 05:26:17 +0000</pubDate>
      <link>https://dev.to/andrea-sunny/data-structures-in-swift-what-every-ios-developer-should-know-in-2026-3g1j</link>
      <guid>https://dev.to/andrea-sunny/data-structures-in-swift-what-every-ios-developer-should-know-in-2026-3g1j</guid>
      <description>&lt;p&gt;Swift is clean, expressive, and powerful - but writing efficient Swift applications requires more than just understanding syntax.&lt;/p&gt;

&lt;p&gt;If you really want to level up as an iOS developer, you need to understand &lt;strong&gt;data structures in Swift&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Because at scale, performance problems usually don’t come from UI code.&lt;br&gt;&lt;br&gt;
They come from choosing the wrong structure for storing, searching, sorting, or updating data.&lt;/p&gt;

&lt;p&gt;That’s why mastering &lt;strong&gt;Swift data structures&lt;/strong&gt; is one of the biggest upgrades you can make as a developer.&lt;/p&gt;

&lt;p&gt;In this article, we’ll break down the most important data structures every Swift developer should know, when to use them, and why they matter in real-world iOS applications.&lt;/p&gt;

&lt;p&gt;👉 Detailed guide here:&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.appxiom.com/blogs/data-structures-in-swift/" rel="noopener noreferrer"&gt;https://www.appxiom.com/blogs/data-structures-in-swift/&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Data Structures Matter in Swift
&lt;/h2&gt;

&lt;p&gt;A lot of developers jump directly into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SwiftUI&lt;/li&gt;
&lt;li&gt;UIKit&lt;/li&gt;
&lt;li&gt;Combine&lt;/li&gt;
&lt;li&gt;Async/Await&lt;/li&gt;
&lt;li&gt;CoreData&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…but skip computer science fundamentals.&lt;/p&gt;

&lt;p&gt;The problem?&lt;/p&gt;

&lt;p&gt;Without understanding data structures, apps become:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;slower&lt;/li&gt;
&lt;li&gt;harder to scale&lt;/li&gt;
&lt;li&gt;memory inefficient&lt;/li&gt;
&lt;li&gt;difficult to optimize&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference between a smooth app and a laggy app often comes down to choosing the right structure.&lt;/p&gt;
&lt;h2&gt;
  
  
  Most Important Data Structures in Swift
&lt;/h2&gt;

&lt;p&gt;Here are the core data structures in Swift every iOS developer should understand.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Data Structure&lt;/th&gt;
&lt;th&gt;Best Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Array&lt;/td&gt;
&lt;td&gt;Ordered collections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Set&lt;/td&gt;
&lt;td&gt;Unique values&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dictionary&lt;/td&gt;
&lt;td&gt;Key-value storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stack&lt;/td&gt;
&lt;td&gt;LIFO operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Queue&lt;/td&gt;
&lt;td&gt;FIFO operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linked List&lt;/td&gt;
&lt;td&gt;Dynamic insertions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tree&lt;/td&gt;
&lt;td&gt;Hierarchical data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Graph&lt;/td&gt;
&lt;td&gt;Connected systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heap&lt;/td&gt;
&lt;td&gt;Priority-based operations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  1. Arrays in Swift
&lt;/h2&gt;

&lt;p&gt;Arrays are the most commonly used Swift data structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Arrays are great when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;order matters&lt;/li&gt;
&lt;li&gt;indexing matters&lt;/li&gt;
&lt;li&gt;you frequently iterate through items&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But many developers misuse arrays for lookups.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This becomes slower as the collection grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Sets in Swift
&lt;/h2&gt;

&lt;p&gt;If you need uniqueness + fast lookups, use &lt;code&gt;Set&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;uniqueUsers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Alex"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Sam"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sets are much faster for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;duplicate prevention&lt;/li&gt;
&lt;li&gt;membership checks&lt;/li&gt;
&lt;li&gt;filtering unique data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A lot of performance issues in Swift apps come from developers using arrays where sets are more appropriate.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Dictionaries in Swift
&lt;/h2&gt;

&lt;p&gt;Dictionaries are essential for key-value access.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;userAge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;"Alex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"Sam"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;caching&lt;/li&gt;
&lt;li&gt;API response mapping&lt;/li&gt;
&lt;li&gt;local storage models&lt;/li&gt;
&lt;li&gt;lookup-heavy operations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Real-World Example
&lt;/h3&gt;

&lt;p&gt;Imagine building a messaging app.&lt;/p&gt;

&lt;p&gt;Bad approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Searching for a message repeatedly becomes expensive.&lt;/p&gt;

&lt;p&gt;Better approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;messagesById&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now lookups become almost instant.&lt;/p&gt;

&lt;p&gt;This is why understanding &lt;strong&gt;data structures in Swift&lt;/strong&gt; matters in production apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Stack Data Structure
&lt;/h2&gt;

&lt;p&gt;Stacks follow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Last In → First Out (LIFO)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;navigation history&lt;/li&gt;
&lt;li&gt;undo systems&lt;/li&gt;
&lt;li&gt;expression parsing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simple implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;Stack&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt;

    &lt;span class="k"&gt;mutating&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;mutating&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popLast&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;
  
  
  5. Queue Data Structure
&lt;/h2&gt;

&lt;p&gt;Queues follow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First In → First Out (FIFO)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Used in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;task scheduling&lt;/li&gt;
&lt;li&gt;networking pipelines&lt;/li&gt;
&lt;li&gt;media buffering&lt;/li&gt;
&lt;li&gt;background processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;Queue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt;

    &lt;span class="k"&gt;mutating&lt;/span&gt; &lt;span class="kd"&gt;func&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;_&lt;/span&gt; &lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;mutating&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;dequeue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isEmpty&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&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;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeFirst&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;
  
  
  6. Linked Lists in Swift
&lt;/h2&gt;

&lt;p&gt;Linked lists are less common in modern iOS apps but still important conceptually.&lt;/p&gt;

&lt;p&gt;They help when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;frequent insertions occur&lt;/li&gt;
&lt;li&gt;dynamic memory behavior matters&lt;/li&gt;
&lt;li&gt;node traversal is needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, Swift arrays are heavily optimized, so linked lists are not always the best practical choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Trees in Swift
&lt;/h2&gt;

&lt;p&gt;Trees are everywhere in software engineering.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;file systems&lt;/li&gt;
&lt;li&gt;view hierarchies&lt;/li&gt;
&lt;li&gt;JSON parsing&lt;/li&gt;
&lt;li&gt;DOM structures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basic tree node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;TreeNode&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;T&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&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;
  
  
  8. Graphs in Swift
&lt;/h2&gt;

&lt;p&gt;Graphs model relationships between objects.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;maps/navigation&lt;/li&gt;
&lt;li&gt;social networks&lt;/li&gt;
&lt;li&gt;recommendation systems&lt;/li&gt;
&lt;li&gt;dependency graphs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is one of the most underrated Swift data structures.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Heap / Priority Queue
&lt;/h2&gt;

&lt;p&gt;Heaps are useful when you constantly need the “highest priority” item.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scheduling systems&lt;/li&gt;
&lt;li&gt;leaderboard ranking&lt;/li&gt;
&lt;li&gt;real-time gaming systems&lt;/li&gt;
&lt;li&gt;notification prioritization&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common Mistakes Swift Developers Make
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Using Arrays Everywhere
&lt;/h3&gt;

&lt;p&gt;Many developers default to arrays for everything.&lt;/p&gt;

&lt;p&gt;That creates performance bottlenecks quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Ignoring Time Complexity
&lt;/h3&gt;

&lt;p&gt;Operations have costs.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Array&lt;/th&gt;
&lt;th&gt;Set&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lookup&lt;/td&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Insert&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remove&lt;/td&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Understanding complexity changes how you architect apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Optimizing Too Late
&lt;/h3&gt;

&lt;p&gt;Bad data structure decisions become expensive to refactor later.&lt;/p&gt;

&lt;p&gt;Choose wisely early.&lt;/p&gt;

&lt;h2&gt;
  
  
  Swift Data Structures and Interviews
&lt;/h2&gt;

&lt;p&gt;A huge reason developers search for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;swift data structures&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data structures in swift&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…is interview preparation.&lt;/p&gt;

&lt;p&gt;Most iOS interviews now include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;arrays&lt;/li&gt;
&lt;li&gt;stacks&lt;/li&gt;
&lt;li&gt;queues&lt;/li&gt;
&lt;li&gt;trees&lt;/li&gt;
&lt;li&gt;recursion&lt;/li&gt;
&lt;li&gt;hashing&lt;/li&gt;
&lt;li&gt;graph traversal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even for senior mobile roles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Way to Learn Data Structures in Swift
&lt;/h2&gt;

&lt;p&gt;Don’t just memorize definitions.&lt;/p&gt;

&lt;p&gt;Instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build them manually&lt;/li&gt;
&lt;li&gt;Use them in small apps&lt;/li&gt;
&lt;li&gt;Analyze time complexity&lt;/li&gt;
&lt;li&gt;Compare tradeoffs&lt;/li&gt;
&lt;li&gt;Study real-world architecture&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s where concepts actually stick.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Data Structures Improve App Performance
&lt;/h2&gt;

&lt;p&gt;Choosing the right structure can improve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;app startup speed&lt;/li&gt;
&lt;li&gt;memory usage&lt;/li&gt;
&lt;li&gt;scrolling performance&lt;/li&gt;
&lt;li&gt;caching efficiency&lt;/li&gt;
&lt;li&gt;database operations&lt;/li&gt;
&lt;li&gt;API processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This becomes extremely important in large-scale SwiftUI apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommended Next Topics
&lt;/h2&gt;

&lt;p&gt;After learning Swift data structures, explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Algorithms in Swift&lt;/li&gt;
&lt;li&gt;Big-O notation&lt;/li&gt;
&lt;li&gt;Memory optimization&lt;/li&gt;
&lt;li&gt;Concurrency in Swift&lt;/li&gt;
&lt;li&gt;SwiftUI architecture&lt;/li&gt;
&lt;li&gt;Combine framework&lt;/li&gt;
&lt;li&gt;Async/Await&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Understanding data structures in Swift is one of the highest-leverage skills for becoming a better iOS developer.&lt;/p&gt;

&lt;p&gt;Frameworks change every year.&lt;/p&gt;

&lt;p&gt;But core computer science fundamentals stay valuable forever.&lt;/p&gt;

&lt;p&gt;If you want the deeper implementation breakdown, examples, and architecture discussion, check out the full guide here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://www.appxiom.com/blogs/data-structures-in-swift/" rel="noopener noreferrer"&gt;https://www.appxiom.com/blogs/data-structures-in-swift/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It covers practical Swift data structures with examples developers can actually use in real projects.&lt;/p&gt;

</description>
      <category>swiftui</category>
      <category>datastructures</category>
      <category>programming</category>
      <category>iosdevelopment</category>
    </item>
    <item>
      <title>Building an Offline-First Android App with Jetpack Compose (2026 Guide)</title>
      <dc:creator>Andrea Sunny</dc:creator>
      <pubDate>Fri, 15 May 2026 06:24:35 +0000</pubDate>
      <link>https://dev.to/andrea-sunny/building-an-offline-first-android-app-with-jetpack-compose-2026-guide-17kd</link>
      <guid>https://dev.to/andrea-sunny/building-an-offline-first-android-app-with-jetpack-compose-2026-guide-17kd</guid>
      <description>&lt;p&gt;Modern Android apps are expected to work even when the internet doesn’t.&lt;/p&gt;

&lt;p&gt;Users open apps inside elevators, during flights, in low-network regions, or while switching between Wi-Fi and mobile data. If your app crashes or becomes unusable offline, users leave quickly.&lt;/p&gt;

&lt;p&gt;That’s why offline-first Android architecture is becoming the default approach in 2026.&lt;/p&gt;

&lt;p&gt;In this guide, you’ll learn how to build an offline-capable Android app using Jetpack Compose, Room, WorkManager, and Retrofit - along with the latest stable dependency versions developers are searching for right now.&lt;/p&gt;

&lt;p&gt;👉 Original detailed guide:&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.appxiom.com/blogs/build-offline-android-app-jetpack-compose/" rel="noopener noreferrer"&gt;https://www.appxiom.com/blogs/build-offline-android-app-jetpack-compose/&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Offline-First Apps Matter
&lt;/h2&gt;

&lt;p&gt;Offline-first apps provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster UI responsiveness&lt;/li&gt;
&lt;li&gt;Better user retention&lt;/li&gt;
&lt;li&gt;Reliable performance in poor networks&lt;/li&gt;
&lt;li&gt;Seamless background synchronization&lt;/li&gt;
&lt;li&gt;Reduced server dependency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apps like WhatsApp, Notion, Spotify, and Google Keep all rely heavily on offline-capable architecture.&lt;/p&gt;
&lt;h2&gt;
  
  
  Tech Stack for Offline Android Apps
&lt;/h2&gt;

&lt;p&gt;Here’s the modern stack most Android developers use today:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Jetpack Compose&lt;/td&gt;
&lt;td&gt;Modern UI toolkit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Room Database&lt;/td&gt;
&lt;td&gt;Local offline storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WorkManager&lt;/td&gt;
&lt;td&gt;Background sync&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retrofit&lt;/td&gt;
&lt;td&gt;API networking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kotlin Coroutines&lt;/td&gt;
&lt;td&gt;Async programming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flow / StateFlow&lt;/td&gt;
&lt;td&gt;Reactive streams&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Latest Android Dependency Versions (2026)
&lt;/h2&gt;

&lt;p&gt;Many developers search for dependency versions directly in Google Search Console, which explains queries like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;androidx.room:room-runtime:2.6.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;androidx.work:work-runtime-ktx:2.9.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;androidx.activity:activity-compose latest version 2026&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are the commonly used stable versions.&lt;/p&gt;
&lt;h3&gt;
  
  
  Room Database
&lt;/h3&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;"androidx.room:room-runtime:2.6.1"&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;"androidx.room:room-ktx:2.6.1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;ksp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.room:room-compiler:2.6.1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You may also see older searches like:&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;"androidx.room:room-runtime:2.5.2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But &lt;code&gt;2.6.1&lt;/code&gt; is the preferred version for most new projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  WorkManager
&lt;/h3&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;"androidx.work:work-runtime-ktx:2.9.0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is currently one of the most searched WorkManager dependencies because developers want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reliable background syncing&lt;/li&gt;
&lt;li&gt;Offline queue processing&lt;/li&gt;
&lt;li&gt;Retry mechanisms&lt;/li&gt;
&lt;li&gt;Battery-efficient background tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Activity Compose
&lt;/h3&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;"androidx.activity:activity-compose:1.9.0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you searched for:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;androidx.activity activity-compose latest version 2026&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;this is the dependency you likely need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 - Create Your Room Entity
&lt;/h2&gt;

&lt;p&gt;Room acts as your local source of truth.&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="nd"&gt;@Entity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tableName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"notes"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;NoteEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;@PrimaryKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;autoGenerate&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="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;title&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="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;content&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="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;synced&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2 - DAO Layer
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Dao&lt;/span&gt;
&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;NoteDao&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onConflict&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OnConflictStrategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;REPLACE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;NoteEntity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT * FROM notes"&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;getAllNotes&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NoteEntity&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;Flow&lt;/code&gt; ensures the UI updates automatically whenever local data changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 - Configure Room Database
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;entities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;NoteEntity&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppDatabase&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;RoomDatabase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;noteDao&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;NoteDao&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;db&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Room&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;databaseBuilder&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="nc"&gt;AppDatabase&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"offline_db"&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4 - Retrofit Networking
&lt;/h2&gt;

&lt;p&gt;Retrofit remains the standard for Android APIs.&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;"com.squareup.retrofit2:retrofit:2.9.0"&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;"com.squareup.retrofit2:converter-gson:2.9.0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A lot of developers also search for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;square retrofit 2.6.0 suspend support release notes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;retrofit latest version 2026 2.9.0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Suspend support is now fully standard in Retrofit.&lt;/p&gt;

&lt;p&gt;Example API service:&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;interface&lt;/span&gt; &lt;span class="nc"&gt;NoteApi&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"notes"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;uploadNote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nd"&gt;@Body&lt;/span&gt; &lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;NoteEntity&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;
  
  
  Step 5 - WorkManager for Background Sync
&lt;/h2&gt;

&lt;p&gt;This is where offline-first architecture becomes powerful.&lt;/p&gt;

&lt;p&gt;Whenever the internet returns, WorkManager syncs unsynced local data automatically.&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;SyncWorker&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="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WorkerParameters&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CoroutineWorker&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;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;doWork&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Upload local notes&lt;/span&gt;
            &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Schedule it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OneTimeWorkRequestBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SyncWorker&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nc"&gt;WorkManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&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="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;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6 - Jetpack Compose UI
&lt;/h2&gt;

&lt;p&gt;Compose makes reactive offline UIs much easier to implement.&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="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;NotesScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;NotesViewModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;notes&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectAsState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nc"&gt;LazyColumn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;note&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="nc"&gt;Text&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="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&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;p&gt;Because Room exposes &lt;code&gt;Flow&lt;/code&gt;, the UI updates instantly whenever local data changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Offline-First Architecture Flow
&lt;/h2&gt;

&lt;p&gt;The recommended architecture is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UI → Local Database → Background Sync → Remote Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UI → API → Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures your app remains functional even without connectivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes Developers Make
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Treating APIs as the Source of Truth
&lt;/h3&gt;

&lt;p&gt;Your local database should always be the primary source.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Syncing Too Frequently
&lt;/h3&gt;

&lt;p&gt;Aggressive background sync drains battery and impacts performance.&lt;/p&gt;

&lt;p&gt;Use WorkManager intelligently.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Ignoring Conflict Resolution
&lt;/h3&gt;

&lt;p&gt;Offline edits can conflict with server data.&lt;/p&gt;

&lt;p&gt;Use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;timestamps&lt;/li&gt;
&lt;li&gt;versioning&lt;/li&gt;
&lt;li&gt;merge strategies&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Does “Offline Capable” Mean?
&lt;/h2&gt;

&lt;p&gt;One interesting search query from Search Console was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;offline capable&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An offline-capable app means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users can view data without internet&lt;/li&gt;
&lt;li&gt;Users can create/update data offline&lt;/li&gt;
&lt;li&gt;Synchronization happens automatically later&lt;/li&gt;
&lt;li&gt;App functionality degrades gracefully&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is now expected behavior for production apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommended Android Architecture in 2026
&lt;/h2&gt;

&lt;p&gt;The best modern Android architecture stack includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MVVM&lt;/li&gt;
&lt;li&gt;Repository pattern&lt;/li&gt;
&lt;li&gt;Room as source of truth&lt;/li&gt;
&lt;li&gt;Retrofit for APIs&lt;/li&gt;
&lt;li&gt;WorkManager for sync&lt;/li&gt;
&lt;li&gt;Compose for UI&lt;/li&gt;
&lt;li&gt;Coroutines + Flow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup improves maintainability, scalability, and user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Architecture Scales Well
&lt;/h2&gt;

&lt;p&gt;Offline-first apps scale better because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;APIs can fail temporarily without breaking UX&lt;/li&gt;
&lt;li&gt;Users continue interacting with the app&lt;/li&gt;
&lt;li&gt;Cached content improves speed&lt;/li&gt;
&lt;li&gt;Sync operations happen in the background&lt;/li&gt;
&lt;li&gt;Mobile battery usage becomes more optimized&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This architecture is especially useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Note-taking apps&lt;/li&gt;
&lt;li&gt;E-commerce apps&lt;/li&gt;
&lt;li&gt;Chat applications&lt;/li&gt;
&lt;li&gt;Healthcare systems&lt;/li&gt;
&lt;li&gt;Logistics platforms&lt;/li&gt;
&lt;li&gt;Field-service apps&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Advanced Improvements You Can Add
&lt;/h2&gt;

&lt;p&gt;Once the basics are working, you can improve the architecture further with:&lt;/p&gt;

&lt;h3&gt;
  
  
  Paging 3
&lt;/h3&gt;

&lt;p&gt;Efficiently load large offline datasets.&lt;/p&gt;

&lt;h3&gt;
  
  
  DataStore
&lt;/h3&gt;

&lt;p&gt;Store preferences and lightweight app settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hilt Dependency Injection
&lt;/h3&gt;

&lt;p&gt;Improve modularity and testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Network Monitoring
&lt;/h3&gt;

&lt;p&gt;Automatically trigger synchronization when connectivity returns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Encryption
&lt;/h3&gt;

&lt;p&gt;Protect local Room database data for security-sensitive apps.&lt;/p&gt;

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

&lt;p&gt;Offline-first Android development is no longer optional.&lt;/p&gt;

&lt;p&gt;If your app depends completely on network availability, users will eventually experience frustration.&lt;/p&gt;

&lt;p&gt;By combining:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;androidx.room:room-runtime:2.6.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;androidx.work:work-runtime-ktx:2.9.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Retrofit&lt;/li&gt;
&lt;li&gt;Jetpack Compose&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;you can build apps that feel fast, modern, and reliable.&lt;/p&gt;

&lt;p&gt;For the complete implementation walkthrough, check the original article:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://www.appxiom.com/blogs/build-offline-android-app-jetpack-compose/" rel="noopener noreferrer"&gt;https://www.appxiom.com/blogs/build-offline-android-app-jetpack-compose/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>roomdatabase</category>
      <category>jetpackcompose</category>
      <category>offlinecapable</category>
    </item>
    <item>
      <title>Sqflite Flutter: A Simple Way to Add Local Database Support in Flutter Apps</title>
      <dc:creator>Andrea Sunny</dc:creator>
      <pubDate>Thu, 14 May 2026 06:03:05 +0000</pubDate>
      <link>https://dev.to/andrea-sunny/sqflite-flutter-a-simple-way-to-add-local-database-support-in-flutter-apps-4pof</link>
      <guid>https://dev.to/andrea-sunny/sqflite-flutter-a-simple-way-to-add-local-database-support-in-flutter-apps-4pof</guid>
      <description>&lt;p&gt;When building production-ready Flutter apps, one thing becomes obvious very quickly - you eventually need local storage.&lt;/p&gt;

&lt;p&gt;Whether it’s:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;offline access,&lt;/li&gt;
&lt;li&gt;caching API responses,&lt;/li&gt;
&lt;li&gt;storing user preferences,&lt;/li&gt;
&lt;li&gt;or maintaining app state,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;having a reliable local database solution matters.&lt;/p&gt;

&lt;p&gt;That’s where &lt;strong&gt;sqflite Flutter integration&lt;/strong&gt; becomes extremely useful.&lt;/p&gt;

&lt;p&gt;If you’ve searched for &lt;strong&gt;sqflite&lt;/strong&gt; tutorials recently, you probably noticed that many examples only cover basic CRUD operations without explaining real-world usage patterns. So in this post, I want to share why sqflite is still one of the best choices for Flutter local storage and where beginners can learn it properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Sqflite?
&lt;/h2&gt;

&lt;p&gt;Sqflite is a Flutter plugin for SQLite.&lt;/p&gt;

&lt;p&gt;It allows Flutter developers to store structured data locally using SQL queries inside mobile applications.&lt;/p&gt;

&lt;p&gt;With sqflite, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create local databases&lt;/li&gt;
&lt;li&gt;Insert/update/delete data&lt;/li&gt;
&lt;li&gt;Perform SQL queries&lt;/li&gt;
&lt;li&gt;Store relational data efficiently&lt;/li&gt;
&lt;li&gt;Build offline-first apps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since SQLite is lightweight and optimized for mobile devices, sqflite has become one of the most popular local database solutions in the Flutter ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Developers Use Sqflite Flutter Solutions
&lt;/h2&gt;

&lt;p&gt;Here are a few reasons why sqflite is widely used in Flutter projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Offline Support
&lt;/h3&gt;

&lt;p&gt;Your app can continue functioning without internet access.&lt;/p&gt;

&lt;p&gt;This is especially important for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;productivity apps,&lt;/li&gt;
&lt;li&gt;note-taking apps,&lt;/li&gt;
&lt;li&gt;expense trackers,&lt;/li&gt;
&lt;li&gt;learning platforms,&lt;/li&gt;
&lt;li&gt;and field-service applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Better Performance
&lt;/h3&gt;

&lt;p&gt;Reading data locally is much faster than making repeated API calls.&lt;/p&gt;

&lt;p&gt;Many apps use sqflite for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;caching,&lt;/li&gt;
&lt;li&gt;session persistence,&lt;/li&gt;
&lt;li&gt;and reducing unnecessary network requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Structured Data Storage
&lt;/h3&gt;

&lt;p&gt;Unlike simple key-value storage systems, sqflite supports relational tables and SQL queries.&lt;/p&gt;

&lt;p&gt;That means you can build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;searchable datasets,&lt;/li&gt;
&lt;li&gt;filtered records,&lt;/li&gt;
&lt;li&gt;connected entities,&lt;/li&gt;
&lt;li&gt;and scalable local architectures.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common Sqflite Flutter Use Cases
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;App Type&lt;/th&gt;
&lt;th&gt;Usage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Todo Apps&lt;/td&gt;
&lt;td&gt;Task persistence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Finance Apps&lt;/td&gt;
&lt;td&gt;Offline transaction storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E-commerce Apps&lt;/td&gt;
&lt;td&gt;Cart &amp;amp; wishlist caching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chat Apps&lt;/td&gt;
&lt;td&gt;Local message history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learning Apps&lt;/td&gt;
&lt;td&gt;Saved progress &amp;amp; lessons&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Things Beginners Usually Struggle With
&lt;/h2&gt;

&lt;p&gt;When starting with sqflite Flutter development, beginners often face challenges like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database initialization&lt;/li&gt;
&lt;li&gt;Async query handling&lt;/li&gt;
&lt;li&gt;Managing database versions&lt;/li&gt;
&lt;li&gt;Writing reusable helper classes&lt;/li&gt;
&lt;li&gt;Structuring CRUD methods cleanly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s why following a proper implementation guide is important instead of just copying snippets from random tutorials.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommended Sqflite Flutter Tutorial
&lt;/h2&gt;

&lt;p&gt;I found this beginner-friendly guide that explains the setup process step-by-step:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://www.appxiom.com/blogs/a-beginners-guide-to-integrating-sqflite-in-flutter-projects/" rel="noopener noreferrer"&gt;https://www.appxiom.com/blogs/a-beginners-guide-to-integrating-sqflite-in-flutter-projects/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;package installation,&lt;/li&gt;
&lt;li&gt;database creation,&lt;/li&gt;
&lt;li&gt;CRUD operations,&lt;/li&gt;
&lt;li&gt;query execution,&lt;/li&gt;
&lt;li&gt;and best practices for organizing sqflite code in Flutter projects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good resource if you’re starting with local database integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sqflite vs Other Flutter Storage Packages
&lt;/h2&gt;

&lt;p&gt;Flutter developers often compare sqflite with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hive&lt;/li&gt;
&lt;li&gt;Drift&lt;/li&gt;
&lt;li&gt;SharedPreferences&lt;/li&gt;
&lt;li&gt;ObjectBox&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each has its strengths, but sqflite still works really well when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you need relational data,&lt;/li&gt;
&lt;li&gt;SQL queries matter,&lt;/li&gt;
&lt;li&gt;or you want full control over your database structure.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Even with newer storage libraries available, &lt;strong&gt;sqflite&lt;/strong&gt; remains one of the most practical solutions for local database management in Flutter.&lt;/p&gt;

&lt;p&gt;If you’re building apps that require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;offline functionality,&lt;/li&gt;
&lt;li&gt;structured local storage,&lt;/li&gt;
&lt;li&gt;or scalable persistence,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then learning &lt;strong&gt;sqflite flutter&lt;/strong&gt; development is definitely worth your time.&lt;/p&gt;

&lt;p&gt;And if you want a clean beginner tutorial, the Appxiom guide linked above is a solid place to start.&lt;/p&gt;

</description>
      <category>sqfliteflutter</category>
      <category>flutter</category>
      <category>fluttersqflitepackage</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The Hidden Problem in Most Flutter Location Implementations</title>
      <dc:creator>Andrea Sunny</dc:creator>
      <pubDate>Tue, 17 Mar 2026 11:00:19 +0000</pubDate>
      <link>https://dev.to/andrea-sunny/the-hidden-problem-in-most-flutter-location-implementations-45kk</link>
      <guid>https://dev.to/andrea-sunny/the-hidden-problem-in-most-flutter-location-implementations-45kk</guid>
      <description>&lt;p&gt;Let’s talk about something most Flutter developers implement…&lt;br&gt;
but rarely &lt;em&gt;optimize&lt;/em&gt; properly.&lt;/p&gt;

&lt;p&gt;👉 Location services.&lt;/p&gt;

&lt;p&gt;At first, it feels simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get location&lt;/li&gt;
&lt;li&gt;Show it on UI&lt;/li&gt;
&lt;li&gt;Done&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But once your app hits real users, things change.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem Most Developers Don’t Notice
&lt;/h2&gt;

&lt;p&gt;Location features don’t fail loudly.&lt;/p&gt;

&lt;p&gt;They don’t crash your app.&lt;br&gt;
They don’t throw obvious errors.&lt;/p&gt;

&lt;p&gt;Instead, they:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drain battery silently&lt;/li&gt;
&lt;li&gt;Reduce app performance&lt;/li&gt;
&lt;li&gt;Deliver inconsistent location accuracy&lt;/li&gt;
&lt;li&gt;Run unnecessary background updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And users don’t say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Your GPS polling strategy is inefficient”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Your app kills my battery.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why Location Services Are Harder Than They Look
&lt;/h2&gt;

&lt;p&gt;Modern location-based apps rely on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GPS&lt;/li&gt;
&lt;li&gt;Network signals&lt;/li&gt;
&lt;li&gt;Background services&lt;/li&gt;
&lt;li&gt;Permission handling&lt;/li&gt;
&lt;li&gt;Real-time updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And each of these comes with trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accuracy vs battery usage&lt;/li&gt;
&lt;li&gt;Frequency vs performance&lt;/li&gt;
&lt;li&gt;Real-time updates vs system load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even small inefficiencies can compound quickly.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Continuous tracking can drain power rapidly&lt;/li&gt;
&lt;li&gt;Poor filtering can create noisy location data&lt;/li&gt;
&lt;li&gt;Frequent updates can overload UI rendering&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Most Implementations Get Wrong
&lt;/h2&gt;

&lt;p&gt;From what I’ve seen (and probably you too), common mistakes include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetching location too frequently&lt;/li&gt;
&lt;li&gt;Not handling permissions properly&lt;/li&gt;
&lt;li&gt;Ignoring background optimization&lt;/li&gt;
&lt;li&gt;Treating all location updates equally&lt;/li&gt;
&lt;li&gt;Not filtering noisy GPS data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result?&lt;/p&gt;

&lt;p&gt;👉 An app that “works” - but performs poorly in the real world.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Better Way to Think About Location in Flutter
&lt;/h2&gt;

&lt;p&gt;Instead of thinking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How do I get the location?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Start thinking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How do I get location efficiently and responsibly?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choosing the right package (&lt;code&gt;geolocator&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;li&gt;Handling permissions gracefully&lt;/li&gt;
&lt;li&gt;Reducing unnecessary updates&lt;/li&gt;
&lt;li&gt;Optimizing for battery&lt;/li&gt;
&lt;li&gt;Structuring location logic cleanly&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where Things Get Interesting
&lt;/h2&gt;

&lt;p&gt;There’s a really solid breakdown I came across that goes deeper into this - not just how to implement location services, but how to optimize them properly in real apps.&lt;/p&gt;

&lt;p&gt;It covers things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Efficient location fetching strategies&lt;/li&gt;
&lt;li&gt;Handling permissions correctly&lt;/li&gt;
&lt;li&gt;Reducing unnecessary updates&lt;/li&gt;
&lt;li&gt;Structuring location logic for performance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 If you want to go beyond basic implementation, this is worth reading:&lt;br&gt;
&lt;a href="https://www.appxiom.com/blogs/optimizing-the-implementation-of-location-services-in-flutter/" rel="noopener noreferrer"&gt;Best Practices for Using Location Services in Flutter&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Fx6grj7qm4fu775gxwbvj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx6grj7qm4fu775gxwbvj.png" alt="Best Practices for Using Location Services in Flutter" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Location services aren’t just a feature.&lt;br&gt;
They’re a system inside your app.&lt;/p&gt;

&lt;p&gt;And like any system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It needs optimization&lt;/li&gt;
&lt;li&gt;It needs balance&lt;/li&gt;
&lt;li&gt;It needs intention&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because in the end, users don’t care how you implemented it.&lt;/p&gt;

&lt;p&gt;They care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Battery&lt;/li&gt;
&lt;li&gt;Accuracy&lt;/li&gt;
&lt;li&gt;Smooth experience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that’s where good engineering shows up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the Full Guide
&lt;/h2&gt;

&lt;p&gt;If you're building anything involving maps, tracking, or real-time location, don’t stop here.&lt;/p&gt;

&lt;p&gt;👉 Read the full detailed guide here:&lt;br&gt;
&lt;a href="https://www.appxiom.com/blogs/optimizing-the-implementation-of-location-services-in-flutter/" rel="noopener noreferrer"&gt;Best Practices for Using Location Services in Flutter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>code</category>
      <category>resources</category>
    </item>
    <item>
      <title>How Do You Extend Jetpack Compose Components Without Making Them Messy?</title>
      <dc:creator>Andrea Sunny</dc:creator>
      <pubDate>Wed, 04 Mar 2026 05:16:17 +0000</pubDate>
      <link>https://dev.to/andrea-sunny/how-do-you-extend-jetpack-compose-components-without-making-them-messy-3h96</link>
      <guid>https://dev.to/andrea-sunny/how-do-you-extend-jetpack-compose-components-without-making-them-messy-3h96</guid>
      <description>&lt;p&gt;When building Android apps with Jetpack Compose, you quickly notice something: UI components evolve constantly. A simple button suddenly needs loading states, analytics tracking, accessibility hints, or animations.&lt;/p&gt;

&lt;p&gt;The easy solution? Modify the component directly.&lt;/p&gt;

&lt;p&gt;The better solution? Use a design pattern that lets you &lt;strong&gt;extend behavior without rewriting the original component.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One pattern that fits this idea perfectly is the &lt;strong&gt;Decorator Pattern&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I recently came across a great walkthrough explaining how this pattern works in Compose, and it’s worth exploring if you care about building reusable UI components.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Full guide here: &lt;a href="https://www.appxiom.com/blogs/decorator-pattern-in-jetpack-compose-android-apps/" rel="noopener noreferrer"&gt;How to Implement the Decorator Pattern in Jetpack Compose&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let’s break down the idea and why it’s useful for modern Android development.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Decorator Pattern Actually Means
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Decorator Pattern&lt;/strong&gt; is a classic design pattern that allows you to &lt;strong&gt;add behavior to an object dynamically without changing its original code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of modifying the base component, you wrap it with another object (a decorator) that enhances its behavior.&lt;/p&gt;

&lt;p&gt;Think of it like layering features.&lt;br&gt;
Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Base Button
  ↓
Loading Decorator
  ↓
Analytics Decorator
  ↓
Accessibility Decorator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer adds something without touching the original implementation.&lt;/p&gt;

&lt;p&gt;This approach keeps components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reusable&lt;/li&gt;
&lt;li&gt;maintainable&lt;/li&gt;
&lt;li&gt;easier to test&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Pattern Works So Well in Compose
&lt;/h2&gt;

&lt;p&gt;Compose is built around &lt;strong&gt;composition and small reusable UI pieces&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That means patterns that rely on &lt;strong&gt;wrapping and layering behavior&lt;/strong&gt; naturally fit its architecture.&lt;br&gt;
In fact, you already use this idea daily through &lt;strong&gt;modifiers&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Modifier
   .padding(16.dp)
   .background(Color.Blue)
   .clickable { }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each modifier is essentially decorating the UI element with additional behavior.&lt;/p&gt;

&lt;p&gt;The Decorator Pattern simply applies the same idea at a &lt;strong&gt;component architecture level&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Simple Implementation Walkthrough
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.appxiom.com/" rel="noopener noreferrer"&gt;Appxiom&lt;/a&gt; guide walks through the pattern using a simple example: enhancing a button.&lt;br&gt;
Here’s the general idea.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Create the Base Component
&lt;/h3&gt;

&lt;p&gt;Start with a minimal composable that does only one thing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Composable
fun BaseButton(text: String, onClick: () -&amp;gt; Unit) {
   Button(onClick = onClick) {
       Text(text)
   }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This component is intentionally simple.&lt;br&gt;
No logging.&lt;br&gt;
No loading state.&lt;br&gt;
No analytics.&lt;/p&gt;

&lt;p&gt;Just a button.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Create Decorators
&lt;/h3&gt;

&lt;p&gt;Decorators wrap the base component and add new behavior.&lt;/p&gt;

&lt;p&gt;For example, a loading decorator might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Composable
fun LoadingDecorator(content: @Composable () -&amp;gt; Unit) {
   CircularProgressIndicator()
   content()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of modifying the button directly, the decorator simply &lt;strong&gt;wraps it and enhances it&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Apply Decorators
&lt;/h3&gt;

&lt;p&gt;Now we can layer behavior on top of the base component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LoadingDecorator {
   BaseButton("Submit") {
       println("Clicked")
   }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Need analytics?&lt;br&gt;
Add another decorator.&lt;/p&gt;

&lt;p&gt;Need animation?&lt;br&gt;
Wrap it again.&lt;/p&gt;

&lt;p&gt;Each feature stays isolated and reusable.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Use the Decorated Button
&lt;/h3&gt;

&lt;p&gt;Once wrapped, the final component behaves like a richer version of the original.&lt;/p&gt;

&lt;p&gt;This keeps your UI architecture flexible because you can &lt;strong&gt;combine decorators depending on the screen’s needs.&lt;/strong&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Frujv7zis549dgzi2u5yt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frujv7zis549dgzi2u5yt.png" alt="Implement the Decorator Pattern in Jetpack Compose" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why This Pattern Matters in Real Apps
&lt;/h2&gt;

&lt;p&gt;As apps grow, UI components often accumulate responsibilities.&lt;/p&gt;

&lt;p&gt;Without patterns like this, you end up with components that look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SuperMegaButton(
   loading = true,
   analytics = true,
   tracking = true,
   animate = true,
   accessibility = true
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eventually, the component becomes hard to maintain.&lt;/p&gt;

&lt;p&gt;The decorator approach keeps responsibilities separate.&lt;/p&gt;

&lt;p&gt;Benefits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;easier testing&lt;/li&gt;
&lt;li&gt;reusable behavior&lt;/li&gt;
&lt;li&gt;cleaner composables&lt;/li&gt;
&lt;li&gt;better separation of concerns&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Real Value of Patterns in Compose
&lt;/h2&gt;

&lt;p&gt;Patterns like this aren’t just academic ideas.&lt;/p&gt;

&lt;p&gt;They solve practical problems that appear in real Android projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;feature layering&lt;/li&gt;
&lt;li&gt;UI reuse&lt;/li&gt;
&lt;li&gt;scalable component design&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compose encourages thinking in small building blocks, and the decorator pattern fits naturally into that mindset.&lt;/p&gt;

&lt;h2&gt;
  
  
  If You Want the Full Walkthrough
&lt;/h2&gt;

&lt;p&gt;This article only scratches the surface.&lt;/p&gt;

&lt;p&gt;The original guide includes a &lt;strong&gt;step-by-step implementation with working examples&lt;/strong&gt; and explains how to structure decorators properly.&lt;br&gt;
👉 Read the full guide here:&lt;br&gt;
&lt;a href="https://www.appxiom.com/blogs/decorator-pattern-in-jetpack-compose-android-apps/" rel="noopener noreferrer"&gt;How to Implement the Decorator Pattern in Jetpack Compose&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’re building reusable UI systems with Jetpack Compose, it’s definitely worth a read.&lt;/p&gt;

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

&lt;p&gt;Jetpack Compose gives developers powerful tools for building UI quickly.&lt;/p&gt;

&lt;p&gt;But writing scalable UI still depends on &lt;strong&gt;good architectural decisions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Patterns like the Decorator Pattern help keep your components small, reusable, and adaptable as your app grows.&lt;/p&gt;

&lt;p&gt;And sometimes, the simplest patterns make the biggest difference.&lt;/p&gt;

</description>
      <category>jetpackcompose</category>
      <category>devops</category>
      <category>tutorial</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
