<?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: Muhammad Zeeshan Farooq</title>
    <description>The latest articles on DEV Community by Muhammad Zeeshan Farooq (@zeeshan_farooq_4ead3782d8).</description>
    <link>https://dev.to/zeeshan_farooq_4ead3782d8</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%2F3943495%2F2ac68b7c-f9fd-42cb-833d-87eb6758ddff.png</url>
      <title>DEV Community: Muhammad Zeeshan Farooq</title>
      <link>https://dev.to/zeeshan_farooq_4ead3782d8</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zeeshan_farooq_4ead3782d8"/>
    <language>en</language>
    <item>
      <title>How I Built a KYC System in Flutter — Lessons in Fintech</title>
      <dc:creator>Muhammad Zeeshan Farooq</dc:creator>
      <pubDate>Wed, 10 Jun 2026 06:07:09 +0000</pubDate>
      <link>https://dev.to/zeeshan_farooq_4ead3782d8/how-i-built-a-kyc-system-in-flutter-lessons-in-fintech-k1b</link>
      <guid>https://dev.to/zeeshan_farooq_4ead3782d8/how-i-built-a-kyc-system-in-flutter-lessons-in-fintech-k1b</guid>
      <description>&lt;p&gt;&lt;strong&gt;Real-world experience building identity verification for banking apps in Pakistan’s fintech ecosystem.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What KYC Actually Involves (The Full Picture)&lt;br&gt;
Most developers think of KYC as:&lt;/p&gt;

&lt;p&gt;Capture front of CNIC (National ID)&lt;br&gt;
Capture back of CNIC&lt;br&gt;
Take a selfie&lt;br&gt;
Submit&lt;br&gt;
The reality in a regulated financial app is closer to:&lt;/p&gt;

&lt;p&gt;Document capture with quality validation (blur, glare, framing)&lt;br&gt;
OCR extraction to prefill form fields&lt;br&gt;
Liveness detection to prevent spoofing with photos&lt;br&gt;
Face match — comparing selfie to ID card photo&lt;br&gt;
Address verification — utility bills, rental agreements&lt;br&gt;
Biometric consent collection and audit trail&lt;br&gt;
Retry logic with graceful degradation when third-party APIs fail&lt;br&gt;
Offline queuing for unstable networks&lt;br&gt;
Compliance logging — every step timestamped for regulatory audit&lt;br&gt;
Underestimate any of these and you’ll be pushing hotfixes at 2am.&lt;/p&gt;

&lt;p&gt;The Architecture That Works&lt;br&gt;
After rebuilding this flow three times across different apps, I landed on a state-machine approach using flutter_bloc. Each KYC step is a discrete state, and transitions are explicit and auditable.&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;// kyc_state.dart&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;KycState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;Equatable&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;KycState&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;KycIdle&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;KycState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@override&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="kt"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;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;DocumentCaptureInProgress&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;KycState&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;DocumentSide&lt;/span&gt; &lt;span class="n"&gt;side&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;DocumentQualityScore&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;lastScore&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;DocumentCaptureInProgress&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;side&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;lastScore&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="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;side&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lastScore&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;DocumentCaptureComplete&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;KycState&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;CapturedDocument&lt;/span&gt; &lt;span class="n"&gt;front&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;CapturedDocument&lt;/span&gt; &lt;span class="n"&gt;back&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;DocumentCaptureComplete&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;front&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;back&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="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;front&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;back&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;LivenessCheckInProgress&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;KycState&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;FaceMatchPending&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;KycState&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;KycSubmitting&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;KycState&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;KycApproved&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;KycState&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;KycFailed&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;KycState&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;KycFailureReason&lt;/span&gt; &lt;span class="n"&gt;reason&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;isRetryable&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 isRetryable flag on KycFailed is something I wish I'd added from day one. Not all failures are equal — a blurry photo is retryable, but a face mismatch after three attempts needs human review, not another retry.&lt;/p&gt;

&lt;p&gt;Document Capture: Where Most Apps Cut Corners&lt;br&gt;
The camera widget seems simple. It isn’t.&lt;/p&gt;

