<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Gidudu Nicholas]]></title><description><![CDATA[Nicholas is a Flutter & Dart expert, open-source contributor, and community builder dedicated to empowering developers across Africa. As the lead organizer of Flutter Kampala and GDG Bugiri, he has impacted hundreds of developers through talks, mentorship, and hands-on learning. His work spans scalable mobile architecture, backend systems with Serverpod, and fintech solutions.]]></description><link>https://gidudunicholas.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/5e6a520daf89662115c0ea14/b492bcef-8ac9-442a-8d5c-a85c31c70b38.jpg</url><title>Gidudu Nicholas</title><link>https://gidudunicholas.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 07 Apr 2026 12:19:12 GMT</lastBuildDate><atom:link href="https://gidudunicholas.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[What Happens When Your Flutter App Stops Being Small
]]></title><description><![CDATA[Flutter is easy to start with.
You build a few screens, connect an API, add some state management… and everything works.
Until it doesn’t.
At some point, your app stops being “small”.
And then:

build]]></description><link>https://gidudunicholas.dev/what-happens-when-your-flutter-app-stops-being-small</link><guid isPermaLink="true">https://gidudunicholas.dev/what-happens-when-your-flutter-app-stops-being-small</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Mobile Development]]></category><category><![CDATA[mobile app development]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[app development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[Clean Architecture]]></category><dc:creator><![CDATA[Gidudu Nicholas]]></dc:creator><pubDate>Tue, 07 Apr 2026 08:15:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/5e6a520daf89662115c0ea14/ee514a1e-4285-456c-a4f8-d54a7200c8dc.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Flutter is easy to start with.</p>
<p>You build a few screens, connect an API, add some state management… and everything works.</p>
<p>Until it doesn’t.</p>
<p>At some point, your app stops being “small”.</p>
<p>And then:</p>
<ul>
<li><p>builds slow down</p>
</li>
<li><p>bugs get harder to trace</p>
</li>
<li><p>Simple features take longer</p>
</li>
<li><p>The codebase feels heavy</p>
</li>
</ul>
<p>This is the shift from:</p>
<blockquote>
<p>simple → scalable</p>
</blockquote>
<p>And if you don’t adjust, things start to break.</p>
<h2>The Illusion of Simplicity</h2>
<p>Flutter makes it easy to move fast early on.</p>
<p>But what works for:</p>
<ul>
<li><p>5 screens</p>
</li>
<li><p>1–2 developers</p>
</li>
</ul>
<p>doesn’t scale to:</p>
<ul>
<li><p>50+ screens</p>
</li>
<li><p>multiple features</p>
</li>
<li><p>long-term maintenance</p>
</li>
</ul>
<p>👉 The issue isn’t Flutter — it’s structure.</p>
<h2>Signs Your App Is Struggling</h2>
<p>You’ll notice:</p>
<ul>
<li><p>“Where is this logic coming from?”</p>
</li>
<li><p>state behaving unpredictably</p>
</li>
<li><p>features breaking each other</p>
</li>
<li><p>development slowing down</p>
</li>
<li><p>onboarding becoming difficult</p>
</li>
</ul>
<p>This is usually a <strong>structure problem</strong>, not a code problem.</p>
<h2>The Root Cause</h2>
<p>Most apps start like this:</p>
<pre><code class="language-plaintext">lib/
  screens/
  widgets/
  services/
</code></pre>
<p>It works early on.</p>
<p>But it’s <strong>layer-based</strong>, not <strong>feature-based</strong>.</p>
<h2>Layer-Based vs Feature-Based</h2>
<pre><code class="language-plaintext">Layer-Based Structure (Hard to scale)

UI (screens/) ─────────────┐
Widgets (widgets/) ────────┼──→ Feature A
Services (services/) ──────┼──→ Feature B
Models (models/) ──────────┘

❌ Logic for one feature is scattered everywhere


Feature-Based Structure (Scalable)

features/
  auth/ ───────────────→ UI + Logic + Data (together)
  dashboard/ ──────────→ UI + Logic + Data (together)
  profile/ ────────────→ UI + Logic + Data (together)

✅ Each feature is self-contained
</code></pre>
<p>👉 This is where most scaling problems begin.</p>
<h2>The Shift: Think in Features</h2>
<p>Instead of organizing by layers:</p>
<pre><code class="language-plaintext">screens/
services/
widgets/
</code></pre>
<p>Move to:</p>
<pre><code class="language-plaintext">features/
  auth/
  dashboard/
</code></pre>
<p>Each feature owns:</p>
<ul>
<li><p>its UI</p>
</li>
<li><p>its logic</p>
</li>
<li><p>its data</p>
</li>
</ul>
<p>👉 This reduces chaos and improves maintainability.</p>
<h2>State at Scale</h2>
<p>State issues usually come from poor organization.</p>
<p>Common mistake:  </p>
<p>👉 too much global state</p>
<p>Better approach:  </p>
<p>👉 scope state to features</p>
<blockquote>
<p>Local first. Global only when needed.</p>
</blockquote>
<h1>Decoupling Matters</h1>
<p>When everything depends on everything, change becomes risky.</p>
<p>Use:</p>
<ul>
<li><p>abstractions</p>
</li>
<li><p>clear boundaries  </p>
</li>
<li><p>Instead of:</p>
</li>
</ul>
<pre><code class="language-plaintext">authService.login();
</code></pre>
<p>Prefer:</p>
<pre><code class="language-plaintext">AuthRepository.login();
</code></pre>
<p>👉 Small change, big flexibility.</p>
<h2>Coupled vs Decoupled System</h2>
<pre><code class="language-plaintext">Tightly Coupled (Fragile)

UI ───→ Service ───→ Database
  └──────────────→ API
        └────────→ Other Feature

❌ Everything depends on everything
❌ Changes break unrelated parts


Decoupled (Scalable)

UI ───→ Repository ───→ Data Source
            │
            ├──→ API
            └──→ Local DB

Features communicate through contracts (interfaces)

