<?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: Noa Tubić</title>
    <description>The latest articles on DEV Community by Noa Tubić (@noatubic).</description>
    <link>https://dev.to/noatubic</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3952606%2F4656287f-4400-48f8-be32-9680314193cf.jpg</url>
      <title>DEV Community: Noa Tubić</title>
      <link>https://dev.to/noatubic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/noatubic"/>
    <language>en</language>
    <item>
      <title>Four Claude Code skills to audit a Flutter app</title>
      <dc:creator>Noa Tubić</dc:creator>
      <pubDate>Wed, 14 Oct 2026 21:03:04 +0000</pubDate>
      <link>https://dev.to/noatubic/four-claude-code-skills-to-audit-a-flutter-app-2ijb</link>
      <guid>https://dev.to/noatubic/four-claude-code-skills-to-audit-a-flutter-app-2ijb</guid>
      <description>&lt;p&gt;Before a release I used to run the same mental checklist. Is the privacy manifest there? Is anything in &lt;code&gt;pubspec.yaml&lt;/code&gt; abandoned? Did someone hardcode a string again? Is a token sitting in the source?&lt;/p&gt;

&lt;p&gt;A checklist in your head gets skipped when the deadline is close. So I turned mine into four Claude Code skills. Each one reads the project, works through a fixed list of checks, and writes a Markdown report with a fix for every finding.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four skills
&lt;/h2&gt;

&lt;p&gt;Security: &lt;code&gt;/owasp-mobile-audit&lt;/code&gt; covers all ten OWASP Mobile Top 10 (2024) categories plus six newer checks, like hardcoded secrets, data leaking into logs, and unsafe sharing. It works on Flutter, native Android, and native iOS code.&lt;/p&gt;

&lt;p&gt;Dependencies: &lt;code&gt;/flutter-dependency-audit&lt;/code&gt; looks at seven things in your packages: outdated and abandoned ones, duplicates that do the same job, license conflicts, known CVEs, loose version constraints, and leftover dependency overrides.&lt;/p&gt;

&lt;p&gt;Localization: &lt;code&gt;/flutter-localization-audit&lt;/code&gt; runs eight checks: hardcoded strings, missing ARB keys, the l10n setup, plural and gender forms, RTL layouts, locale-aware dates and numbers, broken interpolation, and the fallback locale.&lt;/p&gt;

&lt;p&gt;Store readiness: &lt;code&gt;/flutter-store-readiness&lt;/code&gt; runs eight checks for App Store and Google Play: the iOS Privacy Manifest, Android Data Safety, permission strings, screenshots, age rating, metadata limits, and the platform configs such as signing and &lt;code&gt;targetSdk&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Install the ones you want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add NoaTubic/owasp-mobile-audit-skill
npx skills add NoaTubic/flutter-dependency-audit-skill
npx skills add NoaTubic/flutter-localization-audit-skill
npx skills add NoaTubic/flutter-store-readiness-skill
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then, inside Claude Code, in your Flutter project:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/flutter-store-readiness
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Each skill saves its report in the project root, for example &lt;code&gt;store_readiness_report.md&lt;/code&gt;. You can also run only the checks you care about:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/owasp-mobile-audit M9,AE2
/flutter-dependency-audit security,licenses
/flutter-localization-audit hardcoded,arb
/flutter-store-readiness ios
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  What a report looks like
&lt;/h2&gt;

&lt;p&gt;Every report uses the same shape. It opens with a summary: a count per severity, from Critical to Info. The store readiness report also gives each platform a status of ready, issues found, or blocking.&lt;/p&gt;

&lt;p&gt;Then each category gets its findings. A finding has the file and line, the evidence, and one sentence on how to fix it. The report also lists what's already done right, so you don't "fix" something that works. It ends with the three to five fixes that matter most.&lt;/p&gt;

&lt;p&gt;The severity scale is about consequences, not style. In the store readiness skill, Critical means an automatic rejection, like a missing Privacy Manifest or debug signing on a release build. In the security skill, Critical means something directly exploitable, like a hardcoded credential or disabled TLS.&lt;/p&gt;
&lt;h2&gt;
  
  
  A few things they catch that people miss
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A Privacy Manifest that exists but leaves out a Required Reason API your code or an SDK uses&lt;/li&gt;
&lt;li&gt;Permission strings like "This app needs the camera", which Apple tends to reject as too vague&lt;/li&gt;
&lt;li&gt;A package that hasn't had a release in years, with an active alternative&lt;/li&gt;
&lt;li&gt;Dates shown with &lt;code&gt;DateTime.toString()&lt;/code&gt; instead of a locale-aware formatter&lt;/li&gt;
&lt;li&gt;An API key in source, or a token written to the logs&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  What they are and aren't
&lt;/h2&gt;

&lt;p&gt;These are skills, so Claude does the reading. That means they understand context a regex can't, like whether a string is user-facing or a log tag. &lt;strong&gt;It also means a finding is a well-argued suggestion, not a proof.&lt;/strong&gt; Check each one before you act on it, and keep your real scanners and your store review in the loop. The skills are there to catch the obvious misses early.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;Run &lt;code&gt;/flutter-store-readiness&lt;/code&gt; on your app before the next release and fix the Critical items first. The skills, their checks, and the full report templates are here:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/NoaTubic" rel="noopener noreferrer"&gt;
        NoaTubic
      &lt;/a&gt; / &lt;a href="https://github.com/NoaTubic/owasp-mobile-audit-skill" rel="noopener noreferrer"&gt;
        owasp-mobile-audit-skill
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      OWASP Mobile Top 10 (2024) security audit skill for Claude Code — covers all 10 categories + 6 emerging checks, with per-rule filtering
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;OWASP Mobile Audit Skill&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A Claude Code skill that performs a comprehensive &lt;strong&gt;OWASP Mobile Top 10 (2024)&lt;/strong&gt; security audit on mobile codebases and produces a structured, actionable markdown report.&lt;/p&gt;