&lt;p&gt;The Quality Score Problem&lt;br&gt;
Submitting a blurry or glared image to your OCR/verification vendor costs money per API call — and worse, it fails silently (the vendor accepts the image but returns low-confidence data). You want to reject bad images before they leave the device.&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;DocumentQualityAnalyzer&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="n"&gt;DocumentQualityScore&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;analyze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Uint8List&lt;/span&gt; &lt;span class="n"&gt;imageBytes&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;img&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;decodeImageFromList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imageBytes&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;blurScore&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;_computeLaplacianVariance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imageBytes&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;brightnessScore&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;_computeBrightness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imageBytes&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;aspectRatioScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_checkAspectRatio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;img&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="na"&gt;toDouble&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="n"&gt;img&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="na"&gt;toDouble&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;DocumentQualityScore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;blur:&lt;/span&gt; &lt;span class="n"&gt;blurScore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// Laplacian variance — higher = sharper&lt;/span&gt;
      &lt;span class="nl"&gt;brightness:&lt;/span&gt; &lt;span class="n"&gt;brightnessScore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 0.0 to 1.0&lt;/span&gt;
      &lt;span class="nl"&gt;aspectRatio:&lt;/span&gt; &lt;span class="n"&gt;aspectRatioScore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;isAcceptable:&lt;/span&gt; &lt;span class="n"&gt;blurScore&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="n"&gt;brightnessScore&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="n"&gt;brightnessScore&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="n"&gt;aspectRatioScore&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;Liveness Detection: Don’t Roll Your Own&lt;br&gt;
I made the mistake once of building a basic “blink detection” liveness check using the front camera and ML Kit. It worked fine in testing and fooled approximately no one in production — a printed photo with a hole cut out for the eyes passed 40% of the time.&lt;/p&gt;

&lt;p&gt;Write on Medium&lt;br&gt;
Use a proper liveness SDK. Options I’ve integrated in Flutter:&lt;/p&gt;

&lt;p&gt;iProov — most robust, used by UK banks. Flutter plugin available.&lt;br&gt;
FaceTec — widely used in APAC fintech. Good SDK but heavy.&lt;br&gt;
AWS Rekognition Video — simpler to integrate if you’re already on AWS.&lt;br&gt;
Local options — for clients with strict data residency requirements, look at on-device models via TFLite, but set expectations: they’re weaker against sophisticated spoofing.&lt;br&gt;
The integration pattern is the same regardless of vendor:&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;LivenessCheckBloc&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;Bloc&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LivenessEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LivenessState&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;LivenessService&lt;/span&gt; &lt;span class="n"&gt;_service&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;LivenessCheckBloc&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;_service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LivenessIdle&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;on&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;StartLiveness&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;_onStart&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;on&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LivenessSessionComplete&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;_onComplete&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;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_onStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StartLiveness&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Emitter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LivenessState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;emit&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;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LivenessInProgress&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;sessionToken&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;_service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LivenessSessionReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;token:&lt;/span&gt; &lt;span class="n"&gt;sessionToken&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;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LivenessFailed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;error:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nl"&gt;isRetryable:&lt;/span&gt; &lt;span class="kc"&gt;true&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;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;_onComplete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;LivenessSessionComplete&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Emitter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LivenessState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;emit&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="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LivenessVerifying&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;_service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;verifySession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sessionId&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;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;passed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LivenessApproved&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;sessionId:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LivenessFailed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;error:&lt;/span&gt; &lt;span class="s"&gt;'Liveness check failed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;isRetryable:&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;attemptsRemaining&lt;/span&gt; &lt;span class="p"&gt;&amp;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="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;When the upload is queued, show the user a clear message: “We’ve saved your progress. We’ll submit automatically when your connection improves.” Then use a background service (WorkManager on Android, BGTaskScheduler on iOS) to retry.&lt;/p&gt;

&lt;p&gt;Never make the user redo the capture step because of a network failure. That is a UX sin I have witnessed firsthand and it destroys completion rates.&lt;/p&gt;

&lt;p&gt;Security Considerations&lt;br&gt;
A KYC flow handles some of the most sensitive personal data your app will ever touch. A few non-negotiables:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Never store raw ID images in app cache unencrypted. Use flutter_secure_storage for tokens and encrypted SQLite (via sqflite_sqlcipher) if you need local persistence of any captured data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wipe captured images from memory immediately after upload.&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="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;_submitAndCleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KycPayload&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_uploadService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uploadDocuments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Wipe regardless of success or failure&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Zero out byte arrays&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_tempFileManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clearKycTemp&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;ol&gt;
&lt;li&gt;&lt;p&gt;Certificate pinning for your KYC API endpoint. Use dio_certificate_pinning or a custom HttpClient. A KYC endpoint that can be MITMed is a regulatory nightmare.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Screenshot prevention during the flow.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;// In your KYC screen's initState&lt;br&gt;
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);&lt;br&gt;
// For Android: use FLAG_SECURE via platform channel&lt;br&gt;
// For iOS: it's handled at the OS level, but add a blur overlay for app switcher&lt;br&gt;
The Compliance Audit Trail&lt;br&gt;
Every regulator I’ve encountered in Pakistan’s fintech space (SBP, SECP) wants an audit trail. Every step, every retry, every failure — timestamped and associated with the user session.&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;KycAuditLogger&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KycAuditEvent&lt;/span&gt; &lt;span class="n"&gt;event&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;await&lt;/span&gt; &lt;span class="n"&gt;_auditRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;KycAuditEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;userId:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;sessionId:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;step:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;step&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;outcome:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;outcome&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;metadata:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;deviceInfo:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_deviceInfoService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDeviceSnapshot&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nl"&gt;timestamp:&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="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toUtc&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;Log everything on the client side too (not just server-side), because network failures mean some events may never reach the server. When a user calls support claiming “the app ate my KYC,” your client-side logs are gold.&lt;/p&gt;