✅ Clear boundaries
✅ Safer changes
</code></pre>
<p>👉 This is what allows your app to grow safely.</p>
<h2>Scaling Mindset</h2>
<p>As your app grows:</p>
<ul>
<li><p>think in modules</p>
</li>
<li><p>define clear boundaries</p>
</li>
<li><p>design for change</p>
</li>
<li><p>Avoid overengineering early</p>
</li>
</ul>
<h2>Testing &amp; Tooling</h2>
<p>At scale, you need:</p>
<ul>
<li><p>tests (unit + integration)</p>
</li>
<li><p>logging</p>
</li>
<li><p>performance tools</p>
</li>
<li><p>error tracking</p>
</li>
</ul>
<h2>Lessons</h2>
<ul>
<li><p>Small apps hide future complexity</p>
</li>
<li><p>Structure matters more than tools</p>
</li>
<li><p>Boundaries make systems manageable</p>
</li>
<li><p>Architecture should evolve, not be perfect</p>
</li>
</ul>
<h2>Final Thoughts</h2>
<p>Flutter doesn’t break as your app grows.</p>
<p>Poor structure does.</p>
<h1>💬 Final Take</h1>
<p>Small apps are easy.</p>
<p>Scalable apps are designed.</p>
]]></content:encoded></item><item><title><![CDATA[Can AI Work Offline in Flutter? Here’s What’s Possible]]></title><description><![CDATA[AI is everywhere in modern apps. From chat assistants to recommendations and automation, most Flutter apps today rely on cloud-based models like Gemini and other APIs.
But there’s a problem:
What happ]]></description><link>https://gidudunicholas.dev/can-ai-work-offline-in-flutter-here-s-what-s-possible</link><guid isPermaLink="true">https://gidudunicholas.dev/can-ai-work-offline-in-flutter-here-s-what-s-possible</guid><dc:creator><![CDATA[Gidudu Nicholas]]></dc:creator><pubDate>Tue, 07 Apr 2026 02:19:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/5e6a520daf89662115c0ea14/5378f225-ffd7-42fe-806d-79262e2f2b67.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>AI is everywhere in modern apps. From chat assistants to recommendations and automation, most Flutter apps today rely on cloud-based models like Gemini and other APIs.</p>
<p>But there’s a problem:</p>
<p><strong>What happens when there’s no internet?</strong></p>
<p>For many users — especially in regions with unstable or expensive connectivity — this isn’t an edge case.</p>
<p>It’s the default.</p>
<p>So the real question becomes:</p>
<blockquote>
<p><strong>Can AI actually work offline in Flutter apps?</strong></p>
</blockquote>
<p><strong>Short answer:</strong> Yes — but not in the way most people expect.</p>
<h2>The Misconception About “Offline AI”</h2>
<p>When most developers think about AI, they imagine:</p>
<ul>
<li><p>Large language models</p>
</li>
<li><p>Real-time API calls</p>
</li>
<li><p>Cloud processing</p>
</li>
</ul>
<p>And naturally assume:</p>
<blockquote>
<p><strong>“AI = Internet required”</strong></p>
</blockquote>
<p>That’s no longer entirely true.</p>
<p>Today, we have multiple ways to bring intelligence into apps — even without a constant connection.</p>
<h2>Level 1: Fully Offline AI (On-Device Models)</h2>
<p>This is the closest thing to true offline AI.</p>
<p>Instead of calling an API, you run a model directly on the device.</p>
<h3>Modern On-Device Models (Gemma and Beyond)</h3>
<p>Recent advances in lightweight models like Gemma are changing what’s possible on-device.</p>
<p>These models are designed to:</p>
<ul>
<li><p>Run efficiently on local or edge hardware</p>
</li>
<li><p>Support tasks like summarization, Q&amp;A, and structured generation</p>
</li>
<li><p>Operate with reduced memory and compute requirements</p>
</li>
</ul>
<p>This makes them suitable for:</p>
<ul>
<li><p>Offline assistants</p>
</li>
<li><p>Local reasoning</p>
</li>
<li><p>Privacy-sensitive applications</p>
</li>
</ul>
<h2>How This Fits Into Flutter</h2>
<p>Flutter itself doesn’t run these models directly.</p>
<p>Instead, it acts as the UI and orchestration layer.</p>
<p>Typical integration looks like:</p>
<ul>
<li><p>Native Android/iOS layers</p>
</li>
<li><p>TensorFlow Lite</p>
</li>
<li><p>ONNX Runtime</p>
</li>
<li><p>llama. cpp-based runtimes (via FFI)</p>
</li>
</ul>
<h3>Architecture</h3>
<pre><code class="language-plaintext">Flutter → UI + app logic  
Native Layer → Model inference  
</code></pre>
<h2>What’s Actually Possible</h2>
<p>With on-device models like Gemma, you can build:</p>
<ul>
<li><p>Offline summarization tools</p>
</li>
<li><p>Local copilots</p>
</li>
<li><p>Semantic search</p>
</li>
<li><p>Structured data extraction</p>
</li>
<li><p>Lightweight conversational assistants</p>
</li>
</ul>
<h2>Limitations (Let’s Be Honest)</h2>
<p>Offline AI comes with trade-offs:</p>
<ul>
<li><p>Smaller models → less capable than cloud LLMs</p>
</li>
<li><p>Performance depends on device hardware</p>
</li>
<li><p>Memory and battery constraints</p>
</li>
<li><p>Slower inference on low-end devices</p>
</li>
</ul>
<h3>Key Insight</h3>
<p>Models like Gemma don’t replace cloud AI — they make offline AI practical.</p>
<h2>Level 2: Hybrid AI (The Real-World Approach)</h2>
<p>This is where most production systems should live.</p>
<p>Instead of choosing between offline or online:</p>
<p>You combine both.</p>
<h3>How It Works</h3>
<ul>
<li><p>When online → use powerful cloud models (e.g., Gemini)</p>
</li>
<li><p>When offline → fall back to local intelligence (Gemma or cached logic)</p>
</li>
</ul>
<h3>Example</h3>
<pre><code class="language-dart">if (isOnline) {
  return await cloudAI.process(input);
} else {
  return await localAI.process(input);
}
</code></pre>
<h3>Real Use Cases</h3>
<ul>
<li><p>Educational assistants</p>
</li>
<li><p>Fintech insights</p>
</li>
<li><p>Productivity tools</p>
</li>
<li><p>Recommendation systems</p>
</li>
</ul>
<h3>Why This Works</h3>
<ul>
<li><p>Best quality when online</p>
</li>
<li><p>Reliability when offline</p>
</li>
<li><p>Consistent user experience</p>
</li>
</ul>
<h3>Key Insight</h3>
<p>Hybrid AI is not a compromise — it’s the architecture of real-world apps.</p>
<h2>Level 3: “Smart Offline” Without Models</h2>
<p>This is the most underrated approach.</p>
<p>Sometimes, you don’t need a model at all.</p>
<p>You just need good system design.</p>
<h3>Techniques</h3>
<ul>
<li><p>Cached responses</p>
</li>
<li><p>Rule-based logic</p>
</li>
<li><p>Precomputed recommendations</p>
</li>
<li><p>Local data processing</p>
</li>
<li><p>Offline queues</p>
</li>
</ul>
<h3>Example</h3>
<p>Instead of generating everything with AI:</p>
<ul>
<li><p>Reuse known UI patterns</p>
</li>
<li><p>Cache previous responses</p>
</li>
<li><p>Map user intent → predefined actions</p>
</li>
</ul>
<hr />
<h3>Useful Flutter Tools</h3>
<ul>
<li><p>Hive / Isar for local storage</p>
</li>
<li><p>Offline queue systems</p>
</li>
<li><p>Structured UI templates</p>
</li>
</ul>
<hr />
<h3>Key Insight</h3>
<p>Users don’t care if it’s AI — they care if it works.</p>
<h2>Designing Offline-First AI Systems</h2>
<p>To make this work, you must design for offline from the start.</p>
<h3>Separate Intelligence Layers</h3>
<ul>
<li><p>Cloud layer (Gemini)</p>
</li>
<li><p>Local layer (Gemma or rules)</p>
</li>
</ul>
<h3>Cache Aggressively</h3>
<p>Store:</p>
<ul>
<li><p>Responses</p>
</li>
<li><p>Embeddings</p>
</li>
<li><p>UI structures</p>
</li>
</ul>
<h3>Always Have Fallbacks</h3>
<p>Never depend entirely on AI.</p>
<p>Provide:</p>
<ul>
<li><p>Default responses</p>
</li>
<li><p>Fallback UI</p>
</li>
<li><p>Safe states</p>
</li>
</ul>
<h3>Queue Actions</h3>
<p>If something requires internet:</p>
<ul>
<li><p>Don’t fail — queue it</p>
</li>
<li><p>Retry later</p>
</li>
<li><p>Sync automatically</p>
</li>
</ul>
<h3>Design for Latency</h3>
<p>Even offline systems must feel fast:</p>
<ul>
<li><p>Instant feedback</p>
</li>
<li><p>Progressive updates</p>
</li>
<li><p>Clear loading states</p>
</li>
</ul>
<h2>Real Constraints</h2>
<p>Offline AI isn’t magic.</p>
<h3>Device Limitations</h3>
<ul>
<li><p>CPU/GPU constraints</p>
</li>
<li><p>Memory limits</p>
</li>
<li><p>Battery usage</p>
</li>
</ul>
<h3>Model Limitations</h3>
<ul>
<li><p>Smaller context</p>
</li>
<li><p>Reduced reasoning capability</p>
</li>
</ul>
<h3>Platform Differences</h3>
<ul>
<li><p>Android vs iOS hardware</p>
</li>
<li><p>Varying performance</p>
</li>
</ul>
<h3>Debugging Complexity</h3>
<ul>
<li><p>Harder to trace issues</p>
</li>
<li><p>Limited observability</p>
</li>
</ul>
<h2>Lessons Learned</h2>
<ul>
<li><p>Offline is not optional — in many regions, it’s the default</p>
</li>
<li><p>Hybrid systems win — pure cloud or pure offline rarely works</p>
</li>
<li><p>UX &gt; AI — reliability beats intelligence</p>
</li>
<li><p>Simplicity scales — rules + caching often outperform complex models</p>
</li>
</ul>
<hr />
<h2>Final Thoughts</h2>
<p>So, can AI work offline in Flutter?</p>
<p>Yes.</p>
<p>But the better question is:</p>
<blockquote>
<p>How should AI behave when the internet is unreliable?</p>
</blockquote>
<p>The best apps don’t just add AI.</p>
<p>They build systems that:</p>
<ul>
<li><p>Adapt</p>
</li>
<li><p>Degrade gracefully</p>
</li>
<li><p>Remain useful</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Building Agentic Flutter Apps with Gemini/Gemma]]></title><description><![CDATA[AI has moved beyond static chatbots. The next wave of applications are agentic apps — AI-powered apps that can reason, act, and adapt dynamically to user needs. Instead of waiting for pre-defined screens, these apps use LLMs like Gemini (Google DeepM...]]></description><link>https://gidudunicholas.dev/building-agentic-flutter-apps-with-geminigemma</link><guid isPermaLink="true">https://gidudunicholas.dev/building-agentic-flutter-apps-with-geminigemma</guid><dc:creator><![CDATA[Gidudu Nicholas]]></dc:creator><pubDate>Mon, 18 Aug 2025 07:54:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1755503642572/18d1b373-a42d-42b6-b66c-c7c0c6e3f650.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>AI has moved beyond static chatbots. The next wave of applications are <strong>agentic apps</strong> — AI-powered apps that can <strong>reason, act, and adapt dynamically</strong> to user needs. Instead of waiting for pre-defined screens, these apps use LLMs like <strong>Gemini (Google DeepMind)</strong> and <strong>Gemma (open-source)</strong> to decide <em>what the UI should look like, what data to fetch, and which action to take next</em>.</p>
<p>In this article, we’ll explore how you can build an <strong>agentic Flutter app</strong> that integrates with Gemini/Gemma to provide an adaptive, intelligent user experience.</p>
<h2 id="heading-what-are-agentic-apps">What Are Agentic Apps?</h2>
<p>An <strong>agentic app</strong> is different from a traditional AI app:</p>
<ul>
<li><p><strong>Traditional AI app</strong> → User asks a question, AI responds in text.</p>
</li>
<li><p><strong>Agentic app</strong> → AI can:</p>
<ul>
<li><p>Change the UI dynamically (e.g., show a chart if the user asks for data).</p>
</li>
<li><p>Trigger system actions (e.g., call an API, schedule a task).</p>
</li>
<li><p>Remember context and adapt over time.</p>
</li>
</ul>
</li>
</ul>
<p>Think of it as <strong>ChatGPT + Actions + UI Generation</strong> inside Flutter.</p>
<h2 id="heading-why-flutter">Why Flutter?</h2>
<p>Flutter is uniquely positioned to power agentic apps because of:</p>
<ul>
<li><p><strong>Dynamic UI rendering</strong> → Widgets can be generated from JSON or AI instructions.</p>
</li>
<li><p><strong>Cross-platform reach</strong> → One agent can power mobile, web, and desktop.</p>
</li>
<li><p><strong>Rich ecosystem</strong> → Easy integration with APIs, state management, and local storage.</p>
</li>
</ul>
<h2 id="heading-example-use-case-ai-travel-planner">Example Use Case: AI Travel Planner</h2>
<p>Let’s say you’re building a <strong>travel planner agent</strong>:</p>
<ol>
<li><p>User: <em>“Plan me a 3-day trip to Nairobi under $500.”</em></p>
</li>
<li><p>Gemini: Returns structured JSON describing the itinerary.</p>
</li>
<li><p>Flutter: Renders a custom itinerary UI with cards, maps, and action buttons.</p>
</li>
</ol>
<h2 id="heading-high-level-architecture">High-Level Architecture</h2>
<pre><code class="lang-dart">+-------------------+
|   Flutter App     |
|-------------------|
| - Dynamic UI      |
| - Action Handlers |
+---------+---------+
          |
          v
+-------------------+
| Gemini / Gemma    |
| (Reason + Output) |
+---------+---------+
          |
          v
+-------------------+
| Backend / APIs    |
| (Flights, Hotels) |
+-------------------+
</code></pre>
<ul>
<li><p>Flutter handles <strong>UI rendering &amp; action triggers</strong>.</p>
</li>
<li><p>Gemini/Gemma handles <strong>reasoning &amp; planning</strong>.</p>
</li>
<li><p>Backend APIs provide <strong>real-world data</strong>.</p>
</li>
</ul>
<h2 id="heading-flutter-gemini-example">Flutter + Gemini Example</h2>
<p>Here’s a simplified flow:</p>
<h3 id="heading-step-1-install-http-or-googlegenerativeai">Step 1: Install HTTP or <code>google_generative_ai</code></h3>
<pre><code class="lang-dart">dependencies:
  http: ^<span class="hljs-number">1.2</span><span class="hljs-number">.0</span>
  google_generative_ai: ^<span class="hljs-number">0.4</span><span class="hljs-number">.2</span>
</code></pre>
<h3 id="heading-step-2-call-gemini-gemma">Step 2: Call Gemini / Gemma</h3>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:google_generative_ai/google_generative_ai.dart'</span>;

<span class="hljs-keyword">final</span> model = GenerativeModel(
  model: <span class="hljs-string">'gemini-pro'</span>,
  apiKey: <span class="hljs-string">'YOUR_API_KEY'</span>,
);

Future&lt;<span class="hljs-built_in">String</span>&gt; askGemini(<span class="hljs-built_in">String</span> prompt) <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">final</span> response = <span class="hljs-keyword">await</span> model.generateContent([Content.text(prompt)]);
  <span class="hljs-keyword">return</span> response.text ?? <span class="hljs-string">"No response"</span>;
}
</code></pre>
<h3 id="heading-step-3-parse-ai-output-into-widgets">Step 3: Parse AI Output Into Widgets</h3>
<p>If Gemini outputs JSON like:</p>
<pre><code class="lang-dart">{
  <span class="hljs-string">"type"</span>: <span class="hljs-string">"itinerary"</span>,
  <span class="hljs-string">"days"</span>: [
    { <span class="hljs-string">"day"</span>: <span class="hljs-number">1</span>, <span class="hljs-string">"activity"</span>: <span class="hljs-string">"Visit Nairobi National Park"</span> },
    { <span class="hljs-string">"day"</span>: <span class="hljs-number">2</span>, <span class="hljs-string">"activity"</span>: <span class="hljs-string">"Safari at Maasai Mara"</span> }
  ]
}
</code></pre>
<p>You can map it into Flutter UI:</p>
<pre><code class="lang-dart">Widget buildItinerary(<span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">dynamic</span>&gt; days) {
  <span class="hljs-keyword">return</span> ListView.builder(
    itemCount: days.length,
    itemBuilder: (context, index) {
      <span class="hljs-keyword">final</span> day = days[index];
      <span class="hljs-keyword">return</span> Card(
        child: ListTile(
          title: Text(<span class="hljs-string">"Day <span class="hljs-subst">${day[<span class="hljs-string">'day'</span>]}</span>"</span>),
          subtitle: Text(day[<span class="hljs-string">'activity'</span>]),
        ),
      );
    },
  );
}
</code></pre>
<h2 id="heading-adding-agency-actions-decisions">Adding Agency: Actions + Decisions</h2>
<p>The real power comes when the AI doesn’t just generate text, but decides <strong>what the app should do</strong>.</p>
<p>For example:</p>
<pre><code class="lang-dart">{
  <span class="hljs-string">"action"</span>: <span class="hljs-string">"fetch_hotels"</span>,
  <span class="hljs-string">"location"</span>: <span class="hljs-string">"Nairobi"</span>,
  <span class="hljs-string">"budget"</span>: <span class="hljs-number">200</span>
}
</code></pre>
<p>Flutter can interpret this and <strong>call your backend API</strong>, then update the UI accordingly.</p>
<h2 id="heading-challenges-to-consider">Challenges to Consider</h2>
<ul>
<li><p><strong>Security</strong> → Don’t let AI directly execute arbitrary actions without validation.</p>
</li>
<li><p><strong>Prompt Engineering</strong> → Design prompts that constrain the model’s output (JSON, Markdown, etc.).</p>
</li>
<li><p><strong>Latency</strong> → Gemini calls may be slow; use async loading states.</p>
</li>
<li><p><strong>Costs</strong> → API calls to LLMs can add up; consider caching results.</p>
<h2 id="heading-the-future-fully-adaptive-apps">The Future: Fully Adaptive Apps</h2>
</li>
</ul>
<p>Agentic Flutter apps are just beginning. Imagine apps that:</p>
<ul>
<li><p>Redesign their dashboards based on user goals.</p>
</li>
<li><p>Act as personal assistants inside any workflow.</p>
</li>
<li><p>Seamlessly blend <strong>UI + AI + real-world APIs</strong>.</p>
</li>
</ul>
<p>With Gemini and Gemma, you’re not just building apps you’re building <strong>digital teammates</strong></p>
]]></content:encoded></item><item><title><![CDATA[Enhance Your Flutter App with a Real Logger Instead of print()]]></title><description><![CDATA[When you’re starting in Flutter, it feels natural to sprinkle print() statements everywhere. After all, it’s quick, simple, and gets the job done for debugging small projects. But as your app grows in complexity, relying on print() it becomes more of...]]></description><link>https://gidudunicholas.dev/enhance-your-flutter-app-with-a-real-logger-instead-of-print</link><guid isPermaLink="true">https://gidudunicholas.dev/enhance-your-flutter-app-with-a-real-logger-instead-of-print</guid><category><![CDATA[Flutter]]></category><category><![CDATA[ logger]]></category><category><![CDATA[print]]></category><category><![CDATA[Dart]]></category><category><![CDATA[tech ]]></category><dc:creator><![CDATA[Gidudu Nicholas]]></dc:creator><pubDate>Mon, 18 Aug 2025 07:15:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1755501764643/0d42a65d-9b0b-4406-8a6c-b1003b96d390.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When you’re starting in Flutter, it feels natural to sprinkle <code>print()</code> statements everywhere. After all, it’s quick, simple, and gets the job done for debugging small projects. But as your app grows in complexity, relying on <code>print()</code> it becomes more of a liability than a solution.</p>
<p>Let’s talk about why your Flutter app needs a real logger and what benefits you get by upgrading from <code>print()</code>.</p>
<h2 id="heading-the-problem-with-print">The Problem with <code>print()</code></h2>
<p>Using <code>print()</code> It’s tempting because it’s always available, but it has several serious drawbacks:</p>
<ol>
<li><p><strong>No Log Levels</strong><br /> With <code>print()</code>Every message looks the same. Whether it’s an error, a warning, or just a debug note, they all get dumped into the console without context. That makes it harder to filter out the noise when troubleshooting.</p>
</li>
<li><p><strong>No Persistence</strong><br /> Once your app session ends, all those <code>print()</code> messages vanish. If a user reports a bug, you have no way to retrieve logs from their device to understand what went wrong.</p>
</li>
<li><p><strong>Performance Issues</strong><br /> Excessive <code>print()</code> Statements can slow down your app, especially in release builds. They weren’t designed for structured, large-scale logging.</p>
</li>
<li><p><strong>No Formatting or Metadata</strong><br /> <code>print()</code> doesn’t capture timestamps, file origins, or line numbers. When debugging a crash, you’re left guessing where a message came from.</p>
</li>
</ol>
<h2 id="heading-the-case-for-a-real-logger">The Case for a Real Logger</h2>
<p>A proper logging solution like <code>logger</code>, <code>flutter_logs</code>, or your custom implementation, solves these problems. Here’s how:</p>
<ol>
<li><p><strong>Log Levels for Clarity</strong><br /> Categorize your logs as <code>debug</code>, <code>info</code>, <code>warning</code>, or <code>error</code>. This way, you can focus only on what matters at the moment.</p>
<pre><code class="lang-dart"> <span class="hljs-keyword">final</span> logger = Logger();
 logger.i(<span class="hljs-string">"App started"</span>);
 logger.w(<span class="hljs-string">"Low memory warning"</span>);
 logger.e(<span class="hljs-string">"API request failed"</span>);
