<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: FARINU TAIWO</title>
    <description>The latest articles on DEV Community by FARINU TAIWO (@petprog).</description>
    <link>https://dev.to/petprog</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F877215%2F58e7058e-4a89-48ef-bba2-f79e35081469.jpeg</url>
      <title>DEV Community: FARINU TAIWO</title>
      <link>https://dev.to/petprog</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/petprog"/>
    <language>en</language>
    <item>
      <title>Designing Fault-Tolerant Onboarding: How to Resume User Progress After Reinstall</title>
      <dc:creator>FARINU TAIWO</dc:creator>
      <pubDate>Sun, 26 Apr 2026 19:41:51 +0000</pubDate>
      <link>https://dev.to/petprog/designing-fault-tolerant-onboarding-how-to-resume-user-progress-after-reinstall-3j23</link>
      <guid>https://dev.to/petprog/designing-fault-tolerant-onboarding-how-to-resume-user-progress-after-reinstall-3j23</guid>
      <description>&lt;p&gt;When building a multi-role platform, onboarding is not a single problem. It is several distinct problems presenting themselves as one. The customer app experience is a clean five-screen flow. The rider app experience is far more demanding, spanning eight screens that cover identity verification, vehicle documentation, biometrics, and photo uploads. When the architecture is poorly designed, the impact goes beyond reduced conversion. It leads to real data loss when riders abandon the process midway and are forced to start again from the beginning.&lt;/p&gt;

&lt;p&gt;Working across both experiences reveals a clear progression. The customer flow is straightforward and optimized for speed, while the rider onboarding introduces deeper complexity and stricter requirements. This article reflects that journey. It explores how I approach flow design, how I build systems that allow users to resume progress even after reinstalling the app, and how I evaluate the two dominant onboarding patterns used across the industry today.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs73va8dlfqr0wchyzk5r.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs73va8dlfqr0wchyzk5r.jpg" alt="Simple and Complex Onboarding" width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem with Multi-Step Onboarding
&lt;/h2&gt;

&lt;p&gt;Designing the screens is not the hard part. Most teams can build a sequence of forms. The real challenge is handling interruption. Users rarely complete onboarding in one sitting. They get distracted, lose connection, their app crashes, their phone dies, or they simply decide to come back later. Sometimes, they uninstall and return weeks after.&lt;/p&gt;

&lt;p&gt;In a simple customer flow, this interruption is manageable. Restarting the process costs only a few minutes. In a rider flow, it is a different story. By the time a user reaches the KYC stage, they have already verified their phone number, filled in personal details, and submitted vehicle information. If they are forced to start over because their session expired or their data was not preserved, the experience breaks down. What should have been a continuation becomes a reset, and that is where conversion is lost.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The goal is to make every completed step permanent. Not tied to a session, not tied to a device, but saved in a way that the user can always continue from where they stopped.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Apps like the Uber Driver handle this well. If you leave the onboarding process halfway and return a week later, logging back in takes you straight to where you stopped. Your details are still filled in, your documents remain uploaded, and the system clearly shows what is complete and what is still missing. This is the standard onboarding experience should aim for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern A - Gated Sequential Onboarding
&lt;/h2&gt;

&lt;p&gt;The first pattern is the traditional approach. Each step after OTP acts as a gate, and the user cannot access the Home screen until every required step is completed. The onboarding behaves like a strict linear flow with no shortcuts or skipping.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Server as the Source of Truth
&lt;/h3&gt;

&lt;p&gt;The key architectural decision in this pattern is that onboarding state is fully managed on the backend. The app does not try to remember or infer the user’s progress. Instead, it asks the server what has been completed and what is still required, then navigates the user accordingly.&lt;/p&gt;

&lt;p&gt;This means that after OTP verification, once an &lt;code&gt;access token&lt;/code&gt; is issued, every app launch or login triggers a profile status request. The server responds with the user’s current onboarding state, and the app routes them to the correct step. A typical response might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The backend contract - every field has clear meaning&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProfileStatus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isOtpVerified&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isProfileComplete&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// name, phone, address filled&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isKycVerified&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// identity docs approved by ops&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;photoUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c1"&gt;// null = never uploaded&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;Vehicle&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;vehicle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// null = vehicle not added yet&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ProfileStatus&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isOtpVerified&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isProfileComplete&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isKycVerified&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;photoUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;vehicle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Design your null states deliberately: &lt;code&gt;photoUrl == null&lt;/code&gt; means "never uploaded". This is different from &lt;code&gt;photoUrl == ""&lt;/code&gt; which could mean "uploaded then removed". &lt;code&gt;vehicle == null&lt;/code&gt; means "never added" is different from a vehicle object with &lt;code&gt;isDocVerified: false&lt;/code&gt;. Each null state carries information. Document these in your API contract.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Destination Resolver
&lt;/h2&gt;

&lt;p&gt;The logic that decides where to send the user after login is a simple waterfall. Every step is checked in order, and the first incomplete step wins. This function runs in your splash handler or post-login use case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_resolveOnboardingDestination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;ProfileStatus&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isOtpVerified&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Routes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;verifyOtp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isProfileComplete&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Routes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addInfo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;vehicle&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Routes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addVehicle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isKycVerified&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Routes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;kyc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;photoUrl&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;photoUrl&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Routes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addPhotos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Routes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;home&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the entire resumability logic. There is no local step counter, no tracking of the last screen visited, and no complex client-side state machine. The server simply returns a profile status object, and a single routing function translates that response into a navigation decision. Whether the user reinstalls the app, switches devices, or logs in from a web client, they always resume from the correct step because the source of truth lives on the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local Cache as a Hint, Not the Source of Truth
&lt;/h2&gt;

&lt;p&gt;Even with a server-driven approach, caching the last known profile status locally is still useful. Its purpose is not to determine flow, but to improve perceived performance by avoiding a loading state on every app launch.&lt;/p&gt;

&lt;p&gt;The approach I use is to render the cached state immediately while a fresh request runs in the background. Once the server response arrives, the app reconciles and updates the UI if the state has changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;loadAndResolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Render cached route instantly (Better UX performance)&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;cachedRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_prefs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'rider_last_route'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cachedRoute&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_replaceCurrentRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cachedRoute&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Fetch fresh status from server (source of truth)&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_fetchRiderStatusUseCase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_handleError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;resolvedRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_resolveRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_prefs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppKeys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lastRoute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resolvedRoute&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// 3. Reconcile cached route with server truth&lt;/span&gt;
      &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;isStaleCache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resolvedRoute&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;cachedRoute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isStaleCache&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_replaceCurrentRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resolvedRoute&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;One important navigation rule - Every navigation inside the onboarding resolver must use &lt;code&gt;pushAndRemoveUntil&lt;/code&gt; (or &lt;code&gt;go&lt;/code&gt; with a full stack replacement in &lt;code&gt;go_router&lt;/code&gt;). The user should never be able to press back from the Add Info screen and return to the Sign Up screen. Each completed step should be permanently removed from the navigation stack.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  When to Choose Pattern A
&lt;/h3&gt;

&lt;p&gt;Gated sequential onboarding is the right choice when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The app &lt;strong&gt;cannot function without complete data&lt;/strong&gt;. A rider cannot receive deliveries without an approved identity and a profile photo. Letting them reach Home serves no purpose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regulatory compliance requires it&lt;/strong&gt;. KYC in financial apps and driver verification in delivery are often legally mandated before the user can transact.&lt;/li&gt;
&lt;li&gt;The onboarding steps have &lt;strong&gt;natural dependencies&lt;/strong&gt; - KYC often requires a completed profile; photo approval may require a verified identity. The dependency chain makes a linear gate sensible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pattern B - Home-First with Progressive Nudges
&lt;/h2&gt;

&lt;p&gt;This pattern has become increasingly common in consumer apps over the last few years. After OTP verification and receiving an &lt;code&gt;access token&lt;/code&gt;, the user is taken directly to the Home screen, often with partial access to features. The remaining onboarding steps are no longer blocking gates but are surfaced gradually through banners, nudges, or contextual prompts.&lt;/p&gt;

&lt;p&gt;Apps like Duolingo use this approach effectively, and many early-stage fintech apps follow the same model. The underlying principle is simple: time to value is more important than completing all profile data upfront. Letting users experience the product immediately reduces drop-off at the registration stage.&lt;/p&gt;

&lt;p&gt;Instead of forcing completion, additional profile information is collected progressively and in context, only when it becomes relevant to the user’s actions or needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Architecture Shift
&lt;/h3&gt;

&lt;p&gt;In this pattern, your onboarding state still lives on the backend and nothing changes there. What changes is the interpretation layer. Instead of redirecting the user to a form when the state is incomplete, the Home screen subscribes to the profile status and renders nudges accordingly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomeScreen&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RiderProfileProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;profileStatus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Scaffold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="c1"&gt;// Nudges appear in priority order - most critical first&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isProfileComplete&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;OnboardingNudge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;icon:&lt;/span&gt; &lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;person_outline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;message:&lt;/span&gt; &lt;span class="s"&gt;'Complete your profile to go online'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;onTap:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Routes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addInfo&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;vehicle&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;OnboardingNudge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;icon:&lt;/span&gt; &lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;directions_bike_outlined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;message:&lt;/span&gt; &lt;span class="s"&gt;'Add your vehicle to start receiving orders'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;onTap:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Routes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addVehicle&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isKycVerified&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;OnboardingNudge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;icon:&lt;/span&gt; &lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;verified_user_outlined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;message:&lt;/span&gt; &lt;span class="s"&gt;'Verify your identity to unlock all zones'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;onTap:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Routes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;kyc&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;

          &lt;span class="c1"&gt;// ... rest of home content&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nudges are prioritized in order of importance. The most critical missing requirement is shown first. As each item is completed, it is removed and the next one takes its place. There is no separate onboarding flow and no navigation reshuffling. The Home screen simply reacts to the same profile status object it already consumes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nudges as a Post-Launch Feature Delivery Mechanism
&lt;/h3&gt;

&lt;p&gt;This is where Pattern B becomes especially powerful beyond initial onboarding. &lt;strong&gt;Nudges are not just onboarding tools; they become a way to introduce new requirements to existing users without disrupting their experience&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine the rider app has been in production for six months. Early users were onboarded without vehicle insurance verification because it was not part of the original requirements. Now it becomes mandatory. In Pattern A, this creates friction. You either force users back into a gated flow or attempt to reroute them through a modified onboarding process, both of which are disruptive and error-prone.&lt;/p&gt;

&lt;p&gt;In Pattern B, the solution is simpler. You introduce a new field in the backend profile response, and the app automatically adapts without requiring changes to the user’s current state or flow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"isProfileComplete"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"isKycVerified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vehicle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"motorcycle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"plate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ABC-123"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"photoUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://cdn.example.ng/photos/rider_1234.jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Newly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;added&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;requirement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;existing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;riders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;automatically&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"isInsuranceVerified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Home screen checks &lt;code&gt;isInsuranceVerified&lt;/code&gt;, renders a nudge if false, and presents the upload screen when tapped. Existing riders who have not completed it see the prompt at the top of their home screen. New riders see it as part of their natural nudge stack during onboarding. No forced logout, no breaking change, no re-onboarding flow to design.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is also how you gate features by compliance tier. A rider without KYC can still browse the Home screen and view earnings history, but the “Go Online” button is disabled with a tooltip that says: “Verify your identity to go online.” The restriction is contextual rather than blocking. The user has already experienced the product’s value, which makes completing verification feel meaningful instead of bureaucratic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Local State in Pattern B
&lt;/h3&gt;

&lt;p&gt;In Pattern B, local state becomes more important because the UI needs to render immediately, even before the network call completes. This is what allows nudges to appear instantly on the Home screen while fresh profile data is still being fetched in the background.&lt;/p&gt;

&lt;p&gt;To achieve this, you need a local persistence layer that can act as a fast, reactive cache. Any database can technically work, but lightweight embedded databases like &lt;code&gt;Isar&lt;/code&gt; or &lt;code&gt;ObjectBox&lt;/code&gt; are particularly well suited for this pattern. They provide reactive streams out of the box, making it easy for the UI to update automatically when cached onboarding state changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;loadProfileStatus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Render stale data instantly - no loading spinner&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_localRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProfileStatus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;notifyListeners&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Fetch fresh - correct and persist&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_fetchProfileStatusUseCase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fresh&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fresh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;_localRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;saveProfileStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fresh&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="n"&gt;notifyListeners&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this setup, the flow becomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Render cached onboarding state immediately&lt;/li&gt;
&lt;li&gt;Subscribe to local database changes for reactive UI updates&lt;/li&gt;
&lt;li&gt;Sync with the server in the background and overwrite stale state when fresh data arrives&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures the Home screen feels instant while still remaining fully consistent with the server as the source of truth.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to Choose Pattern B
&lt;/h3&gt;

&lt;p&gt;The home-first nudge approach is the right choice when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Early user acquisition is the priority&lt;/strong&gt;. The goal is to drive signups and activation, while encouraging profile completion through engagement rather than enforcing it through blocking gates.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The core value can be demonstrated with minimal user data.&lt;/strong&gt; For example, a food ordering customer can browse restaurants and add items to a cart without providing a delivery address. That detail is only required at checkout.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You are building a platform expected to evolve over time&lt;/strong&gt;. If compliance requirements are likely to increase, designing a nudge-based system from the start reduces significant rework later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You also have early access users whose feedback is important&lt;/strong&gt;. Even with partial profiles, you can still gather geographic distribution, engagement signals, and UX insights, all of which are valuable before the product reaches full maturity.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Hybrid: Combining Both Patterns
&lt;/h2&gt;

&lt;p&gt;In practice, the most thoughtful platforms are not purely one or the other. They use a layered approach: hard gates for the absolute minimum, nudges for everything else.&lt;/p&gt;

&lt;p&gt;For Rider app specifically, a sensible split looks like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Phone + OTP verification&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gated&lt;/td&gt;
&lt;td&gt;You need a verified identity to create any account at all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Full name + vehicle type&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gated&lt;/td&gt;
&lt;td&gt;Minimum required to dispatch an order to a human rider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;KYC document verification&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Nudged&lt;/td&gt;
&lt;td&gt;Required to go online, surfaced as a contextual prompt on Home&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Profile photo upload&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Nudged&lt;/td&gt;
&lt;td&gt;Required for high-value order eligibility - shown after KYC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Vehicle insurance document&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Nudged&lt;/td&gt;
&lt;td&gt;Required for Zone B deliveries - new requirement, existing users prompted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bank account for payouts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Nudged&lt;/td&gt;
&lt;td&gt;Required to receive earnings - shown when rider first completes an order&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Only enforce hard gates on data that makes it impossible for the core product to function. Everything else should be handled as a nudge, with contextual restrictions where necessary. When in doubt, prefer nudging. You can always tighten access later, but loosening a gate that has already frustrated users is much harder to recover from.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Full Backend and Frontend Contract
&lt;/h2&gt;

