<?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: mavoryl</title>
    <description>The latest articles on DEV Community by mavoryl (@mavoryl).</description>
    <link>https://dev.to/mavoryl</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%2F3904420%2Fff629049-c6d6-4895-a48a-deeb252e87ff.png</url>
      <title>DEV Community: mavoryl</title>
      <link>https://dev.to/mavoryl</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mavoryl"/>
    <language>en</language>
    <item>
      <title>I scanned 10 mobile codebases for health issues — here's what I found</title>
      <dc:creator>mavoryl</dc:creator>
      <pubDate>Fri, 08 May 2026 12:23:48 +0000</pubDate>
      <link>https://dev.to/mavoryl/i-scanned-10-mobile-codebases-for-health-issues-heres-what-i-found-3d4k</link>
      <guid>https://dev.to/mavoryl/i-scanned-10-mobile-codebases-for-health-issues-heres-what-i-found-3d4k</guid>
      <description>&lt;p&gt;Every mobile repo I worked on had similar problems: very large PNG images, unused asset files that nobody removed, secret files added to git years ago, gradle files that changed from default settings. So I made a small CLI that scans a mobile repo and gives it a 0–100 health score in four areas (size, speed, stability, hygiene) with 50 checks.&lt;/p&gt;

&lt;p&gt;To test the rules on real projects, I ran it on ten codebases I had on my laptop. Seven are well-known open-source projects. Three are from my own work and stay anonymous below — I will only describe them by stack ("a Flutter app", "an Android app").&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx mobile-repo-doctor scan ./your-repo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Everything runs locally.&lt;/strong&gt; Your code, file paths, and scan results never leave your machine. No account, no login, no telemetry. The CLI is one npm package. If you don't trust me, the npm bundle is a single file you can read.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dataset
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Public (7)&lt;/strong&gt;: AppFlowy, wikipedia-ios, compose-multiplatform, iCarousel, mundraub-android, golden_matrix, auto-dev.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Private (3, anonymous)&lt;/strong&gt;: two Flutter apps, one Android app.&lt;/p&gt;

&lt;p&gt;By stack: 3 Flutter, 2 Android, 2 iOS, 2 KMP, 1 multi-stack (AppFlowy).&lt;/p&gt;

&lt;h2&gt;
  
  
  What the numbers said
&lt;/h2&gt;

&lt;p&gt;The middle score across the ten repos was 95/100. This surprised me — I expected lower scores. But two repos scored below 60. The worst was 52.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Median&lt;/th&gt;
&lt;th&gt;Worst&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Overall score&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Size&lt;/td&gt;
&lt;td&gt;91&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speed&lt;/td&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;83&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stability&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;71&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hygiene&lt;/td&gt;
&lt;td&gt;93&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Total findings across all ten repos: 327. Most are &lt;code&gt;Info&lt;/code&gt; (148) — these are summaries like "here are your top size contributors", not real problems. The rest: 3 Critical, 17 High, 90 Medium, 69 Low.&lt;/p&gt;

&lt;p&gt;I expected stability to be the worst area — security flags, low SDK targets, manifest problems. It was not. People now know these rules well. I saw almost no &lt;code&gt;usesCleartextTraffic="true"&lt;/code&gt;, almost no &lt;code&gt;NSAllowsArbitraryLoads&lt;/code&gt;, almost nobody using a very old API level. The areas where modern repos get lower scores are &lt;strong&gt;size and hygiene&lt;/strong&gt; — boring things nobody checks in CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  The most common findings were not security issues
&lt;/h2&gt;

&lt;p&gt;The top of the list, sorted by how many of the ten repos had each finding:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Repos affected&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Top size contributors (info)&lt;/td&gt;
&lt;td&gt;10/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Module/package inventory (info)&lt;/td&gt;
&lt;td&gt;9/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate asset files&lt;/td&gt;
&lt;td&gt;8/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebP candidates (PNGs that should be WebP)&lt;/td&gt;
&lt;td&gt;8/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oversized images&lt;/td&gt;
&lt;td&gt;7/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unoptimized PNGs&lt;/td&gt;
&lt;td&gt;7/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sensitive files in repo&lt;/td&gt;
&lt;td&gt;6/10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Eight of ten repos have PNG assets large enough to benefit from WebP.&lt;/strong&gt; This is a simple fix — one &lt;code&gt;cwebp -q 80&lt;/code&gt; command and you save real size. compose-multiplatform alone has about 7 MB of PNGs that could be WebP. AppFlowy has another 2.4 MB. One of the private Flutter apps has more than 10 MB. None of these are bugs. Nobody just did it yet.&lt;/p&gt;

