<?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: Eymen Can Orman</title>
    <description>The latest articles on DEV Community by Eymen Can Orman (@eymencan54).</description>
    <link>https://dev.to/eymencan54</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4234174%2Fb1aad2d1-2e9b-43a9-9c14-77737396df5f.png</url>
      <title>DEV Community: Eymen Can Orman</title>
      <link>https://dev.to/eymencan54</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eymencan54"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>Eymen Can Orman</dc:creator>
      <pubDate>Sun, 08 Nov 2026 23:17:30 +0000</pubDate>
      <link>https://dev.to/eymencan54/-5b9n</link>
      <guid>https://dev.to/eymencan54/-5b9n</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/eymencan54/building-movelab-7-real-world-engineering-challenges-i-solved-while-creating-a-mobile-chess-2imi" class="crayons-story__hidden-navigation-link"&gt;Building MoveLab: 7 Real-World Engineering Challenges I Solved While Creating a Mobile Chess Analysis Engine in Flutter&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/eymencan54" class="crayons-avatar  crayons-avatar--l  "&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4234174%2Fb1aad2d1-2e9b-43a9-9c14-77737396df5f.png" alt="eymencan54 profile" class="crayons-avatar__image" width="420" height="420"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/eymencan54" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Eymen Can Orman
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Eymen Can Orman
                
                
              
              &lt;div id="story-author-preview-content-5000636" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/eymencan54" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4234174%2Fb1aad2d1-2e9b-43a9-9c14-77737396df5f.png" class="crayons-avatar__image" alt="" width="420" height="420"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Eymen Can Orman&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/eymencan54/building-movelab-7-real-world-engineering-challenges-i-solved-while-creating-a-mobile-chess-2imi" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 8&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/eymencan54/building-movelab-7-real-world-engineering-challenges-i-solved-while-creating-a-mobile-chess-2imi" id="article-link-5000636"&gt;
          Building MoveLab: 7 Real-World Engineering Challenges I Solved While Creating a Mobile Chess Analysis Engine in Flutter
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/flutter"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;flutter&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/dart"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;dart&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/chess"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;chess&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mobile"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mobile&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/eymencan54/building-movelab-7-real-world-engineering-challenges-i-solved-while-creating-a-mobile-chess-2imi" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt;&amp;nbsp;reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/eymencan54/building-movelab-7-real-world-engineering-challenges-i-solved-while-creating-a-mobile-chess-2imi#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            10 min read
          &lt;/small&gt;
          
          &lt;div class="only-sidebar-menu-item trusted-visible-block print-hidden align-center"&gt;
                
                    
                
          &lt;/div&gt;

        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>buildinpublic</category>
      <category>mobile</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Building MoveLab: 7 Real-World Engineering Challenges I Solved While Creating a Mobile Chess Analysis Engine in Flutter</title>
      <dc:creator>Eymen Can Orman</dc:creator>
      <pubDate>Sun, 08 Nov 2026 22:30:41 +0000</pubDate>
      <link>https://dev.to/eymencan54/building-movelab-7-real-world-engineering-challenges-i-solved-while-creating-a-mobile-chess-2imi</link>
      <guid>https://dev.to/eymencan54/building-movelab-7-real-world-engineering-challenges-i-solved-while-creating-a-mobile-chess-2imi</guid>
      <description>&lt;h1&gt;
  
  
  Building MoveLab: 7 Real-World Engineering Challenges I Solved While Creating a Mobile Chess Analysis Engine in Flutter
&lt;/h1&gt;

&lt;p&gt;Building a chess app for create a player style card with their games is a summer project. Building a &lt;strong&gt;production-grade mobile chess analysis laboratory&lt;/strong&gt;—one that runs &lt;strong&gt;Stockfish&lt;/strong&gt; locally with Multi-PV support, integrates &lt;strong&gt;Lichess&lt;/strong&gt; and &lt;strong&gt;Chess.com&lt;/strong&gt; APIs, profiles player styles, queries &lt;strong&gt;7-piece Syzygy endgame tablebases&lt;/strong&gt;, and renders &lt;strong&gt;5,000+ Grandmaster games&lt;/strong&gt; offline—is a completely different story.&lt;/p&gt;