</code></pre>
</li>
<li><p><strong>Persistent Logs</strong><br /> Many logging libraries allow saving logs to a file or even uploading them to a remote server. That means you can gather insights from real users when they report problems.</p>
</li>
<li><p><strong>Readable and Structured Output</strong><br /> Instead of raw text, logs can include timestamps, colors, JSON pretty-printing, or stack traces—all of which make debugging much easier.</p>
</li>
<li><p><strong>Production-Ready</strong><br /> With a logger, you can configure whether to log everything in debug mode, but only critical errors in production. This keeps your release builds fast and clean.</p>
</li>
</ol>
<h2 id="heading-example-using-logger-in-flutter">Example: Using <code>logger</code> in Flutter</h2>
<p>Here’s a quick example using the <code>logger</code> package:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:logger/logger.dart'</span>;

<span class="hljs-keyword">var</span> logger = Logger();

<span class="hljs-keyword">void</span> main() {
  logger.d(<span class="hljs-string">"This is a debug message"</span>);
  logger.i(<span class="hljs-string">"This is an info message"</span>);
  logger.w(<span class="hljs-string">"This is a warning"</span>);
  logger.e(<span class="hljs-string">"This is an error with stacktrace"</span>, Exception(<span class="hljs-string">"Oops"</span>), StackTrace.current);
}
</code></pre>
<p>Instead of raw <code>print()</code> output, you’ll get colorful, structured logs in your console.</p>
<h2 id="heading-best-practices-for-logging-in-flutter">Best Practices for Logging in Flutter</h2>
<ul>
<li><p><strong>Use log levels wisely</strong>: Don’t mark everything as an error. Reserve <code>e()</code> for real failures.</p>
</li>
<li><p><strong>Avoid sensitive data</strong>: Never log passwords, tokens, or personal user information.</p>
</li>
<li><p><strong>Clean up in production</strong>: Configure loggers to keep production logs minimal to protect performance and security.</p>
</li>
<li><p><strong>Automate log reporting</strong>: Pair your logger with a crash-reporting service like Firebase Crashlytics or Sentry.</p>
</li>
</ul>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p><code>print()</code> might feel harmless in small projects, but it won’t scale with your app or your career as a Flutter developer. A real logger gives you control, visibility, and professionalism in your codebase.</p>
<p>Next time you catch yourself typing <code>print("debug");</code>, consider swapping it for a proper logging solution. Your future self (and your users) will thank you.</p>
]]></content:encoded></item><item><title><![CDATA[A friendlier guide to taming Flutter jank with DevTools 💙]]></title><description><![CDATA[Picture this: you open your shiny new Flutter app, swipe to the next screen—and the animation hiccups. It’s the software-equivalent of spilling coffee on a white shirt. Luckily, Flutter DevTools is basically a laundromat for performance problems. Let...]]></description><link>https://gidudunicholas.dev/a-friendlier-guide-to-taming-flutter-jank-with-devtools</link><guid isPermaLink="true">https://gidudunicholas.dev/a-friendlier-guide-to-taming-flutter-jank-with-devtools</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Dart]]></category><category><![CDATA[devtools]]></category><category><![CDATA[flutter devtools]]></category><category><![CDATA[debugging]]></category><dc:creator><![CDATA[Gidudu Nicholas]]></dc:creator><pubDate>Sun, 01 Jun 2025 11:31:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748777402024/b0572b0c-a22b-42f4-833b-832dcb1c4cc9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Picture this:</strong> you open your shiny new Flutter app, swipe to the next screen—and the animation hiccups. It’s the software-equivalent of spilling coffee on a white shirt. Luckily, Flutter DevTools is basically a laundromat for performance problems. Let’s walk through it like two colleagues at the same desk.</p>
<h3 id="heading-1-warm-up-laps-choose-the-right-run-mode">1. Warm-up laps: choose the right run mode</h3>
<ul>
<li><p><strong>Profile mode (</strong><code>flutter run --profile</code>) keeps the app almost release-fast but leaves tracing hooks turned on.</p>
</li>
<li><p><strong>Release mode</strong> is the final dress-rehearsal; DevTools can still attach for timeline traces if you launch it with <code>--trace-startup</code>.</p>
</li>
<li><p>Flip on the <strong>Performance overlay</strong> (<code>WidgetsApp.showPerformanceOverlay = true</code>) for an always-visible FPS bar—great for a quick gut-check while you test flows. (<a target="_blank" href="https://docs.flutter.dev/tools/devtools/performance?utm_source=chatgpt.com">Flutter Documentation</a>, <a target="_blank" href="https://docs.flutter.dev/perf/ui-performance?utm_source=chatgpt.com">Flutter Documentation</a>)</p>
</li>
</ul>
<h3 id="heading-2-opening-devtools-without-breaking-flow">2. Opening DevTools without breaking flow</h3>
<p>Most IDEs now show an <strong>“Open DevTools”</strong> button as soon as the app connects to a device. Prefer the CLI?</p>
<pre><code class="lang-bash">flutter pub global run devtools
</code></pre>
<p>It’ll pop up in your browser and automatically hook into the first running Flutter process it finds. (<a target="_blank" href="https://docs.flutter.dev/tools/devtools/performance?utm_source=chatgpt.com">Flutter Documentation</a>)</p>
<h3 id="heading-3-reading-the-performance-page-like-a-story">3. Reading the performance page like a story</h3>
<p>When DevTools launches you land on the <strong>Performance</strong> tab. Think of it as a heartbeat monitor:</p>
<ul>
<li><p><strong>Frame chart</strong> – green bars (good), red bars (dropped frames) over time.</p>
</li>
<li><p><strong>Build vs Raster</strong> – hover a bar to see where you spent the milliseconds. Build spikes usually point to over-eager <code>setState</code>; raster spikes hint at big images or shader work.</p>
</li>
<li><p><strong>Timeline view</strong> – three parallel threads (UI, GPU, platform) scroll by. A long bar on the UI thread means you left heavy lifting on the main isolate. (<a target="_blank" href="https://docs.flutter.dev/tools/devtools/performance?utm_source=chatgpt.com">Flutter Documentation</a>)</p>
</li>
</ul>
<h3 id="heading-4-whats-new-in-2025">4. What’s new in 2025 ✨</h3>
<p>Flutter 3.25’s tooling brought a couple of quality-of-life gifts:</p>
<ul>
<li><p><strong>Smart Widget Inspector</strong> explains <em>why</em> a widget rebuilt, not just <em>that</em> it did, and suggests fixes when it spots anti-patterns.</p>
</li>
<li><p><strong>Live in-app metrics overlay</strong> (toggle-able from the DevTools toolbar) paints frame time and memory use directly on the phone—handy when you’re far from your laptop. (<a target="_blank" href="https://nurfazzi.medium.com/flutter-devtools-2025-smarter-debugging-real-time-insight-32b210b0d837?utm_source=chatgpt.com">Medium</a>)</p>
</li>
</ul>
<h3 id="heading-5-the-jank-hunt-step-by-step">5. The jank hunt, step-by-step</h3>
<ol>
<li><p><strong>Do something that feels slow</strong>, then pause recording.</p>
</li>
<li><p>In the frame chart, tap the tallest red bar.</p>
</li>
<li><p>DevTools opens a <strong>Frame analysis</strong> panel—scroll through the “Rebuilt widgets” list; the culprits usually float to the top.</p>
</li>
<li><p>If the raster time is huge on first launch, it’s probably shader compilation. Capture an <strong>SkSL bundle</strong> once, ship it with your APK, and those first-run stutters vanish. (<a target="_blank" href="https://github.com/flutter/flutter/wiki/Reduce-shader-compilation-jank-using-SkSL-warm-up/8fe088fa9ff424a2070fe7456773ccadee33ff04?utm_source=chatgpt.com">GitHub</a>, <a target="_blank" href="https://medium.com/%40chamithmathukorala/shader-compilation-jank-in-flutter-8cc9d92aa8b3?utm_source=chatgpt.com">Medium</a>)</p>
</li>
</ol>
<h3 id="heading-6-quick-peeks-at-cpu-amp-memory">6. Quick peeks at CPU &amp; memory</h3>
<p>During the same session you can jump to the <strong>CPU Profiler</strong> to sample hotspots (it freezes the timeline so context isn’t lost) or grab a <strong>heap snapshot</strong> to see if a screen is leaking objects as you navigate back and forth. It’s all one click away—no separate tools to install. (<a target="_blank" href="https://docs.flutter.dev/tools/devtools/performance?utm_source=chatgpt.com">Flutter Documentation</a>)</p>
<h3 id="heading-7-a-one-sprint-performance-routine">7. A “one-sprint” performance routine</h3>
<ul>
<li><p><strong>Monday:</strong> record baseline in profile mode.</p>
</li>
<li><p><strong>Tuesday:</strong> fix the worst offending widget.</p>
</li>
<li><p><strong>Wednesday:</strong> warm-up shaders on a release build.</p>
</li>
<li><p><strong>Thursday:</strong> run an exploratory memory/CPU session.</p>
</li>
<li><p><strong>Friday:</strong> share a 20-second screen-recording of silky-smooth scroll to celebrate with the team.</p>
</li>
</ul>
<p>Doing a little every sprint beats the “week-before-release” panic every time.</p>
<h3 id="heading-bottom-line">Bottom line</h3>
<p>DevTools isn’t just for senior engineers or crash emergencies; it’s your day-to-day compass for keeping Flutter apps joyful. Fire it up, follow the coloured bars, and watch the stutters melt away—one friendly trace at a time. Your users (and their thumbs) will feel the love. 🚀</p>
]]></content:encoded></item><item><title><![CDATA[Essential Tips for Effective Error Handling in Flutter Apps]]></title><description><![CDATA[Picture this: You’ve just downloaded a new app, tapped a button, and—boom—white screen, cryptic message, or worse, a full crash. Odds are you’ll uninstall it before you even remember its name. As Flutter devs we can’t promise zero bugs, but we can pr...]]></description><link>https://gidudunicholas.dev/essential-tips-for-effective-error-handling-in-flutter-apps</link><guid isPermaLink="true">https://gidudunicholas.dev/essential-tips-for-effective-error-handling-in-flutter-apps</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Dart]]></category><dc:creator><![CDATA[Gidudu Nicholas]]></dc:creator><pubDate>Sat, 31 May 2025 21:10:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748725786742/7a670d5e-5e2b-4987-bb33-a36a9a685907.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Picture this: You’ve just downloaded a new app, tapped a button, and—boom—white screen, cryptic message, or worse, a full crash. Odds are you’ll uninstall it before you even remember its name. As Flutter devs we can’t promise <em>zero</em> bugs, but we can promise our users won’t feel abandoned when things go sideways.</p>
<p>Below is a people-first walkthrough of error handling in Flutter (Dart 3.x, Flutter 4). Less jargon, more common sense.</p>
<h3 id="heading-1-first-lets-name-the-gremlins">1. First, let’s name the gremlins</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Where it breaks</td><td>Real-life example</td><td>How you catch it</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Plain old Dart code</strong></td><td>Dividing by zero, parsing bad JSON, forgetting that <code>null</code> exists</td><td>A simple <code>try {…} catch</code></td></tr>
<tr>
<td><strong>Flutter framework</strong></td><td>A widget tries to paint infinite width</td><td><code>FlutterError.onError</code></td></tr>
<tr>
<td><strong>Background stuff</strong></td><td>Your isolate tries to read a file that isn’t there</td><td><code>runZonedGuarded</code> or <code>PlatformDispatcher.instance.onError</code></td></tr>
</tbody>
</table>
</div><p>Think of these as different rooms in a house. If you only lock the front door, burglars could still climb in through the basement. So let’s lock every entrance.</p>
<h3 id="heading-2-try-catch-finallythe-everyday-seatbelt">2. <code>try / catch / finally</code>—the everyday seatbelt</h3>
<pre><code class="lang-dart"><span class="hljs-keyword">try</span> {
  <span class="hljs-keyword">final</span> user = <span class="hljs-keyword">await</span> api.getUser(id);
} <span class="hljs-keyword">on</span> TimeoutException {
  <span class="hljs-keyword">throw</span> <span class="hljs-keyword">const</span> NetworkFailure(message: <span class="hljs-string">'The server is taking too long.'</span>);
} <span class="hljs-keyword">catch</span> (e, st) {
  debugPrint(<span class="hljs-string">'Unexpected error: <span class="hljs-subst">$e</span>\n<span class="hljs-subst">$st</span>'</span>);
  <span class="hljs-keyword">rethrow</span>; <span class="hljs-comment">// bubble it up so our global handler sees it</span>
} <span class="hljs-keyword">finally</span> {
  loadingSpinner.<span class="hljs-keyword">hide</span>();
}
</code></pre>
<ul>
<li><p>Put the <em>specific</em> catches first (<code>TimeoutException</code>) and the generic catch last.</p>
</li>
<li><p>Keep the stack trace (<code>st</code>). It’s your treasure map when debugging later.</p>
</li>
</ul>
<h3 id="heading-3-your-safety-net-one-line-that-catches-everything">3. Your safety net: one line that catches <em>everything</em></h3>
<pre><code class="lang-dart">Future&lt;<span class="hljs-keyword">void</span>&gt; main() <span class="hljs-keyword">async</span> {
  WidgetsFlutterBinding.ensureInitialized();

  FlutterError.onError = (details) {
    FlutterError.presentError(details); <span class="hljs-comment">// still prints in debug</span>
    report(details.exception, details.stack);
  };

  <span class="hljs-keyword">await</span> runZonedGuarded(() <span class="hljs-keyword">async</span> {
    runApp(<span class="hljs-keyword">const</span> MyApp());
  }, (error, stack) =&gt; report(error, stack));
}
</code></pre>
<p>If something slips past your smaller try/catch blocks, it lands here—not on the user’s lap.</p>
<h3 id="heading-4-show-dont-scare">4. Show, don’t scare</h3>
<ul>
<li><p><strong>Replace the red screen</strong>:</p>
<pre><code class="lang-dart">  ErrorWidget.builder = (details) =&gt; Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            <span class="hljs-keyword">const</span> Icon(Icons.error_outline, size: <span class="hljs-number">64</span>),
            <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Oops! Something went wrong.'</span>),
            TextButton(
              onPressed: () =&gt; _restartApp(),
              child: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Try again'</span>),
            ),
          ],
        ),
      );