&lt;p&gt;Regardless of the onboarding pattern you choose, this contract is the most reliable foundation I have found across both apps. Think of it as the implementation checklist for building a truly resumable onboarding system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend responsibilities
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Every profile field must have a clearly defined state: null, empty, or populated. These meanings should be explicitly documented in the API contract and only changed through versioned migrations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A single &lt;code&gt;/me&lt;/code&gt; or &lt;code&gt;/rider/status&lt;/code&gt; endpoint should return the full onboarding state. This endpoint is called on every app launch after login, so it must be optimized for speed and consistency.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;All onboarding step endpoints must be idempotent. Submitting the same KYC document multiple times should not create duplicates or trigger errors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The access token is issued immediately after OTP verification, not after full profile completion. This decision is what enables resumable onboarding in the first place.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The status endpoint should return granular boolean flags for each requirement rather than a single step index. A step counter only shows progress; it does not accurately represent what is actually missing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend responsibilities
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;On every app launch, after validating the token, the app must fetch the latest profile status before deciding navigation. Cached state alone should never determine routing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Local cache can be used to render UI instantly, but only as a performance hint. The server response is always the source of truth and should correct any stale state.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A local step counter should never be used as the primary resume mechanism&lt;/strong&gt;. It can drift out of sync when users switch devices or when backend actions update state outside the app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Each onboarding screen &lt;strong&gt;should submit its own independent API call&lt;/strong&gt;. There should be no single multi-step form submission. This ensures progress is persisted immediately as each step is completed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Navigation within the onboarding flow must always &lt;strong&gt;reset the back stack&lt;/strong&gt;. Users should not be able to navigate back from a deeper onboarding step to earlier authentication screens.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;The rider app is more difficult than the customer app, not because it has more screens, but because the cost of a poor onboarding experience is much higher. A customer who abandons signup can usually return without friction. A rider who drops off mid-KYC may never come back, and if they do, losing their progress is often enough reason to switch to another platform.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Building resumable, server-driven onboarding from day one is not overengineering. It is the baseline architecture for any serious multi-step flow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Whether you choose to gate steps or use nudges is a product decision. It depends on your growth strategy, compliance requirements, and how much value your product can deliver before full verification. But the underlying infrastructure remains the same: idempotent APIs, well-defined state semantics, local caching as a performance hint rather than a source of truth, and a single resolver that translates profile state into navigation.&lt;/p&gt;

&lt;p&gt;Design the backend contract with care. Build the resolver once. Everything else is just screens.&lt;/p&gt;

</description>
      <category>onboarding</category>
      <category>dart</category>
      <category>flutter</category>
      <category>ux</category>
    </item>
    <item>
      <title>The Manifest You Never Wrote — A Flutter Developer's Guide to Android Manifest Merging</title>
      <dc:creator>FARINU TAIWO</dc:creator>
      <pubDate>Wed, 22 Apr 2026 02:53:13 +0000</pubDate>
      <link>https://dev.to/petprog/the-manifest-you-never-wrote-a-flutter-developers-guide-to-android-manifest-merging-553d</link>
      <guid>https://dev.to/petprog/the-manifest-you-never-wrote-a-flutter-developers-guide-to-android-manifest-merging-553d</guid>
      <description>&lt;p&gt;If you've been building Flutter apps for a while, you've probably touched your &lt;code&gt;AndroidManifest.xml&lt;/code&gt; once during setup, added a permission or two when a plugin asked for it, and moved on. Flutter does a great job keeping Android complexity out of your way. But here's something worth knowing: the manifest you write is never actually the one that ships. By the time your app is packaged into an APK or AAB, Android's build system has quietly merged your manifest together with the manifest of every plugin in your &lt;code&gt;pubspec.yaml&lt;/code&gt;. The result can look very different from what you started with.&lt;/p&gt;

&lt;p&gt;That merged output is sitting in your &lt;code&gt;build/&lt;/code&gt; folder is what the Android OS reads, what users grant permissions against, and what Google Play's policy engine inspects on every upload. A plugin you added for image picking can silently pull in video and audio permissions you never intended to declare, and Play Console will flag them without mercy. This article walks you through how the merge works, how to read the merged output, and how to stay in control of what your app actually ships.&lt;/p&gt;

&lt;p&gt;Here's everything we'll cover:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What Is Android Manifest?&lt;/li&gt;
&lt;li&gt;The Merge Process Under the Hood&lt;/li&gt;
&lt;li&gt;Where to Find the Merged Manifest&lt;/li&gt;
&lt;li&gt;READ_MEDIA_*&lt;/li&gt;
&lt;li&gt;Merge Tools And Markers&lt;/li&gt;
&lt;li&gt;How Flutter Uses the Merged Manifest at Build Time
&lt;/li&gt;
&lt;li&gt;Why This Matters for Play Store Deployment&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What Is Android Manifest?
&lt;/h2&gt;

&lt;p&gt;Every Android application ships with a file called &lt;code&gt;AndroidManifest.xml&lt;/code&gt;. It is the contract between your app and the Android OS. It declares who you are, what you need, and what you can do. Without it, your app simply cannot run.&lt;/p&gt;

&lt;p&gt;In a Flutter project, yours lives at:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;android/app/src/main/AndroidManifest.xml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;At the top level, a manifest answers four fundamental questions the OS always asks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Identity — &lt;code&gt;&amp;lt;manifest&amp;gt;&lt;/code&gt;&lt;br&gt;
Your app's package name (today mapped to applicationId in Gradle), version code, and version name live here. This is what uniquely identifies your app on a device and on the Play Store.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Permissions — &lt;code&gt;&amp;lt;uses-permission&amp;gt;&lt;/code&gt;&lt;br&gt;
Anything your app wants to access that belongs to the user or system, such as camera, location, storage, or internet, must be declared. Without a declaration, the runtime will deny the call even if the user tries to grant it manually.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Internet access — no runtime prompt needed --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.INTERNET"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Camera — triggers a runtime prompt on Android 6+ --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.CAMERA"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Fine location for GPS --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.ACCESS_FINE_LOCATION"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Hardware Features — &lt;code&gt;&amp;lt;uses-feature&amp;gt;&lt;/code&gt;&lt;br&gt;
Declares hardware the app requires, such as a camera or GPS chip. Google Play uses these to filter which devices can install your app. Mark features as &lt;code&gt;required="false"&lt;/code&gt; if they're optional enhancements.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Components — &lt;code&gt;&amp;lt;application&amp;gt;&lt;/code&gt;&lt;br&gt;
Registers every Android component your app uses: &lt;code&gt;Activity&lt;/code&gt;, &lt;code&gt;Service&lt;/code&gt;, &lt;code&gt;BroadcastReceiver&lt;/code&gt;, &lt;code&gt;ContentProvider&lt;/code&gt;. Flutter registers its own &lt;code&gt;FlutterActivity&lt;/code&gt; here, and every plugin that uses a Service or a FileProvider adds its own entries too.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;manifest&lt;/span&gt; &lt;span class="na"&gt;xmlns:android=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.android.com/apk/res/android"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.INTERNET"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;application&lt;/span&gt;
        &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"${applicationName}"&lt;/span&gt;
        &lt;span class="na"&gt;android:label=&lt;/span&gt;&lt;span class="s"&gt;"App Name"&lt;/span&gt;
        &lt;span class="na"&gt;android:icon=&lt;/span&gt;&lt;span class="s"&gt;"@mipmap/ic_launcher"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;activity&lt;/span&gt;
            &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;".MainActivity"&lt;/span&gt;
            &lt;span class="na"&gt;android:exported=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
            &lt;span class="na"&gt;android:launchMode=&lt;/span&gt;&lt;span class="s"&gt;"singleTop"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;intent-filter&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;action&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.intent.action.MAIN"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;category&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.intent.category.LAUNCHER"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/intent-filter&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/activity&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/application&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/manifest&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Merge Process Under the Hood
&lt;/h2&gt;

&lt;p&gt;Here's the critical thing most developers miss: &lt;strong&gt;the manifest you write is never the manifest that ships&lt;/strong&gt;. Android's build system (AGP — Android Gradle Plugin) merges manifests from every source in your project before packaging them into your APK or AAB.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who contributes a manifest?
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  [ Your App ] --------+
  app/src/main         |
                       |
  [ Build Variant ] ---+
  src/debug/release    |       (Manifest Merge)       [ Merged Manifest ]
                       +----------------------------→  What ships in 
  [ Flutter Plugins ]  |           processManifest     your APK
  Every pub.dev pkg    |
                       |
  [ AAR Libraries ] ---+
  Google/Firebase
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tool responsible is called the &lt;strong&gt;Manifest Merger&lt;/strong&gt;, part of AGP. It runs during the &lt;code&gt;processDebugManifest&lt;/code&gt; / &lt;code&gt;processReleaseManifest&lt;/code&gt; Gradle task.&lt;/p&gt;

&lt;h3&gt;
  
  
  Merge priority order
&lt;/h3&gt;

&lt;p&gt;Manifests are merged in a strict priority chain. Higher-priority entries win on conflicts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Priority&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Priority 1&lt;/td&gt;
&lt;td&gt;App manifest&lt;/td&gt;
&lt;td&gt;Your &lt;code&gt;android/app/src/main/AndroidManifest.xml&lt;/code&gt;. Highest priority — always wins.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Priority 2&lt;/td&gt;
&lt;td&gt;Build variant manifest&lt;/td&gt;
&lt;td&gt;e.g. &lt;code&gt;src/staging/AndroidManifest.xml&lt;/code&gt; — used for flavor-specific overrides.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Priority 3&lt;/td&gt;
&lt;td&gt;Library / plugin manifests&lt;/td&gt;
&lt;td&gt;Every package in your &lt;code&gt;pubspec&lt;/code&gt; that has native Android code ships its own manifest. These are lowest priority and can be overridden.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Merge strategies
&lt;/h3&gt;

&lt;p&gt;The merger applies a default strategy per element type. For most nodes, higher-priority entries win. For &lt;code&gt;&amp;lt;uses-permission&amp;gt;&lt;/code&gt;, the union is taken — if any manifest declares a permission, it ends up in the final output.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. merge (default)
&lt;/h4&gt;

&lt;p&gt;Combines all attributes, preferring higher-priority source on conflicts. Most elements use this.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. replace
&lt;/h4&gt;

&lt;p&gt;Higher-priority element fully replaces the lower one. No attribute blending.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. remove
&lt;/h4&gt;

&lt;p&gt;Explicitly removes an element that a lower-priority manifest added. Your only way to un-declare a library's permission.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. strict
&lt;/h4&gt;

&lt;p&gt;Both manifests must declare the exact same value. Build fails otherwise. Good for enforcing consistency.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Permissions accumulate. Because permissions use a union strategy, adding a plugin is enough to add its permissions to your final APK — even if your Dart code never calls the relevant API. This is a common source of Play Store policy violations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Where to Find the Merged Manifest
&lt;/h2&gt;

&lt;p&gt;After any build, AGP writes the fully merged manifest to your build folder. This is the ground truth of what actually ships.&lt;/p&gt;

&lt;h3&gt;
  
  
  File location
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;android/app/build/intermediates/merged_manifests/&amp;lt;variant&amp;gt;/AndroidManifest.xml&lt;/code&gt;&lt;br&gt;
For a typical Flutter project with debug/release:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;android/app/build/intermediates/merged_manifests/debug/AndroidManifest.xml&lt;/code&gt;&lt;br&gt;
&lt;code&gt;android/app/build/intermediates/merged_manifests/release/AndroidManifest.xml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you use flavors (e.g., staging, prod), each combination gets its own folder:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/merged_manifests/stagingRelease/AndroidManifest.xml&lt;/code&gt;&lt;br&gt;
&lt;code&gt;.../merged_manifests/prodRelease/AndroidManifest.xml&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Merge report
&lt;/h3&gt;

&lt;p&gt;AGP also writes a human-readable merge report that tells you exactly which library contributed each entry and why:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;android/app/build/outputs/logs/manifest-merger-&amp;lt;variant&amp;gt;-report.txt&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Android Studio shortcut: Open your source AndroidManifest.xml in Android Studio and click the Merged Manifest tab at the bottom of the editor. It renders the full merged file and shows the source of each element inline — indispensable for debugging.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  After a Flutter build
&lt;/h3&gt;

&lt;p&gt;Running &lt;code&gt;flutter build apk&lt;/code&gt; or &lt;code&gt;flutter build appbundle&lt;/code&gt; triggers the full Gradle build, which includes manifest merging. You can inspect the result immediately after:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build release AAB&lt;/span&gt;
flutter build appbundle &lt;span class="nt"&gt;--flavor&lt;/span&gt; prod &lt;span class="nt"&gt;--dart-define-from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;env&lt;/span&gt;/prod.json

&lt;span class="c"&gt;# Then inspect the merged manifest&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;android/app/build/intermediates/merged_manifests/prodRelease/AndroidManifest.xml

&lt;span class="c"&gt;# Or search specifically for permissions&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"uses-permission"&lt;/span&gt; android/app/build/intermediates/merged_manifests/prodRelease/AndroidManifest.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;READ_MEDIA_*&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This is one of the most common permission headaches in modern Flutter apps. Android 13 (API 33) split the old &lt;code&gt;READ_EXTERNAL_STORAGE&lt;/code&gt; into granular media permissions. If you handle this wrong, your app either crashes on old devices, gets rejected on the Play Store, or worst silently ships permissions you never intended.&lt;/p&gt;

&lt;h3&gt;
  
  
  The old world (≤ Android 12)
&lt;/h3&gt;

&lt;p&gt;One permission covered everything in external storage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.READ_EXTERNAL_STORAGE"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.WRITE_EXTERNAL_STORAGE"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The new world (Android 13+, API 33+)
&lt;/h3&gt;

&lt;p&gt;Android 13 introduced three scoped permissions. You only request what your app actually needs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;API Level&lt;/th&gt;
&lt;th&gt;Permission&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API 33+&lt;/td&gt;
&lt;td&gt;READ_MEDIA_IMAGES&lt;/td&gt;
&lt;td&gt;Read images and photos from shared storage. Replaces READ_EXTERNAL_STORAGE for photos.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API 33+&lt;/td&gt;
&lt;td&gt;READ_MEDIA_VIDEO&lt;/td&gt;
&lt;td&gt;Read video files. Required only if your app accesses video — a chat app or media player, for instance.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API 33+&lt;/td&gt;
&lt;td&gt;READ_MEDIA_AUDIO&lt;/td&gt;
&lt;td&gt;Read audio files. Required only for music or voice note apps.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The trap: image_picker and other plugins
&lt;/h3&gt;

&lt;p&gt;If you use &lt;code&gt;image_picker&lt;/code&gt;, &lt;code&gt;file_picker&lt;/code&gt;, or &lt;code&gt;photo_manager&lt;/code&gt;, check their shipped manifests. Some older versions blanket-declare all three &lt;code&gt;READ_MEDIA_*&lt;/code&gt; permissions even if your app only needs images. You'll find them added to your merged manifest without writing a single line yourself.&lt;/p&gt;