&lt;p&gt;The same with duplicate assets between sub-packages. Half of the multi-package repos had the same logo, icon, or splash image copied across four to six modules. wikipedia-ios has about 3.4 MB of duplicates. Nobody runs &lt;code&gt;find&lt;/code&gt; on these files.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "secrets" finding needs more explanation
&lt;/h2&gt;

&lt;p&gt;Six of ten repos had a sensitive-file finding. But I want to be honest about what this means. Most are Tier-2 hits: &lt;code&gt;google-services.json&lt;/code&gt;, &lt;code&gt;GoogleService-Info.plist&lt;/code&gt;, &lt;code&gt;debug.keystore&lt;/code&gt;. These files are &lt;em&gt;usually&lt;/em&gt; safe to commit (Firebase configs are project-scoped, debug keystores are not release credentials). The check marks them as Low/ReviewNeeded for a reason — they need a careful decision, not an automatic reaction.&lt;/p&gt;

&lt;p&gt;The honest number: &lt;strong&gt;two of ten repos (20%) had Critical-level sensitive files&lt;/strong&gt; — &lt;code&gt;.env&lt;/code&gt; files in tooling subdirectories of two open-source projects. One has been there for years.&lt;/p&gt;

&lt;p&gt;I also found one wrong finding: a &lt;code&gt;_pub.pem&lt;/code&gt; file marked as sensitive because it ends in &lt;code&gt;.pem&lt;/code&gt;. It is a public key — safe to commit. Fix coming soon.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few things that surprised me
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The number of findings does not show health well.&lt;/strong&gt; The repo with the most findings (69) was iCarousel — an old, well-maintained iOS library. Almost all of its findings are info-level summaries, not problems. It scored 97/A. Meanwhile a Flutter repo with 61 findings scored 58/C. The mix of severities matters more than the count.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Even AppFlowy has image problems.&lt;/strong&gt; AppFlowy is a popular OSS project with many contributors. They have ~7 MB of oversized images, 6 MB of unoptimized PNGs, 2.4 MB of WebP candidates. Nobody is bad here — this is what happens to an old project when no one takes care of asset hygiene.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;KMP repos look good.&lt;/strong&gt; auto-dev and compose-multiplatform scored 97 and 78. Only 2 repos so I cannot make strong conclusions, but the KMP ecosystem is young and the asset pipelines are usually clean. I need more samples.&lt;/p&gt;

&lt;h2&gt;
  
  
  The biggest concrete wins were boring
&lt;/h2&gt;

&lt;p&gt;If I had to give each public repo one one-day task:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AppFlowy&lt;/strong&gt;: compress those cover images — ~7 MB of oversized PNGs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;wikipedia-ios&lt;/strong&gt;: remove duplicate assets across packages — ~3.4 MB of duplicates plus ~12 MB of unoptimized PNGs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;compose-multiplatform&lt;/strong&gt;: oversized images add up to ~26 MB. Some are documentation samples, but worth checking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iCarousel&lt;/strong&gt;: really fine. Nothing to do here.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the private apps: the Android one was clean (97/A, 12 findings). The two Flutter apps split — one was almost perfect (94/A), the other had ~80 MB of total reducible size across oversized + unoptimized + duplicate images. That is not a typo. It is also the one that scored 58/C.&lt;/p&gt;

&lt;h2&gt;
  
  
  What else it checks
&lt;/h2&gt;