</code></pre>
</li>
<li><p>Prefer snackbars or banners for fix-able problems (“Lost connection—tap to retry”).</p>
</li>
<li><p>Cache last-known-good data so “offline” still shows <em>something</em>.</p>
</li>
</ul>
<h3 id="heading-5-speak-human-not-stack-trace">5. Speak human, not stack trace</h3>
<p>Instead of throwing raw strings or mysterious <code>Exception</code>, make little story-objects:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthFailure</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Exception</span> </span>{
  <span class="hljs-keyword">const</span> AuthFailure.invalidLogin();
  <span class="hljs-keyword">const</span> AuthFailure.expiredToken();

  <span class="hljs-built_in">String</span> <span class="hljs-keyword">get</span> reason {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">switch</span> (runtimeType) {
      AuthFailureInvalidLogin =&gt; <span class="hljs-string">'Email or password is incorrect.'</span>,
      AuthFailureExpiredToken =&gt; <span class="hljs-string">'Session expired. Please log in again.'</span>,
      _ =&gt; <span class="hljs-string">'Something went wrong. Try again.'</span>,
    };
  }
}
</code></pre>
<p>Now your UI can simply display <code>failure.reason</code>.</p>
<h3 id="heading-6-results-over-roulette">6. Results over roulette</h3>
<p>Some teams skip exceptions entirely for expected failures and use a <code>Result&lt;T, E&gt;</code> (or <code>Either</code>) type:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> Result&lt;User, AuthFailure&gt; result = <span class="hljs-keyword">await</span> repo.signIn(...);
<span class="hljs-keyword">switch</span> (result) {
  <span class="hljs-keyword">case</span> Ok(value: <span class="hljs-keyword">final</span> user):
    showHome(user);
  <span class="hljs-keyword">case</span> Err(error: <span class="hljs-keyword">final</span> failure):
    showError(failure.reason);
}
</code></pre>
<p>It’s like handing someone two neatly-labeled boxes—“success” and “failure”—instead of a jack-in-the-box that might explode in their face.</p>
<h3 id="heading-7-dont-keep-secretslog-and-report">7. Don’t keep secrets—log and report</h3>
<ul>
<li><p><strong>Firebase Crashlytics</strong> or <strong>Sentry</strong> will grab what your global handler reports.</p>
</li>
<li><p>Add breadcrumbs before risky calls: <code>log('Fetching profile for $id')</code>.</p>
</li>
<li><p>Push non-fatal errors too: <code>Crashlytics.instance.recordError(error, stack)</code>.</p>
</li>
</ul>
<p>If an error happens in the forest and no one hears it… it <em>will</em> happen again.</p>
<h3 id="heading-8-practice-failing">8. Practice failing</h3>
<ol>
<li><p><strong>Unit tests</strong>: expect a <code>NetworkFailure</code> when you cut the internet.</p>
</li>
<li><p><strong>Widget tests</strong>: pump a widget with fake exceptions and confirm it shows your fallback UI.</p>
</li>
<li><p><strong>Staging dry-run</strong>: call <code>Crashlytics.instance.crash()</code>—only in debug builds—to be sure reporting works.</p>
</li>
</ol>
<h3 id="heading-9-the-short-checklist">9. The short checklist</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>✔️</td><td>Remember to…</td></tr>
</thead>
<tbody>
<tr>
<td>Wrap risky code in <code>try/catch</code>.</td><td></td></tr>
<tr>
<td>Install global guards (<code>FlutterError</code>, <code>runZonedGuarded</code>).</td><td></td></tr>
<tr>
<td>Replace red screens with friendly ones.</td><td></td></tr>
<tr>
<td>Create readable error models or <code>Result</code> types.</td><td></td></tr>
<tr>
<td>Send every error to your logger/crash reporter.</td><td></td></tr>
<tr>
<td>Write tests that force the app to fail on purpose.</td></tr>
</tbody>
</table>
</div><h3 id="heading-parting-words">Parting words</h3>
<p>Bugs are inevitable; rage-quits are not. Handle errors the way you’d comfort a nervous passenger on a flight: acknowledge the bump, explain the plan, and land smoothly. Your users will stay buckled in for the journey—and maybe even leave a five-star review.</p>
<p>Happy shipping!</p>
]]></content:encoded></item><item><title><![CDATA[A Complete Guide to Implementing Flutter Flavors and Why They're Important]]></title><description><![CDATA[Flutter Flavors Explained: How to Implement and Why You Should
So, you’ve built your Flutter app, and everything is looking great. But then you realize:

You need a development version to test features.

A staging version for your QA team.

And a pro...]]></description><link>https://gidudunicholas.dev/a-complete-guide-to-implementing-flutter-flavors-and-why-theyre-important</link><guid isPermaLink="true">https://gidudunicholas.dev/a-complete-guide-to-implementing-flutter-flavors-and-why-theyre-important</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Flutter flavors]]></category><category><![CDATA[Dart]]></category><category><![CDATA[technology]]></category><category><![CDATA[flavours]]></category><dc:creator><![CDATA[Gidudu Nicholas]]></dc:creator><pubDate>Tue, 27 May 2025 09:07:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748336755899/77666eb6-1dcd-4db7-a87e-0670845aac72.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-flutter-flavors-explained-how-to-implement-and-why-you-should">Flutter Flavors Explained: How to Implement and Why You Should</h2>
<p>So, you’ve built your Flutter app, and everything is looking great. But then you realize:</p>
<ul>
<li><p>You need a <strong>development</strong> version to test features.</p>
</li>
<li><p>A <strong>staging</strong> version for your QA team.</p>
</li>
<li><p>And a <strong>production</strong> version for real users.</p>
</li>
</ul>
<p>How do you manage all these versions <strong>without going crazy</strong>?</p>
<p>That’s where <strong>Flavours</strong> in Flutter come in. Let’s break it down.</p>
<hr />
<h3 id="heading-what-are-flavours-anyway">🌿 What are Flavours, Anyway?</h3>
<p>Think of Flavours like different “toppings” on your app.<br />Same base app, but slightly different depending on what you need.</p>
<p>For example:</p>
<ul>
<li><p>The <strong>dev</strong> app might use a test API, say “Dev Mode” in the title, and have a different icon.</p>
</li>
<li><p>The <strong>prod</strong> app connects to the real API and shows the proper logo.</p>
</li>
<li><p>The <strong>staging</strong> app is what your testers see.</p>
</li>
</ul>
<p>With Flavours, you can:<br />✅ Have different configurations (API endpoints, app names, icons)<br />✅ Build specific versions of your app without manual tweaks<br />✅ Avoid the nightmare of deploying test builds to real users!</p>
<hr />
<h3 id="heading-why-bother-with-flavours">🎯 Why Bother with Flavours?</h3>
<p>Good question!<br />Imagine you’re working on a new feature, but you don’t want to break the live app. Or you want your testers to check something before releasing it.</p>
<p>Flavours let you:</p>
<ul>
<li><p>Build <strong>safe</strong> environments for testing</p>
</li>
<li><p>Separate <strong>production</strong> from <strong>development</strong></p>
</li>
<li><p>Ship <strong>custom versions</strong> for different clients</p>
</li>
</ul>
<p>And you don’t have to keep swapping files or commenting code in and out.</p>
<hr />
<h3 id="heading-how-to-set-up-flavours-step-by-step">🛠️ How to Set Up Flavours (Step by Step)</h3>
<p>Let’s get to the <strong>fun part</strong>—setting up Flavours in your Flutter project.</p>
<hr />
<h4 id="heading-1-create-entry-points-for-each-flavour">1️⃣ Create Entry Points for Each Flavour</h4>
<p>Start by creating separate Dart files for each flavour:</p>
<pre><code class="lang-dart"><span class="hljs-comment">// lib/main_dev.dart</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:your_app/main.dart'</span> <span class="hljs-keyword">as</span> app;

<span class="hljs-keyword">void</span> main() {
  <span class="hljs-keyword">const</span> flavour = <span class="hljs-string">"Development"</span>;
  app.main(flavour);
}

<span class="hljs-comment">// lib/main_prod.dart</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:your_app/main.dart'</span> <span class="hljs-keyword">as</span> app;

<span class="hljs-keyword">void</span> main() {
  <span class="hljs-keyword">const</span> flavour = <span class="hljs-string">"Production"</span>;
  app.main(flavour);
}
</code></pre>
<p>Then update your <code>main.dart</code>:</p>
<pre><code class="lang-dart"><span class="hljs-comment">// lib/main.dart</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-keyword">void</span> main([<span class="hljs-built_in">String</span> flavour = <span class="hljs-string">"Production"</span>]) {
  runApp(MyApp(flavour: flavour));
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyApp</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> flavour;

  <span class="hljs-keyword">const</span> MyApp({<span class="hljs-keyword">super</span>.key, <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.flavour});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> MaterialApp(
      title: <span class="hljs-string">'MyApp (<span class="hljs-subst">$flavour</span>)'</span>,
      home: Scaffold(
        appBar: AppBar(title: Text(<span class="hljs-string">'Hello from <span class="hljs-subst">$flavour</span>'</span>)),
        body: Center(child: Text(<span class="hljs-string">'Running on <span class="hljs-subst">$flavour</span>'</span>)),
      ),
    );
  }
}
</code></pre>
<p>Now, when you run <code>lib/main_dev.dart</code>, it shows "Development"!</p>
<hr />
<h4 id="heading-2-set-up-android-flavours">2️⃣ Set Up Android Flavours</h4>
<p>In your <code>android/app/build.gradle</code> file:</p>
<pre><code class="lang-mermaid">android {
    ...
    flavorDimensions "app"
    productFlavors {
        dev {
            dimension "app"
            applicationIdSuffix ".dev"
            versionNameSuffix "-dev"
        }
        prod {
            dimension "app"
        }
    }
}
</code></pre>
<p>This lets Android know there are different "flavours" of your app.</p>
<hr />
<h4 id="heading-3-set-up-ios-flavours">3️⃣ Set Up iOS Flavours</h4>
<p>iOS is a bit more manual:</p>
<ul>
<li><p>In Xcode, <strong>duplicate the schemes</strong>:</p>
<ul>
<li><p><code>Runner</code> → <code>Runner-dev</code></p>
</li>
<li><p><code>Runner</code> → <code>Runner-prod</code></p>
</li>
</ul>
</li>
<li><p>Change the <strong>Bundle Identifier</strong> in each scheme:</p>
<ul>
<li><p><code>com.yourcompany.app.dev</code></p>
</li>
<li><p><code>com.yourcompany.app</code></p>
</li>
</ul>
</li>
</ul>
<p>This keeps the apps separate when installed.</p>
<hr />
<h4 id="heading-4-run-your-flavours">4️⃣ Run Your Flavours</h4>
<p>Use Flutter’s <code>-t</code> flag:</p>
<pre><code class="lang-bash">flutter run -t lib/main_dev.dart
flutter run -t lib/main_prod.dart
</code></pre>
<p>Or build them:</p>
<pre><code class="lang-bash">flutter build apk -t lib/main_dev.dart
flutter build ipa -t lib/main_prod.dart
</code></pre>
<hr />
<h4 id="heading-5-handle-different-configurations">5️⃣ Handle Different Configurations</h4>
<p>Need different API URLs or secrets?<br />Use a <code>config.dart</code> file or the <a target="_blank" href="https://pub.dev/packages/flutter_dotenv">flutter_dotenv</a> package.</p>
<p>For example:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Config</span> </span>{
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">const</span> apiUrl = <span class="hljs-built_in">String</span>.fromEnvironment(<span class="hljs-string">'API_URL'</span>, defaultValue: <span class="hljs-string">'https://api.prod.com'</span>);
}
</code></pre>
<p>Or, with <code>dotenv</code>, load <code>.env</code> files per flavour.</p>
<hr />
<h3 id="heading-best-practices-from-experience">🌟 Best Practices (From Experience)</h3>
<p>✅ Name your apps clearly: "MyApp Dev", "MyApp Staging".<br />✅ Use <strong>different icons</strong> for each flavour to avoid mix-ups.<br />✅ <strong>Automate builds</strong> with CI/CD.<br />✅ Never hardcode secrets in your code—use environment variables.</p>
<hr />
<h3 id="heading-final-thoughts">✨ Final Thoughts</h3>
<p>Flavours in Flutter <strong>save you time, stress, and mistakes</strong>.<br />Once set up, you can easily switch between environments, test safely, and deploy with confidence.</p>
<p>Want a hands-on guide or GitHub example? Let me know, and I’ll help you get started!</p>
<p>Happy coding! 🚀</p>
]]></content:encoded></item><item><title><![CDATA[Enhance Your Mobile Apps: Optimize Multitasking with Isolates]]></title><description><![CDATA[Now, to help you understand this, imagine your Flutter app as a busy restaurant kitchen. The head cook (the UI Thread), needs to ensure that dishes (your animations, taps, and scrolls) are plated and served correctly and timely. But then you ask that...]]></description><link>https://gidudunicholas.dev/enhance-your-mobile-apps-optimize-multitasking-with-isolates</link><guid isPermaLink="true">https://gidudunicholas.dev/enhance-your-mobile-apps-optimize-multitasking-with-isolates</guid><category><![CDATA[dart-isolates]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[Dart]]></category><dc:creator><![CDATA[Gidudu Nicholas]]></dc:creator><pubDate>Tue, 20 May 2025 19:15:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747768443330/82b7097a-da07-40cb-91c9-df675b820aa5.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Now, to help you understand this, imagine your Flutter app as a busy restaurant kitchen. The head cook (the UI Thread), needs to ensure that dishes (your animations, taps, and scrolls) are plated and served correctly and timely. But then you ask that same chef to also prepare the desserts, clean the ovens, and take orders, and things slow down—and your diners (users) begin tapping their watches or complaining.</p>
<p>Dart <strong>isolates</strong> are like bringing in extra chefs, each with their own fully equipped kitchen. They can tackle the heavy lifting i.e (parsing huge JSON files, resizing images, performing complex calculations) without ever crowding the main thread.</p>
<p>Mobile users expect <strong>buttery‑smooth UIs</strong>—even while an app crunches data, syncs with the cloud, or generates PDFs.<br />On Flutter, the secret weapon for keeping the main thread silky‑smooth is the <strong>isolate</strong>.</p>
<h3 id="heading-1-why-multitasking-matters-on-mobile">1. Why Multitasking Matters on Mobile</h3>
<ul>
<li><p>Every Flutter app runs its UI on a <strong>single main isolate</strong> (the platform’s “UI thread”).</p>
</li>
<li><p>Heavy work (JSON parsing, file I/O, encryption, ML inference) can block that isolate, causing jank or ANRs.</p>
</li>
<li><p>Off‑loading to <strong>background isolates</strong> lets the UI keep pumping frames at 60 – 120 fps.</p>
</li>
</ul>
<h3 id="heading-2-what-exactly-is-an-isolate">2. What Exactly Is an Isolate?</h3>
<p>An <strong>isolate</strong> is a Dart execution context with its <strong>own event loop + memory heap</strong>.<br />Unlike traditional threads, isolates <strong>share no mutable state</strong>—they communicate by passing <strong>messages</strong> (SendPort ↔ ReceivePort) containing simple values or transferable typed data (e.g., <code>Uint8List</code>).</p>
<blockquote>
<p>If you’re coming from Java/Kotlin, think “actor model” rather than “synchronized blocks.”</p>
</blockquote>
<h3 id="heading-3-when-to-reach-for-isolates">3. When to Reach for Isolates</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Use‑case</td><td>Why isolates help</td></tr>
</thead>
<tbody>
<tr>
<td>Large JSON / protobuf decoding</td><td>Off‑load CPU‑heavy parsing</td></tr>
<tr>
<td>Image manipulation, video encoding</td><td>Prevent UI stutter while crunching pixels</td></tr>
<tr>
<td>Cryptography, compression</td><td>Keep expensive math off the main isolate</td></tr>
<tr>
<td>Continuous background polling</td><td>Maintain network loop without frame drops</td></tr>
<tr>
<td>Data science / TensorFlow Lite inference</td><td>Run ML models without dropping frames</td></tr>
</tbody>
</table>
</div><h3 id="heading-4-quick-win-compute-for-oneshot-tasks">4. Quick Win: <code>compute()</code> for One‑Shot Tasks</h3>
<p>Flutter’s <code>compute()</code> spawns a <strong>temporary isolate</strong>, runs a pure function, returns the result, then kills the isolate.</p>
<pre><code class="lang-dart"><span class="hljs-comment">// Heavy JSON decode without blocking UI</span>
Future&lt;<span class="hljs-built_in">List</span>&lt;User&gt;&gt; loadUsers(<span class="hljs-built_in">String</span> jsonStr) <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">return</span> compute(parseUsers, jsonStr);
}