&lt;p&gt;Here's what the merged manifest might look like for a basic profile photo only feature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- ✅ You declared this --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.READ_MEDIA_IMAGES"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- ❌ image_picker added these — you never use video or audio! --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.READ_MEDIA_VIDEO"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.READ_MEDIA_AUDIO"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Still needed for Android 12 and below --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt;
    &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.READ_EXTERNAL_STORAGE"&lt;/span&gt;
    &lt;span class="na"&gt;android:maxSdkVersion=&lt;/span&gt;&lt;span class="s"&gt;"32"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Play Store policy alert. Declaring READ_MEDIA_VIDEO or READ_MEDIA_AUDIO on an app that has no video/audio feature triggers a Sensitive Permissions policy warning in Google Play Console. Your app may be rejected or removed. This is exactly the kind of issue you only catch by inspecting the merged manifest.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The fix: remove what you don't need
&lt;/h3&gt;

&lt;p&gt;In your app’s manifest, use the &lt;code&gt;tools:node="remove"&lt;/code&gt; attribute to explicitly remove unwanted permissions that may be coming from libraries or merged manifests. To use this feature, you must first declare the &lt;code&gt;tools&lt;/code&gt; namespace in the manifest root. &lt;/p&gt;

&lt;p&gt;This allows you to cleanly remove specific permissions without modifying the library that added them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;manifest&lt;/span&gt;
    &lt;span class="na"&gt;xmlns:android=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.android.com/apk/res/android"&lt;/span&gt;
    &lt;span class="na"&gt;xmlns:tools=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.android.com/tools"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Only images needed for profile photo --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.READ_MEDIA_IMAGES"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Constrain READ_EXTERNAL_STORAGE to Android 12 and below --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt;
        &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.READ_EXTERNAL_STORAGE"&lt;/span&gt;
        &lt;span class="na"&gt;android:maxSdkVersion=&lt;/span&gt;&lt;span class="s"&gt;"32"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Remove VIDEO permission added by image_picker --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt;
        &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.READ_MEDIA_VIDEO"&lt;/span&gt;
        &lt;span class="na"&gt;tools:node=&lt;/span&gt;&lt;span class="s"&gt;"remove"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!--  Remove AUDIO permission added by image_picker --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt;
        &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.READ_MEDIA_AUDIO"&lt;/span&gt;
        &lt;span class="na"&gt;tools:node=&lt;/span&gt;&lt;span class="s"&gt;"remove"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    ...
&lt;span class="nt"&gt;&amp;lt;/manifest&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Merge Tools and Markers
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;xmlns:tools&lt;/code&gt; namespace acts as your control panel for the manifest merge process. It allows you to add special instructions to manifest elements that tell the Android manifest merger exactly how to handle them. These instructions override the default merge behavior and give you direct control over the final merged manifest.&lt;/p&gt;

&lt;h3&gt;
  
  
  tools:node — control the element itself
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Remove an element a library added --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt;
    &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.RECORD_AUDIO"&lt;/span&gt;
    &lt;span class="na"&gt;tools:node=&lt;/span&gt;&lt;span class="s"&gt;"remove"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Replace a library's activity declaration entirely --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;activity&lt;/span&gt;
    &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"com.some.lib.SomeActivity"&lt;/span&gt;
    &lt;span class="na"&gt;tools:node=&lt;/span&gt;&lt;span class="s"&gt;"replace"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- your overridden content --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/activity&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Merge children but not attributes --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;application&lt;/span&gt;
    &lt;span class="na"&gt;tools:node=&lt;/span&gt;&lt;span class="s"&gt;"mergeOnlyAttributes"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    ...
&lt;span class="nt"&gt;&amp;lt;/application&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  tools:attr — control individual attributes
&lt;/h3&gt;

&lt;p&gt;Sometimes you need finer control — just override a single attribute from a library without replacing the whole element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Force our label, ignore what the library set --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;activity&lt;/span&gt;
    &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"com.some.lib.SomeActivity"&lt;/span&gt;
    &lt;span class="na"&gt;android:label=&lt;/span&gt;&lt;span class="s"&gt;"@string/app_name"&lt;/span&gt;
    &lt;span class="na"&gt;tools:replace=&lt;/span&gt;&lt;span class="s"&gt;"android:label"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Remove just the theme attribute, keep everything else --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;activity&lt;/span&gt;
    &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"com.some.lib.OtherActivity"&lt;/span&gt;
    &lt;span class="na"&gt;tools:remove=&lt;/span&gt;&lt;span class="s"&gt;"android:theme"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  android:maxSdkVersion — scoping permissions by OS version
&lt;/h3&gt;

&lt;p&gt;This is not a &lt;code&gt;tools:&lt;/code&gt; attribute. It is a native Android attribute that tells the OS to automatically revoke the permission on devices running a newer API level. It's the correct way to handle the &lt;code&gt;READ_EXTERNAL_STORAGE&lt;/code&gt; to &lt;code&gt;READ_MEDIA_*&lt;/code&gt; migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Grant READ_EXTERNAL_STORAGE only on Android 12 (API 32) and below.
     On Android 13+, the granular READ_MEDIA_* permissions take over. --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt;
    &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.READ_EXTERNAL_STORAGE"&lt;/span&gt;
    &lt;span class="na"&gt;android:maxSdkVersion=&lt;/span&gt;&lt;span class="s"&gt;"32"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Similarly, WRITE_EXTERNAL_STORAGE has been a no-op since API 29.
     Constrain it to avoid unnecessary permission requests. --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt;
    &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.WRITE_EXTERNAL_STORAGE"&lt;/span&gt;
    &lt;span class="na"&gt;android:maxSdkVersion=&lt;/span&gt;&lt;span class="s"&gt;"28"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;How maxSdkVersion actually works. It's enforced at install time and at system update time. When a user updates their OS from Android 12 to 13, the OS automatically revokes the permission for apps that declared &lt;code&gt;maxSdkVersion="32"&lt;/code&gt;. Your app never needs to hold the old permission on a new OS version.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  tools:ignore — silence false-positive merge warnings
&lt;/h3&gt;

&lt;p&gt;If you intentionally declare something that triggers a merger warning, suppress it cleanly rather than leaving the warning in CI output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;application&lt;/span&gt;
    &lt;span class="na"&gt;android:allowBackup=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;
    &lt;span class="na"&gt;tools:ignore=&lt;/span&gt;&lt;span class="s"&gt;"GoogleAppIndexingWarning,MissingApplicationIcon"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    ...
&lt;span class="nt"&gt;&amp;lt;/application&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How Flutter Uses the Merged Manifest at Build Time
&lt;/h2&gt;

&lt;p&gt;Understanding the build pipeline helps you know exactly when and where the manifest merge happens relative to your Dart code and assets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flutter build      Dart compile       Gradle assemble      processManifest      package / sign
CLI entry point  → AOT kernel      →  AGP kicks in     →   Manifest merge  →    APK / AAB output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flutter's Gradle plugin (&lt;code&gt;flutter.gradle&lt;/code&gt;) executes the full Android build chain. The Dart compilation happens first, producing a snapshot that is bundled as native assets. Then Gradle takes over and performs the standard Android build — including manifest merging as part of &lt;code&gt;process&amp;lt;Variant&amp;gt;Manifest&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flutter plugin registration contributes to the manifest
&lt;/h3&gt;

&lt;p&gt;When you run &lt;code&gt;flutter pub get&lt;/code&gt;, Flutter's tooling auto-generates the &lt;code&gt;GeneratedPluginRegistrant&lt;/code&gt; class and also ensures each plugin's &lt;code&gt;android/src/main/AndroidManifest.xml&lt;/code&gt; is registered as a library manifest source with Gradle. This is why you don't need to manually add plugin permissions — they merge in automatically. But it also means they merge in whether you want them to or not.&lt;/p&gt;

&lt;p&gt;Flavors and the manifest per build variant&lt;br&gt;
In a repo with dev, staging, and prod flavors, each flavor can have its own source set manifest:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;android/app/src/dev/AndroidManifest.xml&lt;/code&gt; — dev-only debug overrides&lt;/p&gt;

&lt;p&gt;&lt;code&gt;android/app/src/staging/AndroidManifest.xml&lt;/code&gt; — staging environment flags &lt;/p&gt;

&lt;p&gt;&lt;code&gt;android/app/src/prod/AndroidManifest.xml&lt;/code&gt; — production, minimal, clean&lt;/p&gt;

&lt;p&gt;A common pattern is to put &lt;code&gt;android:usesCleartextTraffic="true"&lt;/code&gt; only in the dev manifest (so it never ships to prod), and to declare &lt;code&gt;android:debuggable="false"&lt;/code&gt; explicitly in the prod manifest as a safeguard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;manifest&lt;/span&gt; &lt;span class="na"&gt;xmlns:android=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.android.com/apk/res/android"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;application&lt;/span&gt;
        &lt;span class="na"&gt;android:usesCleartextTraffic=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
        &lt;span class="na"&gt;android:networkSecurityConfig=&lt;/span&gt;&lt;span class="s"&gt;"@xml/network_security_config_dev"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/manifest&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why This Matters for Play Store Deployment
&lt;/h3&gt;

&lt;p&gt;Google Play Console runs its own manifest analysis on every AAB you upload. It extracts the final merged manifest and cross-checks it against multiple policy layers. Unintended permissions in your merged manifest are one of the top reasons for pre-launch warnings, policy violations, and outright rejections.&lt;/p&gt;

&lt;p&gt;Common Play Store issues caused by the merged manifest&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Issue in Play Console&lt;/th&gt;
&lt;th&gt;Root Cause in Manifest&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sensitive permissions warning&lt;/td&gt;
&lt;td&gt;Plugin added &lt;code&gt;READ_MEDIA_VIDEO&lt;/code&gt; or &lt;code&gt;READ_MEDIA_AUDIO&lt;/code&gt; you don't need&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tools:node="remove"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Broad storage access policy&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;READ_EXTERNAL_STORAGE&lt;/code&gt; without &lt;code&gt;maxSdkVersion&lt;/code&gt; — triggers review for API 33+&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;maxSdkVersion="32"&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MANAGE_EXTERNAL_STORAGE requires declaration&lt;/td&gt;
&lt;td&gt;A plugin declares this without your knowledge — it requires a special Play Store form&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;tools:node="remove"&lt;/code&gt; unless needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Background location without foreground&lt;/td&gt;
&lt;td&gt;Library adds &lt;code&gt;ACCESS_BACKGROUND_LOCATION&lt;/code&gt; — needs explicit policy approval&lt;/td&gt;
&lt;td&gt;Remove if unused; otherwise file declaration form&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;QUERY_ALL_PACKAGES policy&lt;/td&gt;
&lt;td&gt;Some plugins query all installed packages — requires justification&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;tools:node="remove"&lt;/code&gt; if avoidable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uses-feature incompatibility&lt;/td&gt;
&lt;td&gt;Plugin declares a required hardware feature, filtering out valid device categories&lt;/td&gt;
&lt;td&gt;Override with &lt;code&gt;required="false"&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cleartext traffic in production&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;usesCleartextTraffic="true"&lt;/code&gt; leaking from a dev-flavored manifest into a prod build&lt;/td&gt;
&lt;td&gt;Scope to dev flavor manifest only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Pre-submission checklist
&lt;/h3&gt;

&lt;p&gt;Before uploading any AAB to Play Console, run through these steps:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Build and inspect.
&lt;/h4&gt;

&lt;p&gt;Run &lt;code&gt;flutter build appbundle --flavor prod&lt;/code&gt; and open the merged manifest. Use grep "uses-permission" to get a clean list of all declared permissions.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Audit against features.
&lt;/h4&gt;

&lt;p&gt;For every permission in the merged manifest, ask: Does my app actually use this? If not, remove it with &lt;code&gt;tools:node="remove"&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Scope storage permissions.
&lt;/h4&gt;

&lt;p&gt;Ensure &lt;code&gt;READ_EXTERNAL_STORAGE&lt;/code&gt; has &lt;code&gt;maxSdkVersion="32"&lt;/code&gt; and &lt;code&gt;WRITE_EXTERNAL_STORAGE&lt;/code&gt; has &lt;code&gt;maxSdkVersion="28"&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Read the merge report.
&lt;/h4&gt;

&lt;p&gt;Check &lt;code&gt;build/outputs/logs/manifest-merger-prodRelease-report.txt&lt;/code&gt; for any merge conflicts flagged as errors or warnings — fix them before upload.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Cross-check with Play Console's App Content section.
&lt;/h4&gt;

&lt;p&gt;If you declare any sensitive permissions, ensure your privacy policy and data safety form are updated to reflect them. Undeclared data collection is a separate but related violation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Add manifest inspection to your CI pipeline. In your GitHub Actions release workflow, after flutter build appbundle, add a step that greps the merged manifest for a blocklist of sensitive permissions. Fail the job if any appear unexpectedly before the AAB ever reaches Play Console.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;.github/workflows/release.yml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Audit merged manifest permissions&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;MANIFEST="android/app/build/intermediates/merged_manifests/prodRelease/AndroidManifest.xml"&lt;/span&gt;
    &lt;span class="s"&gt;BLOCKLIST=(&lt;/span&gt;
      &lt;span class="s"&gt;"READ_MEDIA_VIDEO"&lt;/span&gt;
      &lt;span class="s"&gt;"READ_MEDIA_AUDIO"&lt;/span&gt;
      &lt;span class="s"&gt;"MANAGE_EXTERNAL_STORAGE"&lt;/span&gt;
      &lt;span class="s"&gt;"ACCESS_BACKGROUND_LOCATION"&lt;/span&gt;
      &lt;span class="s"&gt;"QUERY_ALL_PACKAGES"&lt;/span&gt;
    &lt;span class="s"&gt;)&lt;/span&gt;
    &lt;span class="s"&gt;for perm in "${BLOCKLIST[@]}"; do&lt;/span&gt;
      &lt;span class="s"&gt;if grep -q "$perm" "$MANIFEST"; then&lt;/span&gt;
        &lt;span class="s"&gt;echo "❌ BLOCKED: $perm found in merged manifest!"&lt;/span&gt;
        &lt;span class="s"&gt;exit 1&lt;/span&gt;
      &lt;span class="s"&gt;fi&lt;/span&gt;
    &lt;span class="s"&gt;done&lt;/span&gt;
    &lt;span class="s"&gt;echo "✅ No blocked permissions found"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Your source &lt;code&gt;AndroidManifest.xml&lt;/code&gt; is the beginning of the story, not the end. Every plugin you add such as &lt;code&gt;image_picker&lt;/code&gt;, &lt;code&gt;geolocator&lt;/code&gt;, &lt;code&gt;firebase_messaging&lt;/code&gt;, and &lt;code&gt;camera&lt;/code&gt; ships its own manifest, and those entries silently merge into yours at build time.&lt;/p&gt;

