<?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: Nithin</title>
    <description>The latest articles on DEV Community by Nithin (@supejuice).</description>
    <link>https://dev.to/supejuice</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%2F1850604%2Fc7c95d26-6315-4094-8a64-7a9eab006d78.jpeg</url>
      <title>DEV Community: Nithin</title>
      <link>https://dev.to/supejuice</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/supejuice"/>
    <language>en</language>
    <item>
      <title>The best AI workbench is not an IDE</title>
      <dc:creator>Nithin</dc:creator>
      <pubDate>Sat, 21 Mar 2026 21:31:28 +0000</pubDate>
      <link>https://dev.to/supejuice/the-best-ai-workbench-is-not-an-ide-3c9d</link>
      <guid>https://dev.to/supejuice/the-best-ai-workbench-is-not-an-ide-3c9d</guid>
      <description>&lt;p&gt;There is a common beginner mistake in AI-assisted development: picking a single tool and asking it to do everything.&lt;/p&gt;

&lt;p&gt;That is how people end up defending editors as if they were operating systems.&lt;/p&gt;

&lt;p&gt;An editor is not a strategy. It is a window.&lt;/p&gt;

&lt;p&gt;What matters is the execution surface behind the window: authentication, tool access, agent reliability, MCP support, and whether team knowledge can survive a change of shell.&lt;/p&gt;

&lt;p&gt;After using Cursor, Cursor CLI, IntelliJ IDEA with AI Assistant wired to external providers, IntelliJ with Kilo Code, and Codex CLI, I arrived at a conclusion that is not fashionable but is practical:&lt;/p&gt;

&lt;p&gt;IntelliJ should remain the workbench. Codex should become the primary agent. Cursor should be mined for what it does well, mostly skills and rules. Kilo Code should be demoted to utility work such as autocomplete and commit message generation.&lt;/p&gt;

&lt;p&gt;This is not a benchmark of clever demos. It is a benchmark of operational friction.&lt;/p&gt;

&lt;p&gt;This is also a personal conclusion, not a team mandate. The point is not to move the team to a new stack. The point is to build a personal workbench that works best for me while still adapting cleanly to the team's existing Cursor-based conventions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Short verdict
&lt;/h2&gt;

&lt;p&gt;If the goal is a practical daily setup rather than a vendor bet, the cleanest arrangement is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IntelliJ IDEA for editing, navigation, refactoring, and inspections.&lt;/li&gt;
&lt;li&gt;Codex CLI as the primary agent runtime.&lt;/li&gt;
&lt;li&gt;Cursor rules and skills preserved through &lt;code&gt;AGENTS.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Kilo Code used narrowly for autocomplete and commit-message assistance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That conclusion is not based on abstract model preference. It is based on where authentication, MCP access, and team workflow actually held up under daily use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cursor is strongest when you stay inside Cursor
&lt;/h2&gt;

&lt;p&gt;Cursor has one undeniable advantage: if your team has already invested in Cursor rules and skills, the product rewards conformity.&lt;/p&gt;

&lt;p&gt;That matters.&lt;/p&gt;

&lt;p&gt;Institutional knowledge beats raw model quality surprisingly often. A mediocre prompt with strong team conventions can outperform a brilliant model used in a vacuum.&lt;/p&gt;

&lt;p&gt;Cursor authentication also keeps you inside the ecosystem where those skills are first-class citizens. If the team works through Cursor-native skills, Cursor the IDE feels coherent. The tool, the auth model, and the team workflow all point in the same direction.&lt;/p&gt;

&lt;p&gt;But this coherence has a price: it is local to Cursor.&lt;/p&gt;

&lt;p&gt;The moment you try to separate the agent from the IDE, the promise weakens.&lt;/p&gt;

&lt;p&gt;Cursor's own CLI documentation says MCP in the CLI uses the same configuration as the editor and that configured servers should work in both surfaces. In theory, that is elegant. In practice, the theory leaks. In a Cursor forum thread from January 21, 2026, a team member explicitly acknowledged a known issue: &lt;code&gt;cursor-agent&lt;/code&gt; could not handle OAuth correctly for remote MCP servers such as Figma, even though the IDE could. The same thread was still receiving reports on March 18, 2026.&lt;sup id="fnref1"&gt;1&lt;/sup&gt; &lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;For workflows that depend on remote OAuth-backed MCPs, that limitation is enough to keep Cursor CLI out of the primary slot.&lt;/p&gt;

&lt;p&gt;That matters because an increasing amount of useful agent work now sits behind authenticated remote tools rather than local prompts alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real test is OAuth, not chat quality
&lt;/h2&gt;

&lt;p&gt;The market is still pretending that model selection is the hard problem.&lt;/p&gt;

&lt;p&gt;It is not.&lt;/p&gt;

&lt;p&gt;The hard problem is whether the agent can reach the systems that matter without turning your desk into a museum of half-working clients.&lt;/p&gt;

&lt;p&gt;Figma is the cleanest example.&lt;/p&gt;

&lt;p&gt;Figma's remote MCP server requires OAuth. Their official documentation says so directly and includes a Codex CLI flow that prompts for authentication after &lt;code&gt;codex mcp add figma --url https://mcp.figma.com/mcp&lt;/code&gt;.&lt;sup id="fnref3"&gt;3&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;There is also no useful escape hatch through personal access tokens. In a Figma community thread opened on November 18, 2025, a user asked for PAT-based auth for the remote MCP endpoint. A quoted reply from Figma Support said the remote MCP server does not support authentication using personal access tokens and that this cannot be enabled; OAuth is the recommended path.&lt;sup id="fnref4"&gt;4&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;That closes the usual loophole.&lt;/p&gt;

&lt;p&gt;If a client does not handle OAuth correctly, the integration is not merely inconvenient. It is functionally unavailable.&lt;/p&gt;

&lt;p&gt;This is why Cursor CLI is disqualified for my primary workflow today.&lt;/p&gt;

&lt;p&gt;Not because Cursor is weak.&lt;/p&gt;

&lt;p&gt;Because Figma is unforgiving.&lt;/p&gt;

&lt;p&gt;Notion belongs in the same category. Remote MCP access is valuable only when the client can complete the auth dance cleanly and repeatably. A tool that supports MCP on a slide but fails during OAuth is doing theater, not engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  IntelliJ is still the best room in the house
&lt;/h2&gt;

&lt;p&gt;IntelliJ IDEA remains the best physical workspace.&lt;/p&gt;

&lt;p&gt;It is still the superior editor for large codebases, code navigation, inspections, refactors, and plain old concentration. I do not want to keep both Cursor IDE and IntelliJ open just to preserve one niche capability from each.&lt;/p&gt;

&lt;p&gt;That setup is not power-user sophistication. It is a tax.&lt;/p&gt;

&lt;p&gt;JetBrains has moved aggressively on AI Assistant and MCP. Their current documentation shows AI Assistant can connect to MCP servers over STDIO, Streamable HTTP, and SSE.&lt;sup id="fnref5"&gt;5&lt;/sup&gt; JetBrains also allows third-party providers and OpenAI-compatible endpoints, but this path is explicitly API-key driven, and feature coverage varies by provider.&lt;sup id="fnref6"&gt;6&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;p&gt;An IDE feature that can &lt;em&gt;consume a model&lt;/em&gt; is not automatically equivalent to a first-class &lt;em&gt;agent runtime&lt;/em&gt;. The former is a panel. The latter is an operating surface.&lt;/p&gt;

&lt;p&gt;Figma's own remote MCP installation page is a useful reality check here. As of March 22, 2026, it provides setup documentation for Claude Code, Codex by OpenAI, Cursor, and VS Code. IntelliJ is not listed among the supported client setup guides.&lt;sup id="fnref7"&gt;7&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;That does not prove IntelliJ can never be made to work with remote MCP. It does show where official support and documentation currently concentrate.&lt;/p&gt;