<span class="hljs-built_in">List</span>&lt;User&gt; parseUsers(<span class="hljs-built_in">String</span> jsonStr) {
  <span class="hljs-keyword">final</span> data = jsonDecode(jsonStr) <span class="hljs-keyword">as</span> <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">dynamic</span>&gt;;
  <span class="hljs-keyword">return</span> data.map((e) =&gt; User.fromJson(e)).toList();
}
</code></pre>
<p>✅ Zero boilerplate—perfect for one‑off tasks under a few hundred ms.</p>
<h3 id="heading-5-full-control-spawning-longlived-isolates">5. Full Control: Spawning Long‑Lived Isolates</h3>
<p>For streaming, infinite loops, or long pipelines, create and manage isolates yourself.</p>
<pre><code class="lang-dart"><span class="hljs-comment">// main.dart</span>
<span class="hljs-keyword">final</span> ReceivePort uiReceive = ReceivePort();

<span class="hljs-keyword">await</span> Isolate.spawn(backgroundEntry, uiReceive.sendPort);

<span class="hljs-comment">// background isolate</span>
<span class="hljs-keyword">void</span> backgroundEntry(SendPort mainSend) {
  <span class="hljs-keyword">final</span> ReceivePort bgReceive = ReceivePort();
  mainSend.send(bgReceive.sendPort);     <span class="hljs-comment">// hand shake</span>

  bgReceive.listen((message) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">if</span> (message <span class="hljs-keyword">is</span> <span class="hljs-built_in">String</span>) {
      <span class="hljs-keyword">final</span> result = doExpensiveStuff(message);
      mainSend.send(result);             <span class="hljs-comment">// push back to UI</span>
    }
  });
}
</code></pre>
<p>Key points:</p>
<ul>
<li><p><strong>Handshake once</strong>, then stream messages both ways.</p>
</li>
<li><p>Use <code>Isolate.kill(priority: Isolate.immediate)</code> for cleanup.</p>
</li>
<li><p>Transfer binary blobs with <code>TransferableTypedData</code> for zero‑copy speed.</p>
</li>
</ul>
<h3 id="heading-6-pattern-isolate-pool-for-concurrent-jobs">6. Pattern: Isolate Pool for Concurrent Jobs</h3>
<p>If you schedule many short jobs (e.g., image thumbnails), spinning up a new isolate each time is slow.<br />Maintain an <strong>isolate pool</strong> (package <code>isolate_pool</code> or DIY with <code>StreamQueue</code>) to reuse workers.</p>
<p>Steps:</p>
<ol>
<li><p>Spawn <em>N</em> isolates at startup (<code>N = number of CPU cores − 1</code> is common).</p>
</li>
<li><p>Keep a queue of pending tasks.</p>
</li>
<li><p>Dispatch tasks to idle isolates; await results; recycle.</p>
</li>
</ol>
<h3 id="heading-7-best-practices-amp-gotchas">7. Best Practices &amp; Gotchas</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>✔︎ Do</td><td>✘ Don’t</td></tr>
</thead>
<tbody>
<tr>
<td>Design isolate functions as <strong>pure</strong> (no global state).</td><td>Touch platform channels or UI widgets in a background isolate.</td></tr>
<tr>
<td>Transfer <strong>binary data</strong> with <code>TransferableTypedData</code> for speed.</td><td>Pass huge object graphs—flatten them first.</td></tr>
<tr>
<td>Gracefully <strong>handle errors</strong> with <code>Isolate.addErrorListener</code>.</td><td>Forget to kill isolates—memory leaks hurt on low‑RAM devices.</td></tr>
<tr>
<td>Profile with the <strong>Flutter DevTools CPU Profiler</strong> to confirm gains.</td><td>Spawn isolates inside isolate (nested) unless you absolutely need.</td></tr>
</tbody>
</table>
</div><h3 id="heading-8-debugging-tips">8. Debugging Tips</h3>
<ul>
<li><p>Enable <strong>“Track Widget Rebuilds”</strong> in DevTools; if frames still drop, your isolate code isn’t the culprit.</p>
</li>
<li><p>If messages aren’t delivered, verify that both ports stay alive—closing a <code>ReceivePort</code> kills the stream.</p>
</li>
<li><p>On Android ≥ S, background isolates keep running when the app is in the foreground service; else, rely on <code>WorkManager</code> for truly off‑app tasks.</p>
</li>
</ul>
<h3 id="heading-9-putting-it-all-together-sample-architecture">9. Putting It All Together — Sample Architecture</h3>
<pre><code class="lang-mermaid">graph LR
UI[Main Isolate&lt;br&gt;UI &amp; Animation] -- Send heavy payload --&gt; BGIso1[Isolate A&lt;br&gt;Image filter]
UI -- JSON blobs --&gt; BGIso2[Isolate B&lt;br&gt;Parser]
BGIso1 -- Filtered bytes --&gt; UI
BGIso2 -- Parsed objects --&gt; UI
</code></pre>
<blockquote>
<p>The UI isolate stays laser‑focused on views; isolates take the hits.</p>
</blockquote>
<h3 id="heading-10-conclusion">10. Conclusion</h3>
<p>Isolates are <strong>Dart’s built‑in concurrency superpower</strong>.<br />Use <code>compute()</code> for quick wins and custom isolates (or pools) for long‑running or streaming work.<br />Your users—and your app ratings—will thank you for the stutter‑free experience.</p>
<h4 id="heading-further-reading">Further Reading</h4>
<ul>
<li>Official docs: <a target="_blank" href="https://dart.dev/guides/language/concurrency">https://dart.dev/guides/language/concurrency</a></li>
</ul>
<p>Happy multitasking! ✨</p>
]]></content:encoded></item><item><title><![CDATA[Preventing Memory Leaks in Flutter Apps: Essential Tips]]></title><description><![CDATA[What is a Memory Leak in Flutter?
Think of your Flutter app as a workspace. Over time, if you don’t clear out old papers and materials, your desk gets cluttered and difficult to work at. A memory leak is like that — it occurs when your app continues ...]]></description><link>https://gidudunicholas.dev/preventing-memory-leaks-in-flutter-apps-essential-tips</link><guid isPermaLink="true">https://gidudunicholas.dev/preventing-memory-leaks-in-flutter-apps-essential-tips</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Dart]]></category><category><![CDATA[Memory Leak]]></category><category><![CDATA[memory-management]]></category><dc:creator><![CDATA[Gidudu Nicholas]]></dc:creator><pubDate>Fri, 11 Apr 2025 10:01:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1744365536237/32529ffb-94c0-41c3-92c8-b6bc30ef3465.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-is-a-memory-leak-in-flutter">What is a Memory Leak in Flutter?</h2>
<p>Think of your Flutter app as a workspace. Over time, if you don’t clear out old papers and materials, your desk gets cluttered and difficult to work at. A memory leak is like that — it occurs when your app continues to hold onto some data or object that it no longer requires. This overcrowding can bog down your app and cause it to crash over time.</p>
<h2 id="heading-why-do-memory-leaks-occur-in-flutter">Why do memory leaks occur in Flutter?</h2>
<p>Here are a few common culprits that could lead to memory</p>
<ol>
<li><p><strong>Unclosed StreamSubscriptions</strong><br /> Listening to data streams is like opening a door to receive updates. But if you forget to close that door when you’re done, it remains open — and the resources continue to flow.</p>
<pre><code class="lang-dart"> StreamSubscription? _subscription;
 _subscription = myStream.listen((event) {
   <span class="hljs-comment">// handle event</span>
 });
</code></pre>
</li>
<li><p><strong>Undisposed Controllers</strong><br /> Controllers (like TextEditingController, AnimationController, or even ScrollController) can come in real handy, but if you don’t get them to go home when you don’t need them anymore, they’re still hanging around.</p>
<pre><code class="lang-dart"> <span class="hljs-keyword">final</span> controller = TextEditingController();
 <span class="hljs-meta">@override</span>
 <span class="hljs-keyword">void</span> dispose() {
   controller.dispose(); <span class="hljs-comment">// Very important</span>
   <span class="hljs-keyword">super</span>.dispose();
 }
