<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Syed Zubyl N | Software Developer - Flutter</title>
    <subtitle>Syed Zubyl N — Software Developer focused on Java, Spring Boot, backend development, REST APIs, databases, Flutter and production-oriented applications.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://syedzubyl.space/tags/flutter/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://syedzubyl.space"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-05-18T00:00:00+00:00</updated>
    <id>https://syedzubyl.space/tags/flutter/atom.xml</id>
    <entry xml:lang="en">
        <title>What I Learned Building Flutter Applications Around Real APIs</title>
        <published>2026-05-18T00:00:00+00:00</published>
        <updated>2026-05-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Syed Zubyl N
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://syedzubyl.space/blog/building-flutter-apps-around-apis/"/>
        <id>https://syedzubyl.space/blog/building-flutter-apps-around-apis/</id>
        
        <content type="html" xml:base="https://syedzubyl.space/blog/building-flutter-apps-around-apis/">&lt;h3 id=&quot;the-problem&quot;&gt;The Problem&lt;&#x2F;h3&gt;
&lt;p&gt;Building a UI in Flutter is relatively straightforward. The real complexity begins when you connect that beautiful UI to a real-world, unpredictable REST API. Handling token expiration, offline states, local caching, and malformed JSON payloads quickly turns a simple app into a debugging nightmare.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;context&quot;&gt;Context&lt;&#x2F;h3&gt;
&lt;p&gt;While building a mobile LMS (Learning Management System) application in Flutter, I needed to fetch course catalogs, stream videos, and issue certificates. This required robust communication with a backend API while ensuring the app remained responsive even when the user’s internet connection was spotty.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-i-tried&quot;&gt;What I Tried&lt;&#x2F;h3&gt;
&lt;p&gt;I initially relied solely on the network. Every time a user opened a screen, a &lt;code&gt;FutureBuilder&lt;&#x2F;code&gt; would fire off an HTTP request and display a loading spinner until the data arrived.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-failed&quot;&gt;What Failed&lt;&#x2F;h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Poor UX:&lt;&#x2F;strong&gt; Users stared at loading spinners constantly.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Offline Experience:&lt;&#x2F;strong&gt; If a user boarded a subway, the app became completely useless. It crashed or showed generic error screens.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;API Rate Limiting:&lt;&#x2F;strong&gt; Spamming the backend with requests every time a user switched tabs was highly inefficient.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;what-worked-technical-explanation&quot;&gt;What Worked &amp;amp; Technical Explanation&lt;&#x2F;h3&gt;
&lt;p&gt;I shifted to a &lt;strong&gt;Local-First Architecture&lt;&#x2F;strong&gt; using SQLite (via the &lt;code&gt;sqflite&lt;&#x2F;code&gt; package).&lt;&#x2F;p&gt;
&lt;p&gt;Instead of the UI talking directly to the API, the UI only talks to the local SQLite database. A separate background synchronization engine talks to the API.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The Flow:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;User opens the Course List screen.&lt;&#x2F;li&gt;
&lt;li&gt;The UI reads immediately from SQLite and displays cached data (instant load).&lt;&#x2F;li&gt;
&lt;li&gt;In the background, the app fires an API request to check for updates.&lt;&#x2F;li&gt;
&lt;li&gt;If the API returns new data, it updates the SQLite database.&lt;&#x2F;li&gt;
&lt;li&gt;The UI, listening to a stream from the database, automatically rebuilds with the fresh data.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;&lt;strong&gt;Handling API Failures:&lt;&#x2F;strong&gt;
If an API request fails (e.g., user is offline, or the server returns a 500 error), the app simply logs the failure silently. The user is still looking at the cached data from SQLite and can continue interacting with the app.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;lessons-learned&quot;&gt;Lessons Learned&lt;&#x2F;h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Never trust the network.&lt;&#x2F;strong&gt; The network will fail, it will be slow, and it will return unexpected data.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Defensive Parsing:&lt;&#x2F;strong&gt; Always wrap JSON parsing in try-catch blocks or use robust code-generation tools. One unexpected &lt;code&gt;null&lt;&#x2F;code&gt; field from the backend can crash the entire Flutter isolate.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Local State is King:&lt;&#x2F;strong&gt; For an app to feel fast and native, data must be available locally on the device the millisecond the screen renders.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;what-i-would-do-differently&quot;&gt;What I Would Do Differently&lt;&#x2F;h3&gt;
&lt;p&gt;Instead of writing raw SQL queries for SQLite, I would adopt a higher-level reactive database like Isar or ObjectBox. They provide out-of-the-box stream capabilities that make tying local database updates directly to Flutter’s reactive UI significantly easier than building custom &lt;code&gt;StreamControllers&lt;&#x2F;code&gt; over &lt;code&gt;sqflite&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>When API Response Types Don&#x27;t Match Your Flutter Model</title>
        <published>2026-03-05T00:00:00+00:00</published>
        <updated>2026-03-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Syed Zubyl N
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://syedzubyl.space/blog/api-response-types-mismatch/"/>
        <id>https://syedzubyl.space/blog/api-response-types-mismatch/</id>
        
        <content type="html" xml:base="https://syedzubyl.space/blog/api-response-types-mismatch/">&lt;h3 id=&quot;the-problem&quot;&gt;The Problem&lt;&#x2F;h3&gt;
