<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>Syed Zubyl N | Software Developer - Mobile</title>
      <link>https://syedzubyl.space</link>
      <description>Syed Zubyl N — Software Developer focused on Java, Spring Boot, backend development, REST APIs, databases, Flutter and production-oriented applications.</description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://syedzubyl.space/tags/mobile/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Mon, 18 May 2026 00:00:00 +0000</lastBuildDate>
      <item>
          <title>What I Learned Building Flutter Applications Around Real APIs</title>
          <pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate>
          <author>Syed Zubyl N</author>
          <link>https://syedzubyl.space/blog/building-flutter-apps-around-apis/</link>
          <guid>https://syedzubyl.space/blog/building-flutter-apps-around-apis/</guid>
          <description 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;
</description>
      </item>
      <item>
          <title>Designing a Reliable API Layer for a Mobile Application</title>
          <pubDate>Sun, 22 Mar 2026 00:00:00 +0000</pubDate>
          <author>Syed Zubyl N</author>
          <link>https://syedzubyl.space/blog/designing-reliable-api-layer/</link>
          <guid>https://syedzubyl.space/blog/designing-reliable-api-layer/</guid>
          <description xml:base="https://syedzubyl.space/blog/designing-reliable-api-layer/">&lt;h3 id=&quot;the-problem&quot;&gt;The Problem&lt;&#x2F;h3&gt;
&lt;p&gt;In the early stages of a mobile LMS application, HTTP requests were scattered directly inside UI widgets. Buttons would execute &lt;code&gt;http.get&lt;&#x2F;code&gt;, parse JSON inline, and call &lt;code&gt;setState&lt;&#x2F;code&gt;. This quickly became unmaintainable. Token expiration caused random crashes, error handling was inconsistent, and mocking data for tests was impossible.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;context&quot;&gt;Context&lt;&#x2F;h3&gt;
&lt;p&gt;A robust mobile application requires a strict separation of concerns. The UI should only care about &lt;em&gt;displaying&lt;&#x2F;em&gt; state, not fetching it. The networking layer must handle token injection, timeouts, retries, and error standardization before the data ever reaches the UI.&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 to create a single massive &lt;code&gt;ApiService&lt;&#x2F;code&gt; class that contained every single endpoint in the application.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-failed&quot;&gt;What Failed&lt;&#x2F;h3&gt;
&lt;p&gt;The &lt;code&gt;ApiService&lt;&#x2F;code&gt; file grew to thousands of lines. When multiple developers worked on different features (Authentication, Courses, User Profile), merge conflicts became a daily nightmare. Furthermore, handling token refreshes on a per-method basis resulted in heavily duplicated code.&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 re-architected the API layer into a layered, modular system based on the Repository Pattern and Interceptors.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;1. The Base HTTP Client (The Interceptor Layer)&lt;&#x2F;strong&gt;
Instead of using raw &lt;code&gt;http.Client&lt;&#x2F;code&gt;, I implemented a wrapper (using packages like &lt;code&gt;dio&lt;&#x2F;code&gt; or custom &lt;code&gt;http.BaseClient&lt;&#x2F;code&gt; implementations) that intercepts every request.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Request Interceptor:&lt;&#x2F;strong&gt; Automatically injects the &lt;code&gt;Authorization: Bearer &amp;lt;token&amp;gt;&lt;&#x2F;code&gt; header from secure storage into every outgoing request.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Response Interceptor:&lt;&#x2F;strong&gt; Global error handling. If a &lt;code&gt;401 Unauthorized&lt;&#x2F;code&gt; is returned, the interceptor pauses all outgoing requests, silently hits the refresh-token endpoint, updates secure storage, and retries the failed requests.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;2. Domain-Specific Repositories&lt;&#x2F;strong&gt;
Instead of one massive API class, I created focused repositories:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AuthRepository&lt;&#x2F;code&gt;: Handles login, registration, and token management.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;CourseRepository&lt;&#x2F;code&gt;: Fetches course lists and lesson details.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;VideoRepository&lt;&#x2F;code&gt;: Manages video streaming URLs and playback telemetry.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;3. Standardized Result Wrappers&lt;&#x2F;strong&gt;
Instead of throwing exceptions directly into the UI, repositories return a standardized Result type (often using Dart’s &lt;code&gt;Either&lt;&#x2F;code&gt; pattern via the &lt;code&gt;fpdart&lt;&#x2F;code&gt; or &lt;code&gt;dartz&lt;&#x2F;code&gt; package).&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;Future&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;Either&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;ApiFailure&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Course&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; getCourseDetails&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span&gt; id) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;async&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;  try&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&gt; response &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;= await&lt;&#x2F;span&gt;&lt;span&gt; _client.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;&#x2F;courses&#x2F;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&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; Right&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;Course&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;(response.data));&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  } &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;on&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; DioError&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; catch&lt;&#x2F;span&gt;&lt;span&gt; (e) {&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; Left&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;ApiFailure&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;fromDioError&lt;&#x2F;span&gt;&lt;span&gt;(e));&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;p&gt;Separating the UI from the network layer is non-negotiable for production apps. The complexity of handling network latency, offline states, and token lifecycles must be encapsulated so the UI can remain declarative and clean.&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 would implement local caching (using SQLite) much earlier in the architecture. While the API layer was robust, relying entirely on network availability resulted in a poor experience on slow connections. Integrating a repository that checks local storage &lt;em&gt;before&lt;&#x2F;em&gt; hitting the API is the next logical step.&lt;&#x2F;p&gt;
</description>
      </item>
    </channel>
</rss>