&lt;p&gt;Completion Rates: The Metric Nobody Talks About&lt;br&gt;
KYC flows have notoriously low completion rates in the industry. From my experience across 4 fintech apps in Pakistan:&lt;/p&gt;

&lt;p&gt;Average drop-off is 35–50% if you just stack all the steps sequentially&lt;br&gt;
Showing a progress indicator (Step 2 of 4) reduces drop-off by ~15%&lt;br&gt;
Letting users save and resume a partially complete KYC reduced total abandonment by ~22% in one app I worked on&lt;br&gt;
In-app guidance (short animated tutorials before each capture step) reduces retry rate significantly&lt;br&gt;
The technical work means nothing if users don’t complete the flow.&lt;/p&gt;

&lt;p&gt;What I’d Do Differently&lt;br&gt;
If I were starting a new KYC integration today:&lt;/p&gt;

&lt;p&gt;Use a hosted KYC solution (Shufti Pro, Jumio, Sum&amp;amp;Substance) for the heavy lifting — OCR, liveness, face match — and build only the capture UI yourself. The API cost is almost always cheaper than the engineering cost.&lt;br&gt;
Design the state machine before writing a single widget. The KYC flow has more states than you think.&lt;br&gt;
Add analytics from day one — funnel tracking on every step, not just the final submission.&lt;br&gt;
Test on low-end Android devices. The front camera on a ~$80 Android phone in the Pakistani market produces very different image quality than your development device.&lt;br&gt;
KYC is one of those features that looks boring from the outside and is genuinely interesting once you’re deep in it — it sits at the intersection of computer vision, security, UX psychology, and regulatory compliance. Get it right and you’re shipping trust. Get it wrong and you’re fielding compliance audit findings.&lt;/p&gt;

&lt;p&gt;If you’re building something similar or have questions about any of the patterns above, drop a comment — happy to go deeper on any section&lt;/p&gt;

</description>
      <category>dart</category>
      <category>fintech</category>
    </item>
    <item>
      <title>React State Management: Stop Over-Rendering Your Components!</title>
      <dc:creator>Muhammad Zeeshan Farooq</dc:creator>
      <pubDate>Mon, 08 Jun 2026 07:36:12 +0000</pubDate>
      <link>https://dev.to/zeeshan_farooq_4ead3782d8/react-state-management-stop-over-rendering-your-components-27oi</link>
      <guid>https://dev.to/zeeshan_farooq_4ead3782d8/react-state-management-stop-over-rendering-your-components-27oi</guid>
      <description>&lt;p&gt;In the React ecosystem, one of the most common pitfalls developers encounter is the excessive and unnecessary use of useState. As React developers, we often fall into the trap of treating state as the default solution for every piece of data, leading to performance bottlenecks and difficult-to-debug codebases.&lt;/p&gt;

&lt;p&gt;In this article, we will explore how state truly works and how adopting a modular approach can lead to cleaner, more maintainable code.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Understanding State Updates: The Performance Cost
In React, every time a state variable updates, the component re-renders. If you are updating state unnecessarily—or placing state at too high a level in your component tree—you trigger a cascade of re-renders that can significantly degrade your application’s performance.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Golden Rule: If a value is not needed to drive the UI, do not place it in useState. Use useRef or simple variables for internal logic that doesn't require a re-render.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clean Architecture: The Power of Component Decomposition
The days of monolithic, 500-line components are over. The secret to a clean codebase is effective component decomposition. By breaking your UI into smaller, purpose-driven components, you gain several advantages:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Simplified Debugging: When an issue arises, you can instantly isolate the problematic child component rather than hunting through a massive file.&lt;/p&gt;

&lt;p&gt;Enhanced Reusability: Modular components can be easily abstracted and reused across different parts of your application.&lt;/p&gt;