</code></pre>
</li>
<li><p><strong>Improper Use of GlobalKeys</strong></p>
</li>
</ol>
<p>    <code>GlobalKey</code> is a handy tool for maintaining state and controlling widget behavior. However, using them too often or incorrectly is like tying a widget down—preventing it from being cleaned up when it should be.</p>
<ol start="4">
<li><p><strong>Retaining the widgets or built context</strong></p>
<p> We sometimes inadvertently keep widgets (or their contexts, for example, by placing them in global variables) long after we’re supposed to let them go. It’s like hanging onto an outdated, useless citation that takes up space in head.</p>
</li>
<li><p><strong>Global State or An Overabundance of Singletons or Static Variables</strong></p>
<p> Singletons and static variables are so convenient for sharing data but if they are not managed attentively enough, you may find they tend to hoard a lot of data that are not needed after some point.</p>
</li>
</ol>
<h2 id="heading-keeping-your-app-lean-best-practices">✅ Keeping Your App Lean: Best Practices</h2>
<p>Here are some everyday tips to help you keep your Flutter app healthy and free of memory leaks:</p>
<h3 id="heading-1-dispose-resources-properly">1. <strong>Dispose Resources Properly</strong></h3>
<p>Always clean up after yourself. For stateful widgets, override the dispose() method to properly cancel subscriptions, shut down controllers, and stop any timers.</p>
<pre><code class="lang-dart"><span class="hljs-meta">@override</span>
<span class="hljs-keyword">void</span> dispose() {
  _controller.dispose();
  _subscription.cancel();
  _timer?.cancel();
  <span class="hljs-keyword">super</span>.dispose();
}
</code></pre>
<h3 id="heading-2-use-statefulwidget-responsibly">2. <strong>Use</strong> <code>StatefulWidget</code> Responsibly</h3>
<p>Only keep the data you truly need within your widget’s state. The less unnecessary data you hang onto, the smoother your app will run.</p>
<h3 id="heading-3-handle-buildcontext-with-care">3. Handle BuildContext with Care</h3>
<p>Don’t save a <code>BuildContext</code> for later use. When you need to perform an action using the context (like navigating or showing a dialog), first check that the widget is still active:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">if</span> (mounted) {
  Navigator.of(context).pop();
}
</code></pre>
<h3 id="heading-4-limit-use-of-globalkeys">4. <strong>Limit Use of GlobalKeys</strong></h3>
<p>Only use a <code>GlobalKey</code> when absolutely necessary. In many cases, alternatives like <code>InheritedWidget</code>, <code>Provider</code>, or simple callback functions can serve your purposes without risking memory leaks.</p>
<h3 id="heading-5-manage-dependencies-diligently">5. <strong>Manage Dependencies</strong> Diligently</h3>
<p>If you’re using dependency management tools like <code>Provider</code>, <code>Riverpod</code>, or <code>GetIt</code>, make sure that any objects you provide or inject are also properly cleaned up when they’re no longer needed.</p>
<hr />
<h3 id="heading-6-profile-your-app">6. <strong>Profile Your App</strong></h3>
<p>Take some time to periodically review your app’s memory usage:</p>
<ul>
<li><p><strong>Memory Tab in Flutter DevTools:</strong> Check which objects are being allocated.</p>
</li>
<li><p><strong>Leaks Tab:</strong> Look for objects that are not being released.</p>
</li>
<li><p><strong>Performance Tab:</strong> Monitor your app’s overall performance over time.</p>
</li>
</ul>
<h2 id="heading-tools-to-detect-and-debug-memory-leaks">🛠 Tools to Detect and Debug Memory Leaks</h2>
<ul>
<li><p><strong>Flutter DevTools</strong> (Memory tab): Examine allocation stacks, heap snapshots.</p>
</li>
<li><p><strong>Leak Tracker</strong>: A Dart package to track and alert on memory leaks during tests.</p>
</li>
<li><p><strong>Dart Observatory (VM Tools)</strong>: For low-level memory profiling.</p>
</li>
</ul>
<h2 id="heading-summary">📚 Summary</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Issue</td><td>Fix</td></tr>
</thead>
<tbody>
<tr>
<td>Unclosed StreamSubscription</td><td>Cancel in <code>dispose()</code></td></tr>
<tr>
<td>Undisposed Controllers</td><td>Dispose in <code>dispose()</code></td></tr>
<tr>
<td>Retained BuildContext</td><td>Do not store or use it after <code>mounted == false</code></td></tr>
<tr>
<td>Overused GlobalKeys</td><td>Use only when necessary</td></tr>
<tr>
<td>Singleton holding references</td><td>Clean up manually or use weak references if applicable</td></tr>
</tbody>
</table>
</div>]]></content:encoded></item><item><title><![CDATA[The Ultimate Guide to Route Observers in Flutter Apps]]></title><description><![CDATA[Understanding Route Observers in Flutter 🚀
In Flutter, RouteObserver is a powerful way to listen to navigation changes and respond when a screen (route) is pushed, popped, or replaced. This is useful for tracking page visits, analytics, logging, or ...]]></description><link>https://gidudunicholas.dev/the-ultimate-guide-to-route-observers-in-flutter-apps</link><guid isPermaLink="true">https://gidudunicholas.dev/the-ultimate-guide-to-route-observers-in-flutter-apps</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Dart]]></category><category><![CDATA[observers]]></category><dc:creator><![CDATA[Gidudu Nicholas]]></dc:creator><pubDate>Thu, 03 Apr 2025 09:10:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743671148962/2ac6f5a5-ee54-45fb-b234-a4d49b3924e9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-understanding-route-observers-in-flutter">Understanding Route Observers in Flutter 🚀</h3>
<p>In Flutter, <strong>RouteObserver</strong> is a powerful way to listen to navigation changes and respond when a screen (route) is pushed, popped, or replaced. This is useful for tracking page visits, analytics, logging, or refreshing data when a page is revisited.</p>
<hr />
<h3 id="heading-how-to-use-routeobserver-in-flutter">📌 How to Use RouteObserver in Flutter</h3>
<h4 id="heading-1-create-a-routeobserver-instance">1️⃣ <strong>Create a RouteObserver Instance</strong></h4>
<p>Flutter provides a built-in <code>NavigatorObserver</code> class, and we can extend <code>RouteObserver&lt;PageRoute&gt;</code> to create our own.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> RouteObserver&lt;PageRoute&gt; routeObserver = RouteObserver&lt;PageRoute&gt;();
</code></pre>
<h4 id="heading-2-register-the-routeobserver-in-materialapp">2️⃣ <strong>Register the RouteObserver in MaterialApp</strong></h4>
<p>We need to pass the <code>routeObserver</code> to the <code>navigatorObservers</code> property of <code>MaterialApp</code> or <code>CupertinoApp</code>.</p>
<pre><code class="lang-dart">MaterialApp(
  navigatorObservers: [routeObserver],
  home: MyHomePage(),
);
</code></pre>
<h4 id="heading-3-implement-routeaware-in-your-widgets">3️⃣ <strong>Implement RouteAware in Your Widgets</strong></h4>
<p>To listen to route changes, a widget must implement <code>RouteAware</code> and register itself with the <code>RouteObserver</code>.</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  _MyScreenState createState() =&gt; _MyScreenState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_MyScreenState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">MyScreen</span>&gt; <span class="hljs-title">with</span> <span class="hljs-title">RouteAware</span> </span>{
  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> didChangeDependencies() {
    <span class="hljs-keyword">super</span>.didChangeDependencies();
    routeObserver.subscribe(<span class="hljs-keyword">this</span>, ModalRoute.of(context) <span class="hljs-keyword">as</span> PageRoute);
  }

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> dispose() {
    routeObserver.unsubscribe(<span class="hljs-keyword">this</span>);
    <span class="hljs-keyword">super</span>.dispose();
  }

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> didPush() {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'MyScreen Pushed'</span>);
  }

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> didPop() {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'MyScreen Popped'</span>);
  }

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> didPopNext() {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'Returned to MyScreen'</span>);
  }

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> didPushNext() {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'Navigated away from MyScreen'</span>);
  }

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(title: Text(<span class="hljs-string">"Route Observer Example"</span>)),
      body: Center(child: Text(<span class="hljs-string">"Listen to navigation changes"</span>)),
    );
  }
}
</code></pre>
<hr />
<h3 id="heading-use-cases-for-route-observers">🎯 <strong>Use Cases for Route Observers</strong></h3>
<p>✅ <strong>Analytics &amp; Tracking</strong> – Monitor which screens users visit.<br />✅ <strong>Logging</strong> – Track navigation history.<br />✅ <strong>Refreshing Data</strong> – Reload data when returning to a screen.<br />✅ <strong>Pause/Resume Actions</strong> – Handle background tasks when a screen is active/inactive.</p>
<hr />
<p>💡 <strong>Pro Tip:</strong> If you're using <code>GoRouter</code> instead of Flutter's built-in <code>Navigator</code>, use <code>GoRouterObserver</code> instead of <code>RouteObserver</code>.</p>
<p>Try using Route Observers in your Flutter project and let me know how it goes! 🚀😃</p>
<p>#Flutter #Navigation #RouteObserver #StateManagement</p>
]]></content:encoded></item><item><title><![CDATA[Improve Your App's Performance:  5 Effective Tips]]></title><description><![CDATA[If you’re looking to make your Flutter app faster and smoother, you’re in the right place. Improving performance isn’t just about fancy tech—it’s about giving your users the best experience possible. Let’s walk through five practical tips that can ma...]]></description><link>https://gidudunicholas.dev/improve-your-apps-performance-5-effective-tips</link><guid isPermaLink="true">https://gidudunicholas.dev/improve-your-apps-performance-5-effective-tips</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Dart]]></category><category><![CDATA[Performance Optimization]]></category><category><![CDATA[performance]]></category><dc:creator><![CDATA[Gidudu Nicholas]]></dc:creator><pubDate>Sat, 15 Mar 2025 11:24:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742037712163/2b3a8301-2fde-4bf0-ac79-ea4fe2030782.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you’re looking to make your Flutter app faster and smoother, you’re in the right place. Improving performance isn’t just about fancy tech—it’s about giving your users the best experience possible. Let’s walk through five practical tips that can make a difference in your app performance.</p>
<hr />
<h3 id="heading-1-use-const-constructors-amp-embrace-immutable-widgets">1. <strong>Use const Constructors &amp; Embrace Immutable Widgets</strong></h3>
<p><strong>What’s the deal?</strong><br />Using <code>const</code> constructors means Flutter can set up parts of your app at compile time instead of doing all the work when your app runs. Plus, immutable widgets (ones that don’t change) mean fewer unnecessary rebuilds, saving time and memory.</p>
<p><strong>How to make it happen:</strong></p>
<ul>
<li><p>Mark any widget that doesn’t change with <code>const</code>.</p>
</li>
<li><p>Stick to immutable data patterns for widget properties.</p>
</li>
</ul>
<p><em>Example:</em></p>
<pre><code class="lang-dart"><span class="hljs-comment">// Before:</span>
Widget build(BuildContext context) {
  <span class="hljs-keyword">return</span> Text(<span class="hljs-string">'Hello, Nico walter!'</span>);
}

<span class="hljs-comment">// After:</span>
Widget build(BuildContext context) {
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Hello, Nico walter!'</span>);
}
</code></pre>
<hr />
<h3 id="heading-2-keep-widget-rebuilds-in-check-with-smart-state-management">2. <strong>Keep Widget Rebuilds in Check with Smart State Management</strong></h3>
<p><strong>Why care?</strong><br />When too many parts of your app rebuild unnecessarily, it can slow things down. By managing state wisely, you ensure only the pieces that need to update do so, keeping your app running smooth.</p>
<p><strong>Easy steps to try:</strong></p>
<ul>
<li><p>Consider state management solutions like Provider, Riverpod, or Bloc.</p>
</li>
<li><p>Use widgets like <code>Consumer</code> or <code>Selector</code> so only the parts that need updating can rebuild.</p>
</li>
</ul>
<p><em>Remember:</em><br />It’s best not to wrap your whole widget tree in one stateful widget. Instead, break it down so that only the affected areas refresh when something changes.</p>
<hr />
<h3 id="heading-3-get-to-know-your-app-with-flutter-devtools">3. <strong>Get to Know Your App with Flutter DevTools</strong></h3>
<p><strong>Why it matters:</strong><br />Sometimes, performance hiccups aren’t obvious. Flutter DevTools is like a health check-up for your app—it helps you see what’s slowing things down, from heavy rebuilds to layout issues.</p>
<p><strong>What to do:</strong></p>
<ul>
<li><p>Use the performance profiler to track frame rendering times.</p>
</li>
<li><p>Check out the widget rebuild stats and look out for janky frames.</p>
</li>
<li><p>Use the timeline view to spot any slow operations.</p>
</li>
</ul>
<p><em>Tip:</em><br />Keep performance benchmarks handy so you can measure improvements after each tweak.</p>
<hr />
<h3 id="heading-4-streamline-your-assets-and-animations">4. <strong>Streamline Your Assets and Animations</strong></h3>
<p><strong>What’s up?</strong><br />Large images or overly complicated animations can bog down your app, especially on less powerful devices. Optimizing your assets means your app will be leaner and faster.</p>
<p><strong>Simple fixes include:</strong></p>
<ul>
<li><p>Compress and resize images before bundling them with your app.</p>
</li>
<li><p>Use the <code>FadeInImage</code> widget for smooth image transitions.</p>
</li>
<li><p>Offload heavy tasks from the UI thread—try using the <code>compute</code> function for complex operations.</p>
</li>
</ul>
<p><em>Extra tip:</em><br />Stick to Flutter’s built-in animation tools like <code>AnimatedBuilder</code> for smooth, hardware-accelerated animations without the extra hassle.</p>
<hr />
<h3 id="heading-5-simplify-your-widget-tree-and-cut-down-on-overdraw">5. <strong>Simplify Your Widget Tree and Cut Down on Overdraw</strong></h3>
<p><strong>What’s the benefit?</strong><br />A super deep or overly complex widget tree can slow down how quickly your app updates its UI. Keeping things simple helps Flutter render your app faster.</p>
<p><strong>How to simplify:</strong></p>
<ul>
<li><p>Flatten your widget hierarchy whenever you can.</p>
</li>
<li><p>Avoid unnecessary nesting or redundant layouts.</p>
</li>
<li><p>Use the Flutter Inspector to get a clear look at your widget tree and spot areas to simplify.</p>
</li>
</ul>
<p><em>Quick tip:</em><br />Reuse custom widgets and combine similar ones to keep your tree neat and efficient.</p>
<hr />
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>Taking your Flutter app’s performance to the next level is all about making smart tweaks. Whether it’s using <code>const</code> constructors, managing state better, getting familiar with DevTools, optimizing your assets, or simplifying your widget tree, every little bit helps. Tackle these improvements one step at a time, and you’ll soon notice a smoother, faster user experience that sets your app apart.</p>
<p>Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Understanding Streams in Dart: A Complete Guide]]></title><description><![CDATA[Streams are one of the foundations of Dart asynchronous programming, and In its simplest form, a stream is an asynchronous event that can be in the form of single element or collection. Streams are widely used for managing events in Dart applications...]]></description><link>https://gidudunicholas.dev/understanding-streams-in-dart-a-complete-guide</link><guid isPermaLink="true">https://gidudunicholas.dev/understanding-streams-in-dart-a-complete-guide</guid><dc:creator><![CDATA[Gidudu Nicholas]]></dc:creator><pubDate>Sun, 26 Jan 2025 06:05:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1737871464451/1bef24c3-d153-4c7a-a911-dc7608eb0b60.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Streams are one of the foundations of Dart asynchronous programming, and In its simplest form, a stream is an asynchronous event that can be in the form of single element or collection. Streams are widely used for managing events in Dart applications (eg Flutter), such as user interactions, file I/O events, and API responses.  </p>
<p><strong>What is a Stream?</strong>  </p>
<p>A Stream is an asynchronous event sequence. These events can be:  </p>
<p>Single subscription: Only one listener is allowed at a time  </p>
<p>Broadcast: It is permissible with many listeners at the same time.  </p>
<p><strong>Key Concepts</strong></p>
<ul>
<li><p><strong>Stream</strong>: The source of asynchronous data.</p>
</li>
<li><p><strong>StreamController</strong>: Manages the stream and its sink.</p>
</li>
<li><p><strong>StreamSubscription</strong>: Represents the listening process to a stream.</p>
</li>
</ul>
<h3 id="heading-creating-a-stream">Creating a Stream</h3>
<p>a. Using StreamController</p>
<p>StreamController StreamController is often used to create custom streams.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> StreamController&lt;<span class="hljs-built_in">int</span>&gt; controller = StreamController&lt;<span class="hljs-built_in">int</span>&gt;();