&lt;p&gt;Over the past several months, I built and shipped &lt;strong&gt;&lt;a href="https://play.google.com/store/apps/details?id=com.movelab.app" rel="noopener noreferrer"&gt;MoveLab&lt;/a&gt;&lt;/strong&gt; (open-sourced on &lt;a href="https://github.com/eymencan54/MoveLab-mobile-app" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;), a cross-platform chess analysis app written in &lt;strong&gt;Flutter&lt;/strong&gt; and &lt;strong&gt;Dart&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Along the way, almost every major subsystem broke at least once in ways I didn't anticipate. Here is a transparent technical deep-dive into the hardest problems I faced while building MoveLab, why my initial approaches failed, and how I ultimately solved them.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏗️ High-Level Architecture
&lt;/h2&gt;

&lt;p&gt;Before diving into the bugs, here is how MoveLab is structured under the hood:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    UI["Flutter UI Layer (Custom Board, Multi-PV Panel, Style Cards)"]
    Tree["Game Tree Engine (MoveNode DAG, SAN/UCI Translator, NAGs)"]
    SF["Local Stockfish Isolate (UCI Protocol, MultiPV 3, Configurable Threads/Depth)"]
    Ext["External APIs (Lichess OAuth2/Masters/Syzygy, Chess.com Public API, Vercel Puzzle API)"]
    Sync["Persistence &amp;amp; Cloud (SharedPreferences + Firebase Auth &amp;amp; Firestore Sync)"]

    UI &amp;lt;--&amp;gt; Tree
    Tree &amp;lt;--&amp;gt; SF
    Tree &amp;lt;--&amp;gt; Ext
    UI &amp;lt;--&amp;gt; Sync
    Ext &amp;lt;--&amp;gt; Sync&lt;/code&gt;&lt;/pre&gt;