&lt;p&gt;A Flutter application was crashing on the profile screen with a cryptic error in the console:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;type &amp;#39;int&amp;#39; is not a subtype of type &amp;#39;bool?&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The app compiled perfectly, the API returned a 200 OK, but the JSON deserialization failed entirely, resulting in a red screen of death in debug mode (and a blank screen in production).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;context&quot;&gt;Context&lt;&#x2F;h3&gt;
&lt;p&gt;In Dart, type safety is strict. When you parse a JSON response, you typically map a &lt;code&gt;Map&amp;lt;String, dynamic&amp;gt;&lt;&#x2F;code&gt; into a strongly-typed Dart object.&lt;&#x2F;p&gt;
&lt;p&gt;Our model looked like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;dart&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; UserProfile&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  final&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; String&lt;&#x2F;span&gt;&lt;span&gt; name;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  final&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; bool&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;?&lt;&#x2F;span&gt;&lt;span&gt; isPremium;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  UserProfile&lt;&#x2F;span&gt;&lt;span&gt;({&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;required&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; this&lt;&#x2F;span&gt;&lt;span&gt;.name,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; this&lt;&#x2F;span&gt;&lt;span&gt;.isPremium});&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  factory&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; UserProfile&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;fromJson&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;Map&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; dynamic&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; json) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; UserProfile&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; json[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;name&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      isPremium&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; json[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;isPremium&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    );&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;what-i-tried&quot;&gt;What I Tried&lt;&#x2F;h3&gt;
&lt;p&gt;I checked the backend documentation (Swagger&#x2F;OpenAPI). The documentation clearly stated that &lt;code&gt;isPremium&lt;&#x2F;code&gt; was a boolean. I assumed the issue was a null value since &lt;code&gt;isPremium&lt;&#x2F;code&gt; might not exist for legacy users, but I had already marked it as nullable (&lt;code&gt;bool?&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-failed&quot;&gt;What Failed&lt;&#x2F;h3&gt;
&lt;p&gt;Ignoring the type cast didn’t work. The exception specifically complained about an &lt;code&gt;int&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-worked-technical-explanation&quot;&gt;What Worked &amp;amp; Technical Explanation&lt;&#x2F;h3&gt;
&lt;p&gt;I intercepted the raw HTTP response body before it hit the Dart parser.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  &amp;quot;name&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Syed&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  &amp;quot;isPremium&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The backend developer had migrated the database, and the new database driver was serializing SQL &lt;code&gt;TINYINT(1)&lt;&#x2F;code&gt; boolean columns into &lt;code&gt;1&lt;&#x2F;code&gt; and &lt;code&gt;0&lt;&#x2F;code&gt; integers in the JSON payload, rather than &lt;code&gt;true&lt;&#x2F;code&gt; and &lt;code&gt;false&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Because &lt;code&gt;json[&#x27;isPremium&#x27;]&lt;&#x2F;code&gt; contained an integer &lt;code&gt;1&lt;&#x2F;code&gt;, Dart crashed when it implicitly tried to cast it to &lt;code&gt;bool?&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;To fix this, I implemented &lt;strong&gt;defensive serialization&lt;&#x2F;strong&gt;. Instead of trusting the API contract blindly, the Dart parser needs to handle potential type coercion gracefully:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;dart&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;factory&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; UserProfile&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;fromJson&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;Map&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; dynamic&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; json) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; Defensive parsing function&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  bool&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;?&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; parseBool&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;dynamic&lt;&#x2F;span&gt;&lt;span&gt; value) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; (value &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; null&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; null&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; (value &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;is&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; bool&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;return&lt;&#x2F;span&gt;&lt;span&gt; value;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; (value &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;is&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; int&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;return&lt;&#x2F;span&gt;&lt;span&gt; value &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; (value &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;is&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; String&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;return&lt;&#x2F;span&gt;&lt;span&gt; value.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;toLowerCase&lt;&#x2F;span&gt;&lt;span&gt;() &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;true&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; null&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; UserProfile&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; json[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;name&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;??&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;Unknown&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    isPremium&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; parseBool&lt;&#x2F;span&gt;&lt;span&gt;(json[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;isPremium&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;]),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  );&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;lessons-learned&quot;&gt;Lessons Learned&lt;&#x2F;h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;API Contracts Drift:&lt;&#x2F;strong&gt; Backend implementations change, and serialization layers can inadvertently alter the types of JSON payloads (especially converting booleans to integers or numbers to strings).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Dart is Unforgiving:&lt;&#x2F;strong&gt; Unlike JavaScript, Dart will not automatically coerce &lt;code&gt;1&lt;&#x2F;code&gt; into &lt;code&gt;true&lt;&#x2F;code&gt;. You must handle the conversion explicitly.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;what-i-would-do-differently&quot;&gt;What I Would Do Differently&lt;&#x2F;h3&gt;
&lt;p&gt;I will use code-generation tools like &lt;code&gt;json_serializable&lt;&#x2F;code&gt; or &lt;code&gt;freezed&lt;&#x2F;code&gt; with custom &lt;code&gt;JsonConverter&lt;&#x2F;code&gt; classes for primitive types. This abstracts away the boilerplate of defensive parsing and ensures the entire app handles API type mismatches consistently.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Flutter Splash Screens vs Native Android Startup Screens</title>
        <published>2026-01-25T00:00:00+00:00</published>
        <updated>2026-01-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Syed Zubyl N
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://syedzubyl.space/blog/flutter-splash-screens/"/>
        <id>https://syedzubyl.space/blog/flutter-splash-screens/</id>
        
        <content type="html" xml:base="https://syedzubyl.space/blog/flutter-splash-screens/">&lt;h3 id=&quot;the-problem&quot;&gt;The Problem&lt;&#x2F;h3&gt;
&lt;p&gt;A client wanted a highly customized, complex, animated Flutter-style splash screen to play the moment the user tapped the app icon. They wanted their branding to animate immediately, replacing the “boring” default app launch experience.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;context&quot;&gt;Context&lt;&#x2F;h3&gt;
&lt;p&gt;When an Android application launches, there is an unavoidable window of time where the OS loads the app process into memory. During this time, Android displays a system-controlled launch screen. With the introduction of Android 12, Google enforced the &lt;code&gt;SplashScreen&lt;&#x2F;code&gt; API, standardizing this experience to show the app icon and a background color.&lt;&#x2F;p&gt;
&lt;p&gt;Flutter operates inside an Android Activity. The Flutter engine must initialize, load the Dart isolate, and render the first frame. This takes time.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-i-tried&quot;&gt;What I Tried&lt;&#x2F;h3&gt;
&lt;p&gt;The client wanted the animation to start instantly. I initially tried putting the animation directly into the first Flutter widget loaded by &lt;code&gt;runApp()&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-failed&quot;&gt;What Failed&lt;&#x2F;h3&gt;
&lt;p&gt;This resulted in a jarring experience:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;User taps app.&lt;&#x2F;li&gt;
&lt;li&gt;System shows Android 12 static splash screen (icon + background).&lt;&#x2F;li&gt;
&lt;li&gt;System splash disappears.&lt;&#x2F;li&gt;
&lt;li&gt;White flash (briefly, as Flutter attaches).&lt;&#x2F;li&gt;
&lt;li&gt;Flutter animated splash screen begins.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;It looked like two separate splash screens playing back-to-back.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-worked-technical-explanation&quot;&gt;What Worked &amp;amp; Technical Explanation&lt;&#x2F;h3&gt;
&lt;p&gt;The actual limitation is that the system-controlled Android launch splash occurs &lt;em&gt;before&lt;&#x2F;em&gt; the Flutter UI is ready, especially under Android 12+ splash-screen rules. A Flutter animation can begin after the Flutter engine&#x2F;UI becomes available, but it does not replace the system launch sequence in the same way.&lt;&#x2F;p&gt;
&lt;p&gt;The correct approach was a hybrid hand-off:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Configure the native Android 12 &lt;code&gt;SplashScreen&lt;&#x2F;code&gt; via &lt;code&gt;styles.xml&lt;&#x2F;code&gt; to match the exact background color and static logo of the first frame of the Flutter animation.&lt;&#x2F;li&gt;
&lt;li&gt;Use the &lt;code&gt;flutter_native_splash&lt;&#x2F;code&gt; package to hold the native splash screen up until Flutter is fully rendered.&lt;&#x2F;li&gt;
&lt;li&gt;Once Flutter renders its first frame, immediately begin the complex Flutter animation from the exact state the native splash left off.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&amp;lt;!-- android&#x2F;app&#x2F;src&#x2F;main&#x2F;res&#x2F;values-v31&#x2F;styles.xml --&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;style&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;LaunchTheme&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; parent&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;Theme.SplashScreen&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;item&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;windowSplashScreenBackground&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;@color&#x2F;brand_background&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;item&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;item&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;windowSplashScreenAnimatedIcon&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;@drawable&#x2F;launch_icon&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;item&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;item&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;postSplashScreenTheme&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;@style&#x2F;NormalTheme&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;item&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;style&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;lessons-learned&quot;&gt;Lessons Learned&lt;&#x2F;h3&gt;
&lt;p&gt;You cannot fight the OS. The Android lifecycle dictates what happens before your application code runs. Understanding the boundaries between native Android processes and the Flutter engine is critical for smooth UX.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-i-would-do-differently&quot;&gt;What I Would Do Differently&lt;&#x2F;h3&gt;
&lt;p&gt;I will clarify the distinction between “OS Launch Screen” and “App Onboarding Animation” to clients early on. Managing expectations around what happens in the first 500ms of an app’s lifecycle prevents impossible requests later.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Android App Bar Compatibility: When Client Requirements Meet Platform Defaults</title>
        <published>2026-01-10T00:00:00+00:00</published>
        <updated>2026-01-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Syed Zubyl N
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://syedzubyl.space/blog/android-app-bar-compatibility/"/>
        <id>https://syedzubyl.space/blog/android-app-bar-compatibility/</id>
        
        <content type="html" xml:base="https://syedzubyl.space/blog/android-app-bar-compatibility/">&lt;h3 id=&quot;the-problem&quot;&gt;The Problem&lt;&#x2F;h3&gt;
&lt;p&gt;During a recent project, a client expected a highly specific app-bar appearance. The request was straightforward on paper: make the Flutter application look exactly like an Android-native&#x2F;default implementation, matching a specific older Android version’s aesthetic they were used to.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;context&quot;&gt;Context&lt;&#x2F;h3&gt;
&lt;p&gt;When building cross-platform applications with Flutter, the framework paints every pixel on the screen using its own rendering engine (Skia&#x2F;Impeller). It does not use OEM native widgets. While Flutter’s Material widgets do an incredible job of mimicking native Android components, they default to the current Material Design guidelines (Material 3). The client, however, wanted an exact replica of an older Android native app bar behavior and shadowing.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-i-tried&quot;&gt;What I Tried&lt;&#x2F;h3&gt;
&lt;p&gt;I initially tried overriding the &lt;code&gt;AppBar&lt;&#x2F;code&gt; theme properties:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;dart&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;AppBar&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  elevation&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 4.0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  shadowColor&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Colors&lt;&#x2F;span&gt;&lt;span&gt;.black.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;withOpacity&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;0.5&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  backgroundColor&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Theme&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;of&lt;&#x2F;span&gt;&lt;span&gt;(context).primaryColor,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Text&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;Dashboard&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This got us close, but the exact gradient of the shadow and the specific typographic alignment didn’t perfectly match the legacy Android native view the client had side-by-side on their device.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-failed&quot;&gt;What Failed&lt;&#x2F;h3&gt;
&lt;p&gt;Attempting to perfectly mimic a deprecated OEM native view using a modern cross-platform framework’s rendering engine became a game of diminishing returns. I tried building a completely custom &lt;code&gt;PreferredSizeWidget&lt;&#x2F;code&gt; with complex box shadows, but it felt brittle.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-worked-technical-explanation&quot;&gt;What Worked &amp;amp; Technical Explanation&lt;&#x2F;h3&gt;
&lt;p&gt;The solution was a conversation rather than a code hack. The requested result could not be reproduced exactly with the existing implementation constraints without completely changing the approach or hardcoding fragile design values.&lt;&#x2F;p&gt;
&lt;p&gt;I explained to the client the difference between native Android UI conventions (which vary wildly between Android 10, 12, and 14) and Flutter’s unified rendering model. I demonstrated how adhering to Flutter’s Material 3 defaults actually provided &lt;em&gt;better&lt;&#x2F;em&gt; compatibility and visual consistency across all modern Android devices, rather than forcing an older, device-specific look.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;lessons-learned&quot;&gt;Lessons Learned&lt;&#x2F;h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Client expectations vs. platform realities:&lt;&#x2F;strong&gt; Clients often don’t know the difference between a “native default” and a “custom design.” To them, what they see on their personal phone is the “default.”&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Push back with technical reasoning:&lt;&#x2F;strong&gt; Instead of spending hours tweaking pixel-perfect shadows to match an outdated OS, it’s better to explain the benefits of modern platform defaults.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;what-i-would-do-differently&quot;&gt;What I Would Do Differently&lt;&#x2F;h3&gt;
&lt;p&gt;Next time, I will establish the baseline UI components using Flutter’s default Material 3 implementation during the very first design review, ensuring the client signs off on the framework’s native look before development begins.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