<span class="hljs-keyword">void</span> main() {
  <span class="hljs-keyword">final</span> stream = controller.stream;

  stream.listen((data) {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'Data received: <span class="hljs-subst">$data</span>'</span>);
  });

  controller.sink.add(<span class="hljs-number">1</span>); <span class="hljs-comment">// Emit data</span>
  controller.sink.add(<span class="hljs-number">2</span>);

  controller.close(); <span class="hljs-comment">// Close the stream</span>
}
</code></pre>
<h3 id="heading-b-using-streamfromiterable"><strong>b. Using</strong> <code>Stream.fromIterable</code></h3>
<p>Creates a stream from a collection.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> Stream&lt;<span class="hljs-built_in">int</span>&gt; stream = Stream.fromIterable([<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>]);

stream.listen((event) {
  <span class="hljs-built_in">print</span>(<span class="hljs-string">'Event: <span class="hljs-subst">$event</span>'</span>);
});
</code></pre>
<h3 id="heading-c-using-streamperiodic"><strong>c. Using</strong> <code>Stream.periodic</code></h3>
<p>Generates events periodically.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> Stream&lt;<span class="hljs-built_in">int</span>&gt; stream = Stream.periodic(
  <span class="hljs-built_in">Duration</span>(seconds: <span class="hljs-number">1</span>),
  (count) =&gt; count,
);

stream.take(<span class="hljs-number">5</span>).listen((event) {
  <span class="hljs-built_in">print</span>(<span class="hljs-string">'Periodic event: <span class="hljs-subst">$event</span>'</span>);
});
</code></pre>
<h3 id="heading-listening-to-streams"><strong>Listening to Streams</strong></h3>
<p>You can listen to streams using the <code>listen</code> method, which returns a <code>StreamSubscription</code>.</p>
<h3 id="heading-listening-example"><strong>Listening Example</strong></h3>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> Stream&lt;<span class="hljs-built_in">int</span>&gt; stream = Stream.fromIterable([<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>]);

<span class="hljs-keyword">final</span> subscription = stream.listen((data) {
  <span class="hljs-built_in">print</span>(<span class="hljs-string">'Data: <span class="hljs-subst">$data</span>'</span>);
});

subscription.onDone(() {
  <span class="hljs-built_in">print</span>(<span class="hljs-string">'Stream closed'</span>);
});
</code></pre>
<h3 id="heading-transforming-streams"><strong>Transforming Streams</strong></h3>
<p>Streams support powerful transformation operations like mapping, filtering, and reducing.</p>
<h3 id="heading-a-mapping"><strong>a. Mapping</strong></h3>
<pre><code class="lang-dart">stream.map((event) =&gt; event * <span class="hljs-number">2</span>).listen((data) {
  <span class="hljs-built_in">print</span>(<span class="hljs-string">'Mapped Data: <span class="hljs-subst">$data</span>'</span>);
});
</code></pre>
<h3 id="heading-b-filtering"><strong>b. Filtering</strong></h3>
<pre><code class="lang-dart">stream.where((event) =&gt; event % <span class="hljs-number">2</span> == <span class="hljs-number">0</span>).listen((data) {
  <span class="hljs-built_in">print</span>(<span class="hljs-string">'Even Data: <span class="hljs-subst">$data</span>'</span>);
});
</code></pre>
<h3 id="heading-c-reducing"><strong>c. Reducing</strong></h3>
<pre><code class="lang-dart">stream.reduce((acc, curr) =&gt; acc + curr).then((sum) {
  <span class="hljs-built_in">print</span>(<span class="hljs-string">'Sum: <span class="hljs-subst">$sum</span>'</span>);
});
</code></pre>
<h3 id="heading-types-of-streams"><strong>Types of Streams</strong></h3>
<h3 id="heading-a-single-subscription-stream"><strong>a. Single Subscription Stream</strong></h3>
<ul>
<li><p>Default type.</p>
</li>
<li><p>Allows only one listener at a time.</p>
</li>
<li><p>Used for one-time tasks like API calls.</p>
</li>
</ul>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> stream = Stream.fromIterable([<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>]);
</code></pre>
<h3 id="heading-b-broadcast-stream"><strong>b. Broadcast Stream</strong></h3>
<ul>
<li><p>Allows multiple listeners.</p>
</li>
<li><p>Use <code>.asBroadcastStream()</code> or <code>StreamController.broadcast</code>.</p>
</li>
</ul>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> controller = StreamController&lt;<span class="hljs-built_in">int</span>&gt;.broadcast();

controller.stream.listen((data) {
  <span class="hljs-built_in">print</span>(<span class="hljs-string">'Listener 1: <span class="hljs-subst">$data</span>'</span>);
});

controller.stream.listen((data) {
  <span class="hljs-built_in">print</span>(<span class="hljs-string">'Listener 2: <span class="hljs-subst">$data</span>'</span>);
});

controller.add(<span class="hljs-number">1</span>);
controller.add(<span class="hljs-number">2</span>);
</code></pre>
<h3 id="heading-handling-errors"><strong>Handling Errors</strong></h3>
<p>Streams can emit errors, which you can handle using the <code>onError</code> callback.</p>
<pre><code class="lang-dart">stream.listen(
  (data) {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'Data: <span class="hljs-subst">$data</span>'</span>);
  },
  onError: (error) {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'Error: <span class="hljs-subst">$error</span>'</span>);
  },
  onDone: () {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'Stream completed'</span>);
  },
);
</code></pre>
<h3 id="heading-combining-streams"><strong>Combining Streams</strong></h3>
<p>You can merge multiple streams or zip them together.</p>
<h3 id="heading-a-merging-streams"><strong>a. Merging Streams</strong></h3>
<pre><code class="lang-dart">Stream&lt;<span class="hljs-built_in">int</span>&gt; stream1 = Stream.fromIterable([<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>]);
Stream&lt;<span class="hljs-built_in">int</span>&gt; stream2 = Stream.fromIterable([<span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>]);

Stream&lt;<span class="hljs-built_in">int</span>&gt; mergedStream = Stream.fromFutures([
  stream1.toList(),
  stream2.toList(),
]).expand((element) =&gt; element);

mergedStream.listen(<span class="hljs-built_in">print</span>);
</code></pre>
<h3 id="heading-b-zipping-streams"><strong>b. Zipping Streams</strong></h3>
<p>Use external packages like <code>rxdart</code> for advanced combinations.</p>
<h3 id="heading-streamcontroller-types">StreamController Types</h3>
<p>a. <mark>Regular </mark> StreamController</p>
<p>Single-enterprise streams are for</p>
<p>b. <mark>Broadcast</mark> StreamController</p>
<p>For a broadcast stream with multiple listeners.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> controller = StreamController&lt;<span class="hljs-built_in">int</span>&gt;.broadcast();
</code></pre>
<h3 id="heading-asynchronous-generators"><strong>Asynchronous Generators</strong></h3>
<p>Dart provides a convenient way to create streams using the <code>async*</code> keyword.</p>
<pre><code class="lang-dart">Stream&lt;<span class="hljs-built_in">int</span>&gt; generateNumbers(<span class="hljs-built_in">int</span> max) <span class="hljs-keyword">async</span>* {
  <span class="hljs-keyword">for</span> (<span class="hljs-built_in">int</span> i = <span class="hljs-number">1</span>; i &lt;= max; i++) {
    <span class="hljs-keyword">yield</span> i;
    <span class="hljs-keyword">await</span> Future.delayed(<span class="hljs-built_in">Duration</span>(seconds: <span class="hljs-number">1</span>));
  }
}