&lt;p&gt;This post focused on what showed up most in this scan, but the rule set covers more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Android&lt;/strong&gt;: low &lt;code&gt;targetSdk&lt;/code&gt;/&lt;code&gt;minSdk&lt;/code&gt;, debuggable release builds, missing ProGuard rules, missing minification, dependency hygiene (dynamic versions, SNAPSHOT deps, legacy support libs, mixed kapt/ksp), missing ABI splits, APK instead of AAB, native libs forced to extract on install.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flutter&lt;/strong&gt;: unused assets in pubspec, missing assets that &lt;em&gt;are&lt;/em&gt; in pubspec, heavy dependencies (Lottie/SVG runtime weight), &lt;code&gt;dependency_overrides&lt;/code&gt; in production, vendored packages, oversized SVGs, missing split-debug-info, large workspaces with too many local packages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iOS&lt;/strong&gt;: arbitrary network loads in Info.plist, hardcoded user paths in pbxproj, unversioned pods, low deploy target, too many disabled SwiftLint rules, bitcode enabled (deprecated), oversized resources outside sticker packs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-platform / hygiene&lt;/strong&gt;: missing lockfiles (gradle wrapper, podfile.lock, pubspec.lock), deeply nested directory trees, large generated files committed by accident, sensitive files (with the Tier-1/Tier-2 split above).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;71 checks total across all stacks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it on your own repo
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx mobile-repo-doctor scan &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or as a GitHub Action (&lt;a href="https://github.com/marketplace/actions/mobile-repo-doctor" rel="noopener noreferrer"&gt;Marketplace listing&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Mavoryl/mobile-repo-doctor-action@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;scan-path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get HTML + JSON output, a health score, top issues, quick wins, and file paths for each finding. 50 checks for Android / Flutter / iOS / KMP / cross-platform. Runs locally, no telemetry, no account.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;p&gt;Ten repos is a small sample. Per-stack medians (especially KMP at n=2) are noisy — treat them as informal. The dataset is mostly already-maintained projects, so scores are higher than a random sample would be. And the &lt;code&gt;_pub.pem&lt;/code&gt; wrong finding is a reminder: even rules that are 95% correct need a way to suppress them, which is on the roadmap.&lt;/p&gt;

&lt;p&gt;npm: &lt;a href="https://www.npmjs.com/package/mobile-repo-doctor" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/mobile-repo-doctor&lt;/a&gt;&lt;br&gt;
GitHub Marketplace: &lt;a href="https://github.com/marketplace/actions/mobile-repo-doctor" rel="noopener noreferrer"&gt;https://github.com/marketplace/actions/mobile-repo-doctor&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you run it on your own repo, I would love to hear what you found — both real findings and wrong findings. The wrong ones are how the rules get better.&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
      <category>flutter</category>
    </item>
    <item>
      <title>A simpler way to write golden tests in Flutter — golden_matrix</title>
      <dc:creator>mavoryl</dc:creator>
      <pubDate>Tue, 05 May 2026 10:53:03 +0000</pubDate>
      <link>https://dev.to/mavoryl/a-simpler-way-to-write-golden-tests-in-flutter-goldenmatrix-2e8h</link>
      <guid>https://dev.to/mavoryl/a-simpler-way-to-write-golden-tests-in-flutter-goldenmatrix-2e8h</guid>
      <description>&lt;p&gt;I want to share a Flutter package I made. It is called &lt;code&gt;golden_matrix&lt;/code&gt;. It helps you write golden tests when your app has many themes, locales, and devices.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;My app has 5 locales, light and dark themes, and many device sizes. I also need to test different text scales for accessibility.&lt;/p&gt;

&lt;p&gt;If I want to test one button, I need many golden tests:&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;testGoldens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'button - en - light - small phone'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
&lt;span class="n"&gt;testGoldens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'button - en - dark - small phone'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
&lt;span class="n"&gt;testGoldens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'button - ru - light - small phone'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
&lt;span class="n"&gt;testGoldens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'button - ru - dark - small phone'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
&lt;span class="c1"&gt;// ...30 more tests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is too much copy-paste. Most people give up and only test one combination. Then bugs come from the combinations they did not test.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;golden_matrix&lt;/code&gt;, you write one 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;matrixGolden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;'MyButton'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;scenarios:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;MatrixScenario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'default'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;builder:&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;MyButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;label:&lt;/span&gt; &lt;span class="s"&gt;'OK'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;MatrixScenario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'disabled'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;builder:&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;MyButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;label:&lt;/span&gt; &lt;span class="s"&gt;'OK'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;enabled:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nl"&gt;axes:&lt;/span&gt; &lt;span class="n"&gt;MatrixAxes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;themes:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MatrixTheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;light&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MatrixTheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nl"&gt;locales:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Locale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'en'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Locale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'ru'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Locale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'ar'&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
    &lt;span class="nl"&gt;textScales:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nl"&gt;devices:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MatrixDevice&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;phoneSmall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MatrixDevice&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tablet&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes 48 golden files. One declaration. No loops.&lt;/p&gt;

&lt;p&gt;The package knows that Arabic is RTL, so it sets that for you. Each file has a clear name like &lt;code&gt;goldens/default/dark_ar_rtl_2x_tablet.png&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if 48 tests is too many?
&lt;/h2&gt;

&lt;p&gt;You can use smaller test sets:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smoke&lt;/strong&gt; — only a few key combinations:&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;matrixGolden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Widget'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;scenarios:&lt;/span&gt; &lt;span class="p"&gt;[...],&lt;/span&gt; &lt;span class="nl"&gt;axes:&lt;/span&gt; &lt;span class="n"&gt;axes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;sampling:&lt;/span&gt; &lt;span class="n"&gt;MatrixSampling&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;smoke&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// about 5 tests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pairwise&lt;/strong&gt; — covers all pairs of values with the smallest number of tests:&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;matrixGolden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Widget'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;scenarios:&lt;/span&gt; &lt;span class="p"&gt;[...],&lt;/span&gt; &lt;span class="nl"&gt;axes:&lt;/span&gt; &lt;span class="n"&gt;axes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;sampling:&lt;/span&gt; &lt;span class="n"&gt;MatrixSampling&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pairwise&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// about 12 tests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most bugs come from how two settings work together (like dark theme + Arabic). Pairwise testing finds these bugs with much fewer tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Presets&lt;/strong&gt; — for common cases:&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;matrixGolden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Widget'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;scenarios:&lt;/span&gt; &lt;span class="p"&gt;[...],&lt;/span&gt; &lt;span class="nl"&gt;preset:&lt;/span&gt; &lt;span class="n"&gt;MatrixPreset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;componentSmoke&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A nice extra feature
&lt;/h2&gt;

&lt;p&gt;Golden tests check pixels. But sometimes a widget is broken even when pixels look the same.&lt;/p&gt;

&lt;p&gt;For example: your row has an icon, text, and a button. It looks fine. But on a small phone with large text, the button goes off the screen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A RenderFlex overflowed by 613 pixels on the right.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flutter cuts off the extra part, so the image looks "okay". The pixel test passes. But the user sees a broken button.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;golden_matrix&lt;/code&gt; catches these errors. It listens for Flutter warnings during the test. You get a report like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scenario"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"device"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"phoneSmall"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"textScale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.0&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;"passed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"warnings"&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="s2"&gt;"A RenderFlex overflowed by 613 pixels on the right."&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test passes, but you see the warning. You can fix the bug before users find it.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTML reports
&lt;/h2&gt;

&lt;p&gt;After tests run, the package makes one HTML file. You open it and see all your golden images on one page. You can filter by theme, status, or scenario. You can click an image to see it bigger.&lt;/p&gt;

&lt;p&gt;This is much faster than opening files one by one.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to start
&lt;/h2&gt;

&lt;p&gt;Add the package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pubspec.yaml&lt;/span&gt;
&lt;span class="na"&gt;dev_dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;golden_matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^0.6.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set up fonts (do this once):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// test/flutter_test_config.dart&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'dart:async'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:golden_matrix/golden_matrix.dart'&lt;/span&gt;&lt;span class="o"&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;testExecutable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FutureOr&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="kt"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;testMain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;loadAppFonts&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;testMain&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;Write a 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="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:golden_matrix/golden_matrix.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;matrixGolden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;'MyButton'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;scenarios:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="n"&gt;MatrixScenario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'default'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;builder:&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;MyButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;label:&lt;/span&gt; &lt;span class="s"&gt;'OK'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nl"&gt;preset:&lt;/span&gt; &lt;span class="n"&gt;MatrixPreset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;componentSmoke&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run:&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;--update-goldens&lt;/span&gt;
flutter &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing full screens
&lt;/h2&gt;

&lt;p&gt;For screens with navigation or special setup, use &lt;code&gt;screenMatrixGolden&lt;/code&gt;. You give it a function that builds your app:&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;screenMatrixGolden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;'LoginScreen'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;appBuilder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;combination&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;MaterialApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;theme:&lt;/span&gt; &lt;span class="n"&gt;combination&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nl"&gt;locale:&lt;/span&gt; &lt;span class="n"&gt;combination&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;locale&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;LoginScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;errorMessage:&lt;/span&gt; &lt;span class="n"&gt;combination&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;scenario&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'error'&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s"&gt;'Invalid'&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;states:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;MatrixScenario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'default'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;builder:&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;SizedBox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shrink&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
    &lt;span class="n"&gt;MatrixScenario&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="nl"&gt;builder:&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;SizedBox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shrink&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nl"&gt;preset:&lt;/span&gt; &lt;span class="n"&gt;MatrixPreset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;screenSmoke&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You control the app. The package handles the matrix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I made this
&lt;/h2&gt;

&lt;p&gt;Other packages did not work for me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;golden_toolkit&lt;/code&gt; — not updated since 2023&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;alchemist&lt;/code&gt; — good for one case, but no matrix idea&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I made &lt;code&gt;golden_matrix&lt;/code&gt;. It is MIT license, no extra dependencies, 108 tests, and on pub.dev.&lt;/p&gt;

&lt;p&gt;It is not perfect. It may not fit every project. But if you have many themes and devices to test, give it a try.&lt;/p&gt;

&lt;p&gt;If something does not work or you want a new feature, open an issue. I will help.&lt;/p&gt;

&lt;p&gt;Links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pub.dev/packages/golden_matrix" rel="noopener noreferrer"&gt;pub.dev/packages/golden_matrix&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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