&lt;p&gt;Example: A Scalable Pattern&lt;br&gt;
Instead of housing all logic in a single component, separate your concerns:&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%2F03hkbsrzpi8rlk8mr52j.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%2F03hkbsrzpi8rlk8mr52j.png" alt=" " width="753" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Debugging Simplified
When you manage state with intention and prioritize component modularity, debugging becomes a structured process rather than a guessing game:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Leverage React DevTools: Use it to profile your application and identify which specific components are triggering re-renders.&lt;/p&gt;

&lt;p&gt;Optimize useEffect: Always maintain precise dependency arrays to prevent infinite loops and unintended side effects.&lt;/p&gt;

&lt;p&gt;Lift State Judiciously: Only "lift state up" when absolutely necessary. Keep state as close to the point of consumption as possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
React development is not just about updating state; it is about architectural mastery. By being selective with your state and modular with your components, you build applications that are not only performant but also a joy to maintain and scale.&lt;/p&gt;

</description>
      <category>cleancode</category>
      <category>usestate</category>
      <category>react</category>
    </item>
    <item>
      <title>React State Management: When to Use What (A Simple Guide)</title>
      <dc:creator>Muhammad Zeeshan Farooq</dc:creator>
      <pubDate>Thu, 04 Jun 2026 06:14:42 +0000</pubDate>
      <link>https://dev.to/zeeshan_farooq_4ead3782d8/react-state-management-when-to-use-what-a-simple-guide-1cf1</link>
      <guid>https://dev.to/zeeshan_farooq_4ead3782d8/react-state-management-when-to-use-what-a-simple-guide-1cf1</guid>
      <description>&lt;p&gt;We’ve all been there. You start a new React project, everything is simple and clean. But a few hours later, you find yourself "prop-drilling"—passing data through five layers of components just to get it to a button that needs it.&lt;/p&gt;

&lt;p&gt;You start wondering: Should I use Context? Redux? Zustand?&lt;/p&gt;

&lt;p&gt;The problem isn't that there are no solutions; it’s that there are too many. Let’s strip away the technical jargon and use a simple analogy to clear the fog.&lt;/p&gt;

&lt;p&gt;The Three Ways to Manage Your Data&lt;br&gt;
Think of your React app like a House. Where you store your things depends on who needs to use them.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;useState: Your Pocket
useState is like your pocket. It’s meant for small, personal things you need right now (like your keys or phone).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Best for: Local data. If only one component needs to know if a dropdown is open or if an input field is changing, keep it in your "pocket."&lt;/p&gt;

&lt;p&gt;The Rule: Keep it local whenever possible.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Context API: The Family Fridge
Context is like the family fridge. It’s in a central place, and anyone in the house can walk up and grab a snack (data) without asking for permission every time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Best for: Data that many parts of your app need, but rarely changes.&lt;/p&gt;

&lt;p&gt;Examples: User login status, theme settings (dark/light mode), or language preferences.&lt;/p&gt;

&lt;p&gt;The Rule: Use it for "global" things that don't change every single second.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Zustand: The Professional Storage Unit
Zustand is like renting a professional storage unit. It’s clean, organized, and specifically designed for when you have a lot of stuff.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Best for: Complex data that changes frequently and needs to be accessed anywhere in the app (like a shopping cart or a complex dashboard).&lt;/p&gt;

&lt;p&gt;The Rule: Use it when your app starts feeling "heavy" or messy.&lt;/p&gt;

&lt;p&gt;The "When Should I Switch?" Cheat Sheet&lt;br&gt;
Don't overthink it. Follow this simple logic:&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%2Fqk6qaki6aipdm69z492p.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%2Fqk6qaki6aipdm69z492p.png" alt=" " width="800" height="269"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Final Thoughts: Stop Over-Engineering&lt;br&gt;
The biggest mistake developers make is reaching for a complex library before they actually need it.&lt;/p&gt;

&lt;p&gt;My advice: Start with useState. When you feel the pain of passing props through too many layers, move that specific data to Context. If the data becomes complex or the performance starts to lag, that is the perfect time to bring in Zustand.&lt;/p&gt;

&lt;p&gt;Keep it simple, keep it clean, and happy coding!&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Hidden Danger in React Server Actions: How to Prevent Accidental Data Leaks</title>
      <dc:creator>Muhammad Zeeshan Farooq</dc:creator>
      <pubDate>Mon, 01 Jun 2026 06:24:30 +0000</pubDate>
      <link>https://dev.to/zeeshan_farooq_4ead3782d8/the-hidden-danger-in-react-server-actions-how-to-prevent-accidental-data-leaks-69f</link>
      <guid>https://dev.to/zeeshan_farooq_4ead3782d8/the-hidden-danger-in-react-server-actions-how-to-prevent-accidental-data-leaks-69f</guid>
      <description>&lt;p&gt;With Great Power Comes Great Responsibility. Here is how to secure your backend logic when using React's newest server features.&lt;/p&gt;