&lt;p&gt;The merged manifest in your &lt;code&gt;build/&lt;/code&gt; folder is the only version that matters. It is what the OS installs, what Play Console reviews, and what users grant permissions against. Making it a regular part of your release workflow by building it, reading it, and asserting on it in CI is one of the highest leverage habits you can build as a Flutter developer shipping to Android.&lt;/p&gt;

</description>
      <category>android</category>
      <category>flutter</category>
      <category>mobile</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Why String.fromEnvironment() Might Return an Empty String in Dart</title>
      <dc:creator>FARINU TAIWO</dc:creator>
      <pubDate>Thu, 16 Apr 2026 06:23:25 +0000</pubDate>
      <link>https://dev.to/petprog/why-your-dart-env-variables-keep-showing-up-empty-in-dart-23po</link>
      <guid>https://dev.to/petprog/why-your-dart-env-variables-keep-showing-up-empty-in-dart-23po</guid>
      <description>&lt;p&gt;A complete guide to why &lt;code&gt;String.fromEnvironment()&lt;/code&gt; silently returns an empty string when used with &lt;code&gt;final&lt;/code&gt;, what Dart is actually doing under the hood, and how to structure your environment configuration so it can never happen again, from local development all the way to production builds.&lt;/p&gt;

&lt;p&gt;I ran into this issue myself while working on a service class where I used &lt;code&gt;String.fromEnvironment()&lt;/code&gt; with a final variable to read my API base URL. Everything compiled successfully, there were no warnings, and the code looked perfectly valid. But when the app ran, the value kept resolving to an empty string. Nothing explicitly failed, the application simply behaved as if the configuration was missing.&lt;/p&gt;

&lt;p&gt;The only way I was able to trace the issue was through debugging, where I eventually discovered what Dart was actually doing under the hood. If you have ever experienced a mysteriously empty environment variable or want to make sure this subtle bug never happens in your project, this article is for you. &lt;/p&gt;

&lt;h2&gt;
  
  
  final vs const
&lt;/h2&gt;

&lt;p&gt;Both final and const prevent a variable from being reassigned after it has been given a value. That is the only similarity between them. Apart from that, they behave differently in almost every other way.&lt;/p&gt;

&lt;h3&gt;
  
  
  final — assigned once at runtime
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;final&lt;/code&gt; variable is assigned once and cannot be changed after that. However, its value is determined at runtime when the code is executed. This means the value can come from a function call, a network response, user input, or any other data available while the app is running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// All of these are valid final assignments&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;          &lt;span class="c1"&gt;// computed at runtime&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;// read from disk at runtime&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;computeBaseUrl&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;              &lt;span class="c1"&gt;// result of a function at runtime&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  const — a compile-time constant
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;const&lt;/code&gt; value must be fully determined before the app is compiled. The Dart compiler evaluates it, checks it, and embeds the result directly into the compiled binary. It does not exist as a runtime object in memory. Instead, it is stored as a fixed literal inside the generated machine code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// These are compile-time constants&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3.14159&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;appName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'Clock App'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;seconds:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This is NOT valid, DateTime.now() is only known at runtime&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// compile error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  String.fromEnvironment()
&lt;/h2&gt;

&lt;p&gt;Looking at the Dart SDK source, String.fromEnvironment() is declared like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From the Dart SDK — dart:core&lt;/span&gt;
&lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;external&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;factory&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;defaultValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things in that signature matter enormously:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;const factory — it is a &lt;code&gt;const&lt;/code&gt; constructor. The Dart spec says a const constructor can only produce a meaningful value when called in a &lt;code&gt;const&lt;/code&gt; context.&lt;/li&gt;
&lt;li&gt;external — the actual implementation is provided by the compiler toolchain, not by Dart code. When the compiler sees a &lt;code&gt;const&lt;/code&gt; invocation, it substitutes the value from &lt;code&gt;--dart-define&lt;/code&gt;. There is no runtime implementation that reads environment variables.&lt;/li&gt;
&lt;li&gt;defaultValue: '' — if the compiler finds no substitution to make, it uses the default. Silently. No error, no warning.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;String.fromEnvironment&lt;/code&gt; does not actually read any values at runtime. It is a compiler directive that looks like a constructor call. During compilation, the Dart compiler replaces it with a plain string literal. By the time the app runs, there is no special logic left, only the final string value as if it had been written directly in the source code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The two evaluation paths
&lt;/h3&gt;

&lt;p&gt;When the Dart compiler encounters &lt;code&gt;String.fromEnvironment('UPLOAD_URL')&lt;/code&gt;, it takes one of two paths depending on the call context:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Call is in a const context&lt;br&gt;
The compiler looks up &lt;code&gt;UPLOAD_URL&lt;/code&gt; in the &lt;code&gt;--dart-define&lt;/code&gt; flags. If found, it substitutes the literal string value into the binary. If not found, it substitutes '' (the default). Either way, the value is resolved at at compile time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Call is in a final or non-const context&lt;br&gt;
The compiler sees a regular constructor call and defers it to runtime. At runtime there is no mechanism to read &lt;code&gt;--dart-define&lt;/code&gt; flags because those were consumed during compilation. The constructor returns its &lt;code&gt;defaultValue&lt;/code&gt;, which is ''. Every single time.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// What the compiler does with const:&lt;/span&gt;
&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'UPLOAD_URL'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;//  compiler substitutes at build time&lt;/span&gt;
&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'https://upload.example.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// What happens with final:&lt;/span&gt;
&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'UPLOAD_URL'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;//  deferred to runtime  --dart-define is gone&lt;/span&gt;
&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// defaultValue returned every time&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Why it is so hard to debug: static final compiles without error. The analyser produces no warning. Your app launches normally. The empty string only surfaces when a network request silently fails, far from the actual cause, with no stack trace pointing back to the declaration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The same rule applies to all three environment constructors
&lt;/h2&gt;

&lt;p&gt;This is not specific to String.fromEnvironment. All three environment constructors in Dart share the same external const factory signature and behave identically:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Default Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;String&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;String.fromEnvironment('KEY', defaultValue: '')&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reads a string value from environment variables&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;''&lt;/code&gt; (empty string)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool.fromEnvironment('KEY', defaultValue: false)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reads a boolean value from environment variables&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int.fromEnvironment('KEY', defaultValue: 0)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reads an integer value from environment variables&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All three must be called in a const context to produce any value other than their default. Using final with any of them gives you the default silently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Every one of these will silently return its default with 'final'&lt;/span&gt;
&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;apiUrl&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'API_URL'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// ''&lt;/span&gt;
&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;debugMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'DEBUG_MODE'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'APP_VERSION'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// 0&lt;/span&gt;

&lt;span class="c1"&gt;// All three correct with 'const'&lt;/span&gt;
&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;apiUrl&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'API_URL'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// your URL&lt;/span&gt;
&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;debugMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'DEBUG_MODE'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// true/false&lt;/span&gt;
&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'APP_VERSION'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// your int&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is written in the &lt;code&gt;String.fromEnvironment&lt;/code&gt; documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This constructor is only guaranteed to work when invoked as const. It may work as a non-constant invocation on some platforms which have access to compiler options at run-time, but most ahead-of-time compiled platforms will not have this information.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How to structure your environment config correctly
&lt;/h2&gt;

&lt;p&gt;Rather than spreading &lt;code&gt;String.fromEnvironment&lt;/code&gt; calls across your codebase where any of them could mistakenly use &lt;code&gt;final&lt;/code&gt;, keep all environment access in one place. Create a single class responsible for it and ensure this is the only place in your entire project where &lt;code&gt;fromEnvironment&lt;/code&gt; is used.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppEnv&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// API endpoints &lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;apiBaseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'API_BASE_URL'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;uploadUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'UPLOAD_URL'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;socketUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'SOCKET_URL'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the class is &lt;code&gt;abstract&lt;/code&gt;, it cannot be instantiated. Every field is &lt;code&gt;static const&lt;/code&gt;, which makes misuse (calling them on an instance, or shadowing them with &lt;code&gt;final&lt;/code&gt;) structurally impossible. Any other service in the project references it directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/core/network/upload_service.dart&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UploadService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// No fromEnvironment call here, always go through AppEnv&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;_baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AppEnv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uploadUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;$_baseUrl$endpoint&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Enable the lint rule so the analyser catches this automatically
&lt;/h2&gt;

&lt;p&gt;Dart's &lt;code&gt;prefer_const_declarations&lt;/code&gt; lint rule will flag any &lt;code&gt;final&lt;/code&gt; variable whose value is a compile-time constant, including all three &lt;code&gt;fromEnvironment&lt;/code&gt; constructors. This turns a silent runtime bug into a compile-time analyser warning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# analysis_options.yaml&lt;/span&gt;
&lt;span class="na"&gt;linter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;prefer_const_declarations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;prefer_const_constructors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;prefer_const_literals_to_create_immutables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will immediately show a warning in your IDE:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;//  Lint: Prefer const with constant initialized variables.&lt;/span&gt;
&lt;span class="c1"&gt;// Replace 'final' with 'const'.&lt;/span&gt;
&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'UPLOAD_URL'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The env file setup
&lt;/h2&gt;

&lt;p&gt;Correct file structure&lt;br&gt;
The file must be a flat JSON object. No nesting. Keys match exactly what you pass to &lt;code&gt;fromEnvironment&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;env.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;flat,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;nesting,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;exact&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;names&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"API_BASE_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"UPLOAD_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://upload.example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"SOCKET_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"wss://socket.example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"BUILD_ENV"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"production"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ENABLE_ANALYTICS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: All values in the JSON must be strings including booleans and integers. Dart's toolchain reads them as strings and performs the type conversion internally. &lt;code&gt;"false"&lt;/code&gt; not &lt;code&gt;false&lt;/code&gt;, &lt;code&gt;"12"&lt;/code&gt; not &lt;code&gt;12&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The four rules to remember
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Always &lt;code&gt;const&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;fromEnvironment&lt;/code&gt; is a compiler directive. Use &lt;code&gt;static const&lt;/code&gt;. Always. No exceptions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Centralise&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Call &lt;code&gt;fromEnvironment&lt;/code&gt; in exactly one file (&lt;code&gt;AppEnv&lt;/code&gt;). Everywhere else imports from there.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Enable the lint&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;prefer_const_declarations: true&lt;/code&gt; to &lt;code&gt;analysis_options.yaml&lt;/code&gt;. Let the analyser enforce rule 1.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Flat JSON&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Your &lt;code&gt;env.json&lt;/code&gt; must be a flat &lt;code&gt;{ "KEY": "value" }&lt;/code&gt; object. No nesting. All values are strings.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>dart</category>
      <category>flutter</category>
      <category>programming</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>Flutter Widget Design: Write Once, Reuse Everywhere</title>
      <dc:creator>FARINU TAIWO</dc:creator>
      <pubDate>Sun, 05 Apr 2026 17:44:52 +0000</pubDate>
      <link>https://dev.to/petprog/flutter-widget-design-write-once-reuse-everywhere-hfc</link>
      <guid>https://dev.to/petprog/flutter-widget-design-write-once-reuse-everywhere-hfc</guid>
      <description>&lt;p&gt;UIs in Flutter are built from widgets, and as an app grows the number of widgets in the codebase grows with it. Because of this, designing widgets well becomes essential. Good widgets should be declarative, focused, and responsible for doing one thing well so the UI layer stays simple. The way widgets are structured largely determines whether a codebase remains clean and scalable or gradually becomes difficult to maintain.&lt;/p&gt;

&lt;p&gt;The underlying widget system is intentionally simple. Everything is a widget, widgets compose together, and the UI is rebuilt from state. Internally, widgets are lightweight configurations that describe the UI, while the render tree performs the actual layout and painting. This design makes rebuilding inexpensive, but it also requires developers to be intentional about component design. The real challenge is creating widgets that are truly reusable: components that accept only the data they need, render it, and emit events upward without depending on where they are used or which state management sits above them. Ultimately, well-designed Flutter widgets are independent, data-driven, event-based, and built for reusability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prefer Parameters Over Hardcoded Values
&lt;/h2&gt;

&lt;p&gt;The most common mistake is embedding values directly in a widget. Colors, sizes, strings, and padding cannot adapt if they are hard coded."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Don't do this&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PrimaryButton&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;PrimaryButton&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;styleFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;backgroundColor:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// ❌ hardcoded colour&lt;/span&gt;
        &lt;span class="nl"&gt;minimumSize:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;infinity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// ❌ hardcoded size&lt;/span&gt;
        &lt;span class="nl"&gt;shape:&lt;/span&gt; &lt;span class="n"&gt;RoundedRectangleBorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;borderRadius:&lt;/span&gt; &lt;span class="n"&gt;BorderRadius&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;circular&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;// ❌ hardcoded radius&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="c1"&gt;// ❌ no-op — caller can't respond&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This widget above is an example of a rigid button.&lt;br&gt;
This button looks reusable, but it is not because every visual property is built in.&lt;/p&gt;

&lt;p&gt;Now here's how to fix it. Every dimension, colour, and behaviour must flow in as a parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppButton&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;AppButton&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onPressed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isDisabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;textColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;borderRadius&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;52.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;infinity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;VoidCallback&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;onPressed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isDisabled&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;textColor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;borderRadius&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;Widget&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;effectiveColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;backgroundColor&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;colorScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;isActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isDisabled&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;AnimatedOpacity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;duration:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;milliseconds:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;opacity:&lt;/span&gt; &lt;span class="n"&gt;isActive&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="n"&gt;isActive&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;onPressed&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;styleFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;backgroundColor:&lt;/span&gt; &lt;span class="n"&gt;effectiveColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nl"&gt;foregroundColor:&lt;/span&gt; &lt;span class="n"&gt;textColor&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;colorScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onPrimary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nl"&gt;shape:&lt;/span&gt; &lt;span class="n"&gt;RoundedRectangleBorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;borderRadius:&lt;/span&gt; &lt;span class="n"&gt;BorderRadius&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;circular&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;borderRadius&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;isLoading&lt;/span&gt;
              &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;CircularProgressIndicator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nl"&gt;strokeWidth:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;textColor&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;colorScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onPrimary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="nl"&gt;mainAxisSize:&lt;/span&gt; &lt;span class="n"&gt;MainAxisSize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;icon&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;...[&lt;/span&gt;&lt;span class="n"&gt;icon&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
                    &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                  &lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how &lt;code&gt;backgroundColor&lt;/code&gt; falls back to theme.colorScheme.primary. When a caller doesn't pass a colour, you get the theme's primary. When they do, you get exactly what they passed. Defaults first, overrides second.&lt;/p&gt;

&lt;h2&gt;
  
  
  Isolate business logic with callbacks
&lt;/h2&gt;

&lt;p&gt;A reusable widget must not know what happens when it is interacted with. It only knows that something happened and reports it upward. All logic lives in the parent, provider, or use case but never in the widget itself.&lt;/p&gt;