&lt;h2&gt;
  
  
  Challenge 1: Taming Local Stockfish Output, Async Race Conditions, and UI Layout Shifts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;MoveLab runs Stockfish locally on the user's device via the Universal Chess Interface (UCI) protocol (&lt;code&gt;setoption name MultiPV value 3&lt;/code&gt;, configurable depth 1–30, and CPU threads 1–8). Three painful issues emerged as soon as I connected the engine stream to the Flutter UI:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Machine Coordinates vs. Human Notation:&lt;/strong&gt; Stockfish emits principal variations (PV) in raw UCI format (&lt;code&gt;e2e4 e7e5 g1f3 b8c6&lt;/code&gt; or &lt;code&gt;e1g1&lt;/code&gt; for castling). Displaying raw coordinates in the analysis panel looked amateurish, yet I still needed the raw UCI strings to draw engine suggestion arrows (&lt;code&gt;MultiArrowPainter&lt;/code&gt;) and allow single-tap line playback on the board.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale Async Stream Race Conditions:&lt;/strong&gt; When a user rapidly tapped through a game (&lt;code&gt;&amp;gt;&lt;/code&gt; / &lt;code&gt;&amp;lt;&lt;/code&gt;), Stockfish stdout events from position $N$ would arrive milliseconds &lt;em&gt;after&lt;/em&gt; the board had already transitioned to position $N+1$, briefly painting the wrong evaluation bar and arrow on the new board.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The "Jumping Chessboard" Layout Shift:&lt;/strong&gt; Every time a move was made, the 3 Multi-PV lines were cleared (&lt;code&gt;_multiPvLines = ['', '', '']&lt;/code&gt;) while awaiting the new evaluation. Because the text rows collapsed in height when empty, the entire chessboard underneath jumped upward by ~60 pixels and snapped back down a split second later.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dual-Representation Pipeline (&lt;code&gt;NotationHelper&lt;/code&gt;):&lt;/strong&gt; I kept all internal engine state in UCI format so arrow rendering and tap-to-play logic remained fast and unambiguous, and built a just-in-time translator (&lt;code&gt;formatUciLineToSan&lt;/code&gt;) that clones a lightweight board instance at the current FEN, simulates the UCI continuation sequence, and emits clean Standard Algebraic Notation (&lt;code&gt;1. e4 e5 2. Nf3 Nc6&lt;/code&gt;, &lt;code&gt;O-O&lt;/code&gt;) for the UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FEN Guarding &amp;amp; Fixed-Height Containers:&lt;/strong&gt; I added strict state verification (&lt;code&gt;_fenList[_currentMoveIndex] == currentFen&lt;/code&gt;) before committing any asynchronous evaluation callback to state. Finally, to eliminate layout shift, I locked each Multi-PV row container to a strict &lt;code&gt;height: 38&lt;/code&gt; pixels regardless of whether the engine was initializing or streaming depth 22 results—keeping the chessboard millimeter-stable.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Challenge 2: Moving from Destructive Linear Arrays to a Lichess-Style Game Tree (&lt;code&gt;MoveNode&lt;/code&gt;)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;In the earliest versions of MoveLab, a game was represented by two flat lists:&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;List&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;_fenList&lt;/span&gt; &lt;span class="o"&gt;=&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="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_allMoves&lt;/span&gt; &lt;span class="o"&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;_currentMoveIndex&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a user was reviewing move 15 of a 40-move Magnus Carlsen game (or their own Lichess match) and tapped a Stockfish suggestion, an Opening Explorer move, or dragged a piece to test a "what-if" scenario, the code executed &lt;code&gt;removeRange(_currentMoveIndex + 1, _fenList.length)&lt;/code&gt; to append the new move. &lt;strong&gt;This permanently deleted the rest of the original match.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;I refactored the analysis state from flat arrays into a node-based &lt;strong&gt;Game Tree (&lt;code&gt;MoveNode&lt;/code&gt;)&lt;/strong&gt; coupled with non-destructive variation branching:&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;MoveNode&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;fen&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;san&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;uci&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;nag&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// !!, !, ?!, ?, ??&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;MoveNode&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;parent&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;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MoveNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;variations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;MoveNode&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;fen&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;san&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;uci&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;nag&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;parent&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;MoveNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;variations&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;variations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;variations&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when a user clicks an engine suggestion or plays an alternative move mid-game:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;MoveLab first checks if the resulting FEN already matches the next mainline move (&lt;code&gt;chess.fen == _fenList[_currentMoveIndex + 1]&lt;/code&gt;). If so, it simply advances the cursor (&lt;code&gt;_goToMove&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;If the move diverges in &lt;strong&gt;Free Analysis&lt;/strong&gt;, it creates a child &lt;code&gt;MoveNode&lt;/code&gt; inside &lt;code&gt;currentNode.variations&lt;/code&gt; and renders interactive variation chips below the move list so the user can switch branches or return to the main line with a single tap.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;Match &amp;amp; Grandmaster Archive Analysis&lt;/strong&gt;, exploring an engine line enters an ephemeral variation state and triggers &lt;code&gt;startContinuousAnalysis()&lt;/code&gt; without mutating the underlying match history—allowing the user to step back into the real game at any moment.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Challenge 3: Re-Engineering the Chessboard Widget (Pawn Promotions, Custom Setup Mode &amp;amp; Move Interceptors)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Relying on an off-the-shelf &lt;code&gt;flutter_chess_board&lt;/code&gt; package worked for a prototype, but broke down under real user testing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Tap-to-Move Pawn Promotion Bug:&lt;/strong&gt; Dragging a pawn to the 8th rank opened the promotion dialog as expected. However, tapping a pawn on the 7th rank and tapping a target square on the 8th rank either failed to promote entirely or triggered a completely different, unstyled fallback dialog.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Corrupted Board State in Opening Trainer:&lt;/strong&gt; When practicing openings, if a user played an incorrect move, the board widget applied the move to its internal controller &lt;em&gt;before&lt;/em&gt; notifying our parent widget, desynchronizing the visual board from the training tree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Illegal FENs in the Board Editor (Setup Mode):&lt;/strong&gt; When building the Lichess-style Board Editor (where users place/remove pieces freely on the board), clicking "Clear Board" or manually erasing a King produced a kingless FEN string, which immediately crashed the chess validator and froze Stockfish. Similarly, placing a second White King created an invalid two-King board.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;I brought the board implementation in-house (&lt;code&gt;lib/custom_chess_board/&lt;/code&gt;) and overhauled its internals:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Vector Rendering &amp;amp; On-Board NAG Badges:&lt;/strong&gt; Replaced low-res bitmap board textures with crisp Flutter &lt;code&gt;Container&lt;/code&gt; grids (&lt;code&gt;0xFF769656&lt;/code&gt; &amp;amp; &lt;code&gt;0xFFEEEED2&lt;/code&gt;) and overlaid Lichess-style move classification badges (&lt;code&gt;!!&lt;/code&gt;, &lt;code&gt;!&lt;/code&gt;, &lt;code&gt;?&lt;/code&gt;, &lt;code&gt;??&lt;/code&gt;) directly onto the top-right corner of the destination square.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-Commit Move Interception (&lt;code&gt;onMoveAttempt&lt;/code&gt;):&lt;/strong&gt; Added a synchronous gatekeeper callback that fires &lt;em&gt;before&lt;/em&gt; a move is committed to the board state. In the Opening Trainer and Puzzle history viewer, off-book or locked-state moves are rejected before the UI mutates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unified Promotion &amp;amp; Board Editor Invariants (&lt;code&gt;_modifyFenSquare&lt;/code&gt;):&lt;/strong&gt; Unified the tap-to-move and drag-and-drop promotion handlers into a single visual piece selector. In the Board Editor, I enforced strict FEN invariants: the "Clear" tool strips all pieces &lt;em&gt;except&lt;/em&gt; the two Kings (&lt;code&gt;K&lt;/code&gt; and &lt;code&gt;k&lt;/code&gt;), and placing a King on a new square automatically relocates the existing King of that color while recalculating castling availability (&lt;code&gt;KQkq&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Challenge 4: Why the Same Player Got Opposite "Playstyle Cards" on Lichess vs. Chess.com
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;One of MoveLab's most popular features is the &lt;strong&gt;AI Playstyle Profile Card&lt;/strong&gt;, which analyzes a player's recent games to classify them into archetypes (&lt;em&gt;Aggressive&lt;/em&gt;, &lt;em&gt;Tactical Master&lt;/em&gt;, &lt;em&gt;Positional&lt;/em&gt;, &lt;em&gt;Defensive&lt;/em&gt;, &lt;em&gt;Endgame Specialist&lt;/em&gt;, &lt;em&gt;Risk Taker&lt;/em&gt;, &lt;em&gt;Universal&lt;/em&gt;) with percentage breakdowns.&lt;/p&gt;