&lt;p&gt;React's newest architectural shift—Server Components and Server Actions—has completely changed how we build full-stack web applications. Being able to database query directly inside your component or invoke a server-side function with a simple action={formAction} feels like magic.&lt;/p&gt;

&lt;p&gt;But this magic brings a serious architectural risk: Data Leakage and Security Vulnerability.&lt;/p&gt;

&lt;p&gt;In traditional architectures, your Node.js API acts as a hard boundary. If you don't explicitly send a database column to the client, the client never sees it. With React Server Actions, that boundary becomes blurry, and it is incredibly easy to accidentally expose sensitive data or run unsecured code.&lt;/p&gt;

&lt;p&gt;Let’s look at the major issue and how to resolve it properly.&lt;/p&gt;

&lt;p&gt;The Issue: Accidental Over-fetching and Poisoned Payloads&lt;br&gt;
Imagine you have a Server Action to update user profile information. A developer might write something like this:&lt;/p&gt;

&lt;p&gt;// actions.js (Server Action)&lt;br&gt;
'use server'&lt;/p&gt;

&lt;p&gt;import { db } from '@/lib/db';&lt;/p&gt;

&lt;p&gt;export async function updateUserProfile(formData) {&lt;br&gt;
  const userId = formData.get('id');&lt;br&gt;
  const rawData = {&lt;br&gt;
    name: formData.get('name'),&lt;br&gt;
    email: formData.get('email'),&lt;br&gt;
  };&lt;/p&gt;

&lt;p&gt;// ISSUE: Directly updating using raw input without strict validation&lt;br&gt;
  await db.user.update({&lt;br&gt;
    where: { id: userId },&lt;br&gt;
    data: rawData&lt;br&gt;
  });&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Why is this dangerous?&lt;br&gt;
Parameter Injection: A malicious user can intercept the request or modify the hidden form fields to pass extra parameters (like role: "admin" or balance: 99999). If your server-side database logic spreads or directly accepts the object, you've just given them admin rights.&lt;/p&gt;

&lt;p&gt;Missing Token/Session Verification: Because Server Actions look like regular JavaScript functions, it's easy to forget to check if the incoming session actually has permission to modify that specific resource ID.&lt;/p&gt;

&lt;p&gt;🛠️ The Resolution: Strict Input Validation and Context Binding&lt;br&gt;
To fix this structural issue, we need to apply production-grade software engineering principles: Strict Input Validation and Server-Side Context Verification.&lt;/p&gt;

&lt;p&gt;Step 1: Enforce Schemas using Zod&lt;br&gt;
Never trust formData.get() values directly. Wrap them in a strict schema validator.&lt;br&gt;
// actions.js&lt;br&gt;
'use server'&lt;/p&gt;

&lt;p&gt;import { z } from 'zod';&lt;br&gt;
import { db } from '@/lib/db';&lt;br&gt;
import { verifyAuth } from '@/lib/auth'; // Your session handler&lt;/p&gt;

&lt;p&gt;// Enforce strict length and types&lt;br&gt;
const ProfileSchema = z.object({&lt;br&gt;
  name: z.string().min(2).max(50),&lt;br&gt;
  email: z.string().email(),&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;export async function updateUserProfile(formData) {&lt;br&gt;
  // 1. Authenticate the user securely on the server&lt;br&gt;
  const session = await verifyAuth();&lt;br&gt;
  if (!session) throw new Error("Unauthorized access");&lt;/p&gt;

&lt;p&gt;// 2. Safely parse incoming data&lt;br&gt;
  const validatedFields = ProfileSchema.safeParse({&lt;br&gt;
    name: formData.get('name'),&lt;br&gt;
    email: formData.get('email'),&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;if (!validatedFields.success) {&lt;br&gt;
    return { error: "Invalid form input data" };&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;// 3. Update using securely bound session ID, NOT client-supplied ID&lt;br&gt;
  await db.user.update({&lt;br&gt;
    where: { id: session.userId }, // Secure&lt;br&gt;
    data: validatedFields.data,    // Cleaned&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;return { success: true };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Key Takeaways for Production React Apps&lt;br&gt;
If you want to use React's server-driven features safely, memorize these three rules:&lt;/p&gt;

&lt;p&gt;Treat Server Actions like Public APIs: Just because you didn't write an explicit fetch('/api/user') endpoint doesn't mean it isn't one. Under the hood, React creates an HTTP POST endpoint for every server action.&lt;/p&gt;

&lt;p&gt;Never Pass Sensitive Objects as Props: If your server component passes a full user object (including password hashes or internal IDs) down to a Client Component, that data is serialized into the HTML document stream and can be inspected by anyone.&lt;/p&gt;

&lt;p&gt;Validate the Payload Size: Always enforce maximum string lengths on your inputs to avoid buffer or memory calculation hangs during high concurrent server loads.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Digital Cheque Books — How QR Codes Can Replace Physical Cheques in Banking</title>
      <dc:creator>Muhammad Zeeshan Farooq</dc:creator>
      <pubDate>Fri, 22 May 2026 10:55:30 +0000</pubDate>
      <link>https://dev.to/zeeshan_farooq_4ead3782d8/digital-cheque-books-how-qr-codes-can-replace-physical-cheques-in-banking-2fi5</link>
      <guid>https://dev.to/zeeshan_farooq_4ead3782d8/digital-cheque-books-how-qr-codes-can-replace-physical-cheques-in-banking-2fi5</guid>
      <description>&lt;p&gt;The Problem&lt;br&gt;
Fraud — cheques can be forged or altered&lt;br&gt;
Delays — issuance takes 3-7 days&lt;br&gt;
Cost — printing and courier add operational expense&lt;br&gt;
No real-time validation — bounced cheques discovered too late  &lt;/p&gt;

&lt;p&gt;The Solution — Dynamic QR Cheques&lt;br&gt;
Each cheque = a unique encrypted QR code, generated on demand from your phone.&lt;/p&gt;

&lt;p&gt;User requests cheque in app&lt;br&gt;
        ↓&lt;br&gt;
Biometric authentication&lt;br&gt;
        ↓&lt;br&gt;
Bank server generates encrypted QR&lt;br&gt;
        ↓&lt;br&gt;
User shows QR to payee&lt;br&gt;
        ↓&lt;br&gt;
Payee scans → Bank validates → Payment processed&lt;br&gt;
        ↓&lt;br&gt;
QR invalidated — cannot be reused&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Example&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;QR Payload&lt;br&gt;
{&lt;br&gt;
  "cheque_id": "CHQ-2026-UUID-7a3f9c",&lt;br&gt;
  "payee_name": "Ali Hassan",&lt;br&gt;
  "amount": 50000,&lt;br&gt;
  "currency": "PKR",&lt;br&gt;
  "expiry_date": "2026-06-21",&lt;br&gt;
  "signature": "RSA_SIGNED_HASH",&lt;br&gt;
  "one_time_token": "OTT-9f2a1b3c"&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;6 Layers of Fraud Prevention&lt;/p&gt;

&lt;p&gt;Biometric issuance — stolen phone cannot generate cheques&lt;br&gt;
RSA server signature — tampered QR instantly detected&lt;br&gt;
One-time token — scan once, invalidated forever&lt;br&gt;
Time expiry — auto-reject after 30 days&lt;br&gt;
Device binding — new device requires re-authentication&lt;br&gt;
Real-time balance check — no more bounced cheque&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%2Fugfwmgzgiva5luatcrcz.jpeg" 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%2Fugfwmgzgiva5luatcrcz.jpeg" alt=" " width="710" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Conclusion&lt;br&gt;
The technology exists today — Flutter, QR codes, biometrics, real-time APIs. Pakistani banks that implement this will see reduced fraud, lower costs, and better customer experience.&lt;br&gt;
The era of the paper cheque book is ready to end.&lt;/p&gt;

&lt;p&gt;Muhammad Zeeshan Farooq — Software Engineer | Fintech | Flutter&lt;br&gt;
linkedin.com/in/zeeshanfarooq | zeeshan-dev.web.app&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>fintech</category>
      <category>banking</category>
      <category>security</category>
    </item>
    <item>
      <title>How I Built Real-Time Banking APIs with 99.9% Uptime</title>
      <dc:creator>Muhammad Zeeshan Farooq</dc:creator>
      <pubDate>Fri, 22 May 2026 07:28:57 +0000</pubDate>
      <link>https://dev.to/zeeshan_farooq_4ead3782d8/how-i-built-real-time-banking-apis-with-999-uptime-5a3m</link>
      <guid>https://dev.to/zeeshan_farooq_4ead3782d8/how-i-built-real-time-banking-apis-with-999-uptime-5a3m</guid>
      <description>&lt;p&gt;Building real-time banking APIs is not just about writing code — it's about building trust. In fintech, a single failed transaction can cost a customer forever.&lt;br&gt;
After 11 years of building production banking applications, here are my key lessons.&lt;/p&gt;

&lt;p&gt;The Stack That Works&lt;br&gt;
Node.js + Express — Backend&lt;br&gt;
WebSockets — Real-time updates&lt;br&gt;
GraphQL — Flexible data fetching&lt;br&gt;
MongoDB — Transaction storage&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Always Use Circuit Breakers
Banking APIs fail. Third-party services go down. Circuit breakers prevent cascading failures.
const circuitBreaker = async (apiCall) =&amp;gt; {
try {
return await apiCall();
} catch (error) {
console.error('Circuit broken:', error);
return fallbackResponse();
}
};&lt;/li&gt;
&lt;li&gt;&lt;p&gt;WebSockets for Real-Time Updates&lt;br&gt;
Polling kills performance. WebSockets give instant transaction updates.&lt;br&gt;
io.on('connection', (socket) =&amp;gt; {&lt;br&gt;
socket.on('transaction', async (data) =&amp;gt; {&lt;br&gt;
const result = await processTransaction(data);&lt;br&gt;
socket.emit('transaction-status', result);&lt;br&gt;
});&lt;br&gt;
});&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Never Trust Input — Validate Everything&lt;br&gt;
const validateTransaction = (data) =&amp;gt; {&lt;br&gt;
if (!data.amount || data.amount &amp;lt;= 0) {&lt;br&gt;
throw new Error('Invalid amount');&lt;br&gt;
}&lt;br&gt;
if (!data.accountId) {&lt;br&gt;
throw new Error('Account ID required');&lt;br&gt;
}&lt;br&gt;
};&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Results&lt;br&gt;
Following these patterns helped us achieve:&lt;/p&gt;

&lt;p&gt;✅ 99.9% API uptime&lt;br&gt;
✅ 40% faster transaction processing&lt;br&gt;
✅ Zero security breaches in production&lt;/p&gt;

&lt;p&gt;Final Thought&lt;br&gt;
In banking, reliability is everything. Your API is not just code — it is someone's money.&lt;/p&gt;

</description>
      <category>node</category>
      <category>fintech</category>
      <category>banking</category>
      <category>api</category>
    </item>
    <item>
      <title>Building Secure KYC Systems in Flutter — Lessons from Production Banking Apps</title>
      <dc:creator>Muhammad Zeeshan Farooq</dc:creator>
      <pubDate>Thu, 21 May 2026 07:24:58 +0000</pubDate>
      <link>https://dev.to/zeeshan_farooq_4ead3782d8/building-secure-kyc-systems-in-flutter-lessons-from-production-banking-apps-3dad</link>
      <guid>https://dev.to/zeeshan_farooq_4ead3782d8/building-secure-kyc-systems-in-flutter-lessons-from-production-banking-apps-3dad</guid>
      <description>&lt;p&gt;**What is KYC and Why Does it Matter?&lt;br&gt;
**KYC is a mandatory regulatory process that financial institutions use to verify the identity of their customers. In digital banking, this means:&lt;/p&gt;

&lt;p&gt;Verifying government-issued identity documents (NIC, Passport)&lt;br&gt;
Biometric face matching&lt;br&gt;
Liveness detection to prevent spoofing&lt;br&gt;
Real-time database checks against government records&lt;/p&gt;

&lt;p&gt;A poorly implemented KYC system can lead to regulatory fines, fraud, and data breaches. Getting it right is critical.&lt;br&gt;
The Core Architecture&lt;br&gt;
After building KYC systems for multiple banking apps, I settled on a Clean Architecture approach that separates concerns clearly:&lt;br&gt;
Presentation Layer  →  Bloc/Riverpod&lt;br&gt;
Domain Layer        →  Use Cases + Entities&lt;br&gt;
Data Layer          →  Repositories + Remote/Local Sources&lt;br&gt;
This separation means:&lt;/p&gt;

&lt;p&gt;KYC business logic stays independent of UI&lt;br&gt;
Easy to swap SDKs without touching business logic&lt;br&gt;
Testable in isolation — critical for regulated environments&lt;/p&gt;

&lt;p&gt;Key Component 1 — NIC Scanning&lt;/p&gt;

&lt;p&gt;abstract class NicScanRepository {&lt;br&gt;
  Future&amp;gt; scanNic();&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;class NicScanRepositoryImpl implements NicScanRepository {&lt;br&gt;
  final NicScanDataSource dataSource;&lt;/p&gt;

&lt;p&gt;NicScanRepositoryImpl({required this.dataSource});&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/override"&gt;@override&lt;/a&gt;&lt;br&gt;
  Future&amp;gt; scanNic() async {&lt;br&gt;
    try {&lt;br&gt;
      final result = await dataSource.initiateScan();&lt;br&gt;
      return Right(result.toDomain());&lt;br&gt;
    } on ScanException catch (e) {&lt;br&gt;
      return Left(ScanFailure(message: e.message));&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Key Component 2 — Biometric Authentication with IdWise SDK&lt;br&gt;
class BiometricKycDataSource {&lt;br&gt;
  Future initiateKyc({&lt;br&gt;
    required String journeyDefinitionId,&lt;br&gt;
    required String referenceNumber,&lt;br&gt;
  }) {&lt;br&gt;
    final completer = Completer();&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IdWise.initialize(
  clientKey: AppConfig.idWiseClientKey,
  environment: IdWiseEnvironment.production,
);

IdWise.startJourney(
  journeyDefinitionId: journeyDefinitionId,
  referenceNumber: referenceNumber,
  locale: 'en',
  delegate: _KycDelegate(completer),
);

return completer.future;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;class _KycDelegate implements IdWiseSDKDelegate {&lt;br&gt;
  final Completer completer;&lt;br&gt;
  _KycDelegate(this.completer);&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/override"&gt;@override&lt;/a&gt;&lt;br&gt;
  void onJourneyCompleted(String journeyId, bool isCompleted) {&lt;br&gt;
    completer.complete(KycResult(&lt;br&gt;
      journeyId: journeyId,&lt;br&gt;
      status: isCompleted ? KycStatus.completed : KycStatus.failed,&lt;br&gt;
    ));&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/override"&gt;@override&lt;/a&gt;&lt;br&gt;
  void onJourneyResumed(String journeyId) {}&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/override"&gt;@override&lt;/a&gt;&lt;br&gt;
  void onError(IdWiseError error) {&lt;br&gt;
    completer.completeError(KycException(message: error.message));&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Key Component 3 — State Management for KYC Flow&lt;br&gt;
enum KycStep { idle, scanning, biometric, verifying, completed, failed }&lt;/p&gt;

&lt;p&gt;@riverpod&lt;br&gt;
class KycNotifier extends _$KycNotifier {&lt;br&gt;
  &lt;a class="mentioned-user" href="https://dev.to/override"&gt;@override&lt;/a&gt;&lt;br&gt;
  KycState build() =&amp;gt; const KycState(step: KycStep.idle);&lt;/p&gt;

&lt;p&gt;Future startNicScan() async {&lt;br&gt;
    state = state.copyWith(step: KycStep.scanning);&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;final result = await ref.read(nicScanRepositoryProvider).scanNic();

result.fold(
  (failure) =&amp;gt; state = state.copyWith(
    step: KycStep.failed,
    errorMessage: failure.message,
  ),
  (nicData) =&amp;gt; state = state.copyWith(
    step: KycStep.biometric,
    nicData: nicData,
  ),
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;Future startBiometric() async {&lt;br&gt;
    state = state.copyWith(step: KycStep.verifying);&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;final result = await ref.read(biometricKycRepositoryProvider)
    .initiateKyc(referenceNumber: state.nicData!.nicNumber);

result.fold(
  (failure) =&amp;gt; state = state.copyWith(
    step: KycStep.failed,
    errorMessage: failure.message,
  ),
  (kycResult) =&amp;gt; state = state.copyWith(
    step: KycStep.completed,
    kycResult: kycResult,
  ),
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Lessons Learned&lt;br&gt;
After building KYC for multiple production banking apps, here is what I wish I had known earlier:&lt;/p&gt;

&lt;p&gt;Abstract your SDK dependencies early — KYC SDK providers change, merge, and update APIs frequently&lt;br&gt;
Test on real low-end devices — camera performance varies dramatically across the Android ecosystem&lt;br&gt;
Handle network failures gracefully — KYC mid-flow network drops are common; build resumable journeys&lt;br&gt;
Log anonymously — never log NIC numbers or biometric data, even in development&lt;br&gt;
Design for accessibility — banking apps serve all demographics; KYC flows must work for elderly users too&lt;/p&gt;

&lt;p&gt;Conclusion&lt;br&gt;
Building secure KYC systems in Flutter for production banking applications requires a combination of clean architecture, robust state management, and security-first thinking. The patterns described here have been battle-tested across multiple live digital banking applications serving real users in regulated financial environments.&lt;br&gt;
If you are building a fintech app and have questions about KYC implementation, feel free to connect — I am always happy to discuss secure mobile architecture.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>flutter</category>
      <category>mobile</category>
      <category>security</category>
    </item>
  </channel>
</rss>