&lt;p&gt;Covers all 10 official OWASP Mobile categories &lt;strong&gt;plus 6 additional emerging checks&lt;/strong&gt;, with each finding mapped to a severity level and concrete remediation advice.&lt;/p&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it audits&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;ID&lt;/th&gt;

&lt;th&gt;Category&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;M1&lt;/td&gt;

&lt;td&gt;Improper Credential Usage&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;M2&lt;/td&gt;

&lt;td&gt;Inadequate Supply Chain Security&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;M3&lt;/td&gt;

&lt;td&gt;Insecure Authentication / Authorization&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;M4&lt;/td&gt;

&lt;td&gt;Insufficient Input/Output Validation&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;M5&lt;/td&gt;

&lt;td&gt;Insecure Communication&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;M6&lt;/td&gt;

&lt;td&gt;Inadequate Privacy Controls&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;M7&lt;/td&gt;

&lt;td&gt;Insufficient Binary Protections&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;M8&lt;/td&gt;

&lt;td&gt;Security Misconfiguration&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;M9&lt;/td&gt;

&lt;td&gt;Insecure Data Storage&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;M10&lt;/td&gt;

&lt;td&gt;Insufficient Cryptography&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;AE1&lt;/td&gt;

&lt;td&gt;Data Leakage &lt;em&gt;(emerging)&lt;/em&gt;&lt;br&gt;
&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;AE2&lt;/td&gt;

&lt;td&gt;Hardcoded Secrets &lt;em&gt;(emerging)&lt;/em&gt;&lt;br&gt;
&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;AE3&lt;/td&gt;

&lt;td&gt;Insecure Access Control &lt;em&gt;(emerging)&lt;/em&gt;&lt;br&gt;
&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;AE4&lt;/td&gt;

&lt;td&gt;Path Overwrite / Path Traversal &lt;em&gt;(emerging)&lt;/em&gt;&lt;br&gt;
&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;AE5&lt;/td&gt;

&lt;td&gt;Unprotected Endpoints &lt;em&gt;(emerging)&lt;/em&gt;&lt;br&gt;
&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;AE6&lt;/td&gt;

&lt;td&gt;Unsafe Sharing &lt;em&gt;(emerging)&lt;/em&gt;&lt;br&gt;
&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;&lt;/p&gt;


&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npx skills add NoaTubic/owasp-mobile-audit-skill&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Or add it manually by copying &lt;code&gt;SKILL.md&lt;/code&gt; and &lt;code&gt;references/&lt;/code&gt; into your Claude Code skills directory.&lt;/p&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;