&lt;p&gt;In my setup, IntelliJ AI Assistant wired to external providers did not become the stable answer for OAuth-backed remote MCP workflows. The practical result was still inferior to using a dedicated agent client that treats authentication and tool invocation as primary concerns rather than attached features.&lt;/p&gt;

&lt;p&gt;So IntelliJ stays.&lt;/p&gt;

&lt;p&gt;But it stays as the cockpit, not as the pilot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Codex CLI solves the right problem
&lt;/h2&gt;

&lt;p&gt;Codex CLI succeeded where the supposedly more "native" combinations became awkward.&lt;/p&gt;

&lt;p&gt;The decisive point was simple: OAuth-backed MCP worked.&lt;/p&gt;

&lt;p&gt;Figma remote MCP worked properly. Notion MCP worked properly. The boring but essential part of the stack, sign-in, token flow, callback handling, tool availability, simply held together.&lt;/p&gt;

&lt;p&gt;That is what maturity looks like in agent tooling: fewer broken rituals between intention and execution.&lt;/p&gt;

&lt;p&gt;Once Codex handled remote MCP cleanly, the rest of the architecture became obvious.&lt;/p&gt;

&lt;p&gt;Use IntelliJ as the IDE.&lt;/p&gt;

&lt;p&gt;Use Codex as the primary agent runtime.&lt;/p&gt;

&lt;p&gt;Keep Cursor knowledge without keeping Cursor as the center of gravity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bridge matters more than the model
&lt;/h2&gt;

&lt;p&gt;The most expensive part of switching tools is not the subscription.&lt;/p&gt;

&lt;p&gt;It is the loss of team memory.&lt;/p&gt;

&lt;p&gt;That is why the &lt;code&gt;AGENTS.md&lt;/code&gt; bridge is the most important piece of this workbench.&lt;/p&gt;

&lt;p&gt;By teaching Codex to treat &lt;code&gt;.cursor/rules/*.mdc&lt;/code&gt; and &lt;code&gt;.cursor/skills/**/SKILL.md&lt;/code&gt; as source-of-truth instructions, the setup stops being ideological and becomes mechanical. Cursor skills remain usable. Cursor rules remain usable. Team conventions survive. My workbench changes; the shared operating doctrine does not.&lt;/p&gt;

&lt;p&gt;This is the right way to personalize AI tooling inside a team without forcing the team to follow.&lt;/p&gt;

&lt;p&gt;Do not rewrite every rule because a vendor changed.&lt;/p&gt;

&lt;p&gt;Do not ask the team to learn a second set of rituals.&lt;/p&gt;

&lt;p&gt;Move the execution engine locally. Preserve the doctrine globally.&lt;/p&gt;

&lt;p&gt;That is exactly why Codex is a better primary workflow than Cursor IDE in this setup: it can inherit the team's Cursor knowledge while also handling the remote MCP reality that Cursor CLI still fumbles.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that weakens trust
&lt;/h2&gt;

&lt;p&gt;There is another reason not to make Cursor IDE the center of the system: reliability debt.&lt;/p&gt;

&lt;p&gt;I have seen occasional cases where agent changes do not appear correctly in the Git changelist inside Cursor. There are workarounds, restarts, and folklore. I have not found a permanent fix worth trusting a primary workflow to.&lt;/p&gt;

&lt;p&gt;Intermittent bugs are how tools slowly lose authority. A development surface must be boring when it comes to source control visibility. If the user has to wonder whether a file changed or merely failed to appear, the editor has ceased to be infrastructure and has become weather.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison in practice
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;What worked&lt;/th&gt;
&lt;th&gt;What broke or stayed weak&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cursor IDE&lt;/td&gt;
&lt;td&gt;Cursor-native rules and skills, coherent in-product workflow&lt;/td&gt;
&lt;td&gt;Git changelist visibility sometimes unreliable, requires staying in Cursor to get the full benefit&lt;/td&gt;
&lt;td&gt;Useful, but not my center of gravity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cursor CLI&lt;/td&gt;
&lt;td&gt;Reuses Cursor MCP configuration and team skills in principle&lt;/td&gt;
&lt;td&gt;Remote MCP OAuth remained unreliable for the workflows that mattered most&lt;/td&gt;
&lt;td&gt;Not suitable as primary runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IntelliJ + AI Assistant with external providers&lt;/td&gt;
&lt;td&gt;Excellent IDE, strong editing and refactoring environment, growing MCP support&lt;/td&gt;
&lt;td&gt;Not listed in Figma's official remote MCP client setup docs; also did not become the most reliable home for remote OAuth-backed MCP workflows in my setup&lt;/td&gt;
&lt;td&gt;Best editor, not best agent runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex CLI&lt;/td&gt;
&lt;td&gt;Remote MCP with OAuth worked cleanly for Figma and Notion; agent workflow felt dependable&lt;/td&gt;
&lt;td&gt;Less value from Cursor-native ecosystem unless bridged deliberately&lt;/td&gt;
&lt;td&gt;Best primary runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kilo Code&lt;/td&gt;
&lt;td&gt;Helpful for lightweight assistance&lt;/td&gt;
&lt;td&gt;Not the tool I would trust for heavy MCP-centric workflows&lt;/td&gt;
&lt;td&gt;Keep it narrow&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The resulting workbench
&lt;/h2&gt;

&lt;p&gt;The final arrangement is not elegant in the way marketing teams prefer. It is elegant in the way a good wrench is elegant.&lt;/p&gt;

&lt;p&gt;The workbench is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IntelliJ IDEA for editing, navigation, refactoring, and staying in flow.&lt;/li&gt;
&lt;li&gt;Codex CLI as the primary agent for serious work, especially when remote MCP with OAuth is involved.&lt;/li&gt;
&lt;li&gt;Cursor CLI as a secondary utility when team-specific Cursor workflows are still useful, but not as the foundation.&lt;/li&gt;
&lt;li&gt;Kilo Code for lightweight assistance such as autocomplete and commit message generation through OpenAI auth.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This division of labor is healthy.&lt;/p&gt;

&lt;p&gt;Each tool is forced to do only the job it is actually good at.&lt;/p&gt;

&lt;p&gt;That is rare in modern AI tooling, where every vendor wants to be your editor, shell, browser, memory layer, and therapist at the same time.&lt;/p&gt;

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

&lt;p&gt;The winning setup is not Cursor versus Codex versus IntelliJ.&lt;/p&gt;

&lt;p&gt;That framing is too small.&lt;/p&gt;

&lt;p&gt;The real contest is between two philosophies.&lt;/p&gt;

&lt;p&gt;One philosophy wants a single branded surface to own the whole experience.&lt;/p&gt;

&lt;p&gt;The other treats the development environment as a federation: one editor, one primary agent runtime, a few specialized helpers, and a thin compatibility layer to preserve team knowledge.&lt;/p&gt;

&lt;p&gt;The second philosophy wins because it respects reality better.&lt;/p&gt;

&lt;p&gt;Today, that means IntelliJ IDEA as the room, Codex CLI as the worker, Cursor rules and skills as inherited doctrine, and Kilo Code as a useful but limited assistant.&lt;/p&gt;

&lt;p&gt;The best AI workbench is not the one with the most features.&lt;/p&gt;