generateNumbers(<span class="hljs-number">5</span>).listen(<span class="hljs-built_in">print</span>);
</code></pre>
<h3 id="heading-best-practices"><strong>Best Practices</strong></h3>
<ol>
<li><p><strong>Close Controllers</strong>: Always close <code>StreamController</code> to release resources.</p>
</li>
<li><p><strong>Broadcast Streams</strong>: Use broadcast streams for shared data.</p>
</li>
<li><p><strong>Error Handling</strong>: Provide robust error handling.</p>
</li>
<li><p><strong>Use Extensions</strong>: Utilize <code>rxdart</code> or <code>stream_transform</code> for advanced operations.</p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Flutter App Lifecycle Explained: What You Need to Know]]></title><description><![CDATA[Understanding the app lifecycle of your Flutter application is crucial for managing how your app interacts with the system and its users. This guide walks you through the key stages of a Flutter app's lifecycle and provides best practices for handlin...]]></description><link>https://gidudunicholas.dev/flutter-app-lifecycle-explained-what-you-need-to-know</link><guid isPermaLink="true">https://gidudunicholas.dev/flutter-app-lifecycle-explained-what-you-need-to-know</guid><dc:creator><![CDATA[Gidudu Nicholas]]></dc:creator><pubDate>Thu, 19 Sep 2024 22:38:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1726785387216/b4716b60-84f3-45ac-96fd-1f48775ac263.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Understanding the app lifecycle of your Flutter application is crucial for managing how your app interacts with the system and its users. This guide walks you through the key stages of a Flutter app's lifecycle and provides best practices for handling them.</p>
<h4 id="heading-1-overview-of-flutter-app-lifecycle">1. <strong>Overview of Flutter App Lifecycle</strong></h4>
<p>A Flutter app’s lifecycle is the series of states an app can go through, from the moment it’s started until it’s closed. These lifecycle states are influenced by user actions (like navigating away from the app) and system events (such, as the app being terminated to free up resources).</p>
<p>The key states include:</p>
<ul>
<li><p><strong>Inactive</strong>: The app is in a state where it’s not receiving user input. For instance, when a call comes in, the app becomes inactive.</p>
</li>
<li><p><strong>Paused</strong>: The app is still running but not visible to the user. This may happen when the user switches to another app. In this state, resources are still available.</p>
</li>
<li><p><strong>Resumed</strong>: The app is in the foreground and interacting with the user.</p>
</li>
<li><p><strong>Detached</strong>: The app is in the process of shutting down or being killed by the system.</p>
</li>
</ul>
<h4 id="heading-2-flutters-lifecycle-methods">2. <strong>Flutter’s Lifecycle Methods</strong></h4>
<p>Flutter offers several methods to monitor the lifecycle of the app, allowing developers to manage state changes effectively. These methods come from the <code>WidgetsBindingObserver</code> class, which helps in tracking the lifecycle of the application.</p>
<h5 id="heading-implementing-lifecycle-callbacks">Implementing Lifecycle Callbacks</h5>
<p>To handle app lifecycle events in Flutter, you need to implement the <code>WidgetsBindingObserver</code> interface in your widget class:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyApp</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  _MyAppState createState() =&gt; _MyAppState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_MyAppState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">MyApp</span>&gt; <span class="hljs-title">with</span> <span class="hljs-title">WidgetsBindingObserver</span> </span>{
  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> initState() {
    <span class="hljs-keyword">super</span>.initState();
    WidgetsBinding.instance.addObserver(<span class="hljs-keyword">this</span>);
  }

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> didChangeAppLifecycleState(AppLifecycleState state) {
    <span class="hljs-keyword">super</span>.didChangeAppLifecycleState(state);
    <span class="hljs-keyword">if</span> (state == AppLifecycleState.paused) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">"App is paused"</span>);
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (state == AppLifecycleState.resumed) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">"App is resumed"</span>);
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (state == AppLifecycleState.inactive) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">"App is inactive"</span>);
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (state == AppLifecycleState.detached) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">"App is detached"</span>);
    }
  }

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> dispose() {
    WidgetsBinding.instance.removeObserver(<span class="hljs-keyword">this</span>);
    <span class="hljs-keyword">super</span>.dispose();
  }

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text(<span class="hljs-string">"Flutter App Lifecycle"</span>)),
        body: Center(child: Text(<span class="hljs-string">"App Lifecycle Demo"</span>)),
      ),
    );
  }
}
</code></pre>
<p>In this code:</p>
<ul>
<li><p>The <code>WidgetsBindingObserver</code> is used to listen for app lifecycle changes.</p>
</li>
<li><p>The <code>didChangeAppLifecycleState</code> method is triggered when the app transitions between lifecycle states (such as <code>resumed</code>, <code>paused</code>, etc.).</p>
</li>
</ul>
<h4 id="heading-3-lifecycle-states-in-detail">3. <strong>Lifecycle States in Detail</strong></h4>
<h5 id="heading-a-inactive">a) <strong>Inactive</strong></h5>
<ul>
<li><p><strong>Definition</strong>: The app is in a state where it is not receiving any input. This happens in rare cases, like when a phone call comes in, or when the user pulls down the notification shade.</p>
</li>
<li><p><strong>Handling</strong>: In this state, the app should be prepared for potential interruptions but no drastic resource management is needed.</p>
</li>
</ul>
<h5 id="heading-b-paused">b) <strong>Paused</strong></h5>
<ul>
<li><p><strong>Definition</strong>: The app is still running but not visible to the user. This occurs when the user navigates to another app without closing the current one.</p>
</li>
<li><p><strong>Handling</strong>: This is a critical state for saving the app's current state, stopping animations, or pausing expensive operations such as network requests or database updates. Ensure that tasks are gracefully paused to avoid memory leaks or inconsistent data.</p>
</li>
</ul>
<h5 id="heading-c-resumed">c) <strong>Resumed</strong></h5>
<ul>
<li><p><strong>Definition</strong>: The app is in the foreground and is actively interacting with the user.</p>
</li>
<li><p><strong>Handling</strong>: This is where your app should resume any tasks that were paused, such as restarting animations, refreshing UI, or re-fetching data.</p>
</li>
</ul>
<h5 id="heading-d-detached">d) <strong>Detached</strong></h5>
<ul>
<li><p><strong>Definition</strong>: The app is being terminated. At this point, it has not yet been destroyed but is no longer in the running state.</p>
</li>
<li><p><strong>Handling</strong>: This is the stage where you clean up resources, save data, and prepare for the app to be killed by the system.</p>
</li>
</ul>
<h4 id="heading-4-best-practices-for-managing-lifecycle-events">4. <strong>Best Practices for Managing Lifecycle Events</strong></h4>
<p>Managing app lifecycle events correctly ensures a smooth user experience and effective resource management.</p>
<ul>
<li><p><strong>Save User Data</strong>: Always save critical data (like form inputs or unsaved changes) during the <code>paused</code> state. This will allow you to restore the data when the app is resumed.</p>
</li>
<li><p><strong>Pause Animations and Media</strong>: In the <code>paused</code> state, pause animations, music, or other media to save CPU and battery.</p>
</li>
<li><p><strong>Optimize Network Calls</strong>: Suspend ongoing network requests when the app is paused, unless necessary. In the <code>resumed</code> state, check if the data needs to be refreshed.</p>
</li>
<li><p><strong>Memory Management</strong>: Clean up unnecessary resources, such as large images or background tasks, when the app enters the <code>detached</code> state. This will prevent memory leaks and reduce the chances of the app crashing.</p>
</li>
</ul>
<h4 id="heading-5-handling-platform-specific-lifecycle-events">5. <strong>Handling Platform-Specific Lifecycle Events</strong></h4>
<p>If you're targeting both iOS and Android, be mindful of platform-specific differences in lifecycle management.</p>
<ul>
<li><p><strong>Android</strong>: Android apps can be paused or killed more frequently due to resource limitations. Ensure your app saves enough state during the <code>paused</code> state to handle cases where the system might not resume it.</p>
</li>
<li><p><strong>iOS</strong>: iOS apps have more predictable lifecycle patterns, but you need to ensure quick responses to transitions from <code>inactive</code> to <code>paused</code> due to its stricter memory management practices.</p>
</li>
</ul>
<h4 id="heading-6-common-use-cases-for-lifecycle-management">6. <strong>Common Use Cases for Lifecycle Management</strong></h4>
<p>Here are a few scenarios where lifecycle management is essential:</p>
<ul>
<li><p><strong>Handling Push Notifications</strong>: When a push notification comes in, the app might go from <code>resumed</code> to <code>inactive</code> temporarily. It’s important to handle these state transitions properly.</p>
</li>
<li><p><strong>Background Tasks</strong>: Apps that run background tasks, such as location tracking or media playback, must manage their state when the user switches away from the app.</p>
</li>
<li><p><strong>State Restoration</strong>: Users expect apps to return to the same state when they return. Managing the lifecycle well ensures that your app will restore its UI and data correctly.</p>
</li>
</ul>
<h4 id="heading-conclusion">Conclusion</h4>
<p>Understanding and managing the Flutter app lifecycle is essential for building robust, responsive, and efficient applications. By effectively utilizing lifecycle methods, you can ensure your app provides a smooth user experience, even when transitioning between different states or handling interruptions from the system.</p>
<p>With this guide, you should now have a solid grasp of how to monitor and respond to lifecycle changes in your Flutter apps!</p>
]]></content:encoded></item><item><title><![CDATA[Flutter Offline-First App Development: A Beginner's Guide]]></title><description><![CDATA[Let's say you have a portion of your application that requires an internet connection to function properly. Still, you want to have a mechanism where you can cache the latest data received from your server so that the user can still access the latest...]]></description><link>https://gidudunicholas.dev/flutter-offline-first-app-development-a-beginners-guide</link><guid isPermaLink="true">https://gidudunicholas.dev/flutter-offline-first-app-development-a-beginners-guide</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Dart]]></category><category><![CDATA[offline first]]></category><category><![CDATA[offline first apps]]></category><category><![CDATA[newbie]]></category><dc:creator><![CDATA[Gidudu Nicholas]]></dc:creator><pubDate>Wed, 19 Jun 2024 15:34:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1718811004428/6ee76bc8-85e2-48fd-8955-7bc4e5d27ad8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Let's say you have a portion of your application that requires an internet connection to function properly. Still, you want to have a mechanism where you can cache the latest data received from your server so that the user can still access the latest updated data even without an internet connection and update the data in the background when there is internet.</p>
<p>In this article, we shall dive deeper into building an offline-first application with Flutter. We shall be using an open <a target="_blank" href="https://newsapi.org/">news API</a> (Application programming interface), <code>provider</code> (state management), Dio package (API calls), <code>flutter_cache_manager</code> for this purpose.</p>
<h3 id="heading-news-api-httpsnewsapiorghttpsnewsapiorgdocs"><strong>News API (</strong><a target="_blank" href="https://newsapi.org/docs">https://newsapi.org</a>)</h3>
<p>News API is a simple HTTP REST API for searching and retrieving live articles from all over the web. It can help you answer questions like:</p>
<ul>
<li><p>What top stories is TechCrunch running right now?</p>
</li>
<li><p>What new articles were published about the next iPhone today?</p>
</li>
<li><p>Has my company or product been mentioned or reviewed by any blogs recently?</p>
</li>
</ul>
<h3 id="heading-provider"><strong>Provider</strong></h3>
<p>Provider is a state management tool for flutter applications. It's commonly used to efficiently share and update data between different parts of your app, such as widgets, without the need for prop drilling (passing data through multiple widget layers).</p>
<h3 id="heading-flutter-cache-manager"><strong>Flutter cache manager</strong></h3>
<p>A Dart package designed to handle caching of files, including images and other network resources, in Flutter applications. It simplifies caching and retrieving data by providing a unified interface for various caching strategies. This package supports different storage backends, enabling developers to choose the most suitable option for their use case.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718634464890/4b9a76a1-0cb2-4be7-95b4-d62aa2082d81.png" alt class="image--center mx-auto" /></p>
<p><a target="_blank" href="https://www.behance.net/gallery/185709789/Mobile-News-App-Design?tracking_source=search_projects&amp;l=13">click here to check the full UI</a></p>
<ul>
<li><p><strong>Install required packages</strong></p>
<p>  <mark>dart pub add flutter_cache_manager</mark>, use this command to install flutter cache manager. Follow this <a target="_blank" href="https://pub.dev/packages/flutter_cache_manager">flutter cache manager</a> to get more about the package.</p>
</li>
<li><p><mark>dart pub add provider</mark>, use this command to install the flutter provider package. Follow this <a target="_blank" href="https://pub.dev/packages/provider">provider</a> to get more about the package.</p>
</li>
<li><p><mark>dart pub add dio</mark>, this is responsive for network calls</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718812001012/67f15ba9-aafa-4af1-a6ef-8ca6cb98a09e.png" alt class="image--center mx-auto" /></p>
<p>JSON data from the new API</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718802954365/1aa6c402-106e-4215-a4f3-bc9b9cd6e1e1.png" alt class="image--center mx-auto" /></p>
<p><strong>Create the article model:</strong> This will mimic our API response</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718636767237/23bda8f3-7748-440e-9433-3704340831cf.png" alt class="image--center mx-auto" /></p>
<p>We shall create a constant class which contains all the constants in our application. This file will contain the baseUrl and the API key (generated from <a target="_blank" href="https://newsapi.org/docs">https://newsapi.org</a>). Route to <a target="_blank" href="https://newsapi.org/docs">https://newsapi.org</a> to generate your unique api key which serves as authorization to the news API. Replace the apiKey with your generated key.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718807417849/6c4769bb-5459-4b3a-a021-0f5f2566179e.png" alt class="image--center mx-auto" /></p>
<p>Next, create a repository file (home_repository.dart) where we shall make the api call to the news API. We also cache the fetched data using the flutter cache manager package and this improves on the application performance while retrieving the data. Also we make an api call to the server and updates our cache in case of changes in the data as below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718808695510/8f54ae7b-342e-40d8-bbe5-9efb3a03178d.png" alt class="image--center mx-auto" /></p>
<p>We create a controller file to handle user requests.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718809776552/e0f12362-3e1f-4f91-b3e5-8e0732c6f7dc.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>With this implementation, we can call the news API data, store it on the user device using the cache manager and retrieve data from our cache. To access the UI of the app and how to render the retrieved data on the UI (user interface), check out the git repository below. Any issues or anything you want addressed, leave a comment and I will follow up.</p>
<p><a target="_blank" href="https://github.com/Nicopee/News-App">https://github.com/Nicopee/News-App</a></p>
]]></content:encoded></item><item><title><![CDATA[Experience seamless production updates with Shorebird in Flutter]]></title><description><![CDATA[Shorebird is a code-push solution designed specifically for Flutter, providing stability on both Android and iOS platforms. With Shorebird, you can address app issues without publishing updates to the stores, streamlining the development process and ...]]></description><link>https://gidudunicholas.dev/experience-seamless-production-updates-with-shorebird-in-flutter</link><guid isPermaLink="true">https://gidudunicholas.dev/experience-seamless-production-updates-with-shorebird-in-flutter</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Dart]]></category><category><![CDATA[#shorebird]]></category><category><![CDATA[technology]]></category><category><![CDATA[#codenewbies]]></category><dc:creator><![CDATA[Gidudu Nicholas]]></dc:creator><pubDate>Mon, 10 Jun 2024 09:31:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1718012477539/1936cc84-b086-4f8f-be9e-185c6271df4f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Shorebird is a code-push solution designed specifically for Flutter, <strong>providing stability on both Android and iOS platforms</strong>. With Shorebird, you can address app issues without publishing updates to the stores, streamlining the development process and reducing unnecessary delays. This allows you to update your Flutter app instantly over the air, without going through the store update process and directly deploy fixes and new features to your end users’ devices. Interesting? continue.</p>
<h3 id="heading-getting-started"><strong>Getting started</strong></h3>
<p>You can install Shorebird on your machine using the command line with the following command below.</p>
<p>curl --proto '=https' --tlsv1.2 <a target="_blank" href="https://raw.githubusercontent.com/shorebirdtech/install/main/install.sh">https://raw.githubusercontent.com/shorebirdtech/install/main/install.sh</a> -sSf | bash (Mac/Linux)</p>
<p>Set-ExecutionPolicy RemoteSigned -scope CurrentUser # Needed to execute remote scripts iwr -UseBasicParsing '<a target="_blank" href="https://raw.githubusercontent.com/shorebirdtech/install/main/install.ps1'%7Ciex">https://raw.githubusercontent.com/shorebirdtech/install/main/install.ps1'|iex</a> (windows)</p>
<p><strong><em>shorebird init</em></strong></p>
<p>This command configures your Flutter project to use Shorebird and creates a <code>shorebird.yaml</code> file which contains an app_id and uniquely Identifies your app during code push. Refer to the image below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718005666039/7389a88b-07a4-4d63-bb86-0bc4c775ee6d.png" alt /></p>
<p><strong><em>shorebird release android/ ios</em></strong></p>
<p>To start pushing updates, you will need to create a release. This command creates a production-ready release of your app and submits your app to Shorebird and by default, it creates an app bundle <code>(.aab)</code>. You can also add the Flutter version you are currently running with <code>shorebird release android --flutter-version=3.19.0 or shorebird release ios --flutter-version=3.19.0.</code>where 3.19.0 is the version of Flutter you are currently running on your machine.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718009475914/0aedecfe-e837-47ba-b4ae-59ef072b1a1d.png" alt class="image--center mx-auto" /></p>
<p><strong><em>shorebird preview</em></strong></p>
<p>You can preview your release on your physical device (android/ios) or emulator with <code>shorebird preview</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718009444873/e8455dc9-2cfc-4f29-81f0-e3e6ff9297ba.png" alt class="image--center mx-auto" /></p>
<p>This will show your changes and in my case, as seen in the image above I can check the reset password feature and ready to roll it out.</p>
<p><strong><em>shorebird patch</em></strong></p>
<p>Once you have published a release of your app, you can push updates using one of the <code>shorebird patch</code> commands. i.e <code>shorebird patch android or shorebird patch ios.</code></p>
<p>This command does the following:</p>
<ol>
<li><p><code>Builds the artifacts for the update.</code></p>
</li>
<li><p><code>Downloads the corresponding release artifacts.</code></p>
</li>
<li><p><code>Generates a patch using the diff between the release and the current changes.</code></p>
</li>
<li><p><code>Uploads the patch artifacts to the Shorebird backend.</code></p>
</li>
<li><p><code>Promotes the patch to the stable channel.</code></p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718010610392/6a1a39c7-b416-4616-8e79-5e46098c8a64.png" alt class="image--center mx-auto" /></p>
<p>That’s it! Your users will see the update the next time they restart your app.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>In this article, we explored using a code push for flutter <code>(shorebird)</code> and how to add it to your Flutter project, create releases, preview changes, and create a patch for end users. For more about Shorebird visit <a target="_blank" href="https://docs.shorebird.dev/overview/">https://docs.shorebird.dev/overview/</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718011662334/bd66dbe3-0ba2-4679-adf4-0a85079c3d23.png" alt class="image--center mx-auto" /></p>
]]></content:encoded></item></channel></rss>