&lt;p&gt;During beta testing, users reported a bizarre bug: &lt;strong&gt;entering the exact same player's Lichess username and Chess.com username produced almost completely opposite style profiles.&lt;/strong&gt; Furthermore, some games showed double the actual number of moves!&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;Digging into the raw PGN payloads from both APIs revealed two root causes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;PGN Metadata &amp;amp; Clock Tag Pollution:&lt;/strong&gt; Chess.com and Lichess embed different inline comments (such as &lt;code&gt;{[%clk 0:02:59.9]}&lt;/code&gt;) and move-number formatting (&lt;code&gt;1. e4&lt;/code&gt; vs &lt;code&gt;1... e5&lt;/code&gt;). A double-escaped regular expression (&lt;code&gt;RegExp(r'^\\d+\\.+\\s*')&lt;/code&gt; instead of &lt;code&gt;RegExp(r'^\d+\.+\s*')&lt;/code&gt;) combined with unstripped clock tokens caused the parser to miscount plies as full moves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform-Biased Heuristics:&lt;/strong&gt; Time control distributions and average game lengths differ systematically between Lichess and Chess.com API responses. I rewrote the PGN sanitizer to strip all &lt;code&gt;{...}&lt;/code&gt; annotations and headers cleanly, fixed the regex escaping, and recalibrated the playstyle scoring weights around platform-agnostic board features (capture density, early queen/piece activity, king safety / castling timing, and endgame transition rate).&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Challenge 5: Puzzles That Rejected Valid Checkmates &amp;amp; Serverless Cold Starts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Our tactical puzzle module suffered from two frustrating UX issues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Mate False Negatives:&lt;/strong&gt; Puzzles are defined by a sequence of UCI moves from a database. In one position, the user's Queen could deliver immediate checkmate on &lt;strong&gt;both &lt;code&gt;b6&lt;/code&gt; and &lt;code&gt;a5&lt;/code&gt;&lt;/strong&gt;. Because the puzzle database only listed &lt;code&gt;a5&lt;/code&gt; as the solution, playing &lt;code&gt;Qb6#&lt;/code&gt; was marked as &lt;strong&gt;"Wrong Move (-20 pts)"&lt;/strong&gt;!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serverless Cold-Start Lag:&lt;/strong&gt; Fetching puzzles on-demand from our Vercel API endpoint caused a 2–4 second loading spinner whenever the serverless function experienced a cold start—and worse, the app fetched a batch of 50 puzzles, used 1, and discarded the other 49!&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Checkmate-Aware Validation:&lt;/strong&gt; When a user's move doesn't match the hardcoded puzzle string, MoveLab now simulates the move on an internal &lt;code&gt;chess.Chess&lt;/code&gt; instance and checks &lt;code&gt;chess.in_checkmate&lt;/code&gt;. If the move delivers legal checkmate, it is immediately rewarded as correct regardless of the database's preferred square!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0ms Pre-Caching &amp;amp; 1.8s Fallback Timeout:&lt;/strong&gt; Instead of discarding 49 puzzles, I stored the entire batch in a difficulty-indexed static memory cache (&lt;code&gt;_cachedApiPuzzles&lt;/code&gt;) preloaded silently during &lt;code&gt;HomePage.initState&lt;/code&gt;, replenishing in the background whenever the pool drops below 5 puzzles. If the cache is empty and the Vercel API takes longer than &lt;strong&gt;1.8 seconds&lt;/strong&gt;, the request times out gracefully and serves an instant puzzle from the embedded &lt;code&gt;localPuzzlePool&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opponent Lead-In Animation:&lt;/strong&gt; Instead of dropping the user into a static position, puzzles now load &lt;em&gt;one ply earlier&lt;/em&gt;, pause for &lt;code&gt;600ms&lt;/code&gt;, and animate the opponent's blunder with sound effects—while locking board input during the animation and allowing &lt;code&gt;&amp;lt;&lt;/code&gt; / &lt;code&gt;&amp;gt;&lt;/code&gt; history review up to the current solved ply.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Challenge 6: Scaling to 5,000+ Grandmaster Games Without Bloating the APK
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;I wanted to include a &lt;strong&gt;Grandmaster Archive&lt;/strong&gt; featuring over 5,000 classic games from 20 legends (Carlsen, Kasparov, Fischer, Tal, Capablanca, Nakamura, etc.) searchable by player, year, and ECO opening code. Bundling every single PGN file into the app binary forever would continuously inflate the APK/AAB download size as more masters were added, and parsing multi-thousand-game &lt;code&gt;.pgn&lt;/code&gt; files using heavy third-party parsers froze the UI thread.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Custom Zero-Dependency Streaming PGN Parser (&lt;code&gt;splitPgnGames&lt;/code&gt; &amp;amp; &lt;code&gt;parseOneGame&lt;/code&gt;):&lt;/strong&gt; I wrote a lightweight Dart parser in &lt;code&gt;lib/master_archive_pages.dart&lt;/code&gt; that splits multi-game PGN files on the fly, extracts player titles (&lt;code&gt;GM&lt;/code&gt;, &lt;code&gt;IM&lt;/code&gt;), ELO ratings, and ECO codes, and maps ECO codes to human-readable opening names (e.g., &lt;code&gt;C50 — Italian Game&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid Cloud + Local Caching Architecture:&lt;/strong&gt; The core 20 legends remain bundled for 100% offline availability, while additional archives use a hybrid fetcher: when tapped, MoveLab checks local device storage (&lt;code&gt;path_provider&lt;/code&gt;). If missing, it downloads the raw &lt;code&gt;.pgn&lt;/code&gt; from GitHub/Cloud once, caches it to disk, and opens it in milliseconds on every subsequent launch.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Challenge 7: The "Works on Emulator, Breaks on Google Play Store" Trifecta
&lt;/h2&gt;

&lt;p&gt;Every mobile developer eventually hits bugs that &lt;em&gt;only&lt;/em&gt; happen in release builds (&lt;code&gt;.aab&lt;/code&gt; / &lt;code&gt;--release&lt;/code&gt;). MoveLab hit three of them right around launch:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Silent Share Button Failure (&lt;code&gt;RepaintBoundary&lt;/code&gt; &amp;amp; &lt;code&gt;SharePlus&lt;/code&gt;)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Symptom:&lt;/strong&gt; Tapping the "Share" button on the Player Style Card worked flawlessly on the Android emulator, but did nothing—or threw &lt;code&gt;LateInitializationError: Local 'result' has not been initialized&lt;/code&gt;—when downloaded from the Google Play Store.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Replaced unstable &lt;code&gt;SharePlus.instance.share&lt;/code&gt; calls and uninitialized late variables with deterministic &lt;code&gt;RepaintBoundary&lt;/code&gt; image capture (&lt;code&gt;toImage(pixelRatio: 3.0)&lt;/code&gt;), wrote the PNG bytes to &lt;code&gt;getTemporaryDirectory()&lt;/code&gt;, and passed &lt;code&gt;sharePositionOrigin&lt;/code&gt; via &lt;code&gt;RenderBox&lt;/code&gt; coordinates so both Android and iPadOS/iOS sheets anchor and open reliably in release builds without showing a stuck loading overlay.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Lichess OAuth2 PKCE Redirect Dropping the Session
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Symptom:&lt;/strong&gt; Tapping "Sign in with Lichess" opened the Lichess OAuth page via &lt;code&gt;flutter_web_auth_2&lt;/code&gt;. After clicking &lt;em&gt;Authorize&lt;/em&gt;, the browser redirected back to &lt;code&gt;com.movelab.app://&lt;/code&gt;—but either crashed or returned to the app without logging the user in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Implemented the full &lt;strong&gt;OAuth2 PKCE (Proof Key for Code Exchange)&lt;/strong&gt; handshake using &lt;code&gt;crypto&lt;/code&gt; (&lt;code&gt;sha256&lt;/code&gt; challenge over a random &lt;code&gt;code_verifier&lt;/code&gt;), properly registered the custom scheme callback intent filter in &lt;code&gt;AndroidManifest.xml&lt;/code&gt;, exchanged the authorization code for an access token, queried &lt;code&gt;https://lichess.org/api/account&lt;/code&gt; to retrieve the exact Lichess username/title, and automatically merged any locally saved guest matches into the user's &lt;strong&gt;Cloud Firestore&lt;/strong&gt; library so no data was lost on login.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Audio Interrupting Spotify &amp;amp; Play Console &lt;code&gt;AD_ID&lt;/code&gt; Rejections
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Audio Focus:&lt;/strong&gt; Every time a chess piece moved, &lt;code&gt;audioplayers&lt;/code&gt; requested exclusive audio focus from Android/iOS, pausing the user's Spotify or YouTube Music! Configuring the global &lt;code&gt;AudioContext&lt;/code&gt; with &lt;code&gt;AudioContextAndroid(audioFocus: AndroidAudioFocus.none)&lt;/code&gt; / &lt;code&gt;mixWithOthers&lt;/code&gt; allowed crisp move sounds to play right alongside background music.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Play Console &lt;code&gt;AD_ID&lt;/code&gt; Error:&lt;/strong&gt; Adding &lt;code&gt;firebase_analytics&lt;/code&gt; caused the Android manifest merger to inject &lt;code&gt;com.google.android.gms.permission.AD_ID&lt;/code&gt;, which triggered an immediate Google Play Console release blocker because our store declaration stated the app didn't use Advertising IDs for ads. Synchronizing the Play Console Advertising ID declaration with our analytics usage (or explicitly managing the permission in &lt;code&gt;AndroidManifest.xml&lt;/code&gt;) resolved the release block.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💡 Key Takeaways for Fellow Flutter Developers
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Never trust linear lists for undo/redo or analysis:&lt;/strong&gt; If users can step backward and explore alternatives, design your state as a Tree (&lt;code&gt;Node&lt;/code&gt; + &lt;code&gt;children&lt;/code&gt;) from day one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always test &lt;code&gt;RepaintBoundary&lt;/code&gt; and OAuth redirects in &lt;code&gt;--release&lt;/code&gt; builds:&lt;/strong&gt; ProGuard/R8 shrinking, manifest intent filters, and widget frame timing behave very differently outside debug mode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hide serverless latency behind predictive caching:&lt;/strong&gt; Pre-fetching batches in the background and setting an aggressive timeout (&lt;code&gt;1.8s&lt;/code&gt;) with a local fallback turns a sluggish API into a &lt;code&gt;0ms&lt;/code&gt; instant experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build for the user's real environment:&lt;/strong&gt; Small details—like not pausing their Spotify music when they play &lt;code&gt;1. e4&lt;/code&gt;, or keeping the chessboard from shifting by &lt;code&gt;38px&lt;/code&gt; while Stockfish calculates—are what separate a side project from a product players love using daily.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🔗 Try MoveLab &amp;amp; Explore the Code
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Play Store:&lt;/strong&gt; &lt;a href="https://play.google.com/store/apps/details?id=com.movelab.app" rel="noopener noreferrer"&gt;MoveLab on Google Play&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repository (Apache 2.0):&lt;/strong&gt; &lt;a href="https://github.com/eymencan54/MoveLab-mobile-app" rel="noopener noreferrer"&gt;github.com/eymencan54/MoveLab-mobile-app&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Have you ever integrated a C/C++ engine like Stockfish or built custom board games in Flutter? I'd love to hear about your architectural approach and feedback in the comments!&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>chess</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