&lt;p&gt;An example is a product card that includes action buttons such as Add to Cart, a favorite icon, and onTap functionality.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductCard&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ProductCard&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onTap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onAddToCart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onToggleWishlist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isWishlisted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;ProductModel&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;VoidCallback&lt;/span&gt; &lt;span class="n"&gt;onTap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;VoidCallback&lt;/span&gt; &lt;span class="n"&gt;onAddToCart&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;VoidCallback&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;onToggleWishlist&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isWishlisted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;GestureDetector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;onTap:&lt;/span&gt; &lt;span class="n"&gt;onTap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="n"&gt;ProductImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;url:&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;imageUrl&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onToggleWishlist&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;WishlistIcon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="nl"&gt;isFilled:&lt;/span&gt; &lt;span class="n"&gt;isWishlisted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="nl"&gt;onTap:&lt;/span&gt; &lt;span class="n"&gt;onToggleWishlist&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;]),&lt;/span&gt;
            &lt;span class="n"&gt;ProductInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;product:&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;AppButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;label:&lt;/span&gt; &lt;span class="s"&gt;'Add to cart'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="n"&gt;onAddToCart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;ProductCard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;product:&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;isWishlisted:&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WishlistProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;onTap:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/product/&lt;/span&gt;&lt;span class="si"&gt;${product.id}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;onAddToCart:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CartProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;onToggleWishlist:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WishlistProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Composition over inheritance
&lt;/h2&gt;

&lt;p&gt;Flutter explicitly favours composition. Instead of subclassing a widget to add features, build small focused widgets and compose them. This keeps each piece testable, readable, and independently reusable.&lt;/p&gt;

&lt;p&gt;Although the section header is a simple widget, it can be broken down into three separate widgets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Three small, composable widgets&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SectionHeader&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SectionHeader&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;Widget&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;mainAxisAlignment:&lt;/span&gt; &lt;span class="n"&gt;MainAxisAlignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;spaceBetween&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;Theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;textTheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;titleMedium&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SeeAllButton&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SeeAllButton&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onTap&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;VoidCallback&lt;/span&gt; &lt;span class="n"&gt;onTap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;GestureDetector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;onTap:&lt;/span&gt; &lt;span class="n"&gt;onTap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'See all'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;TextStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;Theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;primaryColor&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Compose them freely at call sites&lt;/span&gt;
&lt;span class="n"&gt;SectionHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="s"&gt;'Popular Restaurants'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;action:&lt;/span&gt; &lt;span class="n"&gt;SeeAllButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;onTap:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/restaurants'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can notice that &lt;code&gt;action&lt;/code&gt; is nullable because some section headers do not have an action button like &lt;code&gt;See all&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provide sensible defaults
&lt;/h2&gt;

&lt;p&gt;A button that needs 12 parameters just to render is hard to use. Instead, use optional parameters with default values for the most common case. People using the widget should only have to provide what is different for their situation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Every required parameter is friction. If a value has a sensible default, make it optional. Aim for your widget to be usable with one or two parameters in the most common case, while still allowing overrides for less common scenarios.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let’s take, for example, an empty state widget.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmptyState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EmptyState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'Nothing here yet'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;subtitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'Check back later.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;icon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;inbox_outlined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;iconSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;64.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;subtitle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;IconData&lt;/span&gt; &lt;span class="n"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;iconSize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;Widget&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Minimal usage, defaults handle the rest&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nf"&gt;EmptyState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// Context-specific override&lt;/span&gt;
&lt;span class="n"&gt;EmptyState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="s"&gt;'No orders yet'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;subtitle:&lt;/span&gt; &lt;span class="s"&gt;'Your completed orders will appear here.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;icon:&lt;/span&gt; &lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;receipt_long_outlined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;action:&lt;/span&gt; &lt;span class="n"&gt;AppButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;label:&lt;/span&gt; &lt;span class="s"&gt;'Browse restaurants'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="n"&gt;onBrowse&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Handling theming and responsive sizing
&lt;/h2&gt;

&lt;p&gt;Hardcoded pixel values are the silent killer of cross-device consistency. Always pull colours from &lt;code&gt;Theme.of(context)&lt;/code&gt; and sizes from a responsive utility (like &lt;code&gt;flutter_screenutil&lt;/code&gt;) or define them via ThemeData extensions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppChip&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;AppChip&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onTap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isSelected&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;VoidCallback&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;onTap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;colorScheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;colorScheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;GestureDetector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;onTap:&lt;/span&gt; &lt;span class="n"&gt;onTap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;AnimatedContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;duration:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;milliseconds:&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;symmetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;horizontal:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// responsive via screenutil&lt;/span&gt;
          &lt;span class="nl"&gt;vertical:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nl"&gt;decoration:&lt;/span&gt; &lt;span class="n"&gt;BoxDecoration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;colorScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;primary&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;colorScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;surface&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;border:&lt;/span&gt; &lt;span class="n"&gt;Border&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;colorScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;primary&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;colorScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="nl"&gt;borderRadius:&lt;/span&gt; &lt;span class="n"&gt;BorderRadius&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;circular&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;textTheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;labelMedium&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;copyWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;colorScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onPrimary&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;colorScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onSurface&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nl"&gt;fontWeight:&lt;/span&gt; &lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;w600&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;w400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common anti-patterns to avoid
&lt;/h2&gt;

&lt;p&gt;Designing reusable widgets is not only about following best practices, it is also about avoiding patterns that quietly make a codebase harder to maintain. Many widget problems do not appear immediately, but over time they lead to rigid components, duplicated logic, and difficult refactoring.&lt;/p&gt;

&lt;p&gt;Below are some common anti patterns that often appear in Flutter codebases as they grow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building God widgets
&lt;/h3&gt;

&lt;p&gt;A widget that renders a product card, handles pagination, manages a search bar, and displays error states is not reusable. It is a screen masquerading as a component. Split it. Each widget should do one thing and do it well.&lt;/p&gt;

&lt;p&gt;For example, a “god widget” might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductWidget&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ProductWidget&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProductWidget&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_ProductWidgetState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_ProductWidgetState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProductWidget&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;TextEditingController&lt;/span&gt; &lt;span class="n"&gt;searchController&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TextEditingController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fetchProducts&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fetchProducts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;query:&lt;/span&gt; &lt;span class="n"&gt;searchController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;page:&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Failed to load products"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;CircularProgressIndicator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;controller:&lt;/span&gt; &lt;span class="n"&gt;searchController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;decoration:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;InputDecoration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;hintText:&lt;/span&gt; &lt;span class="s"&gt;"Search products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="nl"&gt;onSubmitted:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fetchProducts&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Expanded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;ListView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;itemCount:&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nl"&gt;itemBuilder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

              &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;ListTile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="nl"&gt;leading:&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                  &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                  &lt;span class="nl"&gt;subtitle:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="err"&gt;\$&lt;/span&gt;&lt;span class="si"&gt;${product.price}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fetchProducts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;page:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Load more"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This widget is trying to do too many things at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetching data&lt;/li&gt;
&lt;li&gt;Handling search input&lt;/li&gt;
&lt;li&gt;Managing pagination&lt;/li&gt;
&lt;li&gt;Showing loading and error states&lt;/li&gt;
&lt;li&gt;Rendering product cards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Widgets that combine all of these responsibilities are difficult to maintain and reuse.&lt;/p&gt;

&lt;p&gt;A better approach is to split responsibilities into smaller, focused widgets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ProductSearchBar&lt;/code&gt; – handles user input for searching&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ProductList&lt;/code&gt; – displays the list of products&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ProductCard&lt;/code&gt; – renders a single product item&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ProductErrorState&lt;/code&gt; – shows errors when data fails to load&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ProductPagination&lt;/code&gt; – manages pagination controls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By giving each widget a single responsibility, they become easier to reuse independently. The resulting UI is also simpler to reason about, easier to test, and much more flexible to modify in the future.&lt;/p&gt;

&lt;h3&gt;
  
  
  Depending on a specific model type
&lt;/h3&gt;

&lt;p&gt;A widget that accepts a RestaurantModel directly can't show a GroceryStoreModel even if the UI is identical. Prefer accepting primitives or a thin display-specific abstraction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Wrong! Only works for RestaurantModel&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VendorTile&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;RestaurantModel&lt;/span&gt; &lt;span class="n"&gt;restaurant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ❌&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Right! Use primitives&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VendorTile&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;VendorTile&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;imageUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;badge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;imageUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;badge&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Defining child widgets as inline functions
&lt;/h3&gt;

&lt;p&gt;Writing helper methods like &lt;code&gt;Widget _buildHeader()&lt;/code&gt; and calling them inside &lt;code&gt;build()&lt;/code&gt; still executes as part of the same build scope. They don't get an independent element and won't be skipped when irrelevant parts of the tree change."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Wrong! Helper method — always runs with the parent&lt;/span&gt;
&lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;_buildHeader&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;   &lt;span class="c1"&gt;// ❌ re-runs every build&lt;/span&gt;
    &lt;span class="n"&gt;_buildBody&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="nf"&gt;_buildHeader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Header'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Right! Separate widget class — gets its own element&lt;/span&gt;
&lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;_Header&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  &lt;span class="c1"&gt;// ✅ skipped if props unchanged&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;_Body&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_Header&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;_Header&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Header'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using boolean flags to switch between entirely different UIs
&lt;/h3&gt;

&lt;p&gt;Parameters like isCompact, isGrid, isDetailed are a sign the widget is doing two or three separate jobs. Each boolean adds a hidden code path and exponentially increases mental overhead.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Three different layouts hidden behind boolean flags&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductCard&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isCompact&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isGrid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isHorizontal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Three focused widgets, each doing one thing&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductCardCompact&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductCardGrid&lt;/span&gt;    &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductCardHorizontal&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Not marking immutable constructors as const
&lt;/h3&gt;

&lt;p&gt;If you leave out const in a widget constructor, Flutter cannot skip rebuilding it. If a widget can be const, always make it const. It makes your app faster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ New instance on every parent rebuild&lt;/span&gt;
&lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="n"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;home&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;       &lt;span class="c1"&gt;// ❌ missing const&lt;/span&gt;
  &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;// ❌ missing const&lt;/span&gt;
  &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Home'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;           &lt;span class="c1"&gt;// ❌ missing const&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Flutter skips it with no cost to rebuild&lt;/span&gt;
&lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;home&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Home'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well-designed Flutter widgets are focused, reusable, and data-driven. Avoid hardcoding, god widgets, and hidden boolean logic. Let each widget do one thing and report events upward. By embracing composition, sensible defaults, and theming, you truly achieve “write once, reuse everywhere,” keeping your code clean, flexible, and future-proof.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>ui</category>
    </item>
    <item>
      <title>Asynchronous Programming in Dart</title>
      <dc:creator>FARINU TAIWO</dc:creator>
      <pubDate>Tue, 11 Apr 2023 08:00:48 +0000</pubDate>
      <link>https://dev.to/petprog/asynchronous-programming-in-dart-4b05</link>
      <guid>https://dev.to/petprog/asynchronous-programming-in-dart-4b05</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I wrote this article based on my interest in asynchronous tasks. Asynchronous programming is important for developers because it allows their applications to perform multiple tasks at the same time, without one task blocking the others. For example, when an application needs to wait for data from a server, asynchronous programming allows it to continue running other tasks, rather than waiting for the data before proceeding. This results in a smoother and more responsive user experience for the application's users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Synchronous And Asynchronous Programming
&lt;/h2&gt;

&lt;p&gt;Explaining to a kid, synchronous programming means doing things one at a time, step by step, and waiting for each step to finish before moving on to the next one. It's like doing your homework one page at a time and not moving to the next page until you finish the one you're working on.&lt;/p&gt;

&lt;p&gt;On the other hand, asynchronous programming means doing things at the same time, without waiting for each step to finish before starting the next one. It's like listening to music and drawing a picture at the same time, without having to stop one activity to do the other.&lt;/p&gt;

&lt;p&gt;In Dart, we can write programs that work synchronously or asynchronously, depending on what we need to do. Synchronous programming is good for simple tasks that can be done quickly, while asynchronous programming is better for tasks that take a long time or need to run in the background while we do something else.&lt;/p&gt;

&lt;p&gt;So, when we write a Dart program, we can choose whether to do things one at a time (synchronously) or things at the same time (asynchronously), depending on what we want to achieve.&lt;/p&gt;

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

&lt;p&gt;Future is used in a dart to denote an operation that is to be completed or not completed. If the task is completed, we get a result otherwise, we return an error. In asynchronous programming, we are forcing the long-processed task to follow an ordered path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Fetching user data...'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;userData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'User data received: &lt;/span&gt;&lt;span class="si"&gt;$userData&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'End of main'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Imagine that this function is more complex and slow.&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;delayed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;seconds:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;'John Doe'&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// simulate network delay&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output:

Fetching user data...
User data received: Instance of '_Future&amp;lt;String&amp;gt;'
End of main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the code above, The &lt;code&gt;fetchUserData()&lt;/code&gt; function returns a &lt;code&gt;Future&lt;/code&gt; object that will be completed with the result of the network request after a delay of 3 seconds, represented by the &lt;code&gt;Future.delayed()&lt;/code&gt; method. In this case, the result is simply a string containing the name 'John Doe'.&lt;/p&gt;

&lt;p&gt;The output shows that the &lt;code&gt;main&lt;/code&gt; function is executed synchronously, printing the first message to the console.&lt;/p&gt;

&lt;p&gt;Next, the &lt;code&gt;fetchUserData()&lt;/code&gt; function is called, which returns a &lt;code&gt;Future&amp;lt;String&amp;gt;&lt;/code&gt; object. The returned future is assigned to the &lt;code&gt;userData&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;The second message is then printed to the console, which shows the value of the &lt;code&gt;userData&lt;/code&gt; variable. Since &lt;code&gt;userData&lt;/code&gt; contains a &lt;code&gt;Future&amp;lt;String&amp;gt;&lt;/code&gt; object, the output displays "Instance of '_Future'" instead of the actual user data.&lt;/p&gt;

&lt;p&gt;Finally, the last message is printed to the console, indicating the end of the main function.&lt;/p&gt;

&lt;p&gt;The reason for this output is that the &lt;code&gt;fetchUserData()&lt;/code&gt; function returns a &lt;code&gt;Future&amp;lt;String&amp;gt;&lt;/code&gt; object, which represents the eventual result of the asynchronous operation. When we assign this future object to a variable and try to print it immediately, we see the type of the object instead of its value.&lt;/p&gt;

&lt;p&gt;To access the value of the &lt;code&gt;Future&lt;/code&gt; object, we need to use the &lt;code&gt;.then()&lt;/code&gt; method or the &lt;code&gt;await&lt;/code&gt; keyword in an &lt;code&gt;async&lt;/code&gt; function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using .then() method
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;.then()&lt;/code&gt; method is used to register a callback function that will be called when a &lt;code&gt;Future&lt;/code&gt; object completes.&lt;/p&gt;