&lt;p&gt;It is the one that authenticates, executes, and gets out of the way.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Cursor CLI MCP docs: &lt;a href="https://docs.cursor.com/cli/mcp" rel="noopener noreferrer"&gt;https://docs.cursor.com/cli/mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Cursor forum, "Cursor CLI - Figma support," January 21, 2026: &lt;a href="https://forum.cursor.com/t/cursor-cli-figma-support/149491" rel="noopener noreferrer"&gt;https://forum.cursor.com/t/cursor-cli-figma-support/149491&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Figma MCP remote server docs: &lt;a href="https://developers.figma.com/docs/figma-mcp-server/remote-server-installation/" rel="noopener noreferrer"&gt;https://developers.figma.com/docs/figma-mcp-server/remote-server-installation/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Figma forum, PAT auth request for remote MCP, November 18, 2025: &lt;a href="https://forum.figma.com/ask-the-community-7/support-for-pat-personal-access-token-based-auth-in-figma-remote-mcp-47465" rel="noopener noreferrer"&gt;https://forum.figma.com/ask-the-community-7/support-for-pat-personal-access-token-based-auth-in-figma-remote-mcp-47465&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;JetBrains AI Assistant MCP docs: &lt;a href="https://www.jetbrains.com/help/ai-assistant/mcp.html" rel="noopener noreferrer"&gt;https://www.jetbrains.com/help/ai-assistant/mcp.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;JetBrains AI Assistant custom model docs: &lt;a href="https://www.jetbrains.com/help/ai-assistant/use-custom-models.html" rel="noopener noreferrer"&gt;https://www.jetbrains.com/help/ai-assistant/use-custom-models.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Cursor documentation says the CLI shares MCP configuration with the editor and exposes &lt;code&gt;cursor-agent mcp login&lt;/code&gt; for authentication. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Cursor staff acknowledged on January 21-22, 2026 that &lt;code&gt;cursor-agent&lt;/code&gt; had a known OAuth issue for remote MCP servers such as Figma and GitHub, with additional user reports still appearing on March 18, 2026. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Figma's remote MCP documentation says the remote server requires OAuth and includes a Codex CLI setup path that prompts for authentication. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;This relies on a quoted support reply in the Figma community thread, so treat it as support guidance surfaced through the forum rather than a standalone product-spec page. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;JetBrains AI Assistant documentation currently lists STDIO, Streamable HTTP, and SSE as supported transports for MCP servers. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;JetBrains documents third-party provider integration through API keys and notes that some feature availability depends on provider and model support. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn7"&gt;
&lt;p&gt;On March 22, 2026, Figma's remote MCP installation page listed setup guides for Claude Code, Codex by OpenAI, Cursor, and VS Code, but not IntelliJ. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
    <item>
      <title>System Design Docs for Flutter Mobile Teams: A Brutal Guide</title>
      <dc:creator>Nithin</dc:creator>
      <pubDate>Thu, 25 Sep 2025 10:40:52 +0000</pubDate>
      <link>https://dev.to/supejuice/system-design-docs-for-flutter-mobile-teams-a-brutal-guide-mm9</link>
      <guid>https://dev.to/supejuice/system-design-docs-for-flutter-mobile-teams-a-brutal-guide-mm9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;"If you can't explain it simply, you don't understand it well enough."&lt;br&gt;&lt;br&gt;
— Albert Einstein&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why Most System Design Docs Are Useless
&lt;/h2&gt;

&lt;p&gt;You’re a Flutter developer. You want to ship features, not write novels. Yet, your team drowns in system design docs nobody reads. Why? Because most are written for the writer, not the reader. They’re verbose, vague, and quickly outdated. The result: confusion, blame, and wasted time.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Write a System Design Doc
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Don’t write a system design doc if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The change is trivial (icon swap, label tweak).&lt;/li&gt;
&lt;li&gt;The feature is isolated (debug screen, theme toggle).&lt;/li&gt;
&lt;li&gt;You’re prototyping (speed &amp;gt; perfection).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can explain it in a Slack message, you don’t need a doc. Just code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do write a system design doc if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You’re touching core system architecture (auth, state, backend).&lt;/li&gt;
&lt;li&gt;Other teams are involved (backend, QA, PM).&lt;/li&gt;
&lt;li&gt;You’re introducing new architectural patterns (custom BLoC, new dependency).&lt;/li&gt;
&lt;li&gt;There are risks (security, compliance, performance).&lt;/li&gt;
&lt;li&gt;The feature is controversial (if two devs argue, document it).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re afraid of being blamed later, write the doc now.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Anatomy of a Brutal System Design Doc
&lt;/h2&gt;

&lt;p&gt;A good system design doc is short, sharp, and actionable. It answers three questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;What are we building?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Why are we building it this way?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;How will we know it works?&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Example: Core System Change
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# User Authentication System&lt;/span&gt;

&lt;span class="gu"&gt;## Problem&lt;/span&gt;
We need secure, fast login. Passwordless preferred.

&lt;span class="gu"&gt;## Solution&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Use Firebase Auth
&lt;span class="p"&gt;-&lt;/span&gt; Email/password fallback
&lt;span class="p"&gt;-&lt;/span&gt; Biometric auth on supported devices

&lt;span class="gu"&gt;## Architecture&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Stateless backend
&lt;span class="p"&gt;-&lt;/span&gt; JWT tokens for session
&lt;span class="p"&gt;-&lt;/span&gt; Secure storage on device

&lt;span class="gu"&gt;## Risks&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; JWT tokens expire in 24h
&lt;span class="p"&gt;-&lt;/span&gt; Password hashing with bcrypt

&lt;span class="gu"&gt;## Success Criteria&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Login &amp;lt; 2s
&lt;span class="p"&gt;-&lt;/span&gt; &amp;lt;5% auth failure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example: Cross-Team System Feature
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Payment System Integration&lt;/span&gt;

&lt;span class="gu"&gt;## Teams&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Mobile (Frontend)
&lt;span class="p"&gt;-&lt;/span&gt; Backend (APIs)
&lt;span class="p"&gt;-&lt;/span&gt; QA (Testing)

&lt;span class="gu"&gt;## Architecture&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; RESTful API for payments
&lt;span class="p"&gt;-&lt;/span&gt; Secure token exchange
&lt;span class="p"&gt;-&lt;/span&gt; Error handling strategy

&lt;span class="gu"&gt;## Timeline&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; API Spec: 2025-09-15
&lt;span class="p"&gt;-&lt;/span&gt; Frontend: 2025-09-20
&lt;span class="p"&gt;-&lt;/span&gt; QA: 2025-09-25
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Only System Design Doc Structure You Need
&lt;/h2&gt;