&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Full audit (all&lt;/h3&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/NoaTubic/owasp-mobile-audit-skill" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/NoaTubic" rel="noopener noreferrer"&gt;
        NoaTubic
      &lt;/a&gt; / &lt;a href="https://github.com/NoaTubic/flutter-dependency-audit-skill" rel="noopener noreferrer"&gt;
        flutter-dependency-audit-skill
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Flutter/Dart dependency audit skill for Claude Code — checks outdated packages, abandoned libs, license conflicts, CVEs, and version pinning issues
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Flutter Dependency Audit Skill&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A Claude Code skill that performs a comprehensive dependency audit on a Flutter/Dart project and produces a structured, actionable markdown report.&lt;/p&gt;
&lt;p&gt;Covers &lt;strong&gt;7 audit categories&lt;/strong&gt; — from outdated and abandoned packages to license conflicts, known CVEs, and version pinning issues.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it audits&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ID&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;D1&lt;/td&gt;
&lt;td&gt;Outdated Packages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D2&lt;/td&gt;
&lt;td&gt;Abandoned / Unmaintained Packages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D3&lt;/td&gt;
&lt;td&gt;Duplicate Functionality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D4&lt;/td&gt;
&lt;td&gt;License Conflicts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D5&lt;/td&gt;
&lt;td&gt;Security Advisories / Known CVEs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D6&lt;/td&gt;
&lt;td&gt;Version Pinning Issues&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D7&lt;/td&gt;
&lt;td&gt;Dependency Overrides&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npx skills add NoaTubic/flutter-dependency-audit-skill&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Or add it manually by copying &lt;code&gt;SKILL.md&lt;/code&gt; and &lt;code&gt;references/&lt;/code&gt; into your Claude Code skills directory.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Full audit (all 7 checks)&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;/flutter-dependency-audit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Audit specific categories only&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;Pass a comma-separated list of category IDs:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;/flutter-dependency-audit security
/flutter-dependency-audit outdated,licenses
/flutter-dependency-audit security,pinning,overrides
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Only the listed categories will be run. The report header notes which categories were in scope.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Output&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;The skill saves a &lt;code&gt;dependency_audit_report.md&lt;/code&gt; file in your project root containing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Executive Summary&lt;/strong&gt;…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/NoaTubic/flutter-dependency-audit-skill" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/NoaTubic" rel="noopener noreferrer"&gt;
        NoaTubic
      &lt;/a&gt; / &lt;a href="https://github.com/NoaTubic/flutter-localization-audit-skill" rel="noopener noreferrer"&gt;
        flutter-localization-audit-skill
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Flutter/Dart localization audit skill for Claude Code — finds hardcoded strings, missing ARB keys, RTL issues, broken plural forms, and locale-unaware formatting
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Flutter Localization Audit Skill&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A Claude Code skill that performs a comprehensive localization audit on a Flutter/Dart project and produces a structured, actionable markdown report.&lt;/p&gt;
&lt;p&gt;Covers &lt;strong&gt;8 audit categories&lt;/strong&gt; — from hardcoded strings and missing ARB translations to RTL support, plural forms, and locale-unaware date formatting.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it audits&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ID&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;L1&lt;/td&gt;
&lt;td&gt;Hardcoded Strings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L2&lt;/td&gt;
&lt;td&gt;ARB File Completeness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L3&lt;/td&gt;
&lt;td&gt;intl / l10n Setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L4&lt;/td&gt;
&lt;td&gt;Pluralization &amp;amp; Gender&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L5&lt;/td&gt;
&lt;td&gt;RTL Support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L6&lt;/td&gt;
&lt;td&gt;Date, Time &amp;amp; Number Formatting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L7&lt;/td&gt;
&lt;td&gt;String Interpolation Issues&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L8&lt;/td&gt;
&lt;td&gt;Locale Fallback Configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npx skills add NoaTubic/flutter-localization-audit-skill&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Or add it manually by copying &lt;code&gt;SKILL.md&lt;/code&gt; and &lt;code&gt;references/&lt;/code&gt; into your Claude Code skills directory.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Full audit (all 8 checks)&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;/flutter-localization-audit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Audit specific categories only&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;Pass a comma-separated list of category IDs:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;/flutter-localization-audit hardcoded
/flutter-localization-audit arb,rtl
/flutter-localization-audit hardcoded,arb,plurals,rtl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Only the listed categories will be run. The report header notes which categories were in scope.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Output&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;The skill saves a &lt;code&gt;localization_audit_report.md&lt;/code&gt;…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/NoaTubic/flutter-localization-audit-skill" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/NoaTubic" rel="noopener noreferrer"&gt;
        NoaTubic
      &lt;/a&gt; / &lt;a href="https://github.com/NoaTubic/flutter-store-readiness-skill" rel="noopener noreferrer"&gt;
        flutter-store-readiness-skill
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      App Store &amp;amp; Google Play submission readiness audit skill for Claude Code — checks iOS Privacy Manifest, Android Data Safety, permissions, screenshots, age rating, and metadata limits
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Flutter Store Readiness Skill&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A Claude Code skill that performs a comprehensive App Store and Google Play submission readiness audit on a Flutter project and produces a structured checklist report.&lt;/p&gt;
&lt;p&gt;Covers &lt;strong&gt;8 audit categories&lt;/strong&gt; — iOS Privacy Manifest, Android Data Safety, permissions, screenshots, age rating, metadata limits, and platform-specific configs — so you catch every rejection reason before Apple or Google does.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it audits&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ID&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;iOS Privacy Manifest (PrivacyInfo.xcprivacy)&lt;/td&gt;
&lt;td&gt;iOS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S2&lt;/td&gt;
&lt;td&gt;Android Data Safety section&lt;/td&gt;
&lt;td&gt;Android&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S3&lt;/td&gt;
&lt;td&gt;Permissions justification&lt;/td&gt;
&lt;td&gt;Both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S4&lt;/td&gt;
&lt;td&gt;Screenshots &amp;amp; store assets&lt;/td&gt;
&lt;td&gt;Both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S5&lt;/td&gt;
&lt;td&gt;Age rating &amp;amp; COPPA compliance&lt;/td&gt;
&lt;td&gt;Both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S6&lt;/td&gt;
&lt;td&gt;Metadata length limits&lt;/td&gt;
&lt;td&gt;Both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S7&lt;/td&gt;
&lt;td&gt;iOS-specific checks (Info.plist, signing, entitlements)&lt;/td&gt;
&lt;td&gt;iOS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S8&lt;/td&gt;
&lt;td&gt;Android-specific checks (targetSdk, signing, bundle, ProGuard)&lt;/td&gt;
&lt;td&gt;Android&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npx skills add NoaTubic/flutter-store-readiness-skill&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Or add it manually by copying &lt;code&gt;SKILL.md&lt;/code&gt; and &lt;code&gt;references/&lt;/code&gt; into your Claude Code skills directory.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Full audit (all 8 checks, both platforms)&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;/flutter-store-readiness
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Audit specific categories only&lt;/h3&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/NoaTubic/flutter-store-readiness-skill" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Feedback and issues are welcome on GitHub.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>claude</category>
      <category>security</category>
      <category>mobile</category>
    </item>
    <item>
      <title>A health check for your Flutter translations</title>
      <dc:creator>Noa Tubić</dc:creator>
      <pubDate>Wed, 14 Oct 2026 20:54:22 +0000</pubDate>
      <link>https://dev.to/noatubic/a-health-check-for-your-flutter-translations-2fm0</link>
      <guid>https://dev.to/noatubic/a-health-check-for-your-flutter-translations-2fm0</guid>
      <description>&lt;p&gt;Translation files only grow. Features get removed, screens get rewritten, but nobody deletes the old keys, because nobody is sure they are unused.&lt;/p&gt;

&lt;p&gt;I ran into this on a production Flutter app with about 2,900 Dart files and 2,400 translation keys. 365 keys were never used. 38 were missing in one of the languages. 247 strings in Dart code never went through localization at all. And 10 translations had invisible zero-width characters hiding in them.&lt;/p&gt;

&lt;p&gt;The analyzer doesn't report any of that, and neither does the code generator. So I wrote a small tool that does.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;l10n_health&lt;/code&gt; reads your &lt;code&gt;.arb&lt;/code&gt; files and your Dart code together and reports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keys that no code uses&lt;/li&gt;
&lt;li&gt;keys missing in one language, or identical to the template (likely never translated)&lt;/li&gt;
&lt;li&gt;placeholders that differ between languages, like &lt;code&gt;{name}&lt;/code&gt; vs &lt;code&gt;{userName}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;broken ICU messages and plural forms a language needs, like Croatian &lt;code&gt;few&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;hardcoded text that should be a translation, and the existing key to use when there is one&lt;/li&gt;
&lt;li&gt;invisible Unicode: zero-width spaces, bidi controls, look-alike spaces&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It works with Flutter's &lt;code&gt;gen_l10n&lt;/code&gt; and with &lt;code&gt;flutter_intl&lt;/code&gt;, and it finds your setup on its own. A full run on that big app takes about a quarter of a second.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dart pub global activate l10n_health
l10n_health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Or as a dev dependency, so the whole team runs the same version:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dart pub add &lt;span class="nt"&gt;--dev&lt;/span&gt; l10n_health
dart run l10n_health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is the output on the small example project in the repo:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Placeholder mismatches (placeholder_mismatch)  1
  error lib/l10n/app_de.arb:4:3  Placeholders in "greeting" differ from en: missing {name}, unexpected {userName}

Hardcoded strings (hardcoded_string)  2
  warn  lib/main.dart:22:9  Hardcoded text "Please select a pharmacy" bypasses localization (prose, en)
  warn  lib/main.dart:24:9  Hardcoded "Close"; use the existing key "close"

Unused keys (unused_key)  2
  warn  lib/l10n/app_en.arb:11:3  Key "legacyBanner" is never used in Dart code
  warn  lib/l10n/app_en.arb:12:3  Key "close" is never used in Dart code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For a report you can open in the browser or attach in CI:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;l10n_health &lt;span class="nt"&gt;--format&lt;/span&gt; html &lt;span class="nt"&gt;-o&lt;/span&gt; l10n.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Deleting keys without fear
&lt;/h2&gt;

&lt;p&gt;Finding dead keys is half the job. Removing them is where people get nervous, so &lt;code&gt;prune&lt;/code&gt; is careful:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;l10n_health prune          &lt;span class="c"&gt;# dry run, writes nothing&lt;/span&gt;
l10n_health prune &lt;span class="nt"&gt;--apply&lt;/span&gt;  &lt;span class="c"&gt;# removes the keys from every language&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It edits the files as text, so your diff is only the removed lines. Every file is checked to be valid JSON before anything is written. &lt;strong&gt;If a key also appears as a plain string somewhere&lt;/strong&gt;, it might be looked up dynamically, so it is held back and listed for you to review instead of deleted.&lt;/p&gt;

&lt;p&gt;After pruning, regenerate and build. If a key was still used, you find out at compile time, not from a user.&lt;/p&gt;
&lt;h2&gt;
  
  
  In CI
&lt;/h2&gt;

&lt;p&gt;Record the current state once, then fail only on new problems:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;l10n_health &lt;span class="nt"&gt;--update-baseline&lt;/span&gt;
l10n_health &lt;span class="nt"&gt;--fail-on&lt;/span&gt; warning
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;There are also &lt;code&gt;--format gitlab&lt;/code&gt; and &lt;code&gt;--format github&lt;/code&gt; outputs, so findings show up inline in merge requests and pull requests.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it on your project
&lt;/h2&gt;

&lt;p&gt;Run &lt;code&gt;l10n_health&lt;/code&gt; once in your app and look at the unused keys first. They're usually the quickest win. Everything else is documented here:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/NoaTubic" rel="noopener noreferrer"&gt;
        NoaTubic
      &lt;/a&gt; / &lt;a href="https://github.com/NoaTubic/l10n_health" rel="noopener noreferrer"&gt;
        l10n_health
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Health check for the translation layer of Dart and Flutter apps: unused and missing ARB keys, placeholder mismatches, hardcoded strings, invisible Unicode, safe prune.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;l10n_health&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A health check for the translation layer of Dart and Flutter apps.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;l10n_health&lt;/code&gt; reads your &lt;code&gt;.arb&lt;/code&gt; files and your Dart code together, and
reports what neither the analyzer nor the code generator will tell you
keys nobody uses, translations that are missing or were never translated
placeholders that differ between languages, text that bypasses localization
entirely, and invisible characters hiding in your strings. It can then prune
the dead keys for you, safely.&lt;/p&gt;
&lt;p&gt;It works with Flutter's built-in &lt;code&gt;gen_l10n&lt;/code&gt;, with &lt;code&gt;flutter_intl&lt;/code&gt; /
&lt;code&gt;intl_utils&lt;/code&gt;, and with any other ARB-based setup. Configuration is
optional; the setup is detected from &lt;code&gt;l10n.yaml&lt;/code&gt; or &lt;code&gt;pubspec.yaml&lt;/code&gt;.&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;$ l10n_health
l10n_health  flutter_intl · template de · 2439 keys · 2931 Dart files · strict usage · 235 ms
  Locales   de 2434  en 2406

Placeholder mismatches (placeholder_mismatch)  2
  error lib/l10n/intl_en.arb:313:3  Placeholders in "hint1" differ from de: missing {day} {month} {year}, unexpected {DateTime}
  ...
Malformed ICU&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/NoaTubic/l10n_health" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://pub.dev/packages/l10n_health" rel="noopener noreferrer"&gt;https://pub.dev/packages/l10n_health&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feedback and issues are welcome on GitHub.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>opensource</category>
      <category>i18n</category>
    </item>
    <item>
      <title>Write your Flutter lint rules in YAML</title>
      <dc:creator>Noa Tubić</dc:creator>
      <pubDate>Wed, 14 Oct 2026 20:52:03 +0000</pubDate>
      <link>https://dev.to/noatubic/write-your-flutter-lint-rules-in-yaml-1hf3</link>
      <guid>https://dev.to/noatubic/write-your-flutter-lint-rules-in-yaml-1hf3</guid>
      <description>&lt;p&gt;Every codebase has house rules. Use the design tokens, not &lt;code&gt;Colors.grey&lt;/code&gt;. Don't let &lt;code&gt;domain/&lt;/code&gt; import &lt;code&gt;presentation/&lt;/code&gt;. Never call &lt;code&gt;ref.watch&lt;/code&gt; from a button handler.&lt;/p&gt;

&lt;p&gt;They live in a style guide or a wiki page, and nothing checks them. So they drift. The worst kind is a design token typo: &lt;code&gt;'Text/bdy'&lt;/code&gt; instead of &lt;code&gt;'Text/body'&lt;/code&gt;. Token lookups usually fall back to a default color, so nothing fails. The diff looks fine in review, and a pink widget ships.&lt;/p&gt;

&lt;p&gt;The Dart analyzer can't express rules like these. The usual answer is a custom analyzer plugin: a second package, Dart code per rule, and upkeep every time the analyzer changes. For a handful of project conventions, that felt like too much. So I wrote &lt;code&gt;house_rules&lt;/code&gt;, where a rule is a few lines of YAML.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;avoid_palette_colors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;error&lt;/span&gt;
    &lt;span class="na"&gt;exclude&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;lib/theme/**&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;palette&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Colors&lt;/span&gt;
    &lt;span class="na"&gt;allowed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;transparent&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;white&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's the whole rule. It ships with ten of them, all configured from one &lt;code&gt;house_rules.yaml&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;known_design_tokens&lt;/code&gt;: every token you look up by string must exist&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;layer_boundaries&lt;/code&gt;: which folders may import which&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;no_watch_in_callback&lt;/code&gt;: no &lt;code&gt;ref.watch&lt;/code&gt; inside handlers and listeners&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;no_widget_returning_function&lt;/code&gt; and &lt;code&gt;no_method_in_build&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;avoid_palette_colors&lt;/code&gt;, &lt;code&gt;avoid_raw_colors&lt;/code&gt;, &lt;code&gt;avoid_inline_text_styles&lt;/code&gt; and &lt;code&gt;magic_spacing&lt;/code&gt; for design system discipline&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unjustified_ignore&lt;/code&gt;: a suppression comment has to say why&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dart pub global activate house_rules
house_rules init              &lt;span class="c"&gt;# writes a starter house_rules.yaml&lt;/span&gt;
house_rules                   &lt;span class="c"&gt;# see what it finds&lt;/span&gt;
house_rules &lt;span class="nt"&gt;--update-baseline&lt;/span&gt; &lt;span class="c"&gt;# accept what is already there&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;After the baseline, only new problems fail. It needs Dart 3.11 or newer.&lt;/p&gt;
&lt;h2&gt;
  
  
  What a finding looks like
&lt;/h2&gt;

&lt;p&gt;This is the output on the example project in the repo:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lib/features/cart/presentation/widgets/cart_row.dart
  12:12    ERROR   avoid_palette_colors
           `Colors.grey` bypasses the design tokens

  21:48    ERROR   known_design_tokens
           'Text/bdy' is not a defined design token
           did you mean:
             'Text/body'
             'Text/headline'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Each finding says why the rule exists and, where it can, what you probably meant. &lt;code&gt;known_design_tokens&lt;/code&gt; reads your token map, so it knows the real names and catches the typo before it ships.&lt;/p&gt;
&lt;h2&gt;
  
  
  Fast enough to run on every save
&lt;/h2&gt;

&lt;p&gt;It only parses files and never resolves types. That is a real limit, but it keeps a scan cheap. On a 2,937-file Flutter app with all ten rules, the compiled binary checks the whole project in 0.285 seconds, and a single file in 0.08 seconds.&lt;/p&gt;

&lt;p&gt;That makes it practical as an editor or agent hook:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;house_rules &lt;span class="nt"&gt;--hook&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CHANGED_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It stays silent when the file is clean and exits with code 2 when it isn't. If the linter itself crashes, it exits 0. &lt;strong&gt;A broken linter should never be the reason someone can't commit.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Adopting it on an old codebase
&lt;/h2&gt;

&lt;p&gt;Turning on a new rule in an existing project usually means hundreds of findings on day one. &lt;code&gt;--update-baseline&lt;/code&gt; records them, so CI stays green and only new violations fail.&lt;/p&gt;

&lt;p&gt;The baseline is keyed by a hash of the offending code, not by line numbers, so it survives &lt;code&gt;dart format&lt;/code&gt; and unrelated edits. For rules where every finding is a real bug, set &lt;code&gt;baseline: false&lt;/code&gt; and they can never be hidden.&lt;/p&gt;

&lt;p&gt;There are also Markdown, HTML, JSON and GitLab Code Quality reports, and a &lt;code&gt;--check-docs&lt;/code&gt; mode that fails when your written guidelines and your config disagree.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it on your project
&lt;/h2&gt;

&lt;p&gt;Run &lt;code&gt;house_rules init&lt;/code&gt;, keep the two or three rules you already agree on, and baseline the rest. You can tighten it later. The code and the docs for every rule are here:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/NoaTubic" rel="noopener noreferrer"&gt;
        NoaTubic
      &lt;/a&gt; / &lt;a href="https://github.com/NoaTubic/house_rules" rel="noopener noreferrer"&gt;
        house_rules
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Write your project's lint rules in YAML instead of building a custom_lint plugin. Design tokens, layering, widget and state-management rules.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;house_rules&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Write your project's lint rules in YAML.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every codebase has house rules. &lt;em&gt;Use the design tokens, not &lt;code&gt;Colors.grey&lt;/code&gt;
Don't let &lt;code&gt;domain/&lt;/code&gt; import &lt;code&gt;presentation/&lt;/code&gt;. Never call &lt;code&gt;ref.watch&lt;/code&gt; from a
button handler.&lt;/em&gt; They end up in a style guide, a wiki page, a &lt;code&gt;CONTRIBUTING.md&lt;/code&gt;
— and nothing checks any of them, so they drift.&lt;/p&gt;
&lt;p&gt;The Dart analyzer can't express rules like these. The usual answer is to
write a &lt;a href="https://pub.dev/packages/custom_lint" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;custom_lint&lt;/code&gt;&lt;/a&gt; plugin, which
means a second package, Dart code per rule, and a plugin that has to be kept
in step with the analyzer. For a handful of project conventions, that is a lot
of ceremony.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;house_rules&lt;/code&gt; gives you the same enforcement as four lines of YAML:&lt;/p&gt;
&lt;div class="highlight highlight-source-yaml notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-ent"&gt;rules&lt;/span&gt;:
  &lt;span class="pl-ent"&gt;avoid_palette_colors&lt;/span&gt;:
    &lt;span class="pl-ent"&gt;severity&lt;/span&gt;: &lt;span class="pl-s"&gt;error&lt;/span&gt;
    &lt;span class="pl-ent"&gt;exclude&lt;/span&gt;: &lt;span class="pl-s"&gt;[lib/theme/**]&lt;/span&gt;
    &lt;span class="pl-ent"&gt;palette&lt;/span&gt;: &lt;span class="pl-s"&gt;Colors&lt;/span&gt;
    &lt;span class="pl-ent"&gt;allowed&lt;/span&gt;: &lt;span class="pl-s"&gt;[transparent, white]&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;lib/features/cart/widget.dart
  88:14  ERROR  avoid_palette_colors
         `Colors.grey` bypasses the design tokens
         A literal palette colour will not follow&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/NoaTubic/house_rules" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://pub.dev/packages/house_rules" rel="noopener noreferrer"&gt;https://pub.dev/packages/house_rules&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feedback and issues are welcome on GitHub.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>opensource</category>
      <category>codequality</category>
    </item>
    <item>
      <title>Block rooted and jailbroken devices in Flutter</title>
      <dc:creator>Noa Tubić</dc:creator>
      <pubDate>Wed, 14 Oct 2026 20:51:15 +0000</pubDate>
      <link>https://dev.to/noatubic/block-rooted-and-jailbroken-devices-in-flutter-gh6</link>
      <guid>https://dev.to/noatubic/block-rooted-and-jailbroken-devices-in-flutter-gh6</guid>
      <description>&lt;p&gt;If your app handles payments, health data, or anything else worth stealing, a rooted phone is a problem. Root access lets other apps read your storage, hook your code, and change what your app sees.&lt;/p&gt;

&lt;p&gt;Most teams agree they should check for this. Then the check ends up scattered across &lt;code&gt;main()&lt;/code&gt;, a splash screen, and a few platform channels, with no clear rule for when to block and when to just log.&lt;/p&gt;

&lt;p&gt;I wanted one place for it. So I wrote &lt;code&gt;device_integrity_guard&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;It runs a set of checks before &lt;code&gt;runApp&lt;/code&gt;, and then either starts your app or shows a block screen.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Root on Android: known &lt;code&gt;su&lt;/code&gt; binaries, root apps, and a writable system folder&lt;/li&gt;
&lt;li&gt;Jailbreak on iOS: known jailbreak files and write access outside the app sandbox&lt;/li&gt;
&lt;li&gt;Emulators, from the device info Android and iOS report&lt;/li&gt;
&lt;li&gt;An attached debugger&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A policy decides what each signal means. You can block it, only log it, or allow it, and you can switch all of it off in debug builds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter pub add device_integrity_guard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then wrap your app in &lt;code&gt;main()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;guard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DeviceIntegrityGuard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;policy:&lt;/span&gt; &lt;span class="n"&gt;DeviceIntegrityPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;allowDebugMode:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;kReleaseMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;blockOnCompromise:&lt;/span&gt; &lt;span class="n"&gt;kReleaseMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nl"&gt;onViolation:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&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;debugPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;${result.blockedSignals}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;runGuardedApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;app:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nl"&gt;guard:&lt;/span&gt; &lt;span class="n"&gt;guard&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;That's the whole setup. In a release build, a rooted or jailbroken device gets the block screen instead of your app. In debug, the checks are skipped, so you can still work on an emulator.&lt;/p&gt;
&lt;h2&gt;
  
  
  Block, log, or allow
&lt;/h2&gt;

&lt;p&gt;The part I use most is the policy. Not every signal deserves the same answer.&lt;/p&gt;

&lt;p&gt;A rooted phone in production is usually a block. An emulator in your QA build is expected. A debugger attached to a release build is worth knowing about, even if you don't block it yet.&lt;/p&gt;

&lt;p&gt;So each detector can be switched on or off, each kind of signal can be allowed or not, and &lt;code&gt;onViolation&lt;/code&gt; hands you the full result. That includes every signal, the reason for each one, and how long the scan took. Send it to your analytics first, see what real users trigger, and only then turn on blocking.&lt;/p&gt;
&lt;h2&gt;
  
  
  Your own screen and your own checks
&lt;/h2&gt;

&lt;p&gt;The default block screen shows a short English message and the list of what was found. You can pass your own widget instead, or a builder that receives the summary of what was found:&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;runGuardedApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;app:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nl"&gt;guard:&lt;/span&gt; &lt;span class="n"&gt;guard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;blockedBuilder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary&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;MyBlockedScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;summary:&lt;/span&gt; &lt;span class="n"&gt;summary&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;Checks are pluggable too. Implement &lt;code&gt;IntegritySignalDetector&lt;/code&gt; for anything specific to your app, like an APK signature check, and add it next to the built-in ones.&lt;/p&gt;
&lt;h2&gt;
  
  
  What it can't do
&lt;/h2&gt;

&lt;p&gt;These checks run on the device, so a determined attacker can hide from them. &lt;strong&gt;Treat this as your first line, not your only one.&lt;/strong&gt; For anything that really matters, pair it with server-side attestation: Play Integrity on Android and App Attest on iOS. The guard keeps casual tampering and test devices out of production. Attestation lets your backend refuse requests from a device it can't trust.&lt;/p&gt;

&lt;p&gt;One iOS detail: the jailbreak check also asks whether the &lt;code&gt;cydia://&lt;/code&gt; URL scheme can be opened. iOS only answers that if &lt;code&gt;cydia&lt;/code&gt; is listed under &lt;code&gt;LSApplicationQueriesSchemes&lt;/code&gt; in your &lt;code&gt;Info.plist&lt;/code&gt;, so add it there.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;Add the package, ship it with blocking off and logging on for one release, and look at what your users actually trigger. The example app and the full API are here:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/NoaTubic" rel="noopener noreferrer"&gt;
        NoaTubic
      &lt;/a&gt; / &lt;a href="https://github.com/NoaTubic/device_integrity_guard" rel="noopener noreferrer"&gt;
        device_integrity_guard
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Reusable toolkit that lets you stop a Flutter application before it boots when the runtime environment looks unsafe. It bundles common integrity detectors (emulator, root, jailbreak, debugger) and gives you a cohesive policy + UI surface to decide whether suspicious signals should block the UX, simply be logged, or be ignored in specific builds.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Device Integrity Guard&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;code&gt;device_integrity_guard&lt;/code&gt; is a reusable toolkit that lets you stop a Flutter application before it boots when the runtime environment looks unsafe. It bundles common integrity detectors (emulator, root, jailbreak, debugger) and gives you a cohesive policy + UI surface to decide whether suspicious signals should block the UX, simply be logged, or be ignored in specific builds.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Highlights&lt;/h3&gt;
&lt;/div&gt;


&lt;ul&gt;

&lt;li&gt;🔐 &lt;strong&gt;Configurable policy&lt;/strong&gt; – Toggle each detector, decide if emulators/root/jailbreak/debuggers are acceptable, and bypass checks entirely in debug builds.&lt;/li&gt;

&lt;li&gt;🧪 &lt;strong&gt;Swappable detectors&lt;/strong&gt; – Implement the &lt;code&gt;IntegritySignalDetector&lt;/code&gt; interface to plug in custom probes (e.g., corporate MDM, binary signatures). Default detectors live under &lt;code&gt;src/detection/&lt;/code&gt;.&lt;/li&gt;

&lt;li&gt;🧭 &lt;strong&gt;Actionable results&lt;/strong&gt; – &lt;code&gt;DeviceIntegrityGuard.checkAndMaybeBlock()&lt;/code&gt; returns a &lt;code&gt;DeviceIntegrityResult&lt;/code&gt; that lists all &lt;code&gt;IntegritySignal&lt;/code&gt;s plus how long scanning took.&lt;/li&gt;

&lt;li&gt;🖥️ &lt;strong&gt;Built‑in blocking UI&lt;/strong&gt; – &lt;code&gt;CompromisedEnvironmentScreen&lt;/code&gt; ships with sensible English copy and can be replaced with any widget at runtime.&lt;/li&gt;

&lt;li&gt;🚀 &lt;strong&gt;Drop-in bootstrap helper&lt;/strong&gt; – &lt;code&gt;runGuardedApp&lt;/code&gt; runs the…&lt;/li&gt;

&lt;/ul&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/NoaTubic/device_integrity_guard" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;a href="https://pub.dev/packages/device_integrity_guard" rel="noopener noreferrer"&gt;https://pub.dev/packages/device_integrity_guard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feedback and issues are welcome on GitHub.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>security</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>A Riverpod 3 architecture for Flutter apps</title>
      <dc:creator>Noa Tubić</dc:creator>
      <pubDate>Wed, 14 Oct 2026 20:50:13 +0000</pubDate>
      <link>https://dev.to/noatubic/a-riverpod-3-architecture-for-flutter-apps-6ak</link>
      <guid>https://dev.to/noatubic/a-riverpod-3-architecture-for-flutter-apps-6ak</guid>
      <description>&lt;p&gt;Riverpod tells you how to hold state. It doesn't tell you how a feature should be put together. So every project, and often every developer, ends up with a different answer.&lt;/p&gt;

&lt;p&gt;Almost every screen still does the same thing. Show a spinner, call the API, then show the data or an error. Without a shared structure, every notifier writes that again, slightly differently each time.&lt;/p&gt;

&lt;p&gt;After a few projects I stopped reinventing it. &lt;code&gt;riverpod_architecture&lt;/code&gt; is the structure I now start every Flutter app with.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;Each feature follows the same path: a repository returns either a failure or the data, a notifier turns that into state, and the screen renders the state. The package gives you the pieces for each layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mappers that turn API responses into your own models, and forms into requests&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BaseNotifier&lt;/code&gt;: one &lt;code&gt;execute()&lt;/code&gt; call sets loading, then data or error&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PaginatedNotifier&lt;/code&gt;: infinite lists with next page, refresh, and errors that keep the items already loaded&lt;/li&gt;
&lt;li&gt;Global loading, failure, and info messages for the whole app&lt;/li&gt;
&lt;li&gt;Sealed states, so the compiler checks you handled every case&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's built on Riverpod 3 and uses &lt;code&gt;Either&lt;/code&gt; for results, so a failure is a value you return, not an exception you hope someone catches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter pub add riverpod_architecture flutter_riverpod either_dart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Your repository returns either a failure or the 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;EitherFailureOr&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="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;getUser&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;id&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="n"&gt;Right&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;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="n"&gt;id&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;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stackTrace&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;Left&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="s"&gt;'Could not load user'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;error:&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;stackTrace:&lt;/span&gt; &lt;span class="n"&gt;stackTrace&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The notifier is a few lines:&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;UserNotifier&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;AutoDisposeBaseNotifier&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="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;prepareForBuild&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fetchUser&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;id&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;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userRepositoryProvider&lt;/span&gt;&lt;span class="p"&gt;)&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="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nl"&gt;globalFailure:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// show errors on this screen&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 the screen switches over the state:&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="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userNotifierProvider&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;BaseInitial&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;BaseLoading&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;CircularProgressIndicator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;BaseError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;failure&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;BaseData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;data&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;UserCard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;user:&lt;/span&gt; &lt;span class="n"&gt;data&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;Forget a case and it won't compile.&lt;/p&gt;
&lt;h2&gt;
  
  
  One call, many behaviors
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;execute()&lt;/code&gt; takes a few switches for the cases that come up again and again:&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;saveProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;withLoadingState:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// keep the form on screen&lt;/span&gt;
  &lt;span class="nl"&gt;globalLoading:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// dim the whole app instead&lt;/span&gt;
  &lt;span class="nl"&gt;globalFailure:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// show the error as a snackbar&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Saving a form is the classic example. You don't want the form replaced by a spinner. You want an overlay, and an error message that leaves the user's input alone. That's one call instead of a page of state handling.&lt;/p&gt;

&lt;p&gt;By default, failures go to a single &lt;code&gt;BaseWidget&lt;/code&gt; near the top of your app, so you decide once how errors look. Pass &lt;code&gt;globalFailure: false&lt;/code&gt; when a screen should show its own error, like the user screen above.&lt;/p&gt;
&lt;h2&gt;
  
  
  Pagination without the edge cases
&lt;/h2&gt;

&lt;p&gt;Paginated lists are where hand-written notifiers usually break. You load page 2 twice, or an error wipes out the 40 items already on screen.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PaginatedNotifier&lt;/code&gt; handles that. You write one method that loads a page:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="nd"&gt;@override&lt;/span&gt;
&lt;span class="n"&gt;PaginatedEitherFailureOr&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="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;getListOrFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;parameter&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;repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;page:&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;The notifier tracks the page, ignores duplicate "load more" calls, and keeps the loaded items when a page fails.&lt;/strong&gt; Your screen calls &lt;code&gt;getNextPage()&lt;/code&gt; when the user nears the end, and &lt;code&gt;refresh()&lt;/code&gt; for pull to refresh.&lt;/p&gt;
&lt;h2&gt;
  
  
  When you don't need it
&lt;/h2&gt;

&lt;p&gt;For a small app with two or three screens, Riverpod's own &lt;code&gt;AsyncNotifier&lt;/code&gt; and &lt;code&gt;AsyncValue&lt;/code&gt; are enough. Adding base classes there is more structure than the app needs.&lt;/p&gt;

&lt;p&gt;It pays off when you have dozens of screens and want them to load, fail, and paginate the same way. It also helps when a new teammate should be able to open any notifier and know where everything is.&lt;/p&gt;

&lt;p&gt;The package started as a Riverpod 3 port of Q Agency's Q-Architecture, and the credit for the original design is theirs.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;Pick one screen that loads data and move its notifier to &lt;code&gt;BaseNotifier&lt;/code&gt;. If it feels lighter, do the next one. The example app has a single user screen and a paginated list:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/NoaTubic" rel="noopener noreferrer"&gt;
        NoaTubic
      &lt;/a&gt; / &lt;a href="https://github.com/NoaTubic/riverpod_architecture" rel="noopener noreferrer"&gt;
        riverpod_architecture
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A Flutter state management architecture built on Riverpod 3.0, providing reusable notifier classes and utilities for clean, scalable applications. This package reduces boilerplate by providing standardized patterns for handling state, errors, pagination, and data mapping.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Riverpod Architecture&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://pub.dev/packages/riverpod_architecture" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/cf082996620a5ca98be6764640d356753035abfb0db814205c684edd1b29c1f6/68747470733a2f2f696d672e736869656c64732e696f2f7075622f762f7269766572706f645f617263686974656374757265" alt="Pub Version"&gt;&lt;/a&gt;
&lt;a href="https://github.com/NoaTubic/riverpod_architecture/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667" alt="License"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A Flutter state management architecture built on &lt;strong&gt;Riverpod 3.x&lt;/strong&gt;, providing reusable notifier classes and utilities for clean, scalable applications. This package reduces boilerplate by providing standardized patterns for handling state, errors, pagination, and data mapping.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Modern Riverpod 3.x&lt;/strong&gt;: Fully migrated to latest Riverpod with &lt;code&gt;Notifier&lt;/code&gt; base classes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State Management&lt;/strong&gt;: &lt;code&gt;BaseNotifier&lt;/code&gt; for handling initial/loading/error/data states&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pagination&lt;/strong&gt;: Built-in &lt;code&gt;PaginatedNotifier&lt;/code&gt; with infinite scroll support&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Handling&lt;/strong&gt;: Functional error handling with &lt;code&gt;Either&amp;lt;Failure, T&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global Providers&lt;/strong&gt;: App-wide loading, failure, and info notifications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Mapping&lt;/strong&gt;: Mappers for API responses, forms, and requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type Safety&lt;/strong&gt;: Sealed classes with pattern matching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto Dispose&lt;/strong&gt;: Memory-efficient with auto-dispose variants&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Add to your &lt;code&gt;pubspec.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight highlight-source-yaml notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-ent"&gt;dependencies&lt;/span&gt;:
  &lt;span class="pl-ent"&gt;riverpod_architecture&lt;/span&gt;: &lt;span class="pl-s"&gt;^1.0.6&lt;/span&gt;
  &lt;span class="pl-ent"&gt;flutter_riverpod&lt;/span&gt;: &lt;span class="pl-s"&gt;^3.2.1&lt;/span&gt;
  &lt;span class="pl-ent"&gt;either_dart&lt;/span&gt;: &lt;span class="pl-s"&gt;^1.0.0&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Then run:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;flutter pub get&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick Start&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;1. Add &lt;code&gt;BaseWidget&lt;/code&gt; through &lt;code&gt;MaterialApp.builder&lt;/code&gt;
&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;BaseWidget&lt;/code&gt; shows the global loading overlay and reports global failures…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/NoaTubic/riverpod_architecture" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://pub.dev/packages/riverpod_architecture" rel="noopener noreferrer"&gt;https://pub.dev/packages/riverpod_architecture&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feedback and issues are welcome on GitHub.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>riverpod</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