&lt;p&gt;When we have a &lt;code&gt;Future&lt;/code&gt; object representing an asynchronous operation that will complete at some point in the future, we can use the &lt;code&gt;.then()&lt;/code&gt; method to attach a callback function that will be executed when the future is resolved with a result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Fetching user data...'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'User data received: &lt;/span&gt;&lt;span class="si"&gt;$userData&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'End of main'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output:

Fetching user data...
User data received: John Doe
End of main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we use the &lt;code&gt;.then()&lt;/code&gt; method to register a callback function to be called when the Future returned by &lt;code&gt;fetchUserData()&lt;/code&gt; is completed. The callback function takes the &lt;code&gt;userData&lt;/code&gt; string as its argument, which is then printed to the console along with another message indicating the end of the main function.&lt;/p&gt;

&lt;p&gt;Note that in the example above, the &lt;code&gt;print()&lt;/code&gt; statement after the &lt;code&gt;fetchUserData()&lt;/code&gt; call has been removed, because we are now using the &lt;code&gt;.then()&lt;/code&gt; method to access the result of the &lt;code&gt;Future&lt;/code&gt; instead of assigning it to a variable and printing it directly.&lt;/p&gt;

&lt;p&gt;What if we place &lt;code&gt;print()&lt;/code&gt; after the &lt;code&gt;fetchUserData()&lt;/code&gt; call?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Fetching user data...'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'User data received: &lt;/span&gt;&lt;span class="si"&gt;$userData&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'End of main'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output:

Fetching user data...
End of main
User data received: John Doe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The third message ("End of main") is printed before the &lt;code&gt;Future&lt;/code&gt; is completed, because the call to &lt;code&gt;fetchUserData()&lt;/code&gt; returns a &lt;code&gt;Future&lt;/code&gt; immediately, and the &lt;code&gt;.then()&lt;/code&gt; method is used to register a callback function that will be executed later, when the &lt;code&gt;Future&lt;/code&gt; is complete.&lt;/p&gt;

&lt;p&gt;This example shows how we can use the &lt;code&gt;.then()&lt;/code&gt; method to perform some action with the result of an asynchronous operation once it becomes available, without blocking the main thread.&lt;/p&gt;

&lt;p&gt;For an ordered execution, you need to place the print inside the &lt;code&gt;.then()&lt;/code&gt; callback after &lt;code&gt;userData&lt;/code&gt; is retrieved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using async/await
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;await&lt;/code&gt; keywords are used to write asynchronous code that looks synchronous and is easier to read and understand, without blocking the main thread.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Fetching user data...'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;userData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'User data received: &lt;/span&gt;&lt;span class="si"&gt;$userData&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'End of main'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output:

Fetching user data...
User data received: John Doe
End of main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;async&lt;/code&gt; keyword is used to mark the &lt;code&gt;main&lt;/code&gt; function as asynchronous, which allows us to use the &lt;code&gt;await&lt;/code&gt; keyword inside the function to pause its execution until a &lt;code&gt;Future&lt;/code&gt; is completed with a result.&lt;/p&gt;

&lt;p&gt;In this case, when the await keyword is used with the &lt;code&gt;fetchUserData()&lt;/code&gt; function call, it pauses the execution of the &lt;code&gt;main&lt;/code&gt; function until the Future returned by &lt;code&gt;fetchUserData()&lt;/code&gt; is resolved with a result.&lt;/p&gt;

&lt;p&gt;Once the &lt;code&gt;Future&lt;/code&gt; is completed, the &lt;code&gt;await&lt;/code&gt; keyword assigns the result to the &lt;code&gt;userData&lt;/code&gt; variable, and the execution of the &lt;code&gt;main&lt;/code&gt; function resumes, printing the second message to the console.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;await&lt;/code&gt; in this way makes it easier to write and read asynchronous code, because it looks similar to synchronous code, and makes it clear what the flow of the code is. It also makes it easier to handle errors and exceptions that might be thrown by asynchronous operations, because they can be caught using a &lt;code&gt;try-catch&lt;/code&gt; block like in synchronous code.&lt;/p&gt;

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

&lt;p&gt;In Dart, asynchronous programming can be achieved using &lt;code&gt;Future&lt;/code&gt; objects, which represent a value that may not be available yet, and the &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;await&lt;/code&gt; keywords, which allow us to write asynchronous code that looks synchronous and is easier to read and understand.&lt;/p&gt;

</description>
      <category>asynchronous</category>
      <category>dart</category>
      <category>flutter</category>
      <category>mobile</category>
    </item>
    <item>
      <title>How To Make Custom Radio Buttons With Cool Effects in Flutter</title>
      <dc:creator>FARINU TAIWO</dc:creator>
      <pubDate>Sun, 02 Apr 2023 10:06:59 +0000</pubDate>
      <link>https://dev.to/petprog/how-to-make-custom-radio-buttons-with-cool-effects-in-flutter-3ahf</link>
      <guid>https://dev.to/petprog/how-to-make-custom-radio-buttons-with-cool-effects-in-flutter-3ahf</guid>
      <description>&lt;p&gt;This tutorial will help you to understand how to create a group of custom radio buttons. Radio buttons are buttons that can be used to choose from different options.&lt;/p&gt;

&lt;p&gt;So, we are creating the screen below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdd8ab5403buzqiq8vkfm.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdd8ab5403buzqiq8vkfm.PNG" alt="custom radio button final app screen" width="473" height="872"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Basic knowledge of Dart programming language.&lt;/li&gt;
&lt;li&gt;Familiarity with Flutter framework and its widgets.&lt;/li&gt;
&lt;li&gt;A working installation of Flutter SDK and Android Studio or VS Code&lt;/li&gt;
&lt;li&gt;Understanding of Stateful widgets and their role in managing state in a Flutter app.&lt;/li&gt;
&lt;li&gt;Basic knowledge of layout and positioning in Flutter&lt;/li&gt;
&lt;li&gt;Familiarity with using image assets in Flutter.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tutorial
&lt;/h2&gt;

&lt;p&gt;Open either of the IDE. Create a flutter project (Application) and name the project any name of your choice.&lt;/p&gt;

&lt;p&gt;After the project is created, run the application on your device or emulator.&lt;/p&gt;

&lt;p&gt;Download the three payment platforms' logos and the tick circle icon. I downloaded them from the images on Google.&lt;/p&gt;

&lt;p&gt;Add the four images to the &lt;code&gt;assets&lt;/code&gt; folder, or you may need to create the folder at the root of the project folder.&lt;br&gt;
&lt;code&gt;assets/images/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Add the path of the images to pubspec.yaml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt; &lt;span class="c1"&gt;# To add assets to your application, add an assets section, like this:&lt;/span&gt;
&lt;span class="na"&gt;assets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;assets/images/visa.png&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;assets/images/mastercard.png&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;assets/images/paypal.png&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;assets/images/tick-circle.png&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In main.dart file, replace your code with the code below. The code sets up a basic Flutter app with an app bar and an empty container on the home page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/material.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;runApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;MaterialApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="s"&gt;'Flutter Demo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;debugShowCheckedModeBanner:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;theme:&lt;/span&gt; &lt;span class="n"&gt;ThemeData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;primaryColor:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF5C6ED1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;home:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomePage&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_HomePageState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_HomePageState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@override&lt;/span&gt;
    &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Scaffold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;appBar:&lt;/span&gt; &lt;span class="n"&gt;AppBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Custom Radio Button Demo"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;HomePage&lt;/code&gt; class is a stateful widget that has a state object of type &lt;code&gt;_HomePageState&lt;/code&gt;. The &lt;code&gt;build()&lt;/code&gt; method of this class returns a &lt;code&gt;Scaffold&lt;/code&gt; widget, which is a basic visual structure for the app. The Scaffold widget has an &lt;code&gt;AppBar&lt;/code&gt; and a &lt;code&gt;body&lt;/code&gt;. The &lt;code&gt;AppBar&lt;/code&gt; has a title with the text "Custom Radio Button Demo", and the body is an empty &lt;code&gt;Container&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_HomePageState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;selectedPayment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;CustomPaymentCardButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;assetName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;OutlinedButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;selectedPayment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;OutlinedButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;styleFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;symmetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;horizontal:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;vertical:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nl"&gt;shape:&lt;/span&gt;
            &lt;span class="n"&gt;RoundedRectangleBorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;borderRadius:&lt;/span&gt; &lt;span class="n"&gt;BorderRadius&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;circular&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="nl"&gt;side:&lt;/span&gt; &lt;span class="n"&gt;BorderSide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selectedPayment&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selectedPayment&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;green&lt;/span&gt;
                &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;blue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shade600&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="n"&gt;assetName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;fit:&lt;/span&gt; &lt;span class="n"&gt;BoxFit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selectedPayment&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;Positioned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;top:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;right:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"assets/images/tick-circle.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nl"&gt;fit:&lt;/span&gt; &lt;span class="n"&gt;BoxFit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the &lt;code&gt;_HomePageState&lt;/code&gt; which extends &lt;code&gt;State&lt;/code&gt; class. There is a variable named &lt;code&gt;selectedPayment&lt;/code&gt; that is initially set to 0. It is used to keep track of the selected payment option. There is a method &lt;code&gt;CustomPaymentCardButton&lt;/code&gt; takes two parameters, assetName and index. assetName is a string that represents the path to an image asset, and index is an integer that represents the position of the button in a list of payment options.&lt;/p&gt;

&lt;p&gt;The method returns an &lt;code&gt;OutlinedButton&lt;/code&gt; widget, which is a Material Design styled button with an outlined border. The &lt;code&gt;onPressed&lt;/code&gt; callback of the button sets the value of &lt;code&gt;selectedPayment&lt;/code&gt; to the index of the button that is pressed.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;child&lt;/code&gt; property of the button is a Stack widget that contains an &lt;code&gt;Image.asset&lt;/code&gt; widget with the image specified by the &lt;code&gt;assetName&lt;/code&gt; parameter. The &lt;code&gt;Stack&lt;/code&gt; also contains a Positioned widget that shows a checkmark icon if the button is selected.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;style&lt;/code&gt; property of the button is used to set its padding, border shape, and color based on whether it is selected or not. If the button is selected, its border width is set to 2.0, and its color is set to green. Otherwise, its border width is set to 0.5, and its color is set to blue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Scaffold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;appBar:&lt;/span&gt; &lt;span class="n"&gt;AppBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Custom Radio Button Demo"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;symmetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;horizontal:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;mainAxisAlignment:&lt;/span&gt; &lt;span class="n"&gt;MainAxisAlignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;spaceBetween&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="n"&gt;Expanded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;CustomPaymentCardButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"assets/images/visa.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;Expanded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;CustomPaymentCardButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                      &lt;span class="s"&gt;"assets/images/mastercard.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;CustomPaymentCardButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"assets/images/paypal.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The body is defined using a &lt;code&gt;Padding&lt;/code&gt; widget with a horizontal padding of 20. The padding contains a &lt;code&gt;Column&lt;/code&gt; widget that contains a list of payment options.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Row&lt;/code&gt; widget contains two &lt;code&gt;Expanded&lt;/code&gt; widgets that each contain a custom payment card button defined using the &lt;code&gt;CustomPaymentCardButton&lt;/code&gt; method. The first button shows an image of a Visa card and has an index of 0. The second button shows an image of a Mastercard and has an index of 1. The &lt;code&gt;Row&lt;/code&gt; widget is used to display the two buttons side-by-side with an equal width.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;CustomPaymentCardButton&lt;/code&gt; method is called again with an image of a PayPal logo and an index of 2 to display a third button below the &lt;code&gt;Row&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The overall code is the code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/material.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;runApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;MaterialApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="s"&gt;'Flutter Demo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;debugShowCheckedModeBanner:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;theme:&lt;/span&gt; &lt;span class="n"&gt;ThemeData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;primaryColor:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF5C6ED1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;home:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomePage&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_HomePageState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_HomePageState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;selectedPayment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;CustomPaymentCardButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;assetName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;OutlinedButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;selectedPayment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;OutlinedButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;styleFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;symmetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;horizontal:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;vertical:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nl"&gt;shape:&lt;/span&gt;
            &lt;span class="n"&gt;RoundedRectangleBorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;borderRadius:&lt;/span&gt; &lt;span class="n"&gt;BorderRadius&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;circular&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="nl"&gt;side:&lt;/span&gt; &lt;span class="n"&gt;BorderSide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selectedPayment&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selectedPayment&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;green&lt;/span&gt;
                &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;blue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shade600&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="n"&gt;assetName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;fit:&lt;/span&gt; &lt;span class="n"&gt;BoxFit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selectedPayment&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;Positioned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;top:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;right:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"assets/images/tick-circle.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nl"&gt;fit:&lt;/span&gt; &lt;span class="n"&gt;BoxFit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Scaffold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;appBar:&lt;/span&gt; &lt;span class="n"&gt;AppBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Custom Radio Button Demo"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;symmetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;horizontal:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;mainAxisAlignment:&lt;/span&gt; &lt;span class="n"&gt;MainAxisAlignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;spaceBetween&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="n"&gt;Expanded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;CustomPaymentCardButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"assets/images/visa.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;Expanded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;CustomPaymentCardButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                      &lt;span class="s"&gt;"assets/images/mastercard.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;CustomPaymentCardButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"assets/images/paypal.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the app.&lt;/p&gt;

&lt;p&gt;The overall code creates an app that displays custom payment card buttons that can be selected and displays a tick mark for the selected payment option.&lt;/p&gt;

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

&lt;p&gt;Custom radio buttons can provide a great way to enhance the user experience of your Flutter app. By using custom widgets and carefully selecting visual cues, you can create radio buttons that stand out and communicate their purpose to your users. With the example code we've covered here, you can get started on creating your custom radio buttons in your Flutter projects. With some practice and attention to detail, you can create radio buttons that are both functional and visually appealing for your users.&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

</description>
      <category>customradiobuttons</category>
      <category>flutter</category>
      <category>radiobutton</category>
      <category>payment</category>
    </item>
    <item>
      <title>Getting into GitHub Campus Expert (GCE) Program</title>
      <dc:creator>FARINU TAIWO</dc:creator>
      <pubDate>Sat, 24 Sep 2022 09:05:27 +0000</pubDate>
      <link>https://dev.to/petprog/getting-into-github-campus-expert-gce-program-3egm</link>
      <guid>https://dev.to/petprog/getting-into-github-campus-expert-gce-program-3egm</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Joining GitHub Campus Expert Program was a great experience for me though I struggled when I joined the program. I overcame those difficulties by learning during the program curriculum and by asking pertinent questions. I wrote this article to share few tips that helped me during the application process and during the program and I hope these tips will help you too.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I should have written this article some time ago, but I kept postponing it due to procrastination. So, I finally decided to share what I think you need to know when applying for the GCE program.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application
&lt;/h2&gt;