&lt;p&gt;Your system design docs should fit in a single directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FlutterSystemDocs/
├── Architecture/
│   ├── ADRs/
│   ├── StateManagement.md
│   └── AppStructure.png
├── Features/
│   ├── [Feature]/
│   │   ├── SystemSpec.md
│   │   └── SequenceDiagram.png
├── UI-Kit/
│   ├── WidgetCatalog.md
│   └── Theming.md
├── Platform/
│   ├── iOS.md
│   ├── Android.md
└── RELEASE.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anything more is waste.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Document
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Architecture Decisions:&lt;/strong&gt; One file per decision. Why Riverpod, not Bloc? Why go_router?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System Flows:&lt;/strong&gt; Sequence diagrams, data flow, error handling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature Specs:&lt;/strong&gt; Problem, solution, edge cases, metrics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Widget Catalog:&lt;/strong&gt; Screenshot, code, props, usage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform Differences:&lt;/strong&gt; Only if iOS/Android diverge.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What NOT to Document
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Obvious Flutter practices (&lt;code&gt;Scaffold&lt;/code&gt;, &lt;code&gt;Container&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Temporary hacks (use code comments).&lt;/li&gt;
&lt;li&gt;Every small decision (only long-term impact).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Maintain System Design Docs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Update docs in the same PR as code.&lt;/li&gt;
&lt;li&gt;Delete outdated docs—don’t mark as deprecated.&lt;/li&gt;
&lt;li&gt;Review docs in code reviews. Bad docs block merges.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Works
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Focused:&lt;/strong&gt; Only what matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-Platform:&lt;/strong&gt; Handles iOS/Android differences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Composable:&lt;/strong&gt; Widget catalog = design system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lightweight:&lt;/strong&gt; Docs don’t become a project.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Hard Truth
&lt;/h2&gt;

&lt;p&gt;If your system design docs are a burden, you’re doing it wrong. Good docs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are faster to update than to ignore.&lt;/li&gt;
&lt;li&gt;Answer common questions before they’re asked.&lt;/li&gt;
&lt;li&gt;Help onboard new devs in days, not weeks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If not, delete and start over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Words
&lt;/h2&gt;

&lt;p&gt;A system design doc is not for you. It’s for the next developer—maybe you in six months. No doc? Own the chaos. Doc? Make it short, brutal, and actionable. No fluff. No "maybe." Decide.&lt;/p&gt;

&lt;p&gt;Now stop documenting. Go build something useful.&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>mobile</category>
      <category>productivity</category>
      <category>flutter</category>
    </item>
    <item>
      <title>Finagle’s Fourth Law and BDD</title>
      <dc:creator>Nithin</dc:creator>
      <pubDate>Thu, 14 Aug 2025 02:27:42 +0000</pubDate>
      <link>https://dev.to/supejuice/finagles-fourth-law-and-bdd-ji1</link>
      <guid>https://dev.to/supejuice/finagles-fourth-law-and-bdd-ji1</guid>
      <description>&lt;p&gt;You think you’re fixing your app. You’re not. You’re just moving the bugs around. This is not a curse. It’s a feature. The sooner you accept it, the sooner you’ll stop pretending you’re smarter than entropy.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Finagle’s Fourth Law:&lt;/strong&gt; &lt;em&gt;Once a job is fouled up, anything done to improve it makes it worse.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. The Law You Break Every Day
&lt;/h2&gt;

&lt;p&gt;Fred Brooks said adding people to a late project makes it later. Finagle said any “improvement” to a broken thing creates new failure modes. You know this. You’ve seen it. You patch a leak, water comes out of the ceiling. You refactor your Flutter state management, and now the UI is stale. Welcome to reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Bug-Driven Development: The Only Honest Method
&lt;/h2&gt;

&lt;p&gt;Forget “Test-Driven Development.” Forget “Clean Architecture.” Here’s the only process that works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fix the bug that hurts the most.&lt;/li&gt;
&lt;li&gt;Accept that your fix will create new bugs.&lt;/li&gt;
&lt;li&gt;Use those new bugs as clues.&lt;/li&gt;
&lt;li&gt;Repeat.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You don’t need a perfect rewrite. You need controlled chaos. Perfection is a myth sold by consultants.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Stop Worshipping Patterns
&lt;/h2&gt;

&lt;p&gt;Most teams treat Finagle’s Law as a warning: “Be careful!” They freeze. They design Bloc event/state machines that look like a PhD thesis. They map JSON:API to RFC 9457 with factories and adapters and mappers. They do nothing. BDD says: break things, but break them on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Controlled Breakage: JSON:API vs RFC 9457
&lt;/h2&gt;

&lt;p&gt;If you’ve ever mapped API errors, you know the trap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;JSON:API&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"404"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Not found"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;RFC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9457&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Not Found"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You want to support both error formats. You will fail. Here’s what you do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unify parsing for the format you use most.&lt;/li&gt;
&lt;li&gt;When a bug appears in the other format, fix only that branch.&lt;/li&gt;
&lt;li&gt;Notice the shared logic. Refactor only when you see it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stop pretending you can do everything at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Responsive Design: The Domino Effect
&lt;/h2&gt;

&lt;p&gt;You scale your UI for tablets. Now buttons are billboards, labels overlap, tap targets are huge. BDD says:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fix the chart label density.&lt;/li&gt;
&lt;li&gt;When small phones get too few labels, extract breakpoint logic.&lt;/li&gt;
&lt;li&gt;Let bugs show you where the real problems are.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Discipline or Disaster
&lt;/h2&gt;

&lt;p&gt;Finagle + BDD is dangerous. You need discipline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep fixes small. Roll back often.&lt;/li&gt;
&lt;li&gt;Test critical paths: prices, rendering, error handling.&lt;/li&gt;
&lt;li&gt;Avoid “quick fixes” with global variables and hidden timers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without discipline, you’re just firefighting. The goal is to let bugs guide you, not to live in chaos.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. When Big Bang Rewrites Are Allowed
&lt;/h2&gt;

&lt;p&gt;Sometimes, you need a big rewrite. Rarely.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modules are isolated.&lt;/li&gt;
&lt;li&gt;Technical debt blocks all progress.&lt;/li&gt;
&lt;li&gt;Requirements change drastically.&lt;/li&gt;
&lt;li&gt;You have strong tests.&lt;/li&gt;
&lt;li&gt;The team is ready.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: Your monolithic Flutter app can’t handle dynamic modules. You migrate to plugins, write tests, isolate the migration. This is justified. Most rewrites are not.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. The Final Rules
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Small, surgical fixes. Big Bang rewrites are for gamblers.&lt;/li&gt;
&lt;li&gt;Bugs are GPS markers. Follow them.&lt;/li&gt;
&lt;li&gt;Architect last, refactor always.&lt;/li&gt;
&lt;li&gt;Measure every fix. If things get worse, revert.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  9. Why No Big Bang?
&lt;/h2&gt;

&lt;p&gt;Big Bang rewrites break everything. Controlled fixes isolate issues, measure impact, and let you revert. Shrink the blast radius. Don’t gamble the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. What Is a Fouled Up Job?
&lt;/h2&gt;

&lt;p&gt;A job is “fouled up” when any change is risky. Complexity, technical debt, workarounds. In these cases, even good intentions create new bugs. BDD with Finagle’s Law accepts this. Bugs are signposts. Clean up in small, reversible steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Integrating APIs&lt;/em&gt;: Add a small adapter for the new format. Keep the old one. Fix only what breaks.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Domain Layer Mess&lt;/em&gt;: Extract business logic for one feature. Use bugs to guide the next extraction.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Global State&lt;/em&gt;: Replace it in the worst widget first. Use new bugs to find the next target.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Finagle’s Fourth Law is not a curse. It’s reality. Bug-Driven Development uses it as a compass. Don’t avoid making things worse. Make them worse in controlled ways, so you can make them better.&lt;/p&gt;

&lt;p&gt;In Flutter, this means smaller migrations, isolated logic, and never believing a pattern will save you.&lt;/p&gt;

&lt;p&gt;Entropy wins. You can only play along.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>architecture</category>
      <category>softwareengineering</category>
      <category>flutter</category>
    </item>
    <item>
      <title>Stop Lying in Daily Standups</title>
      <dc:creator>Nithin</dc:creator>
      <pubDate>Thu, 31 Jul 2025 18:18:47 +0000</pubDate>
      <link>https://dev.to/supejuice/stop-lying-in-daily-standups-95k</link>
      <guid>https://dev.to/supejuice/stop-lying-in-daily-standups-95k</guid>
      <description>&lt;p&gt;Daily standups are a theater. Every developer knows it. You stare at Slack, try to remember what you did, lie a little, and type something vague like “worked on the dashboard bug.” Rinse. Repeat.&lt;/p&gt;

&lt;p&gt;I stopped doing that. Now my commits tell the truth. I wrote a Shell script. It reads my Git logs, filters by my email, and generates my standup notes. That’s it. No Jira. No Notion. No retrospection gymnastics. Just Git, Terminal, and Copilot.&lt;/p&gt;

&lt;p&gt;And yes, it saves me time. But more importantly, it eliminates human memory from the equation. Memory lies. Commits don’t.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack (If You Care)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;VSCode (because Emacs is dead)&lt;/li&gt;
&lt;li&gt;Git (the only real source of truth)&lt;/li&gt;
&lt;li&gt;GitHub Copilot (to type what I don’t want to)&lt;/li&gt;
&lt;li&gt;Google Sheets (don’t ask)&lt;/li&gt;
&lt;li&gt;Slack (optional, unfortunately)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;I write code.&lt;/li&gt;
&lt;li&gt;I commit often, with Copilot writing the messages.&lt;/li&gt;
&lt;li&gt;At the end of the day, I run a script.&lt;/li&gt;
&lt;li&gt;The script reads recent commits based on time of day.&lt;/li&gt;
&lt;li&gt;The output becomes my standup report.&lt;/li&gt;
&lt;li&gt;I paste it where the team expects me to.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it. No plugin. No integration. Just plain text.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Script That Does My Job
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;REPO1_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/path/to/project-one"&lt;/span&gt;
&lt;span class="nv"&gt;REPO2_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/path/to/project-two"&lt;/span&gt;
&lt;span class="nv"&gt;AUTHOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your.email@example.com"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OSTYPE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"linux-gnu"&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;alias date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"date --date"&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;HOUR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%H&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OSTYPE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"linux-gnu"&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOUR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-lt&lt;/span&gt; 12 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nv"&gt;START_DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"yesterday"&lt;/span&gt; &lt;span class="s2"&gt;"+%Y-%m-%d"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nv"&gt;START_DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="s2"&gt;"+%Y-%m-%d"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;fi
    &lt;/span&gt;&lt;span class="nv"&gt;DAY_OF_WEEK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$START_DATE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"+%u"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else
    if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOUR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-lt&lt;/span&gt; 12 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nv"&gt;START_DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-1d&lt;/span&gt; &lt;span class="s2"&gt;"+%Y-%m-%d"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nv"&gt;START_DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="s2"&gt;"+%Y-%m-%d"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;fi
    &lt;/span&gt;&lt;span class="nv"&gt;DAY_OF_WEEK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-j&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"%Y-%m-%d"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$START_DATE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"+%u"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DAY_OF_WEEK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 6 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DAY_OF_WEEK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 7 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;START_DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-Fri&lt;/span&gt; &lt;span class="s2"&gt;"+%Y-%m-%d"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;START&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;START_DATE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;T00:00:00"&lt;/span&gt;
&lt;span class="nv"&gt;END&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;START_DATE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;T23:59:59"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"What we did on &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;START_DATE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"==========================================="&lt;/span&gt;

&lt;span class="o"&gt;{&lt;/span&gt;
    git &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REPO1_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; log &lt;span class="nt"&gt;--author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AUTHOR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--since&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$START&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--until&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$END&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;         &lt;span class="nt"&gt;--pretty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;format:&lt;span class="s2"&gt;"- %s"&lt;/span&gt; &lt;span class="nt"&gt;--abbrev-commit&lt;/span&gt; &lt;span class="nt"&gt;--no-merges&lt;/span&gt;
    git &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REPO2_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; log &lt;span class="nt"&gt;--author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AUTHOR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--since&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$START&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--until&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$END&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;         &lt;span class="nt"&gt;--pretty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;format:&lt;span class="s2"&gt;"- %s"&lt;/span&gt; &lt;span class="nt"&gt;--abbrev-commit&lt;/span&gt; &lt;span class="nt"&gt;--no-merges&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq

echo&lt;/span&gt; &lt;span class="s2"&gt;"==========================================="&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"End of Update"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Output That Doesn’t Embarrass Me
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What we did on 2025-07-23
===========================================
- Refactor: Adjust padding in Combined OI screen
- Add: Support for toggle between chart and table views
- Fix: Crash on missing data in open interest API
- Chore: Remove unused debug logs from dashboard
- Update: Improve spacing in TopBanner layout
===========================================
End of Update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Google Sheets: My Personal Logbook
&lt;/h2&gt;

&lt;p&gt;Each row is a day. Each cell is a commit log. Meetings, code reviews, discussions—those get manually added. I don’t mind. It’s five lines. I don’t write novels.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Reviews Without Anxiety
&lt;/h2&gt;

&lt;p&gt;When the cycle begins, I open the sheet. I trim the log into a focused summary—no digging, just filtering.&lt;/p&gt;

&lt;p&gt;It’s unemotional. And it’s honest. Try doing that with Jira.&lt;/p&gt;

&lt;h2&gt;
  
  
  For Teams
&lt;/h2&gt;

&lt;p&gt;Want your team to stop pretending they were “looking into a few things”?&lt;/p&gt;

&lt;p&gt;Let them run the same script with their email. Keep it local. Keep it personal. If you want, post it in a shared thread. But don’t centralize it. Autonomy matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to Try?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Save the script as &lt;code&gt;update_scrum.sh&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;chmod +x update_scrum.sh&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Replace the paths and author email&lt;/li&gt;
&lt;li&gt;Run it: &lt;code&gt;./update_scrum.sh&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Pipe output to file (optional): &lt;code&gt;./update_scrum.sh &amp;gt; daily_log_2025-07-23.md&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;Most standups are lies. This one isn’t.&lt;/p&gt;

&lt;p&gt;Write code. Commit often. Let Git speak for you.&lt;/p&gt;

</description>
      <category>scrum</category>
      <category>git</category>
      <category>shell</category>
      <category>programming</category>
    </item>
    <item>
      <title>Dart and Exception Handling: No Guardrails, Only Discipline</title>
      <dc:creator>Nithin</dc:creator>
      <pubDate>Thu, 31 Jul 2025 17:13:30 +0000</pubDate>
      <link>https://dev.to/supejuice/dart-and-exception-handling-no-guardrails-only-discipline-16m0</link>
      <guid>https://dev.to/supejuice/dart-and-exception-handling-no-guardrails-only-discipline-16m0</guid>
      <description>&lt;p&gt;Have you ever relied on Dart’s &lt;code&gt;throw&lt;/code&gt; and thought, “Great—now my callers are forced to handle this”? Think again. In Java and its cousins, exception chaining is a first-class citizen: you catch, chain, and let the compiler enforce your discipline. In Dart (and by extension Flutter), you’re on your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exception Chaining in Java: A Quick Recap
&lt;/h2&gt;

&lt;p&gt;Java’s checked exceptions bite when they must. If a method declares &lt;code&gt;throws IOException&lt;/code&gt;, every caller up the chain either handles it or propagates it. And when you rethrow, you do it right:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;riskyOperation&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MyAppException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to do the thing"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That little &lt;code&gt;, e&lt;/code&gt; preserves the stack trace and makes debugging a dream. Learn more on exception chaining &lt;a href="https://en.wikipedia.org/wiki/Exception_chaining" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dart’s “Freedom” Feels a Lot Like No Guardrails
&lt;/h2&gt;

&lt;p&gt;Dart’s type system doesn’t distinguish between checked and unchecked exceptions—all are unchecked. You can write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;doSomethingRisky&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;StateError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'something went terribly wrong'&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;…and your callers aren’t even reminded to catch it. There’s no compiler error, no warning—nothing. You have to remember, by convention, that &lt;code&gt;doSomethingRisky()&lt;/code&gt; might explode.&lt;/p&gt;

&lt;h3&gt;
  
  
  Even &lt;code&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt; Patterns Don’t Save You
&lt;/h3&gt;

&lt;p&gt;“Okay,” you say, “I’ll wrap everything in a &lt;code&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt; so that forgetting to handle it is impossible.” Not quite. In Dart:&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;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;getUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There’s… nothing stopping you from parking &lt;code&gt;result&lt;/code&gt; in a corner, ignoring its &lt;code&gt;.isSuccess&lt;/code&gt;, and then accessing &lt;code&gt;.value&lt;/code&gt;. At runtime? Boom—a throw you forgot to anticipate. You traded one potential uncaught exception for another.&lt;/p&gt;

&lt;h2&gt;
  
  
  Catching Errors (If You Care)
&lt;/h2&gt;

&lt;p&gt;A good rule of thumb is “catch if you can, otherwise let it bubble”—but since Dart never forces you to catch in the first place, the habit rarely forms. Consider these behaviors:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dart&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No catch&lt;/td&gt;
&lt;td&gt;Bubbles up silently—no compile-time reminder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;catch (e) { rethrow; }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bubbles up &lt;strong&gt;with&lt;/strong&gt; original stack trace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;catch (e) { throw e; }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bubbles up &lt;strong&gt;losing&lt;/strong&gt; original stack trace&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Nullable Responses: The Invisibility Cloak
&lt;/h2&gt;

&lt;p&gt;Returning &lt;code&gt;T?&lt;/code&gt; or &lt;code&gt;Future&amp;lt;T?&amp;gt;&lt;/code&gt; might seem like a gentle way to sidestep exceptions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fetchUser&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;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUser&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;log&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="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But now, instead of an explicit error, all you have is a silent &lt;code&gt;null&lt;/code&gt;. The real crime? You’ve lost the ability to bubble up the original error. The stack trace, the exception type, the context—all vanished in the void. Without that, diagnosing failures becomes a wild goose chase.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Pragmatic Prescription
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Be deliberate.&lt;/strong&gt; Whether you throw or return &lt;code&gt;null&lt;/code&gt; (or wrap in &lt;code&gt;Result&lt;/code&gt;), pick a convention and document it.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Catch at the edges.&lt;/strong&gt; Localize unchecked exceptions at boundaries—UI layer, service layer—where you can log and recover.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preserve your traces.&lt;/strong&gt; If you catch and rethrow, use &lt;code&gt;rethrow&lt;/code&gt; so the original stack isn’t lost.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce via tooling.&lt;/strong&gt; Automate linting to flag un-caught futures or raw &lt;code&gt;throw&lt;/code&gt; without surrounding &lt;code&gt;try/catch&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Educate your team.&lt;/strong&gt; Habits form from code reviews: make error-handling expectations crystal clear.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Dart may not force error handling—but with discipline, conventions, and the right practices, you can. Catch if you can; otherwise, be ready to bear the pain.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>mobile</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Creating a Line Chart with Secondary Y-Axis in Flutter</title>
      <dc:creator>Nithin</dc:creator>
      <pubDate>Fri, 06 Dec 2024 09:25:37 +0000</pubDate>
      <link>https://dev.to/supejuice/creating-a-line-chart-with-secondary-y-axis-in-flutter-5beg</link>
      <guid>https://dev.to/supejuice/creating-a-line-chart-with-secondary-y-axis-in-flutter-5beg</guid>
      <description>&lt;p&gt;In this guide, we’ll explore how to create a line chart in Flutter with a secondary Y-axis for datasets that have different scales. By using &lt;code&gt;fl_chart&lt;/code&gt;, we’ll develop a flexible charting solution that works for any generic data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Secondary Y-Axis?
&lt;/h2&gt;

&lt;p&gt;Sometimes, two datasets that share an X-axis (e.g., timestamps) but have different Y-axis scales need to be visualized together. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Primary Y-Axis: Represents Dataset A.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Secondary Y-Axis: Represents Dataset B (on a different scale).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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%2F8pfyfzcobheiscnz0lxu.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%2F8pfyfzcobheiscnz0lxu.png" alt="Sample 1 represents single y axis. Sample 2 represents dual y-axis with scaling" width="800" height="1692"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Above image displays the need for secondary y-axis in certain scenarios. Sample 1 represents single y axis. Sample 2 represents dual y-axis with scaling&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 1: Define the Data for the Chart
&lt;/h2&gt;

&lt;p&gt;First, define two datasets that will be plotted on the chart. In our example, these datasets will represent different measurements that we wish to compare over time.&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;double&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;primaryData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;  &lt;span class="c1"&gt;// Dataset A&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;double&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;secondaryData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;  &lt;span class="c1"&gt;// Dataset B&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;xAxisLabels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Jan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Feb"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Mar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Apr"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"May"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;  &lt;span class="c1"&gt;// X-Axis labels (time, categories, etc.)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Create the Widget for the Chart
&lt;/h2&gt;

&lt;p&gt;Now, create a new Flutter widget that will display the chart. We will use the LineChart widget from fl_chart.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 Define the Widget Class
&lt;/h3&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;DualAxisLineChart&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;DualAxisLineChart&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;primaryData&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;secondaryData&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;xAxisLabels&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;primaryColor&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;secondaryColor&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;scaleSecondaryData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.2 Define Chart Properties
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;primaryData&lt;/code&gt; and &lt;code&gt;secondaryData&lt;/code&gt;: Lists of values for the primary and secondary data lines.&lt;br&gt;
&lt;code&gt;xAxisLabels&lt;/code&gt;: A list of X-axis labels (e.g., months or categories).&lt;br&gt;
&lt;code&gt;primaryColor&lt;/code&gt; and &lt;code&gt;secondaryColor&lt;/code&gt;: Colors for the primary and secondary data lines.&lt;br&gt;
&lt;code&gt;scaleSecondaryData&lt;/code&gt;: A function to scale the secondary data to fit within the primary Y-axis.&lt;/p&gt;
&lt;h3&gt;
  
  
  2.3 Build the LineChart Widget
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="nd"&gt;@override&lt;/span&gt;
&lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;16.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;LineChart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;LineChartData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;gridData:&lt;/span&gt; &lt;span class="n"&gt;_buildGridData&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nl"&gt;titlesData:&lt;/span&gt; &lt;span class="n"&gt;_buildTitlesData&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nl"&gt;lineBarsData:&lt;/span&gt; &lt;span class="n"&gt;_buildLineBarsData&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nl"&gt;borderData:&lt;/span&gt; &lt;span class="n"&gt;FlBorderData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;show:&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="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2.4 Define Grid Data
&lt;/h3&gt;

&lt;p&gt;Configure the grid lines using &lt;code&gt;FlGridData&lt;/code&gt;. This section will make the chart's background grid visible. Modify as per requirement.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;FlGridData&lt;/span&gt; &lt;span class="nf"&gt;_buildGridData&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;FlGridData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;drawVerticalLine:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;getDrawingHorizontalLine:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;FlLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;grey&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;strokeWidth:&lt;/span&gt; &lt;span class="mf"&gt;0.5&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;h3&gt;
  
  
  2.5 Define Titles Data
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;FlTitlesData&lt;/code&gt; to configure the axis titles. You will define both left (primary Y-axis), right (secondary Y-axis), and bottom (X-axis) titles.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;FlTitlesData&lt;/span&gt; &lt;span class="nf"&gt;_buildTitlesData&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;FlTitlesData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;/// display underlying price on right side. utilise scaling to emulate second y-axis.&lt;/span&gt;
      &lt;span class="nl"&gt;rightTitles:&lt;/span&gt; &lt;span class="n"&gt;AxisTitles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;sideTitles:&lt;/span&gt; &lt;span class="n"&gt;SideTitles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;showTitles:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;getTitlesWidget:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TitleMeta&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;tentativeIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                    &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;axisPosition&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parentAxisSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;secondaryData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toInt&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;int&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tentativeIndex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secondaryData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="nl"&gt;text:&lt;/span&gt; &lt;span class="c1"&gt;// TODO: format text and set,&lt;/span&gt;
                &lt;span class="p"&gt;);&lt;/span&gt;
              &lt;span class="p"&gt;})),&lt;/span&gt;
    &lt;span class="c1"&gt;/// empty top axis&lt;/span&gt;
    &lt;span class="nl"&gt;topTitles:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;AxisTitles&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="c1"&gt;// TODO: define other title here similarly&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.6 Define Line Data for Both Axes
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;lineBarsData&lt;/code&gt; section defines the lines for both datasets. We will use &lt;code&gt;LineChartBarData&lt;/code&gt; to create a line for each dataset. Utilise &lt;code&gt;FlDotData&lt;/code&gt; to specify points on the graph to draw the line through.&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="n"&gt;LineChartBarData&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_buildLineBarsData&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="p"&gt;[&lt;/span&gt;
      &lt;span class="n"&gt;LineChartBarData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;primaryColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;barWidth:&lt;/span&gt; &lt;span class="n"&gt;barWidth&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;dotData:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;FlDotData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;show:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nl"&gt;spots:&lt;/span&gt; &lt;span class="n"&gt;primaryData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mapIndexed&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;value&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;FlSpot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&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;value&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="na"&gt;toList&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;LineChartBarData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;secondaryColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;barWidth:&lt;/span&gt; &lt;span class="n"&gt;barWidth&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;dotData:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;FlDotData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;show:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nl"&gt;spots:&lt;/span&gt; &lt;span class="n"&gt;secondaryData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mapIndexed&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;value&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;FlSpot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&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;scaleSecondaryData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&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="na"&gt;toList&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nl"&gt;dashArray:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&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;ul&gt;
&lt;li&gt;For each dataset, the &lt;code&gt;LineChartBarData&lt;/code&gt; defines how the line is drawn.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scaleSecondaryData&lt;/code&gt;: A function that scales the data to fit the chart’s axis.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dashArray&lt;/code&gt;: this will render the secondary line as dashed specifying width and gap.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3: Define a Scaling Function for the Secondary Data
&lt;/h2&gt;

&lt;p&gt;To ensure the secondary dataset fits within the scale of the primary Y-axis, we need to create a scaling function. This function will adjust the secondary data to match the range of the primary data.&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;double&lt;/span&gt; &lt;span class="nf"&gt;scaleSecondaryData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;primaryMin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;primaryMax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;secondaryMin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;secondaryMax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;primaryRange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;primaryMax&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;primaryMin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;secondaryRange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;secondaryMax&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;secondaryMin&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;primaryMin&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;secondaryMin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primaryRange&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;secondaryRange&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Create the Widget to Display the Chart
&lt;/h2&gt;

&lt;p&gt;Now, we will create a &lt;code&gt;MaterialApp&lt;/code&gt; widget and add the chart widget to the screen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;runApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MaterialApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;home:&lt;/span&gt; &lt;span class="n"&gt;Scaffold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;appBar:&lt;/span&gt; &lt;span class="n"&gt;AppBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Dual Axis Line Chart"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;DualAxisLineChart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;primaryData:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// Example Dataset A&lt;/span&gt;
        &lt;span class="nl"&gt;secondaryData:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// Example Dataset B&lt;/span&gt;
        &lt;span class="nl"&gt;xAxisLabels:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Jan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Feb"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Mar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Apr"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"May"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// Example X-axis&lt;/span&gt;
        &lt;span class="nl"&gt;primaryColor:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;secondaryColor:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;red&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;scaleSecondaryData:&lt;/span&gt; &lt;span class="n"&gt;scaleSecondaryData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Run the Application
&lt;/h2&gt;

&lt;p&gt;After implementing the above code, run your Flutter app, and you should see a line chart with two Y-axes. The primary data will be on the left Y-axis, while the secondary data will be scaled to fit the primary Y-axis and shown on the right.&lt;/p&gt;

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

&lt;p&gt;You have now successfully created a line chart in Flutter with a secondary Y-axis! This approach can be used to compare two datasets with different ranges, such as temperatures and rainfall, stock prices and trading volume, or any other comparable metrics.&lt;/p&gt;

&lt;p&gt;Feel free to adjust the styling, scaling, and data to suit your specific use case. The &lt;code&gt;fl_chart&lt;/code&gt; package provides a lot of flexibility to customize your chart to your needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://app.flchart.dev" rel="noopener noreferrer"&gt;fl_chart widgets&lt;/a&gt;&lt;br&gt;
&lt;a href="https://pub.dev/packages/fl_chart" rel="noopener noreferrer"&gt;Pub Package&lt;/a&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>flchart</category>
      <category>dart</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Testing Dependent Events in Flutter Bloc: Seeding State &amp; Handling Multiple Events</title>
      <dc:creator>Nithin</dc:creator>
      <pubDate>Tue, 30 Jul 2024 10:55:02 +0000</pubDate>
      <link>https://dev.to/supejuice/testing-dependent-events-in-flutter-bloc-seeding-state-handling-multiple-events-2ll1</link>
      <guid>https://dev.to/supejuice/testing-dependent-events-in-flutter-bloc-seeding-state-handling-multiple-events-2ll1</guid>
      <description>&lt;p&gt;In Flutter Bloc testing, there are two main approaches to test the dependent Event in scenarios where the Event in test depends on the outcome of a previous event: seeding the state or calling a pre-event with &lt;code&gt;skip&lt;/code&gt;. Both methods have their own advantages and disadvantages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seeding State
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Advantages
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Direct Control:&lt;/strong&gt; You can directly set the Bloc's state to a desired state without having to dispatch any events. This provides a precise starting point for your tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity:&lt;/strong&gt; Seeding is straightforward and easy to understand, as it eliminates the need for additional event handling and state transitions.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Disadvantages
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Unrealistic Testing:&lt;/strong&gt; Directly seeding a state might not accurately reflect the real-world usage of your Bloc, where states are typically reached through event dispatching and state transitions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Initialization Complexity:&lt;/strong&gt; Creating and passing the correct initial state can be complex, especially when the state has multiple parameters or requires specific configurations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limited Coverage:&lt;/strong&gt; This approach may not cover all the transitions and side effects that occur when events are dispatched, potentially missing bugs in event handling logic.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Calling Pre-Event with Skip
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Advantages
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Realistic Testing:&lt;/strong&gt; By dispatching events to reach a desired state, you mimic the actual behavior of your application, ensuring that all transitions and side effects are covered.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Efficient Event Handling:&lt;/strong&gt; Often, setting up the initial state by dispatching a pre-event is straightforward and may be simpler than manually constructing a complex state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thorough Coverage:&lt;/strong&gt; This approach tests the entire flow, from event dispatch to state transition, providing a more comprehensive test of your Bloc's logic.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Disadvantages
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Event Dependency:&lt;/strong&gt; If your tests require many pre-events to reach a desired state, it can make the test setup more verbose, although each individual pre-event is typically simple to add.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance:&lt;/strong&gt; Dispatching multiple events to reach the desired state can slow down your tests, especially if there are many intermediate states. However, this is usually a minor issue for most test cases.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Example in Flutter Bloc Test
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Seeding State
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;blocTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;'CounterBloc emits [10] when seeded with 9'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;build:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;CounterBloc&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nl"&gt;seed:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;act:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bloc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bloc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CounterEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;expect:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Calling Pre-Event with Skip
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;blocTest&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyBloc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MyBlocState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
  &lt;span class="s"&gt;'emits [MyBlocState] when SearchItemEvent is added'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;build:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;MyBloc&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nl"&gt;act:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bloc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;bloc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ListItemsEvent&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// Start with loading the list&lt;/span&gt;
    &lt;span class="n"&gt;bloc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SearchItemEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;item:&lt;/span&gt; &lt;span class="s"&gt;'Apple'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Start searching loaded list&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nl"&gt;skip:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Skip emissions for intermediate states if not needed&lt;/span&gt;
  &lt;span class="nl"&gt;expect:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;MyBlocState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;filteredList:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'Green Apple'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Red Apple'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use Seeding State When:&lt;/strong&gt; You need to quickly set up a simple initial state, or when you are testing scenarios where event-driven transitions are either not needed or not practical like in above example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid Seeding State When:&lt;/strong&gt; Your focus is on understanding how your Bloc reacts to sequences of events, how it transitions between states, or when you want to ensure that all parts of the Bloc’s behavior are tested as they would occur in a live application.&lt;/p&gt;

&lt;p&gt;In practice, a combination of both methods might be useful depending on the scenario. Seeding can be a useful tool, but it’s important to ensure that it aligns with the objectives of your tests and does not bypass critical event-driven logic.&lt;/p&gt;

&lt;p&gt;Feel free to share your thoughts and experiences in the comments below!&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>testing</category>
    </item>
    <item>
      <title>How to Write BLoC Tests to Improve Code Quality</title>
      <dc:creator>Nithin</dc:creator>
      <pubDate>Sun, 28 Jul 2024 09:23:41 +0000</pubDate>
      <link>https://dev.to/supejuice/how-to-write-bloc-tests-to-improve-code-quality-4ol</link>
      <guid>https://dev.to/supejuice/how-to-write-bloc-tests-to-improve-code-quality-4ol</guid>
      <description>&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1213571513439399936-113" src="https://platform.twitter.com/embed/Tweet.html?id=1213571513439399936"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1213571513439399936-113');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1213571513439399936&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;In the world of Flutter development, testing is paramount to ensuring robust and maintainable applications. Among the various testing strategies, BLoC (Business Logic Component) testing stands out for its ability to convert events into states, whether synchronously or asynchronously. Writing BLoC tests can significantly enhance code quality, uncover hidden issues, and ensure that your state management logic is sound. In this article, we'll explore how to write effective BLoC tests using the &lt;code&gt;bloc_test&lt;/code&gt; library and provide practical pseudocode examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the BLoC Pattern?
&lt;/h2&gt;

&lt;p&gt;The BLoC pattern is a state management solution that separates business logic from UI components. It uses streams to handle the flow of data, allowing events to trigger state changes in a predictable and testable way. The BLoC pattern is widely adopted in Flutter applications for its scalability and maintainability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Write BLoC Tests?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Improving Code Quality
&lt;/h3&gt;

&lt;p&gt;The primary goal of BLoC testing is to improve code quality. By thoroughly testing all events and state transitions, you can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Uncover Hidden Events:&lt;/strong&gt; Identify events that aren’t triggered from the UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid Spaghetti Code:&lt;/strong&gt; Prevent complex and tangled state transitions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplify State Management:&lt;/strong&gt; Ensure that most events only emit one or two states (usually loading and success/failure).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adhere to SOLID Principles:&lt;/strong&gt; Identify and remove unnecessary data and business logic from repository methods.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1596031809229565952-449" src="https://platform.twitter.com/embed/Tweet.html?id=1596031809229565952"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1596031809229565952-449');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1596031809229565952&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with BLoC Testing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Set Up Your Test Environment
&lt;/h3&gt;

&lt;p&gt;Add the &lt;code&gt;bloc_test&lt;/code&gt; library to your &lt;code&gt;pubspec.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;dev_dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;bloc_test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^8.0.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Create Test Repositories
&lt;/h3&gt;

&lt;p&gt;Implement two versions of your abstract repository class: one for success and one for failure. Use real JSON samples from the API or create samples using AI tools like Gemini or Copilot.&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;SuccessTestRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;AbstractRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fetchData&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;return&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;successJson&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;FailureTestRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;AbstractRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fetchData&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;throw&lt;/span&gt; &lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Failed to fetch data'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Set Up BLoC Instances
&lt;/h3&gt;

&lt;p&gt;Create instances of your BLoC in the main testing function, one with the success repository and one with the failure repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'BLoC Tests'&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;late&lt;/span&gt; &lt;span class="n"&gt;MyBloc&lt;/span&gt; &lt;span class="n"&gt;successBloc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;late&lt;/span&gt; &lt;span class="n"&gt;MyBloc&lt;/span&gt; &lt;span class="n"&gt;failureBloc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;successBloc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MyBloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SuccessTestRepository&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
      &lt;span class="n"&gt;failureBloc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MyBloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FailureTestRepository&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="n"&gt;tearDown&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;successBloc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="n"&gt;failureBloc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Add tests here&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Write Tests Using blocTest
&lt;/h3&gt;

&lt;p&gt;For each event, write a test that describes the expected behavior. Use the &lt;code&gt;blocTest&lt;/code&gt; function to test the BLoC instance, adding events and asserting the resulting states.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;blocTest&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyBloc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MyState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
  &lt;span class="s"&gt;'emits [Loading, Success] for FetchDataEvent - success'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;build:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;successBloc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;act:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bloc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bloc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FetchDataEvent&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="nl"&gt;expect:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;isA&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LoadingState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;isA&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SuccessState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;having&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isNotEmpty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'data list not empty'&lt;/span&gt;&lt;span class="p"&gt;,&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;blocTest&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyBloc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MyState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
  &lt;span class="s"&gt;'emits [Loading, Failure] for FetchDataEvent - failure'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;build:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;failureBloc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;act:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bloc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bloc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FetchDataEvent&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="nl"&gt;expect:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;isA&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LoadingState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;isA&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FailureState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;having&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'error'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isNotEmpty&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Handle Edge Scenarios
&lt;/h3&gt;

&lt;p&gt;For edge scenarios where certain states depend on previous events, add the list of events in the act method. Use the skip method to isolate the test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;
&lt;span class="n"&gt;blocTest&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyBloc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MyState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
  &lt;span class="s"&gt;'emits [Loading, Success] after pre-event'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;build:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;successBloc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;act:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bloc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;bloc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PreEvent&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// previous event&lt;/span&gt;
    &lt;span class="n"&gt;bloc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FetchDataEvent&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// the dependant event&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nl"&gt;skip:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// skip states produced by one or more preceding events&lt;/span&gt;
  &lt;span class="nl"&gt;expect:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;isA&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LoadingState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;isA&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SuccessState&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Document Edge Case Tests
&lt;/h3&gt;

&lt;p&gt;Make sure to document edge case tests with comments and keep them simple. This practice helps in understanding the context and reasoning behind certain tests, especially when dealing with complex scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Run Tests with Coverage
&lt;/h3&gt;

&lt;p&gt;Ensure comprehensive test coverage by running tests with coverage reporting. Use the following command to generate coverage reports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--coverage&lt;/span&gt;
genhtml coverage/lcov.info &lt;span class="nt"&gt;-o&lt;/span&gt; coverage/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using &lt;code&gt;isA&lt;/code&gt; and &lt;code&gt;having&lt;/code&gt; Matchers
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;isA&lt;/code&gt; and &lt;code&gt;having&lt;/code&gt; functions are powerful tools in Dart's testing framework, allowing for granular and precise state validation. Using these matchers, you can ensure that the emitted states not only belong to a certain type but also contain specific values or properties.&lt;/p&gt;

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

&lt;p&gt;Consider a BLoC that fetches user data. You want to test that the &lt;code&gt;SuccessState&lt;/code&gt; contains the correct user data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;blocTest&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyBloc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MyState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
  &lt;span class="s"&gt;'emits [Loading, Success] with correct user data'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;build:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;successBloc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;act:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bloc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bloc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FetchUserEvent&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="nl"&gt;expect:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;isA&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LoadingState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;isA&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SuccessState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;having&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;having&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&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="s"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'John Doe'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="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;In this example, &lt;code&gt;isA&amp;lt;SuccessState&amp;gt;()&lt;/code&gt; verifies the state type, while &lt;code&gt;having&lt;/code&gt; checks the specific properties of the state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits of Nested Matchers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Precision:&lt;/strong&gt; Ensure that the state not only matches a type but also contains expected values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Readability:&lt;/strong&gt; Provide clear, readable assertions about the expected state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging:&lt;/strong&gt; Easier to debug when tests fail, as the error messages specify which part of the state did not meet the expectations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Best Practices for BLoC Testing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write Descriptive Test Names:&lt;/strong&gt; Ensure that your test names clearly describe the scenario being tested.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test One Thing at a Time:&lt;/strong&gt; Focus on testing a single event or state transition in each test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep Tests Independent:&lt;/strong&gt; Ensure that tests do not depend on the outcome of other tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Realistic Data:&lt;/strong&gt; Use real API data or realistic mock data to make your tests more robust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review and Refactor:&lt;/strong&gt; Regularly review and refactor your tests to improve readability and maintainability.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Community Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://bloclibrary.dev/testing/" rel="noopener noreferrer"&gt;Bloc Testing Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pub.dev/packages/bloc_test" rel="noopener noreferrer"&gt;Bloc Test Package on pub.dev&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Writing BLoC tests is a rewarding endeavor that significantly improves code quality. By converting events into states and testing these transitions, you can uncover hidden issues, simplify state management, and adhere to best practices. With tools like the &lt;code&gt;bloc_test&lt;/code&gt; library and matchers like &lt;code&gt;isA&lt;/code&gt; and &lt;code&gt;having&lt;/code&gt;, writing these tests becomes a structured and systematic process. Happy testing!&lt;/p&gt;

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