&lt;p&gt;When you apply for the GitHub Campus Expert program, the first thing to know is always capitalizing ‘H’ in GitHub.&lt;/p&gt;

&lt;p&gt;I am joking, however it is good you know this. Wherever you mention GitHub, take note of capitalizing the letter ‘H’. It is Git + Hub. I learnt this from one of the program managers at GitHub Education.&lt;/p&gt;

&lt;p&gt;Let’s dive in to why you are interested in this article…&lt;/p&gt;

&lt;p&gt;Firstly, let me share my story. I became the second GitHub Campus Expert in Ladoke Akintola University of Technology early (LAUTECH) 2019, however I submitted my application around September 2018. It was life changing experience for me when I got into the program. I learned about the program from my eldest brother’s friend, who was the GCE at the time. He sent me the application link, and I applied. I had recently started my exam around that time. So I remembered that I had applied for the program at the Olusegun Oke Library.&lt;/p&gt;

&lt;p&gt;One thing I know I can’t avoid mentioning is the problem that practically all tech communities have, which I feel I will fix with the help of other tech community organizers if I become a GCE. It is the gender ratio (percentage of male to female) of the participants in any tech program.&lt;br&gt;
Unless the event is centered on females, most tech activities in LAUTECH at the time involve a few ladies. I had no idea that I still didn’t know what it meant to address this problem.&lt;/p&gt;

&lt;p&gt;I applied with the intention of helping every female student on campus to join the campus tech community. That aided my application. I eventually got in.&lt;/p&gt;

&lt;p&gt;Getting in was thus simple back then. The problem I encountered occurred during the program’s learning phase, which will put my talents in technical writing, tech community inclusion, and public speaking to the test. Please keep in mind that all of your abilities will be expressed in writing. So, if you are strong at technical writing, the GCE program is for you; if you are not, like me, the curriculum will teach you how to write properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application tips
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Technical writing: Basic writing is one of the essential skills you need when applying for this program.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Know your community: The Tech community is made up of people that are interested in technology or are about to embark on a career in technology. The majority of participants in campus tech communities are people that enjoy technology as a hobby, whether it be design, programming, product, cloud, or anything else.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Contribution to your existing group: Be passionate about contributing to your tech ecosystem. As a student, you can start this with your department or faculty.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Show it in your application that you have organized a meetup or were a volunteer for an event is a plus.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GitHub will review your application to determine what hurdles your tech group has overcome and what kind of problems you will solve if you join the program. Students can currently join a variety of IT groups on campus. The Developer Student Club (DSC), the Google Developer Group (GDG), Pyladies, Figma, and departmental groups are among them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GitHub is looking for someone who can serve as a mentor to other tech organizers by assisting them in resolving issues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Highlight problems: The issues confronting the tech group are numerous. Accessing tech events, handing over duties after each tenure to the right people, and gender ratio are part of the issues. There are not enough trainers to teach about technology. How can a hackathon foster collaboration? Maybe you are organizing your first tech event.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Solutions: Giving answers to the problems you address in your application also demonstrates your problem-solving skills.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inclusive community: In your application, demonstrate what an inclusive community by telling them how you can make your tech community inclusive. Inclusive community is also centered on diversity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Networking: Once accepted into the program, I recommend you begin networking on campus. Know the organizers of every tech groups on your campus. Share contacts, connect with them, and explain how you can assist them.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Some of my unforgettable experience
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;I got my swags (hoodie, flag, bag and so on). Twitter&lt;/li&gt;
&lt;li&gt;I got to speak at a tech meetup full of women in tech.&lt;/li&gt;
&lt;li&gt;I met with a lot of other GCEs online, I travelled to another state to speak while mentoring over 120 secondary school students.&lt;/li&gt;
&lt;li&gt;I met with the GitHub CEO at a GitHub event in Lagos held in Civic Center Towers, Lagos 2019 despite arriving late due to traffic.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Congratulatory Email
&lt;/h2&gt;

&lt;p&gt;If you are selected you to the program, you will be invited to do some tasks to know who you are. More like onboarding to the program.&lt;/p&gt;

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

&lt;p&gt;Give the essay application your best. You can do it.&lt;/p&gt;

&lt;p&gt;If you still don’t know how to go about your application you can DM on Twitter.&lt;/p&gt;

&lt;p&gt;I wish you the best and I hope that you get into the program. It is an awesome experience.&lt;/p&gt;

</description>
      <category>github</category>
      <category>campus</category>
      <category>expert</category>
      <category>inclusive</category>
    </item>
    <item>
      <title>Connect your android device using Wi-Fi on VS Code</title>
      <dc:creator>FARINU TAIWO</dc:creator>
      <pubDate>Sat, 16 Jul 2022 06:55:28 +0000</pubDate>
      <link>https://dev.to/petprog/connect-your-android-device-using-wi-fi-on-vs-code-2ai7</link>
      <guid>https://dev.to/petprog/connect-your-android-device-using-wi-fi-on-vs-code-2ai7</guid>
      <description>&lt;p&gt;For mobile developers building an Android app, it's important that you always test your app on a real device (phone, tablet) before releasing it to users.&lt;/p&gt;

&lt;p&gt;Connecting your device through a USB cord when running your app can be stressful when you are using the device for other things like checking other apps and changing music or charging your device (if it is low). All of these can cause the USB to detach from the device and you will need to connect and run it again when the device is detached.&lt;/p&gt;

&lt;p&gt;This solution is too simple, in my opinion. Once your device has been connected to a USB port, you can allow Wi-Fi to maintain the connection.&lt;/p&gt;

&lt;p&gt;Android Debug Bridge (ADB) connection is used to connect our Android smartphone to our PC.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up a device for development
&lt;/h2&gt;

&lt;p&gt;To set up a device for development read &lt;a href="https://developer.android.com/studio/run/device#setting-up" rel="noopener noreferrer"&gt;this&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect to your device using USB
&lt;/h2&gt;

&lt;p&gt;You should already be aware that a USB cable is necessary.&lt;/p&gt;

&lt;p&gt;Check you have Android studio installed on your PC and your have installed the SDK Platform Tools.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fueyztvrh3lueypvbq46a.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fueyztvrh3lueypvbq46a.JPG" alt="sdk platform" width="800" height="97"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Verify that your device is connected by running the &lt;code&gt;adb devices&lt;/code&gt; command from your &lt;code&gt;android_sdk/platform-tools/&lt;/code&gt; directory. If connected, you'll see the device listed&lt;/p&gt;

&lt;p&gt;Make sure the platform-tools directory is present in your environment variable. Adding the directory is required if not.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff9n9h3ysfoz4vk67lwtv.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff9n9h3ysfoz4vk67lwtv.JPG" alt="User variable" width="800" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For flutter developers, before running your app, check if &lt;code&gt;flutter doctor&lt;/code&gt; is working correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect to your device using Wi-Fi
&lt;/h2&gt;

&lt;p&gt;Ensure that your workstation or PC and device are connected to the same wireless network.&lt;br&gt;
This wireless network can be established by sharing your PC's hotspot with your android device or vice-versa.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install extension
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Click on the extension tab in the left area on VS Code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5fnwa11ceb88nl52g63b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5fnwa11ceb88nl52g63b.png" alt="extensions.png" width="358" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Search for &lt;code&gt;Android ADB WLAN&lt;/code&gt; by deskbtm (developer name). Install the extension.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0cg06dly6zg2bvnjgmc.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0cg06dly6zg2bvnjgmc.JPG" alt="wlandevice" width="800" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Restart your VS Code. The extension added beside the &lt;code&gt;run button&lt;/code&gt; on your VS Code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgf71ax85uw50k27jcdcf.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgf71ax85uw50k27jcdcf.JPG" alt="run wlan icon.JPG" width="800" height="40"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click on the icon and select the android device name you want to connect.&lt;/li&gt;
&lt;li&gt;Select the number by pressing &lt;code&gt;Enter&lt;/code&gt;, &lt;/li&gt;
&lt;li&gt;Select the prompted &lt;code&gt;IP address&lt;/code&gt; of the WIFI your PC is connected to by pressing &lt;code&gt;Enter&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Connect successfully&lt;/li&gt;
&lt;li&gt;Unplug your device and enjoy!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If there is any issue &lt;code&gt;restart the adb&lt;/code&gt; by using the extension button and follow the process again.&lt;/p&gt;

&lt;p&gt;Thanks for reading. Don't forget to share.&lt;/p&gt;

&lt;p&gt;Contact me&lt;br&gt;
&lt;a href="//twitter.com/TaiwoFarinu"&gt;Twitter&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.linkedin.com/in/taiwo-farinu-063b18120/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>wifi</category>
      <category>vscode</category>
      <category>flutter</category>
    </item>
    <item>
      <title>Best libraries for Android Developers</title>
      <dc:creator>FARINU TAIWO</dc:creator>
      <pubDate>Sat, 16 Jul 2022 06:48:31 +0000</pubDate>
      <link>https://dev.to/petprog/best-libraries-for-android-developers-fp4</link>
      <guid>https://dev.to/petprog/best-libraries-for-android-developers-fp4</guid>
      <description>&lt;p&gt;Android libraries to help you with your app development. Some of them you may already know, but I am sure you can find a hidden gem among these! I have used some of these libraries here that have made development fun and easy for me.&lt;br&gt;
There are hundreds of new libraries being developed every day, but very few can impress developers and prove potent&lt;/p&gt;

&lt;h2&gt;
  
  
  What are Android libraries?
&lt;/h2&gt;

&lt;p&gt;Libraries are major game changers in software development irrespective of platform or stack. With libraries, developers leverage the efforts of other developers to perform actions/functions faster, more effective, and with lesser boilerplate codes. In this article, we will look at various categories in Android development and the common libraries used in them.&lt;br&gt;
These libraries for developers provide with basic pre-written codes and other important elements that can be used instantly rather than performing these tasks from scratch. &lt;/p&gt;

&lt;p&gt;Before we start, why don’t we take a moment and understand what Dependency Injector is?&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependency Injector Library
&lt;/h2&gt;

&lt;p&gt;Dependency injection (DI) is a method that is broadly used in programming for Android development. One can create a good Android library architecture for apps by following the principles of DI.&lt;/p&gt;

&lt;p&gt;You get the following benefits for using Android libraries for Dependency Injection: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reusability of code&lt;/li&gt;
&lt;li&gt;Ease of testing &lt;/li&gt;
&lt;li&gt;Ease of refactoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Popular libraries used for DI are Hilt, Dagger2 and Koin.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/googlecodelabs/android-hilt" rel="noopener noreferrer"&gt;Hilt&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Hilt, famously known as the dependency injection library, helps decrease the boilerplate of performing manual dependency injection in your Android project. Performing manual dependency injection needs the construction of each class and its dependencies. Also, the containers will help to reuse and organize dependencies.&lt;/p&gt;

&lt;p&gt;Hilt makes it easy to integrate Dagger dependency injection into Android apps. Hilt’s goals are to make Dagger-related infrastructure for Android apps easier to use. To make setup, readability/understanding, and code exchange between apps easier, a common set of components and scopes was created.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/InsertKoinIO/koin" rel="noopener noreferrer"&gt;Koin&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Navigation is known as a framework to easily move between destinations within the Android app. Using Navigation Library makes it easier to create navigation in your Android app. However, it only works with an app that offers a proper API regardless of the destination implementation.&lt;/p&gt;

&lt;p&gt;To utilize this component in your app, add Activities, Fragments, or other components as well. Add the Google Maven repository to add dependencies on Navigation.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://dagger.dev/" rel="noopener noreferrer"&gt;Dagger&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;We cannot complete the list without mentioning Dagger here. &lt;br&gt;
Dagger is a fully static, compile-time dependency injection framework for Java, Kotlin, and Android. It is an adaptation of an earlier version created by Square and now maintained by Google.&lt;/p&gt;

&lt;p&gt;Dagger sets itself apart from other dependency injection libraries.  &lt;/p&gt;

&lt;p&gt;The dependency injector library helps to provide smaller components to another model and helps them remain intact with each other.&lt;/p&gt;

&lt;p&gt;While developing larger applications, it would be challenging to handle dependency injection. This time, Dagger will save you in this situation as it creates a dependency injection graph in compile-time via annotation processors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Networking Libraries
&lt;/h2&gt;

&lt;p&gt;Here are the tools that you need for establishing any kind of network communication within the Android app.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/square/retrofit" rel="noopener noreferrer"&gt;Retrofit&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Retrofit is a type-safe HTTP client for Android and Java – developed by Square.&lt;/p&gt;

&lt;p&gt;Retrofit is the best library that lets you connect to HTTP-based API services from your Android applications. It leverages the &lt;a href="https://github.com/square/okhttp" rel="noopener noreferrer"&gt;OkHttp&lt;/a&gt; library’s core functionality, adding a bunch of features to it while eliminating the boilerplate code.&lt;/p&gt;

&lt;p&gt;One can effortlessly manipulate endpoints and headers, add a request body and query parameters, and select request methods – all with just annotations in Retrofit. Moreover, this Android library also takes care of parsing POJOs by using converters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Image Loading Libraries
&lt;/h2&gt;

&lt;p&gt;Image loading libraries are a knight in shining armor combating the problem of “out memory errors” in Android apps caused by loading multiple images at a time. Let’s look at the best Android libraries options out there in this category.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/square/picasso" rel="noopener noreferrer"&gt;Picasso&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Maintained by Square, Picasso is a trusted and widely used Android image library. Picasso claims to allow for hassle-free image loading in the application—often in one line of code. &lt;/p&gt;

&lt;p&gt;Some of the pitfalls that Picasso takes care of include handling ImageView recycling and downloading cancellation in an adapter, facilitating complex image transformations using minimal memory, automatic memory, and caching.&lt;/p&gt;

&lt;p&gt;Additional features that makes Picasso a popular choice among Android app developers are –&lt;/p&gt;

&lt;p&gt;Picasso automatically detects adapter re-use and the previously canceled download.&lt;br&gt;
It easily and effectively transforms images to make them fit better into layouts and reduce memory size.&lt;br&gt;
For more advanced effects, one can specify custom transformations.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/bumptech/glide" rel="noopener noreferrer"&gt;Glide&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Glide is yet another praised image loader and one of the best new Android libraries for developers, managed by Bumptech. Not just this, but it is also recommended straight by Google.&lt;/p&gt;

&lt;p&gt;Glide not only provides animated GIF support while handling image loading and caching but also helps in fetching, decoding, displaying video calls, images, and these GIFs. It also includes a flexible  API allowing developers and programmers to plug in any network stack, as its default stack is HttpUrlConnection.  &lt;/p&gt;

&lt;p&gt;This library primarily aims at making the scrolling process for any list of images as smooth as it can be. More so, it is also effective in case you need to fetch, resize, or even display a remote image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging libraries
&lt;/h2&gt;

&lt;p&gt;Almost an indispensable step in the app development process, debugging an application before its final release is as important as it gets. For this purpose, let’s have a look at some useful libraries.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/facebook/stetho" rel="noopener noreferrer"&gt;Stetho&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Stetho claims to be a “sophisticated debug bridge for Android applications”. The use of this library is not limited to just Network inspection, Database inspection, JavaScript Console, etc. but developers use it to have access to the Chrome Developer Tools features that are natively part of the Chrome desktop browser. Moreover, developers can choose to enable the optional dump app tool offering a powerful command-line interface to application internals.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/hypertrack/hyperlog-android" rel="noopener noreferrer"&gt;Hyperlog-Android&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;This is a standard Android Log class for storing logs in an Android database library and push them to a distant server for debugging. It is also a utility logger library.&lt;/p&gt;

&lt;p&gt;This library provides end to end visibility and helps to debug issues. HyperTrack SDK pushes log to the Hyperlog server, and the server makes use of ELK stack to process the logs and visualize them on Kibana.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reactive Programming libraries
&lt;/h2&gt;

&lt;p&gt;In Reactive programming, data is released from a component (a source if you will) to the other component known as Subscriber, assisting in handling asynchronous tasks efficiently. So in short, these libraries help with transferring data from source to subscribers. Some of the widely used Android app development libraries for this purpose are:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/ReactiveX/RxJava" rel="noopener noreferrer"&gt;RxJava2&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;If you are looking for a library that helps you in implementing reactive programming (create reactive applications), then RxJava is an ideal choice for you. It is officially described as “a library for composing asynchronous and event-based programs by using observable sequences”. &lt;/p&gt;

&lt;p&gt;This library is considered unbeatable by Android app developers because it simplifies the process to chain async operations, opens a more explicit way to declare how concurrent operations should work, is able to highlight errors sooner than other libraries, among other things&lt;/p&gt;

&lt;p&gt;Observables are the data sources and they exist in various types: &lt;code&gt;Observer&lt;/code&gt;, &lt;code&gt;Single&lt;/code&gt;, &lt;code&gt;Flowable&lt;/code&gt;, &lt;code&gt;Maybe&lt;/code&gt;, and &lt;code&gt;Completable&lt;/code&gt;. Each of these types has a unique use case which we can read more about &lt;a href="https://mindorks.com/course/demo/learn-rxjava/chapter/id/2" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/ReactiveX/RxAndroid" rel="noopener noreferrer"&gt;RxAndroid&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;RxAndroid, on the other hand, is an extension of RxJava2. It offers functionalities just peculiar to the Android platform, like the provision of a &lt;code&gt;Scheduler&lt;/code&gt; that schedules on the main thread or any given &lt;code&gt;Looper&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drawing libraries
&lt;/h2&gt;

&lt;p&gt;Here are some Android apps graphic libraries that have stood apart from the crowd. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/PhilJay/MPAndroidChart" rel="noopener noreferrer"&gt;MPAndroidChart&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;MPAndroidChart is an impeccable Android chart/graph view library. It supports radar, line, bar, bubble, pie, candlestick charts along with scaling, animations, and panning.&lt;/p&gt;

&lt;p&gt;If you are looking for its iOS counterpart then, Charts is the one for iOS app development.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/Androguide/HoloGraphLibrary" rel="noopener noreferrer"&gt;Holo Graph library&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;It is another new graphic library which is continuously becoming a favorite of many Android app development companies. This library is ideal for adding impeccably designed graphs and charts into Android applications. It includes: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LineGraph view (which looks something like this) –&lt;/li&gt;
&lt;li&gt;BarGraph View&lt;/li&gt;
&lt;li&gt;PieGraph View&lt;/li&gt;
&lt;li&gt;MultiSeriesDonutGraph View&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/razerdp/AnimatedPieView" rel="noopener noreferrer"&gt;AnimatedPieView&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;AnimatedPieView is another method for showing pie charts and ring graphs on Android.&lt;/p&gt;

&lt;p&gt;This library offers various benefits like: alpha animation on touch, fitting text field position itself during an animation, transformation between a pie diagram and a ring chart, animation while drawing charts and so on.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/florent37/MyLittleCanvas" rel="noopener noreferrer"&gt;MyLittleCanvas&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;It is one of the top libraries for Android developers. This Android Library is used to achieve features such as custom underline on a &lt;code&gt;TextView&lt;/code&gt;. It is also used to apply Textshape, lineshape, etc. with canvas methods. If you don't know how to use Canvas, use MyLittleCanvas instead!&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing libraries
&lt;/h2&gt;

&lt;p&gt;In scaling up your skills as an android developer, you need to learn how to test your code. Writing tests as you code is one of the best practices in android development. Testing your project shows the competence and efficacy of your mobile app. You can use these libraries to test your mobile app before you ship it to the market.&lt;/p&gt;

&lt;h3&gt;
  
  
  Espresso
&lt;/h3&gt;

&lt;p&gt;A part of the Android testing support library, Espresso is evidently a test framework that enables developers to build user interface tests for Android applications. Implying that this library lets you write tests and check whether the text of a TextView is similar to another text or not. It imparts the impression of a real user using the app while it is running on  both real device and emulators.&lt;br&gt;
To use this library, you are required to add dependencies to the app module &lt;code&gt;build.gradle&lt;/code&gt; file.&lt;br&gt;
Once you have done this, you can set the instrumentation runner and sync the Gradle files, followed by creating a test file. This is the reason why businesses prefer Android app bundles.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/mockito/mockito" rel="noopener noreferrer"&gt;Mockito&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Most times, the classes we intend to write tests for depend on other classes. Configuring these classes just for this purpose can be hectic. This is where Mockito comes in. It is a mocking framework that helps us create and configure mock (fake) objects. It is usually used with together with JUnit.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/robolectric/robolectric" rel="noopener noreferrer"&gt;Robolectric&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Robolectric is another unparalleled unit testing library. What this library does is that it handles inflation of resource loading, views, including other things. It makes the tests created in the library more efficacious and potent in performing functions that real devices with Android framework dependencies perform. In a sense, Robolectric simulates the Android SDK for the tests, eliminating the need for additional mocking frameworks like Mockito.&lt;/p&gt;

&lt;p&gt;Now again, you need to add the dependency in your app’s build.gradle file, followed by creating a sample test class . &lt;/p&gt;

&lt;p&gt;These Android libraries list have made it to this record based on the popular recommendation of the Android developers. Their features, functions, and performance together emit excellence in their niche of purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database Helper Library
&lt;/h2&gt;

&lt;p&gt;Offline data persistence is very important to enhance user experience. We usually need our applications to store important information that will be required on a next startup of the app or to make data available when no internet connection is available. As storing data is more complex than just combining key and value pairs, numerous libraries have been created to make storing this task easier in Android. In this section, we will look at one great persistence library: Room.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://developer.android.com/jetpack/androidx/releases/room?gclid=Cj0KCQjwlK-WBhDjARIsAO2sErSBy1x720SEkwTM43Ek6zkSKYTZhdx-p0_XS38r3cAQRT6P_Fr-q8caAoVuEALw_wcB&amp;amp;gclsrc=aw.ds" rel="noopener noreferrer"&gt;Room&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Room is a persistence library which is part of the Android Architecture Components. Room provides local data persistence with minimal boilerplate code. It provides an abstraction layer over SQLite, thereby making it easier to work with databases in our app. This library comes with a lot of advantages such as verifying SQL queries at compile time, rejecting database queries on the main thread (except when explicitly stated while initializing the database), providing implementation best practices, etc.&lt;/p&gt;

&lt;p&gt;Room is composed of three main components: the Database, the DAO (Data Access Objects), and Entities. Each of them is co-related in order to make the library functional. The Entity class represents a database table and has to be annotated with &lt;code&gt;@Entity&lt;/code&gt;. The variables in the class represent the columns the table will have. The DAO is an interface that contains the methods used for accessing the database. Room uses the interface to generate an implementation class for us.&lt;/p&gt;

&lt;p&gt;There are four specific annotations for the basic DAO operations: &lt;code&gt;@Insert&lt;/code&gt;, &lt;code&gt;@Update&lt;/code&gt;, &lt;code&gt;@Delete&lt;/code&gt;, and &lt;code&gt;@Query&lt;/code&gt;. Then, we have the Database class. This is an abstract class annotated with &lt;code&gt;@Database&lt;/code&gt; and that extends RoomDatabase. This class defines the list of entities and their DAOs to be used.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/objectbox/objectbox-java" rel="noopener noreferrer"&gt;Obectbox&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Object Box is a widely used Android databinding library that allows you to devote your valuable time to Various USPs of the product instead of storing and retrieving data. This library acts as an object oriented embedded database considered as a right alternative for SQLite. Since its documentations and portfolio are well defined, it is a perfect suit for IoT (Internet of Things) apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Fonts Library
&lt;/h2&gt;

&lt;p&gt;Almost every Android developer is passionate about the look and feel of their app. Sometimes we might need to go the extra mile into choosing a unique font for the app to give it the same feel across all devices. In situations like this, there are some libraries that can help us to use a custom font for all our texts in the app.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/chrisjenx/Calligraphy" rel="noopener noreferrer"&gt;Calligraphy&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Calligraphy is one of the most popular custom font libraries available and it is quite easy to get along with. With this library, we can easily declare a single font across our whole application or define fonts individually to a text.&lt;/p&gt;

&lt;h2&gt;
  
  
  View Binding Libraries
&lt;/h2&gt;

&lt;p&gt;The need for view binding libraries first surfaced when the need to reduce the boilerplate code when assigning views to variables arose. As a matter of fact, the number of Android support libraries worth mentioning for this purpose is numbered and two of the most prominent of them are:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://developer.android.com/topic/libraries/view-binding" rel="noopener noreferrer"&gt;Android View Binding&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;View binding is a feature that allows you to more easily write code that interacts with views. Once view binding is enabled in a module, it generates a binding class for each XML layout file present in that module. An instance of a binding class contains direct references to all views that have an ID in the corresponding layout.&lt;/p&gt;

&lt;p&gt;In most cases, view binding replaces &lt;code&gt;findViewById&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://developer.android.com/topic/libraries/data-binding" rel="noopener noreferrer"&gt;Android Data Binding&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The Data Binding Library is a support library that allows you to bind UI components in your layouts to data sources in your app using a declarative format rather than programmatically.&lt;/p&gt;

&lt;p&gt;Layouts are often defined in activities with code that calls UI framework methods. &lt;/p&gt;

&lt;p&gt;Binding components in the layout file lets you remove many UI framework calls in your activities, making them simpler and easier to maintain. This can also improve your app's performance and help prevent memory leaks and null pointer exceptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scanning Libraries
&lt;/h2&gt;

&lt;p&gt;In order to integrate scanning features while developing custom Android apps and increase their functionality level, developers prefer the below-mentioned libraries.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/journeyapps/zxing-android-embedded" rel="noopener noreferrer"&gt;Zxing&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Acronym for ‘Zebra Crossing’, ZXing is a barcode image-processing Android library that is implemented in Java, with ports to other programming languages. This library also has support for the 1D product, 1D industrial, and 2D barcodes.&lt;/p&gt;

&lt;p&gt;Google also uses ZXing in order to make millions of barcodes indexable on the web.  It also forms the basis of Android’s Barcode Scanner app and is integrated into Google Book Search and Google Product.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/LivotovLabs/CamView" rel="noopener noreferrer"&gt;CAMView&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;This is an effective alternative to the ZXing barcode scanner. It is an Android camera easy access library with an embedded QR scanner which is based on ZXing. &lt;/p&gt;

&lt;p&gt;CamView library possesses a set of components (views in simple words) which are set to be put to your layout files, allowing developers and giving immediate access to-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Live preview video feed from the device camera&lt;/li&gt;
&lt;li&gt;Scanning barcodes with the help of ZXing’s built-in decoding engine&lt;/li&gt;
&lt;li&gt;To perform your own camera live data processing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Image Cropper
&lt;/h2&gt;

&lt;p&gt;Tools used in cropping images and also adding some styling to the image.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/CanHub/Android-Image-Cropper" rel="noopener noreferrer"&gt;CanHub&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;CanHub is a powerful tool used to zoom and rotate the image from the camera or gallery. The image cropped can be customized in shape, limits and style. It is a project forked from &lt;a href="https://github.com/ArthurHub/Android-Image-Cropper" rel="noopener noreferrer"&gt;ArthurHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://developer.android.com/jetpack" rel="noopener noreferrer"&gt;Android Jetpack&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Android Jetpack is a suite of libraries to help developers follow best practices, reduce boilerplate code, and write code that works consistently across Android versions and devices so that developers can focus on the code they care about. Some libraries like Room, View binding and Data Binding are part of Android jetpack. Other libraries are &lt;a href="https://developer.android.com/topic/libraries/architecture/workmanager" rel="noopener noreferrer"&gt;WorkManager&lt;/a&gt;, &lt;a href="https://developer.android.com/guide/navigation/navigation-getting-started" rel="noopener noreferrer"&gt;Navigation&lt;/a&gt;, &lt;a href="https://developer.android.com/training/camerax" rel="noopener noreferrer"&gt;CameraX&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Jetpack libraries are published in the &lt;code&gt;androidx&lt;/code&gt; namespace.&lt;/p&gt;

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

&lt;p&gt;These libraries/tools significantly reduce the amount of boilerplate code written to perform various functions while developing for Android. Knowing the best libraries can help us improve the quality of our apps and make us produce more in less time.&lt;/p&gt;

&lt;p&gt;Contact me&lt;br&gt;
&lt;a href="https://twitter.com/petprog" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.linkedin.com/in/taiwo-farinu-063b18120/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>developers</category>
    </item>
    <item>
      <title>Why Technical Writing?</title>
      <dc:creator>FARINU TAIWO</dc:creator>
      <pubDate>Thu, 07 Jul 2022 22:27:43 +0000</pubDate>
      <link>https://dev.to/petprog/why-technical-writing-49n3</link>
      <guid>https://dev.to/petprog/why-technical-writing-49n3</guid>
      <description>&lt;p&gt;Helping people to understand tech has always been my passion.&lt;/p&gt;

&lt;p&gt;Being a developer has made me realize that I can code with any tool if I understand the basic concept of the tool. For instance, most programming language tutorials start with how to print "Hello world!". Then, understanding how "Hello world!" is printed in the next step. This shows that a programmer does not only need to know how to instruct a computer but can understand how the computer carries out the instruction. The more you know the basics the better it will be to understand the advanced concepts.&lt;/p&gt;

&lt;p&gt;Technical writing helps users understand a skill or product by providing detail-oriented instructions.&lt;br&gt;
Instruction is detailed information about how something should be done or operated.&lt;br&gt;
Technical writing helps users efficiently use tools and products. &lt;br&gt;
Technical writing also gives you a better sense of user empathy. When you write, you must pay more attention to what the readers or users of a product feel rather than what you think.&lt;/p&gt;

</description>
      <category>writing</category>
      <category>community</category>
    </item>
  </channel>
</rss>
