<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://www.christianfindlay.com/index.xml" rel="self" type="application/atom+xml" /><link href="https://www.christianfindlay.com/" rel="alternate" type="text/html" /><updated>2026-06-13T03:19:20+00:00</updated><id>https://www.christianfindlay.com/index.xml</id><entry><title type="html">The US Government Switched Off Anthropic’s Most Powerful AI for the Entire World</title><link href="https://www.christianfindlay.com/blog/ai-export-ban" rel="alternate" type="text/html" title="The US Government Switched Off Anthropic’s Most Powerful AI for the Entire World" /><published>2026-06-13T00:00:00+00:00</published><updated>2026-06-13T00:00:00+00:00</updated><id>https://www.christianfindlay.com/blog/ai-export-ban</id><content type="html" xml:base="https://www.christianfindlay.com/blog/ai-export-ban"><![CDATA[<p>The United States government has ordered a private American company to switch off its most advanced product for almost everyone on the planet — and the company complied within hours.</p>

<p>On Friday 12 June 2026, Anthropic <a href="https://www.anthropic.com/news/fable-mythos-access">disabled access to its two most powerful AI models, Fable 5 and Mythos 5</a>, to comply with an export-control directive from the US government. In the company’s own words, the order required it to “suspend all access to Fable 5 and Mythos 5 by any foreign national, whether inside or outside the United States, including foreign national Anthropic employees.” There was no way to honour that while keeping the models live for anyone, so Anthropic pulled them for <em>every</em> customer worldwide.</p>

<p>It is worth slowing down on what actually happened here, because it is far bigger than a product outage. A government reached into a lawful, three-day-old commercial product used by hundreds of millions of people and turned it off by decree. The precedent it sets, if it holds, threatens the American AI industry far more than whatever it claims to protect — and for the roughly 96% of humanity that is not American, it is a blunt message about who frontier AI is really for.</p>

<h2 id="what-actually-happened">What actually happened</h2>

<p>The facts matter here, and they are well documented.</p>

<p>On 12 June 2026, Anthropic received a directive from the US government at 5:21pm ET and, within hours, <a href="https://www.cnbc.com/2026/06/12/anthropic-disables-access-to-fable-5-and-mythos-5-to-comply-with-government-directive.html">disabled both Fable 5 and Mythos 5 for every single customer</a> — not just foreign ones — because that was the only way to guarantee compliance. Anthropic’s <a href="https://www.anthropic.com/news/fable-mythos-access">own statement</a> describes the “net effect” plainly: “we must abruptly disable Fable 5 and Mythos 5 for all our customers to ensure compliance. Access to all other Anthropic models will not be affected.”</p>

<p>According to <a href="https://www.axios.com/2026/06/12/anthropic-trump-mythos-fable-national-security">Axios, which broke the scoop</a>, the order came as a letter from Commerce Secretary Howard Lutnick to Anthropic CEO Dario Amodei, placing the models under export controls “to any location outside of the U.S. and to all foreign persons within the country,” and requiring a licence for the “export, re-export or domestic transfer” of the models. <a href="https://www.investing.com/news/stock-market-news/us-blocks-foreign-access-to-anthropics-most-advanced-ai-models-axios-reports-4740798">Reuters reported the same</a>: the Trump administration is blocking foreign governments, companies and individuals from accessing Anthropic’s most advanced models, and Anthropic responded by cutting everyone off. By Friday night the story was driving a visible spike in <a href="https://trends.google.com/trending?geo=US">searches for “anthropic”</a>, which tells you the wider world noticed too.</p>

<p>The timing is brutal. Anthropic <a href="https://www.anthropic.com/news/claude-fable-5-mythos-5">only released these two models on 9 June</a> — Fable 5 as the safeguarded public model, and Mythos 5 as the same underlying model with some protections removed for vetted users. <a href="https://techcrunch.com/2026/06/09/anthropics-claude-fable-5-is-a-version-of-mythos-the-public-can-access-today/">TechCrunch covered the launch</a> three days before the government pulled the plug. So this was a model that lived, as a publicly available product, for roughly seventy-two hours.</p>

<h2 id="what-fable-and-mythos-actually-are">What Fable and Mythos actually are</h2>

<p>It is worth being clear about what these models are, because the naming is doing a lot of work. By Anthropic’s own description, Fable 5 and Mythos 5 are a new “Mythos-class” tier that the company positions <em>above</em> Claude Opus — its most capable models yet. Fable 5 and Mythos 5 are, underneath, the same model. The difference is the safety layer: Fable 5 is the version released to the general public <em>with</em> additional safeguards around dual-use capabilities, while <a href="https://www.anthropic.com/news/claude-fable-5-mythos-5">Mythos 5 is the same model with those measures removed</a>, made available only to a small set of approved organisations — initially through a US-government cybersecurity collaboration called Project Glasswing. The <a href="https://platform.claude.com/docs/en/about-claude/models/introducing-claude-fable-5-and-claude-mythos-5">model documentation</a> lists both at the same price: $10 per million input tokens, $50 per million output.</p>

<p>This sharpens the irony. The model the government pulled was <em>Fable 5 — the safeguarded public version Anthropic deliberately built to be safe for general release.</em> It was not the unrestricted Mythos variant. The government did not just pull the dangerous one; it pulled the one with the seatbelts bolted on, for everyone, worldwide.</p>

<h2 id="the-jailbreak-that-triggered-this-was-asking-the-model-to-fix-a-bug">The “jailbreak” that triggered this was asking the model to fix a bug</h2>

<p>Here is the part that should give everyone pause. The government did not, according to Anthropic, hand over a detailed technical case. <a href="https://www.anthropic.com/news/fable-mythos-access">Anthropic’s statement</a> says: “The letter did not provide specific details of its national security concern. Our understanding is that the government believes it has become aware of a method of bypassing, or ‘jailbreaking’ Fable 5.”</p>

<p>So what was the terrifying jailbreak? In Anthropic’s words: “To date, the government has only given us verbal evidence of a potential narrow, non-universal jailbreak, which essentially consists of asking the model to read a specific codebase and fix any software flaws.”</p>

<p>Read that again. The capability that supposedly threatens national security is <em>asking an AI to read code and fix the bugs in it</em> — the single most common, mundane thing developers do with these tools every single day. Anthropic goes further and says it “validated that the level of capability displayed there is widely available from other models (including OpenAI’s GPT-5.5), and is used every day by the defenders who keep systems safe.”</p>

<p><a href="https://www.axios.com/2026/06/12/anthropic-trump-mythos-fable-national-security">Axios reports</a> the chain of events: another company claimed it could jailbreak Mythos, this alarmed the administration, the administration tried to get Anthropic to pause the launch, Anthropic declined, and the export-control letter followed. Anthropic’s blunt assessment of the standard being applied: “If this standard was applied across the industry, we believe it would essentially halt all new model deployments for all frontier model providers.”</p>

<h2 id="to-be-fair-washingtons-fear-is-not-irrational">To be fair: Washington’s fear is not irrational</h2>

<p>It is only fair to put the government’s case at its strongest, because the underlying worry is real. The Mythos family is genuinely frightening on cybersecurity. When Anthropic previewed it in April, <a href="https://red.anthropic.com/2026/mythos-preview/">its own red team reported</a> that Mythos Preview “is capable of identifying and then exploiting zero-day vulnerabilities in every major operating system and every major web browser when directed by a user to do so” — and that engineers with no security training could get a complete working exploit overnight. That is not nothing. <a href="https://fortune.com/2026/04/23/anthropic-mythos-ai-cybersecurity-critical-infrastructure-kemba-walden/">Former US National Cyber Director Kemba Walden called Mythos</a> “a clarion call to address weaknesses in our cyber ecosystem,” describing a model that “autonomously builds and chains exploits — and then covers its tracks.”</p>

<p>So I get why officials are nervous. But notice that Anthropic <em>anticipated</em> exactly this. Fable 5 was deliberately shipped as “<a href="https://cyberscoop.com/anthropic-claude-fable-5-release-mythos-guardrails/">Mythos on a leash</a>” — its classifiers route sensitive cybersecurity and biology queries to its next-most-capable public model, Opus 4.8, and Anthropic kept the full-strength Mythos behind the vetted Project Glasswing circle. The company built the deployment controls <em>first</em>, then the government overrode them by banning the leashed version too. If the fear is real, the remedy still has to make sense — and recalling the safeguarded model while the same capability stays on sale elsewhere does not.</p>

<h2 id="this-is-an-export-control-understand-what-that-machinery-is">This is an export control. Understand what that machinery is.</h2>

<p>The word “export” is doing enormous work here, so it is worth being precise about the legal apparatus, because it is genuinely alarming once you see it.</p>

<p>US export controls are administered by the Commerce Department’s <a href="https://www.commerce.gov/bureaus-and-offices/bis">Bureau of Industry and Security</a> (BIS) under the Export Administration Regulations. The concept that lets a directive reach <em>me, in Australia, and also a foreign-national engineer sitting in an office in San Francisco</em> is the <strong>deemed export</strong>. It is not a metaphor; it is black-letter regulation. <a href="https://www.law.cornell.edu/cfr/text/15/734.13">15 CFR § 734.13</a> defines “export” to include “releasing or otherwise transferring technology or source code to a foreign person in the United States,” and says any such release “is a deemed export to the foreign person’s most recent country of citizenship or permanent residency.” <a href="https://www.bis.gov/learn-support/deemed-exports/what-deemed-export">BIS spells it out</a>: showing controlled technology to a foreign national, even one standing in your own office, is legally an export to their home country. That is why Anthropic’s order explicitly covers “foreign national Anthropic employees.” The US government has not just blocked a product at the border. It has reclassified a commercial AI model as something closer to a controlled munition, where the act of <em>showing it to the wrong nationality</em> is the regulated event.</p>

<p>Where does the power come from? The permanent authority is the <a href="https://www.law.cornell.edu/uscode/text/50/4812">Export Control Reform Act of 2018</a>, and in particular its provision for controlling <a href="https://www.law.cornell.edu/uscode/text/50/4817">“emerging and foundational technologies” essential to national security</a> — which is the natural hook for frontier AI. Sitting behind that is the <a href="https://www.law.cornell.edu/uscode/text/50/1702">International Emergency Economic Powers Act</a>, which lets the President “investigate, block … regulate … prevent or prohibit, any … exportation … [or] transfer” of property once a national emergency is declared. And crucially, this is not unprecedented as an <em>idea</em>: the Biden administration’s <a href="https://www.federalregister.gov/documents/2025/01/15/2025-00636/framework-for-artificial-intelligence-diffusion">AI Diffusion Rule</a> (January 2025) already created a control — ECCN 4E091 — that treated the <em>weights</em> of the most advanced closed models as a controlled commodity, before the Trump administration <a href="https://www.bis.gov/press-release/department-commerce-announces-rescission-biden-era-artificial-intelligence-diffusion-rule-strengthens">rescinded it in May 2025</a>. The machinery to treat a model like a weapon has been quietly assembled over years. Friday was the day someone pulled the lever on a live consumer product.</p>

<p>Sit with the implication. A huge fraction of the engineers who build these systems in America are foreign nationals. An order that bars foreign nationals from a model bars some of the very people who built it from touching their own work. Anthropic itself flagged this — the directive hits its own employees.</p>

<h2 id="we-have-already-watched-this-movie-with-chips">We have already watched this movie with chips</h2>

<p>The US has spent years treating AI as a national-security asset to be fenced off, and we have a very clear record of how that goes. The chip controls are the precedent, and they are not a happy one.</p>

<p>It started in <a href="https://www.csis.org/analysis/insight-us-semiconductor-export-controls-update">October 2022, when BIS restricted advanced AI accelerators to China</a>, instantly making Nvidia’s A100 and H100 non-compliant. Nvidia simply designed the cut-down A800 and H800 to sit just under the threshold. So in <a href="https://www.federalregister.gov/documents/2023/10/25/2023-23055/implementation-of-additional-export-controls-certain-advanced-computing-items-supercomputer-and">October 2023 BIS rewrote the rule</a> to close that gap. Nvidia designed the even-more-cut-down H20. In April 2025 the government <a href="https://www.investing.com/news/stock-market-news/nvidia-expects-up-to-55-billion-charge-in-first-quarter-3986890">required a licence for the H20 too, and Nvidia took a ~$5.5 billion charge</a>. Then it reversed course and let H20s flow again — under a <a href="https://www.pbs.org/newshour/politics/under-new-unusual-agreement-u-s-will-get-a-15-cut-of-nvidia-and-amd-chip-sales-to-china">bizarre arrangement where Nvidia and AMD hand 15% of their China chip revenue to the US government</a> for the privilege of an export licence. Whatever that is, it is not a coherent national-security policy.</p>

<p>And the man whose company lived it has been blunt about the result. Nvidia CEO Jensen Huang stood up at Computex in 2025 and <a href="https://finance.yahoo.com/news/jensen-huang-blasts-u-curbs-071300758.html">said the quiet part out loud</a>: “The export control was a failure.” His reasoning is the whole point: “AI researchers are still doing AI research in China. If they don’t have enough Nvidia, they will use their own.” By his own account, Nvidia’s share of China’s AI-chip market <a href="https://www.tomshardware.com/tech-industry/jensen-huang-says-nvidia-china-market-share-has-fallen-to-zero">fell from about 95% to effectively zero</a> — not because demand vanished, but because the controls “gave them the spirit, the energy, and the government support to accelerate their development.”</p>

<p>That is the lesson nobody in Washington seems to absorb: <strong>when you wall off an American product, you do not delete demand — you redirect it.</strong> And the redirection has a name now. <a href="https://www.csis.org/analysis/deepseek-huawei-export-controls-and-future-us-china-ai-race">CSIS documented how DeepSeek’s R1</a>, released in January 2025, “cemented its reputation as the top frontier AI research lab in China and caused a reassessment of assumptions about the landscape of global AI competition” — frontier-class performance at far lower cost, with user adoption so fast its servers buckled. Customers do not stop wanting frontier AI because they cannot have Claude. They go and get someone else’s — and increasingly, someone else’s is very, very good.</p>

<h2 id="code-as-speech--an-old-fight-unresolved">Code as speech — an old fight, unresolved</h2>

<p>There is a deeper, almost philosophical problem buried in here, and it is not new. In the 1990s the US tried to treat strong encryption as a controlled munition. A mathematician named Daniel Bernstein challenged it, and in <a href="https://www.eff.org/cases/bernstein-v-us-dept-justice"><em>Bernstein v. United States</em></a> the courts wrestled with whether source code is protected speech under the First Amendment. The Ninth Circuit panel famously found that it was, and the entire crypto-export regime eventually crumbled under that pressure.</p>

<p>A model is not source code in the Bernstein sense, but the analogy is uncomfortably close: the government is asserting the power to control who may <em>read the outputs of, and interact with,</em> a piece of software, on national-security grounds, with the regulated act being the transfer of information to a foreigner. We litigated a version of this thirty years ago and the government lost. Nobody should assume the AI version is settled law.</p>

<h2 id="what-it-means-for-developers-outside-america">What it means for developers outside America</h2>

<p>The message just delivered to every developer, startup, and enterprise outside the United States is stark: <em>American frontier AI can be revoked at any moment, retroactively, by executive directive, with no warning and no process to appeal.</em> Anyone who had built workflows on Fable 5 watched them break on Friday — not for anything they did, but because a government they did not vote for and are not citizens of decided foreign access to the model was a national-security risk. Asking a chatbot to read and fix your code is now, apparently, a controlled activity if you hold the wrong passport.</p>

<p>Anthropic, to its credit, <a href="https://www.anthropic.com/news/fable-mythos-access">apologised</a> — “We apologize for this disruption to our customers” — and says it believes this is a misunderstanding it is working to reverse. That is almost certainly true, and Anthropic is not the party that pulled the trigger. But goodwill does not change the risk calculus. You cannot build a business on an input that a foreign government can switch off without notice or appeal. The rational response is to hedge and diversify away — not out of disloyalty to American AI, but as basic risk management. And that quiet, rational flight is exactly the outcome that should worry Washington most.</p>

<h2 id="why-this-could-break-anthropic--and-hand-the-game-to-its-rivals">Why this could break Anthropic — and hand the game to its rivals</h2>

<p>Here is the part I keep coming back to, and it is the genuinely dangerous bit for the American AI industry.</p>

<p>Anthropic is a US company. So is OpenAI. The entire pitch of American AI to the world is “build on us.” A huge share of these companies’ usage and revenue is international. An API-and-cloud business is uniquely exposed to a “no foreign nationals” rule in a way that, say, an exported physical good is not — the product <em>is</em> remote access, and remote access is exactly what got switched off. Cut Anthropic off from every foreign national on the planet and you have not regulated a model; you have amputated its market.</p>

<p>And notice the cruel asymmetry. Anthropic itself points out that the very capability in question is <a href="https://www.anthropic.com/news/fable-mythos-access">available in OpenAI’s GPT-5.5</a>. So this action does not make the capability disappear from the world. It selectively kneecaps one American company while its American competitor keeps selling. Today it is Anthropic. There is nothing about the mechanism that stops it being OpenAI next week, on someone else’s say-so. A precedent that lets the government disable any frontier model on a single unverified jailbreak claim is a sword hanging over <em>all</em> of them.</p>

<p>Zoom out and it is worse. Every foreign customer Anthropic just lost is a customer that will go shopping — and the alternatives are no longer only American. The world now has capable models out of China and Europe. A US export-control regime that makes American AI unreliable for the 96% of humanity that is not American is, functionally, the single best growth strategy a non-American AI lab could ask for. We may look back on this as the moment the US handed away the one market that actually mattered: everyone else’s.</p>

<p>This is the irony that should keep policymakers up at night. The stated goal is to protect American national security by controlling powerful AI. The likely effect is to make American AI globally untrustworthy, push the world onto non-American models, and weaken the exact companies the policy claims to champion.</p>

<h2 id="the-anti-ai-mood-is-what-made-this-possible">The anti-AI mood is what made this possible</h2>

<p>None of this happened in a vacuum. A government only reaches for an emergency switch like this when it believes the public will applaud — and the public mood on AI has curdled. <a href="https://www.pewresearch.org/short-reads/2026/03/12/key-findings-about-how-americans-view-artificial-intelligence/">Pew Research</a> now finds half of US adults are <em>more concerned than excited</em> about AI in daily life, against just 10% who are more excited than concerned. That concerned share has climbed from 37% in 2021 — a 13-point jump in four years. Stanford’s <a href="https://hai.stanford.edu/ai-index/2026-ai-index-report/public-opinion">2026 AI Index</a> documents the same anxiety, and a widening gap between AI insiders and everyone else.</p>

<p>I am an AI optimist, so I say this carefully: a lot of that anxiety is earned. People watch their juniors’ job prospects, their kids’ homework, and their newsfeeds get churned by this stuff, and they are uneasy. Then a model arrives that can <a href="https://red.anthropic.com/2026/mythos-preview/">autonomously find and exploit zero-days</a>, the cyber threat is serious enough that it <a href="https://www.artificialintelligence-news.com/news/anthropic-mythos-ai-cybersecurity-white-house/">brings Anthropic’s CEO back into the White House to meet the Chief of Staff and the Treasury Secretary</a>, and the doom narrative writes itself. In that climate, “we switched off the scary AI to keep you safe” is a <em>politically rewarding</em> sentence, regardless of whether it withstands ten minutes of technical scrutiny.</p>

<p>And here is the bitter irony I cannot get past. A lot of the loudest anti-AI campaigning comes from people who cast themselves as opponents of unchecked corporate <em>and</em> state power — populist critics and online commentators who built big audiences arguing that AI is a scam, a theft machine, a clear and present danger. Whatever you make of those arguments on their merits, look at what the campaign actually produced. It did not rein in the labs. It handed the <em>government</em> a loaded political weapon and a public primed to cheer when it goes off. And the first thing that weapon was used for was not protecting artists or workers — it was the executive branch reaching into a private company and switching off a lawful product for the entire planet, with no due process, on a claim it would not even put in writing. The anti-AI movement said it wanted a check on power. What it manufactured was a blank cheque for it.</p>

<p>That is the trap. Anti-AI sentiment creates a permission structure for heavy-handed, under-reasoned state action — and because the action is dressed in the language of safety, the normal checks feel like obstruction. The danger is that we let a genuine, legitimate unease about AI be laundered into a precedent that lets the government disable lawful software by decree. You can be worried about AI <em>and</em> alarmed that this is how your government chose to act on that worry. I am both.</p>

<h2 id="the-precedent-is-the-real-story">The precedent is the real story</h2>

<p>Strip away the specifics and what remains is this: a government reached into a private company and switched off a lawful, just-launched product used by — in Anthropic’s words — “hundreds of millions of people,” on the strength of a narrow, verbally communicated claim it would not document, and which the company says it could not even reproduce as anything serious.</p>

<p>Anthropic has been consistent about where it stands, and I think it is right: “we believe the government should have the ability to block unsafe deployments, as part of a statutory process that is transparent, fair, clear, and grounded in technical facts. This action does not adhere to those principles.” A government absolutely should be able to act against genuinely dangerous technology. But it has to do so through a process — notice, evidence, a chance to respond, judicial review — not a Friday-evening letter that nukes a product for everyone.</p>

<p>This is not even Anthropic’s first collision with Washington this year. As <a href="https://www.cnbc.com/2026/06/12/anthropic-disables-access-to-fable-5-and-mythos-5-to-comply-with-government-directive.html">CNBC notes</a>, the Department of Defense earlier declared Anthropic a “supply chain risk” — a label historically reserved for foreign adversaries. <a href="https://breakingdefense.com/2026/03/judge-grants-anthropic-preliminary-injunction-but-pentagon-cto-says-ban-still-stands/">Breaking Defense reported</a> that the designation came under Title 10 § 3252, and that a federal judge, Rita Lin, granted Anthropic a preliminary injunction, finding the stated reasons “were pretextual and that [the government’s] real motive was unlawful retaliation.” <a href="https://fortune.com/2026/03/26/us-judge-blocks-anthropic-ban-supply-chain-risk-orwellian-notion/">Fortune quoted the judge’s line in full</a>: “Nothing in the governing statute supports the Orwellian notion that an American company may be branded a potential adversary and saboteur of the U.S. for expressing disagreement with the government.”</p>

<p>This is now serious enough that the <a href="https://www.congress.gov/crs-product/IF13217">Congressional Research Service has written it up for lawmakers</a>, and legal scholars at <a href="https://www.lawfaremedia.org/article/pentagon's-anthropic-designation-won't-survive-first-contact-with-legal-system">Lawfare warned</a> that punishing a domestic company “without notice or an opportunity to be heard” looked like “the beginning of a partial nationalization.” So the relationship between the leading US AI <em>safety</em> lab and the US government is now openly adversarial — litigated in multiple courts. Read that sentence again and tell me it sounds like a healthy basis for “winning the AI race.”</p>

<p>And it is worth being specific about <em>why</em> they fell out, because it reframes everything that has happened since. <a href="https://breakingdefense.com/2026/03/judge-grants-anthropic-preliminary-injunction-but-pentagon-cto-says-ban-still-stands/">Breaking Defense reported</a> that the Pentagon’s blacklisting stemmed from disagreements over safeguards Anthropic placed on the use of its AI systems for <strong>surveillance and autonomous weapons.</strong> Anthropic would not drop them. Negotiations collapsed. The “supply chain risk” label followed — and a judge then found that label was likely unlawful retaliation for the company “expressing disagreement with the government.”</p>

<p>Now hold that pattern up against Friday. A company that has repeatedly told the US government <em>no</em> on weapons and surveillance, that is currently <em>suing</em> the administration, ships the most capable models it has ever built — and within seventy-two hours that same administration invokes emergency national-security export powers to switch those models off for the entire world. I cannot prove motive and I will not pretend to. But a federal judge has already ruled that the <em>last</em> time this administration reached for a national-security label against Anthropic, the label was a pretext for payback. You do not need a conspiracy theory to ask whether this is the next chapter of the same story. You just need to read the docket.</p>

<h2 id="safety-is-the-costume-containment-is-the-play">Safety is the costume. Containment is the play.</h2>

<p>Let me state plainly what I think is actually going on, because the “safety” framing does not survive contact with the mechanism they chose.</p>

<p>If your genuine concern is a dangerous capability, you restrict the <em>capability</em>. You do not restrict it by <em>passport</em>. But an export control is, by construction, a nationality instrument — the <a href="https://www.law.cornell.edu/cfr/text/15/734.13">deemed-export doctrine</a> makes the regulated event “a foreigner saw it,” not “harm occurred.” This order does not make Fable’s capabilities disappear. By Anthropic’s own account the same capability ships today <a href="https://www.anthropic.com/news/fable-mythos-access">in OpenAI’s GPT-5.5</a>. So the policy is not “this is too dangerous to exist.” The policy is “this is too good to share.” Those are completely different statements, and only one of them is about safety.</p>

<p>We have even seen the blueprint written down. The <a href="https://www.federalregister.gov/documents/2025/01/15/2025-00636/framework-for-artificial-intelligence-diffusion">AI Diffusion Rule</a> explicitly carved the planet into tiers: a handful of favoured allies with near-unrestricted access, most of the world capped, and a long list of countries barred outright. The ambition to ration frontier AI by geography is not a conspiracy theory; it is published policy. Friday’s directive is that same instinct applied with a sledgehammer to a single live product. Strip off the safety costume and what is underneath is techno-nationalism: keep the crown jewels inside the border, and let the other ~96% of humanity make do with less. “Safety” is the word you reach for because it is the one word that makes the rest of us feel churlish for objecting to being locked out.</p>

<h2 id="wrap-up">Wrap-up</h2>

<p>The facts are damning enough on their own. The trigger was a model fixing a bug. The mechanism was an export-control deemed-export rule designed for munitions and microchips. The blast radius was every foreign national on Earth, including Anthropic’s own staff. The legal theory has lost in court before. And the predictable result is to weaken American AI companies while doing nothing to remove the capability from the world, because the same capability ships in a competitor’s product today.</p>

<p>If the United States wants to lead in AI, this is precisely how it loses that lead — not to a better model, but to its own willingness to make its best companies unusable to everyone outside its borders. Anthropic may well be right that this is a misunderstanding that gets reversed quickly. But the precedent has now been set, and precedents are sticky. The developers, startups, and enterprises outside America who built on these models — and the foreign-national engineers who built the models in the first place — are watching very closely, and many are already thinking about their hedges.</p>

<p><em>I will update this post as more primary documentation becomes public. As of writing, the government had not released the underlying directive, and Anthropic said it would <a href="https://www.anthropic.com/news/fable-mythos-access">share more details over the following 24 hours</a>.</em></p>]]></content><author><name>Christian Findlay</name></author><category term="software development" /><category term="ai" /><summary type="html"><![CDATA[The US government ordered Anthropic to disable Fable 5 and Mythos 5 for every foreign national on Earth. Here is what this export ban means for developers, for Anthropic, and for the future of AI.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.christianfindlay.com/assets/images/blog/ai-export-ban/header.webp" /><media:content medium="image" url="https://www.christianfindlay.com/assets/images/blog/ai-export-ban/header.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Why I Built Basilisk</title><link href="https://www.christianfindlay.com/blog/why-i-built-basilisk" rel="alternate" type="text/html" title="Why I Built Basilisk" /><published>2026-06-06T14:00:00+00:00</published><updated>2026-06-06T14:00:00+00:00</updated><id>https://www.christianfindlay.com/blog/why-i-built-basilisk</id><content type="html" xml:base="https://www.christianfindlay.com/blog/why-i-built-basilisk"><![CDATA[<blockquote>
  <p><strong>Key Takeaways</strong>: Python has a tooling problem, not a typing problem. Pylance is proprietary, editor-locked, and too fragile for the way many developers work now. Basilisk is Nimblesite’s attempt to build a complete open-source Python language server, type checker, debugger, and profiler that works everywhere.</p>
</blockquote>

<p>A while back, I started building <a href="https://github.com/Nimblesite/nimble_agent">Nimble Agent</a>. It’s a Python AI coding agent built around a simple idea: an agent shouldn’t be allowed to hand-wave its way to “done”. It should have acceptance criteria. It should test its work. It should understand the codebase instead of spraying text into files and hoping for the best.</p>

<p>That led me straight into Python’s type system.</p>

<p>Python is where the AI agent world lives. LangChain, LangGraph, CrewAI, AutoGen, OpenAI’s Python SDK, Anthropic’s Python SDK, and half the ML ecosystem are all Python-first. That’s slightly annoying, because Python is dynamically typed, and dynamic typing confuses the hell out of AI agents. Humans can usually infer intent from messy code. Agents aren’t as good at that. They need contracts. They need explicit shapes. They need a type system that says things out loud.</p>

<p>Python actually has a decent type system now. It’s not TypeScript, but it’s far better than people give it credit for. The problem isn’t the syntax. The problem is enforcement.</p>

<p><a href="https://peps.python.org/pep-0484/">PEP 484</a> made type hints explicitly voluntary. It says no type checking happens at runtime, and it assumes a separate checker that you run over your source. That’s fine as a language design choice, but it means the entire value of typing depends on tooling. If the tooling is weak, optional, proprietary, or locked to one editor, your type annotations aren’t contracts. They’re comments with nicer syntax.</p>

<p>That’s what finally pushed me to build <a href="https://www.basilisk-python.dev">Basilisk</a>.</p>

<h2 id="the-pylance-wall">The Pylance Wall</h2>

<p>The obvious answer was supposed to be Pylance, and that’s exactly the problem. Pylance is the thing Python developers are told to use, but the experience is brittle. Large workspaces get slow or memory hungry. Whole-workspace diagnostics and indexing come with tradeoffs. Import and module resolution often need <code class="language-plaintext highlighter-rouge">python.analysis.extraPaths</code>, workspace reshaping, or other settings before Pylance sees the same code Python can already run. Microsoft’s own <a href="https://github.com/microsoft/pylance-release/blob/main/FAQ.md">Pylance FAQ</a> has whole sections on import and module resolution, and its <a href="https://github.com/microsoft/pylance-release/wiki/Pylance-Configuration-Tips">configuration tips</a> describe tuning analysis and indexing for large workspaces.</p>

<p>That’s a lot of ceremony for the default Python experience.</p>

<p>That same <a href="https://github.com/microsoft/pylance-release/blob/main/FAQ.md">FAQ</a> explains the relationship clearly: Pylance uses the open-source Pyright type checker underneath, then bolts extra language server features on top.</p>

<p><a href="https://github.com/microsoft/pyright">Pyright</a> is open source, and it’s a very good checker. It has excellent conformance to the Python typing spec, and you can use it outside VS Code. Pyright isn’t the problem. Pylance is.</p>

<p>Pylance is the richer language server experience Python developers know from VS Code, and it’s proprietary. The <a href="https://marketplace.visualstudio.com/items/ms-python.vscode-pylance/license">Pylance license</a> limits installation and use to Microsoft Visual Studio products and services, and Microsoft documents that <a href="https://code.visualstudio.com/docs/supporting/oss-extensions">some extensions use proprietary licenses</a> even when the related source projects are open.</p>

<p>So you get a weird split. Pyright is open. Pylance isn’t. The core checker is right there, but the IDE experience is locked to Microsoft’s distribution. That might have been fine in 2020. It’s a lot less fine now.</p>

<h2 id="the-world-moved-to-vs-code-forks">The World Moved To VS Code Forks</h2>

<p>The most interesting developer tools aren’t just VS Code anymore.</p>

<p>Cursor is a VS Code fork. Windsurf is a VS Code fork. Google’s <a href="https://antigravity.google/docs/editor?id=GoogleAntigravity">Antigravity</a> is based on the VS Code codebase and uses Open VSX for extensions. Developers move between VS Code, Cursor, Windsurf, Antigravity, Zed, Neovim, and whatever comes next. The editor is becoming a shell around agents, LSPs, debuggers, terminals, and project context.</p>

<p>That should be great. The whole point of the <a href="https://microsoft.github.io/language-server-protocol/">Language Server Protocol</a> is that language intelligence is portable. The editor shouldn’t own the language.</p>

<p>But Python isn’t in a great place here. If you use official VS Code, you get Pylance. If you use one of the AI-first forks, you get a confusing mix of old Pylance builds, Pyright forks, Basedpyright, marketplace remapping, or custom extensions trying to recreate the thing developers expected in the first place.</p>

<p>This isn’t theoretical. Cursor users have been complaining about it in their own forum. There are threads about <a href="https://forum.cursor.com/t/cursor-cannot-install-latest-pylance-extension/50654">not being able to install the latest Pylance extension</a>, <a href="https://forum.cursor.com/t/unable-to-find-pylance-in-the-extension-marketplace/105099">Pylance not being supported in Cursor</a>, and <a href="https://forum.cursor.com/t/python-pyright-extension-loading-problem/100607">Cursor’s Python/Pyright extension getting stuck while analyzing files</a>. In one of them, Cursor staff tell users to install Anysphere’s Python extension, because Pylance isn’t supported inside Cursor.</p>

<p>Windsurf has the same shape of problem. Its own documentation recommends <a href="https://docs.windsurf.com/windsurf/recommended-extensions">Windsurf Pyright</a>, described as a Pylance-like language server from Open VSX. That wording tells you everything: the market wants complete Python code intelligence, just outside Pylance’s licensing boundary.</p>

<p>Antigravity users are already asking the same question. There are Reddit threads asking for a <a href="https://www.reddit.com/r/GoogleAntigravityIDE/comments/1pmyoyi/pylance_alternative_in_google_antigravity/">Pylance alternative in Google Antigravity</a> and a <a href="https://www.reddit.com/r/GoogleAntigravityIDE/comments/1r3ow7e/any_working_python_language_server_for_antigravity/">working Python language server for Antigravity</a>. The pattern keeps repeating because the underlying ecosystem is fractured.</p>

<h2 id="geoffrey-huntley-was-right">Geoffrey Huntley Was Right</h2>

<p>Geoffrey Huntley wrote an article called <a href="https://ghuntley.com/fracture/">Visual Studio Code is designed to fracture</a>, and it’s worth reading in full.</p>

<p>His argument is that VS Code’s source being open isn’t the same thing as the VS Code ecosystem being open. Microsoft can publish an open-source editor codebase, ship proprietary binaries, control marketplace access, and make proprietary extensions the default. That splits the open version from the version developers actually want to use.</p>

<p>Huntley specifically called out Python. He pointed to the replacement of Microsoft’s open-source Python Language Server with Pylance, and argued the default had quietly moved to a proprietary extension. <a href="https://visualstudiomagazine.com/articles/2021/11/05/vscode-python-nov21.aspx?p=1">Visual Studio Magazine</a> reported the same shift: the open-source Microsoft Python Language Server reached end of life, and Pylance became the replacement. <a href="https://www.theregister.com/2021/09/07/python_extension_vs_code_september/">The Register</a> noted the open-source cost of swapping the old language server for a closed one.</p>

<p>Microsoft employs great engineers, Pyright is good software, and VS Code is a genuinely excellent IDE. None of that makes Pylance a healthy default for the Python ecosystem.</p>

<p>But we should be honest about the incentives. When the best default experience is proprietary and tied to one vendor’s product, the whole ecosystem bends around that product. Every fork, cloud IDE, open-source build, and AI-first editor either accepts a worse Python experience or burns engineering time recreating what should have been a portable language server in the first place.</p>

<p>That’s bad for Python.</p>

<h2 id="python-typing-is-better-than-people-think">Python Typing Is Better Than People Think</h2>

<p>Python’s type system isn’t the problem. The <a href="https://typing.python.org/en/latest/spec/">Python typing spec</a> is genuinely good, and in some areas it’s ahead of languages people reflexively think of as “more typed”.</p>

<p>Take unions. Since <a href="https://peps.python.org/pep-0604/">PEP 604</a>, you can write this:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>

<span class="k">def</span> <span class="nf">load_config</span><span class="p">(</span><span class="n">source</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="n">Path</span> <span class="o">|</span> <span class="nb">bytes</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
    <span class="bp">...</span>
</code></pre></div></div>

<p>That’s a real union type. The value can be a <code class="language-plaintext highlighter-rouge">str</code>, a <code class="language-plaintext highlighter-rouge">Path</code>, or <code class="language-plaintext highlighter-rouge">bytes</code>, and the checker narrows it as the code proves what it has. You can compose it with <code class="language-plaintext highlighter-rouge">None</code> too:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">find_user</span><span class="p">(</span><span class="nb">id</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">User</span> <span class="o">|</span> <span class="bp">None</span><span class="p">:</span>
    <span class="bp">...</span>
</code></pre></div></div>

<p>C# still doesn’t have that as an ordinary, stable language feature. You reach for <code class="language-plaintext highlighter-rouge">User?</code> for nullability, pattern matching, <code class="language-plaintext highlighter-rouge">object</code>, overloads, custom wrapper types, or a third-party library. Microsoft has a <a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/unions">C# union types proposal</a>, which proves the need is real, but Python already lets you express the idea directly in everyday code.</p>

<p>Dart has a sound type system, and Dart 3’s <a href="https://dart.dev/language/class-modifiers">sealed classes</a> and <a href="https://dart.dev/language/patterns">patterns</a> are great. But Dart still won’t let you write a lightweight free union like <code class="language-plaintext highlighter-rouge">String | Uri | Uint8List</code>. You model it with a sealed hierarchy or fall back to <code class="language-plaintext highlighter-rouge">Object</code>, then write all the surrounding plumbing.</p>

<p>Python can express tagged results without that ceremony:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
<span class="kn">from</span> <span class="n">typing</span> <span class="kn">import</span> <span class="n">Literal</span>

<span class="nd">@dataclass</span><span class="p">(</span><span class="n">frozen</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">Success</span><span class="p">:</span>
    <span class="n">kind</span><span class="p">:</span> <span class="n">Literal</span><span class="p">[</span><span class="sh">"</span><span class="s">success</span><span class="sh">"</span><span class="p">]</span>
    <span class="n">value</span><span class="p">:</span> <span class="nb">int</span>

<span class="nd">@dataclass</span><span class="p">(</span><span class="n">frozen</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">Failure</span><span class="p">:</span>
    <span class="n">kind</span><span class="p">:</span> <span class="n">Literal</span><span class="p">[</span><span class="sh">"</span><span class="s">failure</span><span class="sh">"</span><span class="p">]</span>
    <span class="n">message</span><span class="p">:</span> <span class="nb">str</span>

<span class="n">Result</span> <span class="o">=</span> <span class="n">Success</span> <span class="o">|</span> <span class="n">Failure</span>

<span class="k">def</span> <span class="nf">render</span><span class="p">(</span><span class="n">result</span><span class="p">:</span> <span class="n">Result</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="k">match</span> <span class="n">result</span><span class="p">:</span>
        <span class="k">case</span> <span class="nc">Success</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">):</span>
            <span class="k">return</span> <span class="nf">str</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
        <span class="k">case</span> <span class="nc">Failure</span><span class="p">(</span><span class="n">message</span><span class="o">=</span><span class="n">message</span><span class="p">):</span>
            <span class="k">return</span> <span class="n">message</span>
</code></pre></div></div>

<p>In C#, you’d create a base record or interface and derive the cases. In Dart, you’d create a sealed class and subclasses. Those approaches work, but they’re heavier. Python lets you write the union directly, and the annotation is the contract. Add <code class="language-plaintext highlighter-rouge">Literal</code>, <code class="language-plaintext highlighter-rouge">TypedDict</code>, <code class="language-plaintext highlighter-rouge">Protocol</code>, <code class="language-plaintext highlighter-rouge">TypeGuard</code>, and <a href="https://typing.python.org/en/latest/spec/narrowing.html"><code class="language-plaintext highlighter-rouge">TypeIs</code></a>, and Python typing gets a lot more sophisticated than the industry narrative suggests.</p>

<p>Most developers don’t know this, because the tooling has trained them not to care. The <a href="https://engineering.fb.com/2024/12/09/developer-tools/typed-python-2024-survey-meta/">2024 Python typing survey from Meta, JetBrains, and Microsoft</a> found that type hints are well adopted but tooling usability is still a major problem. A big chunk of developers use types regularly without running a checker in CI. The survey also called out inconsistent checker behavior and poor discoverability as sources of friction.</p>

<p>That’s the real damage. Python has a good type system, and the default tooling lets people miss it. Teams add type hints because they know they help, then forget to turn on strict checking. Or they configure it in the editor but not CI. Or one developer gets different errors from another, because the editor extension and the CLI checker aren’t the same version. Or the checker is technically present, but it lets untyped code drift through the codebase anyway.</p>

<p>Pylance even has several type checking modes. <a href="https://github.com/microsoft/pyright/blob/main/docs/configuration.md">Pyright’s configuration docs</a> describe <code class="language-plaintext highlighter-rouge">off</code>, <code class="language-plaintext highlighter-rouge">basic</code>, <code class="language-plaintext highlighter-rouge">standard</code>, and <code class="language-plaintext highlighter-rouge">strict</code>, with <code class="language-plaintext highlighter-rouge">standard</code> as the Pyright default. Pylance’s own <a href="https://github.com/microsoft/pylance-release/blob/main/docs/settings/python_analysis_typeCheckingMode.md"><code class="language-plaintext highlighter-rouge">python.analysis.typeCheckingMode</code> documentation</a> lists <code class="language-plaintext highlighter-rouge">off</code> as the Pylance default. Either way, strictness is something you have to opt into.</p>

<p>That’s the wrong default for AI-generated Python. An agent will happily write this:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">enrich_customer</span><span class="p">(</span><span class="n">customer</span><span class="p">,</span> <span class="n">metadata</span><span class="p">):</span>
    <span class="k">if</span> <span class="n">metadata</span><span class="p">[</span><span class="sh">"</span><span class="s">active</span><span class="sh">"</span><span class="p">]:</span>
        <span class="n">customer</span><span class="p">.</span><span class="n">score</span> <span class="o">=</span> <span class="n">metadata</span><span class="p">[</span><span class="sh">"</span><span class="s">score</span><span class="sh">"</span><span class="p">]</span>
    <span class="k">return</span> <span class="n">customer</span>
</code></pre></div></div>

<p>That code might be fine. It might also be nonsense. What’s <code class="language-plaintext highlighter-rouge">customer</code>? What’s <code class="language-plaintext highlighter-rouge">metadata</code>? Is <code class="language-plaintext highlighter-rouge">metadata["score"]</code> an <code class="language-plaintext highlighter-rouge">int</code>, a <code class="language-plaintext highlighter-rouge">float</code>, a <code class="language-plaintext highlighter-rouge">str</code>, or <code class="language-plaintext highlighter-rouge">None</code>? What happens when an agent refactors this later and invents a field that never existed?</p>

<p>In a dynamic language, that ambiguity spreads. The agent reads ambiguous code, writes more ambiguous code, and eventually your test suite is the only thing standing between you and a runtime failure.</p>

<p>Tests are necessary. Tests aren’t a type system.</p>

<h2 id="basedpyright-proves-the-demand-exists">Basedpyright Proves The Demand Exists</h2>

<p>Basedpyright is another sign the problem is real.</p>

<p><a href="https://docs.basedpyright.com/">Basedpyright</a> is a fork of Pyright that adds stricter defaults and reimplements some Pylance features outside the closed-source extension. That’s a clear signal from the community. People want stronger defaults. People want Pylance-like behavior in editors that aren’t official VS Code. People want the editor and the CLI to agree.</p>

<p>It solves real problems, and I’m glad it exists. But I wanted to go further. I didn’t want a better Pyright wrapper. I wanted a complete open-source Python developer experience that doesn’t fundamentally depend on Microsoft’s closed extension, Node.js, or a specific editor marketplace.</p>

<p>That’s Basilisk.</p>

<h2 id="what-basilisk-is">What Basilisk Is</h2>

<p><a href="https://www.basilisk-python.dev">Basilisk</a> is an open-source Python language server, type checker, debugger, and profiler. It’s written in Rust, ships as a single binary, and targets VS Code, Cursor, Windsurf, Antigravity, Zed, and Neovim.</p>

<p>The idea is simple:</p>

<p><strong>Python should have a complete open-source language server that works everywhere.</strong></p>

<p>Not a degraded fallback. Not a half-compatible substitute. Not a tool that only works properly in one editor. One language server, one set of diagnostics, one CLI, one editor experience.</p>

<p>Basilisk is strict by default. There’s no permissive mode to forget about, and no global “relax everything” switch. If a function parameter is untyped, Basilisk says so. If a function has no return annotation, Basilisk says so. If an assignment conflicts with its declared type, Basilisk says so.</p>

<p>For example:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">greet</span><span class="p">(</span><span class="n">name</span><span class="p">):</span>
    <span class="k">return</span> <span class="sh">"</span><span class="s">Hello </span><span class="sh">"</span> <span class="o">+</span> <span class="n">name</span>
</code></pre></div></div>

<p>Basilisk reports:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>error[BSK-E0001]: Missing parameter type annotation for `name`
error[BSK-E0002]: Missing return type annotation
</code></pre></div></div>

<p>The fixed version is explicit:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">greet</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="k">return</span> <span class="sh">"</span><span class="s">Hello </span><span class="sh">"</span> <span class="o">+</span> <span class="n">name</span>
</code></pre></div></div>

<p>This isn’t about making Python annoying. It’s about making contracts visible.</p>

<h2 id="the-checker-is-only-the-start">The Checker Is Only The Start</h2>

<p>The type checker is the wedge, but Basilisk isn’t only a type checker. The goal is a complete Python development stack:</p>

<ul>
  <li>Type checking and inference, strict by default</li>
  <li>LSP diagnostics, hover, go-to-definition, inlay hints, and code actions</li>
  <li>Refactorings like extract variable, extract function, inline variable, move to file, rename symbol, and change signature</li>
  <li>Debugging through <code class="language-plaintext highlighter-rouge">debugpy</code></li>
  <li>Profiling through <code class="language-plaintext highlighter-rouge">py-spy</code></li>
  <li>Ruff-backed formatting and import organization where it makes sense</li>
  <li>uv integration for modern Python project workflows</li>
</ul>

<p>I’m not interested in reinventing every good Python tool. Ruff is excellent. uv is excellent. debugpy and py-spy already exist. The point is to pull the developer experience together behind one open language server and make it portable across editors.</p>

<p>This matters more now, because AI agents are editor-native. Cursor, Windsurf, and Antigravity aren’t just editors with autocomplete. They’re agent environments. If Python language intelligence is degraded in those environments, the agent is degraded with it. An agent that can’t trust the type information in a project is a worse agent.</p>

<h2 id="lets-shake-things-up">Let’s Shake Things Up</h2>

<p>Tooling is not ideology. Use what works, ship software, and don’t waste your life performing loyalty to a toolchain. But some tooling decisions shape what the whole ecosystem can become.</p>

<p>When the best Python IDE experience only lives behind one proprietary extension, every new editor starts behind. Open-source builds start behind. Cloud IDEs start behind. AI-first forks start behind. Neovim and Zed users start behind. The community keeps rebuilding the same experience around a closed center, over and over.</p>

<p>The status quo just isn’t good enough anymore. Portable language intelligence is the entire promise of the LSP, and Python developers still don’t really have it. The way forward is to build open tools that are good enough to be the default everywhere.</p>

<p>That’s why Basilisk exists. It’s a pro-Python, pro-open-tooling project, for people who want the same excellent Python experience in Cursor, Windsurf, Antigravity, Zed, Neovim, VS Code, and whatever comes after them.</p>

<h2 id="where-basilisk-is-at">Where Basilisk Is At</h2>

<p>Basilisk is still early, and I won’t pretend otherwise. Pyright and Pylance are mature. mypy has the oldest ecosystem. Astral’s ty is pushing the space forward too. That’s a good thing. Python needs more serious type checking work, not less.</p>

<p>What makes Basilisk different is the stance:</p>

<ul>
  <li>Strict by default</li>
  <li>Open source</li>
  <li>Single Rust binary</li>
  <li>A complete LSP, not just a CLI checker</li>
  <li>The same experience across editors</li>
  <li>Built for the AI-agent era</li>
</ul>

<p>I built it because I wanted my Python agent code to be honest. I wanted missing types to be errors, not vibes. I wanted one language server that didn’t care whether I was in VS Code, Cursor, Windsurf, Antigravity, Zed, or Neovim.</p>

<p>Python developers shouldn’t have to choose between an amazing AI editor and a proper Python language experience. That’s the problem Basilisk is trying to fix.</p>

<p>If you’ve ever opened a VS Code fork, searched for Pylance, found some weird substitute, and wondered why Python suddenly felt second-class, Basilisk is for you.</p>

<p>Give it a try: <a href="https://www.basilisk-python.dev">basilisk-python.dev</a>.</p>

<p><small>Note that some or all of the content here was written with the assistance of AI. <a href="ai-writing">View the AI Content Policy</a></small></p>]]></content><author><name>Christian Findlay</name></author><category term="software development" /><category term="python" /><category term="ai" /><summary type="html"><![CDATA[Why I built Basilisk: an open-source Python language server, type checker, debugger, and profiler for VS Code, Cursor, Windsurf, Antigravity, Zed, and Neovim.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.christianfindlay.com/assets/images/blog/basilisk/header.webp" /><media:content medium="image" url="https://www.christianfindlay.com/assets/images/blog/basilisk/header.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">RestClient.Net 7: Compile-Time Safety and OpenAPI MCP Generation</title><link href="https://www.christianfindlay.com/blog/restclient-net-7" rel="alternate" type="text/html" title="RestClient.Net 7: Compile-Time Safety and OpenAPI MCP Generation" /><published>2025-10-21T00:00:00+00:00</published><updated>2025-10-21T00:00:00+00:00</updated><id>https://www.christianfindlay.com/blog/restclient-net-7</id><content type="html" xml:base="https://www.christianfindlay.com/blog/restclient-net-7"><![CDATA[<p>RestClient.Net 7 is a complete architectural rewrite from the ground up, bringing discriminated union-style Result types, compile-time exhaustiveness checking, and Model Context Protocol integration to C#. As a C# type-safe REST client, it follows C# HttpClient best practices while adding compile-time guarantees. If you’ve been burned by hidden exceptions or missed error cases in production, RestClient.Net 7 is the right choice for you.</p>

<h2 id="the-problem-c-doesnt-have-exhaustive-pattern-matching-on-type">The Problem: C# Doesn’t Have Exhaustive Pattern Matching on Type</h2>

<p>Traditional C# pattern matching has a critical flaw that causes production bugs:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">enum</span> <span class="n">Status</span> <span class="p">{</span> <span class="n">Success</span><span class="p">,</span> <span class="n">Error</span><span class="p">,</span> <span class="n">Pending</span> <span class="p">}</span>

<span class="c1">// This compiles just fine...</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="n">status</span> <span class="k">switch</span>
<span class="p">{</span>
    <span class="n">Status</span><span class="p">.</span><span class="n">Success</span> <span class="p">=&gt;</span> <span class="s">"OK"</span><span class="p">,</span>
    <span class="n">Status</span><span class="p">.</span><span class="n">Error</span> <span class="p">=&gt;</span> <span class="s">"Failed"</span><span class="p">,</span>
 <span class="c1">// Missing Status.Pending case</span>
<span class="p">};</span>
<span class="c1">// ...but throws SwitchExpressionException at runtime!</span>
</code></pre></div></div>

<p>When you’re making HTTP calls, you have multiple distinct failure modes: success responses, client errors (400s), server errors (500s), network timeouts, DNS failures, and serialization exceptions. Each requires different handling. Missing any case means production bugs.</p>

<p>This is probably going to become a core part of the C# language soon, but for the time being, <a href="https://github.com/dotnet/csharplang/blob/18a527bcc1f0bdaf542d8b9a189c50068615b439/proposals/TypeUnions.md">discriminated unions are only at the proposal</a> stage in the C# GitHub repo.</p>

<p>RestClient.Net 7 solves this problem now with exhaustive pattern matching that catches missing cases at compile-time, not runtime.</p>

<h2 id="result-types-no-exceptions">Result Types: No Exceptions</h2>

<p>RestClient.Net 7 uses the <a href="https://www.nuget.org/packages/Outcome">Outcome Nuget package</a>. It contains result types (basically, discriminated unions) like <code class="language-plaintext highlighter-rouge">Result&lt;TSuccess, HttpError&lt;TError&gt;&gt;</code>. They represent all possible outcomes explicitly:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">var</span> <span class="n">httpClient</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HttpClient</span><span class="p">();</span>

<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="n">httpClient</span><span class="p">.</span><span class="nf">GetAsync</span><span class="p">(</span>
    <span class="n">url</span><span class="p">:</span> <span class="s">"https://api.example.com/posts/1"</span><span class="p">.</span><span class="nf">ToAbsoluteUrl</span><span class="p">(),</span>
    <span class="n">deserializeSuccess</span><span class="p">:</span> <span class="n">DeserializePost</span><span class="p">,</span>
    <span class="n">deserializeError</span><span class="p">:</span> <span class="n">DeserializeError</span>
<span class="p">);</span>

<span class="c1">// The compiler FORCES you to handle all cases</span>
<span class="kt">var</span> <span class="n">output</span> <span class="p">=</span> <span class="n">result</span> <span class="k">switch</span>
<span class="p">{</span>
    <span class="nf">OkPost</span><span class="p">(</span><span class="kt">var</span> <span class="n">post</span><span class="p">)</span> <span class="p">=&gt;</span> 
        <span class="s">$"Success: </span><span class="p">{</span><span class="n">post</span><span class="p">.</span><span class="n">Title</span><span class="p">}</span><span class="s">"</span><span class="p">,</span>
    <span class="nf">ErrorPost</span><span class="p">(</span><span class="nf">ResponseErrorPost</span><span class="p">(</span><span class="kt">var</span> <span class="n">errorBody</span><span class="p">,</span> <span class="kt">var</span> <span class="n">statusCode</span><span class="p">,</span> <span class="n">_</span><span class="p">))</span> <span class="p">=&gt;</span> 
        <span class="s">$"HTTP Error </span><span class="p">{</span><span class="n">statusCode</span><span class="p">}</span><span class="s">: </span><span class="p">{</span><span class="n">errorBody</span><span class="p">.</span><span class="n">Message</span><span class="p">}</span><span class="s">"</span><span class="p">,</span>
    <span class="nf">ErrorPost</span><span class="p">(</span><span class="nf">ExceptionErrorPost</span><span class="p">(</span><span class="kt">var</span> <span class="n">exception</span><span class="p">))</span> <span class="p">=&gt;</span> 
        <span class="s">$"Network Exception: </span><span class="p">{</span><span class="n">exception</span><span class="p">.</span><span class="n">Message</span><span class="p">}</span><span class="s">"</span><span class="p">,</span>
 <span class="c1">// Missing a case? Your build fails!</span>
<span class="p">};</span>
</code></pre></div></div>

<p>There are exactly three possible outcomes in the closed type hierarchy:</p>

<ol>
  <li><strong>Ok</strong> - Success with your data</li>
  <li><strong>Error with ResponseError</strong> - HTTP error response (4xx, 5xx) with the error body</li>
  <li><strong>Error with ExceptionError</strong> - Network exception (timeout, DNS failure, connection refused)</li>
</ol>

<p>No hidden exceptions. No surprise throws. All error paths are visible in your code.</p>

<h2 id="exhaustion-analyzer--compile-time-safety-net">Exhaustion Analyzer:  Compile-Time Safety Net</h2>

<p>Here’s how I fixed a core deficiency in C#. RestClient.Net 7 includes the <a href="https://www.nuget.org/packages/Exhaustion">Exhaustion package</a> I created specifically for this purpose. It’s a Roslyn analyzer that provides compile-time exhaustiveness checking. If you forget to handle a case, your build fails with this error:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>error EXHAUSTION001: Switch on Result is not exhaustive;
Matched: Ok&lt;Post, HttpError&lt;ErrorResponse&gt;&gt;, 
 Error&lt;Post, HttpError&lt;ErrorResponse&gt;&gt; with ErrorResponseError&lt;ErrorResponse&gt;
Missing: Error&lt;Post, HttpError&lt;ErrorResponse&gt;&gt; with ExceptionError&lt;ErrorResponse&gt;
</code></pre></div></div>

<p>This shifts errors from runtime (production) to compile-time (development). You literally cannot ship code that doesn’t handle all possible outcomes. For critical applications involving payments, authentication, or data processing, this compile-time guarantee is invaluable.</p>

<h2 id="model-context-protocol-make-your-apis-ai-ready">Model Context Protocol: Make Your APIs AI-Ready</h2>

<p>RestClient.Net 7 appears to be the first .NET HTTP client with native <a href="https://modelcontextprotocol.io/">Model Context Protocol</a> server code generation. MCP is Anthropic’s open standard (now adopted by OpenAI and Google) that lets AI applications like Claude Code or Cursor discover and invoke your API operations.</p>

<p>Here’s how it works:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Install the MCP generator</span>
dotnet add package RestClient.Net.McpGenerator.Cli

<span class="c"># Generate an MCP server from your OpenAPI spec</span>
dotnet run <span class="nt">--project</span> RestClient.Net.McpGenerator.Cli <span class="nt">--</span> <span class="se">\</span>
 <span class="nt">--openapi-url</span> api.yaml <span class="se">\</span>
  <span class="nt">--output-file</span> Generated/McpTools.g.cs <span class="se">\</span>
 <span class="nt">--namespace</span> YourApi.Mcp <span class="se">\</span>
  <span class="nt">--server-name</span> YourApi
</code></pre></div></div>

<p>Now AI agents can discover and call your API operations through natural language. You’ve essentially created a bridge between your enterprise APIs and the GenAI ecosystem. This positions your APIs so that AI can easily leverage them. You can essentially automate your app with an AI agent.</p>

<h2 id="openapi-code-generation-with-type-safety">OpenAPI Code Generation with Type Safety</h2>

<p>RestClient.Net 7 generates type-safe C# clients from OpenAPI 3.x specifications:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dotnet add package RestClient.Net.OpenApiGenerator
</code></pre></div></div>

<p>Unlike other generators that just produce DTOs, RestClient.Net generates complete extension methods on HttpClient with Result type aliases for pattern matching:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// All of this is generated from your OpenAPI spec</span>
<span class="kt">var</span> <span class="n">user</span> <span class="p">=</span> <span class="k">await</span> <span class="n">httpClient</span><span class="p">.</span><span class="nf">GetUserById</span><span class="p">(</span><span class="s">"123"</span><span class="p">,</span> <span class="n">cancellationToken</span><span class="p">);</span>

<span class="kt">var</span> <span class="n">message</span> <span class="p">=</span> <span class="n">user</span> <span class="k">switch</span>
<span class="p">{</span>
    <span class="nf">OkUser</span><span class="p">(</span><span class="kt">var</span> <span class="n">success</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="s">$"User: </span><span class="p">{</span><span class="n">success</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s">"</span><span class="p">,</span>
    <span class="nf">ErrorUser</span><span class="p">(</span><span class="nf">ResponseError</span><span class="p">(</span><span class="kt">var</span> <span class="n">body</span><span class="p">,</span> <span class="m">404</span><span class="p">,</span> <span class="n">_</span><span class="p">))</span> <span class="p">=&gt;</span> <span class="s">"User not found"</span><span class="p">,</span>
    <span class="nf">ErrorUser</span><span class="p">(</span><span class="nf">ResponseError</span><span class="p">(</span><span class="n">_</span><span class="p">,</span> <span class="kt">var</span> <span class="n">status</span><span class="p">,</span> <span class="n">_</span><span class="p">))</span> <span class="k">when</span> <span class="n">status</span> <span class="p">&gt;=</span> <span class="m">500</span> <span class="p">=&gt;</span> 
        <span class="s">"Service unavailable"</span><span class="p">,</span>
    <span class="nf">ErrorUser</span><span class="p">(</span><span class="nf">ExceptionError</span><span class="p">(</span><span class="kt">var</span> <span class="n">ex</span><span class="p">))</span> <span class="p">=&gt;</span> <span class="s">$"Network error: </span><span class="p">{</span><span class="n">ex</span><span class="p">.</span><span class="n">Message</span><span class="p">}</span><span class="s">"</span><span class="p">,</span>
<span class="p">};</span>
</code></pre></div></div>

<p>Notice how the generated code maintains compile-time safety throughout. If you add a new error type to your result type, every switch expression fails to compile until you handle it. It makes the error handling impossible to miss.</p>

<h2 id="design-philosophy-functional-programming-in-c">Design Philosophy: Functional Programming in C#</h2>

<p>RestClient.Net 7 embraces functional programming principles:</p>

<ul>
  <li><strong>Pure functions</strong> with no interfaces to mock</li>
  <li><strong>Immutable types</strong> throughout (including the Urls library for safe URL construction)</li>
  <li><strong>Zero exceptions</strong> - Result types instead of throwing</li>
  <li><strong>Explicit over implicit</strong> - all error paths visible</li>
</ul>

<p>The architecture uses extension methods on HttpClient, which means it works naturally with <code class="language-plaintext highlighter-rouge">IHttpClientFactory</code> for proper connection pooling. No more socket exhaustion issues:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">httpClient</span> <span class="p">=</span> <span class="n">httpClientFactory</span><span class="p">.</span><span class="nf">CreateClient</span><span class="p">();</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="n">httpClient</span><span class="p">.</span><span class="nf">GetAsync</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">deserialize</span><span class="p">,</span> <span class="n">deserializeError</span><span class="p">);</span>
</code></pre></div></div>

<p>For testing, use the standard <code class="language-plaintext highlighter-rouge">DelegatingHandler</code> pattern - no special mocking framework required.</p>

<h2 id="getting-started">Getting Started</h2>

<p>Install the core package:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dotnet add package RestClient.Net
</code></pre></div></div>

<p>This includes the Result types (Outcome package), Exhaustion analyzer, and Urls library. For OpenAPI generation, add:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dotnet add package RestClient.Net.OpenApiGenerator
</code></pre></div></div>

<p>Check out the <a href="https://github.com/MelbourneDeveloper/RestClient.Net/tree/main/Samples">samples folder</a> for complete working examples.</p>

<h2 id="why-this-matters">Why This Matters</h2>

<p>I’ve been working on RestClient.Net for over seven years, and version 7 represents everything I’ve learned about making HTTP calls safely and reliably. The combination of Result types, exhaustive pattern matching, and MCP integration creates something unique in the .NET ecosystem.</p>

<p>If you’re building AI-powered applications, need compile-time guarantees for critical systems, or just want to stop debugging production crashes from missing error cases, give RestClient.Net 7 a try. The analyzer will make your life easier.</p>

<p>The package targets .NET Standard 2.1, which means you can use it with .NET Framework 4.5+ through .NET 9+, supports all platforms, and maintains <a href="https://github.com/MelbourneDeveloper/RestClient.Net/blob/8f997cd3dfde475c58cc9d6a6f068412e68ce80a/.github/workflows/pr-build.yml#L40">100% test coverage with a 100%+ mutation score</a> according to <a href="https://stryker-mutator.io/docs/stryker-net/introduction/">Stryker Mutator</a>. It has an MIT license and is ready for production use.</p>

<p><strong>Try it</strong>: Install the package and experience compile-time safety with exhaustive pattern matching. Generate an MCP server from your OpenAPI spec. Let the compiler catch bugs before they reach production.</p>

<p>The safest way to make REST calls in C# is here.</p>]]></content><author><name>Christian Findlay</name></author><category term="dotnet" /><category term="restclient-net" /><category term="csharp" /><category term="functional-programming" /><category term="mcp" /><category term="openapi" /><summary type="html"><![CDATA[Generate an MCP server from OpenAPI in .NET and use exhaustive pattern matching in C# with RestClient.Net 7, a type-safe REST client.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.christianfindlay.com/assets/images/blog/restclientdotnet/logo.png" /><media:content medium="image" url="https://www.christianfindlay.com/assets/images/blog/restclientdotnet/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">AI Content Policy</title><link href="https://www.christianfindlay.com/blog/ai-writing" rel="alternate" type="text/html" title="AI Content Policy" /><published>2025-09-30T00:00:00+00:00</published><updated>2025-09-30T00:00:00+00:00</updated><id>https://www.christianfindlay.com/blog/ai-writing</id><content type="html" xml:base="https://www.christianfindlay.com/blog/ai-writing"><![CDATA[<p>AI has recently become capable of producing high-quality written content. While some people dispute how well AI can write, it’s undeniable that it affects the way content gets created. I want to make it clear that, from now on, I will utilise AI to generate content, provide fact checks, conduct research, and edit content. However, the quality of this website will not diminish. Actually, it’s going to get better.</p>

<h2 id="why-ai-generated-content">Why AI-Generated Content?</h2>
<p>This is a small blog, and I don’t have unlimited time to deliver content. However, I want to deliver content that is of the same quality as prior to AI or better, and I want to deliver more of it. AI allows me to do this. <a href="https://chatgpt.com">ChatGPT</a> alone does not produce well-written content, but a combination of <a href="https://www.anthropic.com/news/research">Claude AI Research</a> and <a href="https://openai.com/index/introducing-deep-research/">OpenAI Deep Research</a> with AI agents like Claude Code produces amazing content that draws on extensive sources.</p>

<h2 id="googles-ai-summaries-impacted-my-traffic">Google’s AI Summaries Impacted My Traffic</h2>

<p>Recently, my monthly statistics took a pounding. It wasn’t quite clear why this happened. At first, I thought it was because of changes to Google’s algorithm, but then I learned that <a href="https://www.theguardian.com/technology/2025/jul/24/ai-summaries-causing-devastating-drop-in-online-news-audiences-study-finds">Google’s AI summaries caused a devastating drop</a> in many websites.</p>

<p>It turns out that <a href="https://ahrefs.com/blog/ai-generated-content-does-not-hurt-your-google-rankings/">AI-generated content does not hurt Google rankings</a>, but I cannot justify spending the amount of time I was spending on writing content when the returns are diminishing.</p>

<h2 id="my-commitment">My Commitment</h2>
<p>I am committed to posting informative, original and accurate content on this website. I will meticulously review all content, edit it for readability and accuracy, and ensure the same high level of quality that this website has always maintained.</p>

<p>So far, what I’m seeing is that AI research tools greatly enhance my ability to provide accurate background information, ensure links are correct and provide plenty of further reading.</p>

<h2 id="content-changes">Content Changes</h2>
<p>The content on this website is changing. I will post more long-form articles, with a greater emphasis on research and a reduced emphasis on tutorials. This isn’t because AI creates a firehose of cheap content for this purpose. This is because this is the kind of content that I have always wanted to post on this blog, but it was too time-consuming without AI. Moreover, AI makes code samples trivial, and few people click through to my tutorials just for the purpose of seeing tutorials that AI can often write for them.</p>

<p>As always, please report issues on the <a href="https://github.com/MelbourneDeveloper/ChristianFindlay.com/issues">issues section</a> to improve article quality.</p>]]></content><author><name>Christian Findlay</name></author><category term="tech" /><summary type="html"><![CDATA[Read about the written AI content policy for ChristianFindlay.com]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.christianfindlay.com/assets/images/blog/ai/aicontent.png" /><media:content medium="image" url="https://www.christianfindlay.com/assets/images/blog/ai/aicontent.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Screen Time Gaslighting: Devices Are Not To Blame For Declining Play</title><link href="https://www.christianfindlay.com/blog/screen-gaslighting" rel="alternate" type="text/html" title="Screen Time Gaslighting: Devices Are Not To Blame For Declining Play" /><published>2025-09-30T00:00:00+00:00</published><updated>2025-09-30T00:00:00+00:00</updated><id>https://www.christianfindlay.com/blog/screen-gaslighting</id><content type="html" xml:base="https://www.christianfindlay.com/blog/screen-gaslighting"><![CDATA[<p>Parents receive endless judgment about their kids’ screen time. Apparently, they’re responsible for raising a generation glued to devices. We all agree that outdoor activities, exercise, and socialisation are critical for childhood development. We all know that this has been in decline in the modern world. But the actual research doesn’t show that screens are stealing outdoor childhood. The economic realities and the systematic destruction of public infrastructure are to blame for this problem.</p>

<p>A <a href="https://ijbnpa.biomedcentral.com/articles/10.1186/s12966-021-01097-9">comprehensive systematic review of 107 studies across 29 countries</a> found that <strong>screen time didn’t even reach statistical significance as a predictor of outdoor play in most studies examined</strong>. Meanwhile, economic and environmental factors were the real drivers. We’ve dismantled everything that made outdoor play possible. Then, various parties dared to blame parents for the mess we created.</p>

<h4 id="australian-government-social-media-ban">Australian Government Social Media Ban</h4>

<p>The Australian government is in the <a href="https://apo.org.au/node/328608">process of banning social social media for children under sixteen</a>. This article is part of a series I am writing about this topic. I encourage you to read this <a href="https://apo.org.au/node/328608">open letter</a>, and write to the relevant people who can make changes to this process.</p>

<ul>
  <li><strong>Anika Wells MP</strong> — <a href="mailto:Anika.Wells.MP@aph.gov.au">Anika.Wells.MP@aph.gov.au</a> (<a href="https://www.aph.gov.au/Senators_and_Members/Parliamentarian?MPID=264121&amp;lang=en&amp;utm_source=chatgpt.com" title="Hon Anika Wells MP - Parliament of Australia">aph.gov.au</a>)</li>
  <li><strong>Minister Wells (Communications Office)</strong> — <a href="mailto:Minister.Wells@mo.communications.gov.au">Minister.Wells@mo.communications.gov.au</a> (<a href="https://www.anikawells.com.au/contact/?utm_source=chatgpt.com" title="Contact - Anika Wells">anikawells.com.au</a>)</li>
  <li><strong>Michelle Rowland MP</strong> — via contact form (Attorney-General) on ministers.ag.gov.au (<a href="https://ministers.ag.gov.au/hon-michelle-rowland-mp/contact?utm_source=chatgpt.com" title="The Hon Michelle Rowland MP − Contact | Our ministers – Attorney ...">ministers.ag.gov.au</a>)</li>
  <li><strong>Minister Rowland (Communications)</strong> — <a href="mailto:Minister.rowland@mo.communications.gov.au">Minister.rowland@mo.communications.gov.au</a> (<a href="https://minister.infrastructure.gov.au/rowland/contact?utm_source=chatgpt.com" title="The Hon Michelle Rowland MP contact details | Ministers for the ...">Infrastructure Ministers Australia</a>)</li>
</ul>

<h2 id="when-yards-become-luxury-goods">When Yards Become Luxury Goods</h2>

<p>The <a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC7306417/">NET-Works longitudinal study</a> followed 531 children over three years using objective measurements. It made a critical finding: each additional hectare of yard space increased children’s physical activity by 12.72 minutes per week. This isn’t a wishy-washy correlation. It’s proven causation, with rigorous methodology controlling for previous activity levels and confounders. Yet, housing affordability has reached crisis levels. The Economic Policy Institute <a href="https://www.epi.org/publication/press_releases_hardships/">found that 29% of working families with children can’t afford basic necessities</a>, including adequate housing. When housing costs consume family budgets, kids’ enrichment activities, including outdoor play, often get jettisoned.</p>

<p>The <a href="https://www.mdpi.com/1660-4601/18/7/3617">Canadian Multi-Site Study</a> revealed the harsh reality. Rural children receive 8.4 hours of outdoor play per week, while urban kids receive only 6.8 hours. This isn’t about screens. Urban children don’t have more devices than rural kids. It’s about space. When <a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC6843675/">urban families are stuck in apartments, rentals, and properties without yards</a>, children’s indoor lives reflect economic constraints, not parental failures. The evidence confirms what struggling families already know: outdoor play has become a privilege of property ownership.</p>

<h2 id="vanished-infrastructure-and-fortress-childhood">Vanished Infrastructure And Fortress Childhood</h2>

<p>The way we parent has changed over time. In 1971, 80% of seven and eight-year-olds travelled to school independently. <a href="https://ora.ox.ac.uk/objects/uuid:89103856-a239-489a-8e7e-b6c1bad43a0f">By 1990, that figure crashed to just 9%</a>. That’s an 88% decline that happened decades before smartphones even existed. This transformation reflects fundamental changes in community design and social expectations. It’s not a technology apocalypse.</p>

<p>The UK shows just how far infrastructure has collapsed: <strong><a href="https://www.holcim.co.uk/news-and-resources/press-releases/right-play-fois-reveal-huge-decline-play-park-facilities-uk">793 playgrounds closed over the past decade</a></strong>, with public play area spending <a href="https://www.api-play.org/news-events/nowhere-to-play/">crashing 44% since 2017</a>. Meanwhile, organised activities have replaced free play, with families now forking out <a href="https://projectplay.org/state-of-play-2022/costs-to-play-trends">$883 per sport per child</a>, creating a two-tier system where outdoor engagement is financially accessible only to those who can afford it. <a href="https://www.solutionhealth.org/2024/06/04/overbooked-kids-the-overscheduled-epidemic/">Barker and colleagues</a> found that kids in heavily structured activities showed “greater challenges with self-directed executive function.” Yet, parents get judged whether they can afford organised sports or have to rely on free screen-based activities.</p>

<p>The fear of crime significantly influences parental decisions regarding outdoor play. Mothers are more likely than fathers to report such fears due to heightened vulnerability concerns for their children. <a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC3172153/">Research shows that parents respond to neighbourhood violence</a> by avoiding certain locations, which limits activity times and requires them to supervise their siblings. The <a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC3058513/">Fragile Families Study</a> found that mothers’ outdoor play fears correlated with neighbourhood poverty and low collective efficacy. It had little to do with actual crime rates. These fears reflect genuine structural problems. Communities often feature car-centred design, lack sidewalks, and are built as if children don’t exist.</p>

<h2 id="the-screen-time-myth-crumbles-under-scrutiny">The Screen Time Myth Crumbles Under Scrutiny</h2>

<p>Meta-analyses by leading researchers like <a href="https://journals.sagepub.com/doi/10.1177/0956797619830329">Amy Orben and Andrew Przybylski</a> consistently find tiny or nonexistent effects when proper controls are applied. The <a href="https://www.frontiersin.org/journals/public-health/articles/10.3389/fpubh.2022.1042822/full">GECKO Drenthe Birth Cohort’s</a> longitudinal data revealed that the supposed displacement effect is laughably minimal. Every additional 10 minutes of daily screen time correlated with just one minute less outdoor play. <a href="https://www.frontiersin.org/journals/public-health/articles/10.3389/fpubh.2022.1042822/full">Socioeconomic status proved a significant predictor of both behaviours</a>. At 3-4 years, low-income children showed more outdoor play. By the ages of 10-11, high-income children had caught up in outdoor play and showed less screen time at higher usage levels, while patterns suggested that low-income children had relatively more screen time.</p>

<p>Orben’s analysis of <a href="https://www.ox.ac.uk/news/2019-01-15-technology-use-explains-most-04-adolescent-wellbeing">17,247 adolescents found technology use explained at most 0.4% of wellbeing variance</a>. <a href="https://journals.sagepub.com/doi/10.1177/0956797619830329">Wearing glasses showed stronger negative associations than screens</a>. When researchers <a href="https://www.mdpi.com/1660-4601/17/10/3661">preregistered their analyses</a> to prevent cherry-picking results, screen effects basically disappeared. The evidence is clear: apparent screen impacts vanish when studies control for family income, neighbourhood quality, and parental resources. These are the actual determinants of childhood experiences.</p>

<p>Research from <a href="https://taughtbyfinland.com/finnish-style-recess-at-a-u-s-school/">Finland</a>, where children maintain high outdoor engagement despite full technology access, proves screens don’t inherently reduce outdoor play. Finnish schools mandate 15-minute outdoor breaks between 45-minute classes, regardless of the weather. This demonstrates that cultural and structural support trumps any supposed screen magnetism.</p>

<h2 id="individual-differences-and-systemic-blame">Individual Differences And Systemic Blame</h2>

<p>Not all kids are wired for rough-and-tumble outdoor play. Research from the <a href="https://www.frontiersin.org/journals/integrative-neuroscience/articles/10.3389/fnint.2020.00022/full">University of Valencia</a> found <a href="https://www.frontiersin.org/journals/integrative-neuroscience/articles/10.3389/neuro.07.029.2009/full">42-88% of autistic children</a> and <a href="https://ijbnpa.biomedcentral.com/articles/10.1186/1479-5868-10-102">50% of children with ADHD</a> experience sensory processing differences that affect activity preferences. These children may find outdoor environments overwhelming, regardless of whether screens are available. Their needs reflect neurology, not parenting failures.</p>

<p>Parents often experience significant confusion and stress when making decisions about screen time. <a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC10039437/">Research shows</a> mothers tend to be more restrictive about screen time than fathers, who take a more relaxed approach. Yet, both struggle with unclear and conflicting guidelines from experts. This is not parental failure. It reflects the impossible position parents face: navigating ubiquitous digital technology without consistent evidence-based guidance. This follows what <a href="https://journals.sagepub.com/doi/full/10.1177/1745691620919372">Christopher Ferguson</a> calls the “Sisyphean cycle of technology panics”. Every generation’s new technology becomes the scapegoat for complex social problems. The pattern repeats: radio, television, video games, now smartphones, all blamed for issues actually rooted in economic inequality.</p>

<p>The childcare crisis exposes this cruel dynamic. <a href="https://www.ffyf.org/resources/2023/03/the-first-five-things-you-need-to-know-impact-of-the-child-care-crisis-on-women-mothers/">46% of unemployed mothers cite childcare problems</a> as their reason for leaving the workforce. Quality childcare is <a href="https://www.epi.org/child-care-costs-in-the-united-states/">devouring huge chunks of modest incomes</a>. So, screens become a necessity, not a choice. As one researcher noted, the idea that all families can fill evenings with “enriching games of chess” rather than screens reflects profound economic privilege.</p>

<h2 id="when-barriers-are-removed-children-play">When Barriers Are Removed, Children Play</h2>

<p>The evidence becomes crystal clear when you examine what happens when barriers are removed. Australia’s <a href="https://ijbnpa.biomedcentral.com/articles/10.1186/s12966-017-0625-5">REVAMP study</a> documented a <strong>670% increase</strong> in play area visitation after playground installation, with no changes to screen policies. Belgium’s <a href="https://ijbnpa.biomedcentral.com/articles/10.1186/s12966-015-0171-y">Play Streets intervention</a>, temporarily closing streets to traffic, increased children’s outdoor play from 2 to 3 days weekly, with <a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC4334854/">62% of parents reporting it replaced screen time</a>. Children had full device access but chose outdoor play when given safe and accessible spaces.</p>

<p>The <a href="https://ijbnpa.biomedcentral.com/articles/10.1186/s12966-021-01097-9">systematic review of 107 studies</a> found the factors that actually predict outdoor play: yard access, neighbourhood greenness, traffic safety, parental support, and weather. Screen time appeared nowhere on this list. When economic barriers were removed through free programs, safe streets, or quality public spaces, children naturally engaged outdoors, regardless of their access to technology.</p>

<h2 id="stop-gaslighting-parents">Stop Gaslighting Parents</h2>

<p>The research reveals a harsh reality: society has systematically eroded the conditions necessary for outdoor childhood, driven by housing costs that force families into apartment complexes without yards, the elimination of public play spaces, car-centric community design, and excessive work demands. <a href="https://onlinelibrary.wiley.com/doi/10.1111/tesg.12505">Research shows</a> parents’ safety concerns severely restrict children’s free play, with mothers particularly affected by the “culture of fear”. They internalise the belief that allowing unsupervised outdoor play would label them a “bad parent”. This is true even when actual risks are disproportionately small.</p>

<p>The evidence is crystal clear: children don’t lack outdoor play because of screens. They lack it because adults built a world hostile to childhood, eliminated <a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC1963283/">public goods that supported play</a>, and created <a href="https://onlinelibrary.wiley.com/doi/10.1111/cdev.13365">economic conditions forcing impossible choices</a> onto families. Devices are only a scapegoat. Until we address housing affordability, rebuild public play infrastructure, design walkable communities, and support working families with real childcare solutions, blaming parents for excessive screen time remains nothing other than gaslighting.</p>

<p><small>Note that some or all of the content here was written with the assistance of AI. <a href="ai-writing">View the AI Content Policy</a></small></p>]]></content><author><name>Christian Findlay</name></author><category term="tech" /><summary type="html"><![CDATA[Read about what academic studies say about the link between screen time and outdoor play in children. Learn about the hidden causes in the change of outdoor play that the media rarely talks about.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.christianfindlay.com/assets/images/blog/screens/header.png" /><media:content medium="image" url="https://www.christianfindlay.com/assets/images/blog/screens/header.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Introduction To Development With AI Coding Agents</title><link href="https://www.christianfindlay.com/blog/intro-ai-agent-dev" rel="alternate" type="text/html" title="Introduction To Development With AI Coding Agents" /><published>2025-04-10T00:00:00+00:00</published><updated>2025-04-10T00:00:00+00:00</updated><id>https://www.christianfindlay.com/blog/intro-ai-agent-dev</id><content type="html" xml:base="https://www.christianfindlay.com/blog/intro-ai-agent-dev"><![CDATA[<blockquote>
  <p><strong>Key Takeaways</strong>: AI coding agents are IDE-based (Cursor, Copilot, Cline), CLI-based (Claude Code, Aider), or cloud-based (Devin). Build custom agents with LangChain/LangGraph. Python dominates the AI agent space. Agents will change how we write code within 5 years.</p>
</blockquote>

<p>AI agents are new LLM (AI) based tools tools that can really enhance your productivity. As AI agents for software development mature, they are transforming how we approach coding, testing, and deployment tasks. This article introduces you to the various tooling available right now, talks a little about the theory and mentions how development-based AI agents might evolve over the next few years.</p>

<h2 id="what-are-ai-agents">What Are AI Agents?</h2>
<p>AI agents are virtual assistants that can take action to achieve specific goals on your behalf. <a href="https://www.oracle.com/au/artificial-intelligence/ai-agents/">According to Oracle</a>,</p>

<blockquote>
  <p>AI agents are software entities that can be assigned tasks, examine their environments, take actions as prescribed by their roles, and adjust based on their experiences.</p>
</blockquote>

<p>They can operate autonomously, use tools, and make and implement plans. This is different from something like ChatGPT, which simply responds to your messages.</p>

<blockquote>
  <p>For example, you could create an agent to know everything about your company’s product catalog so it can draft detailed responses to customer questions or automatically compile product details for an upcoming presentation.</p>
</blockquote>

<p><a href="https://news.microsoft.com/source/features/ai/ai-agents-what-they-are-and-how-theyll-change-the-way-we-work/">Microsoft</a></p>

<p>The term “agentic AI” refers to systems that actively pursue goals and objectives versus merely performing simple tasks or responding to queries.</p>

<h2 id="what-are-software-development-ai-agents-or-coding-agents">What are Software Development AI Agents or Coding Agents?</h2>

<p>Most agents perform specific types of tasks. Coding agents perform software development tasks like writing or refactoring code, writing tests, or performing other software development activities. They have access to your code and can run CLI tools to perform tasks on your computer.</p>

<h2 id="types-of-coding-agents">Types of Coding Agents</h2>

<h3 id="ide-based-agents">IDE-Based Agents</h3>

<p>IDE-based AI agents are built into your development environment. In any AI coding assistant tools comparison, these rank among the most accessible options. They offer real-time assistance as you code. These tools provide code completion with AI and automated refactoring and can respond to natural language. They usually have a chat interface, much like ChatGPT. Most importantly, the have built-in AI agents that have access to your code and can edit it directly as you work.</p>

<p><strong>Examples:</strong></p>

<ul>
  <li><a href="https://www.cursor.com"><strong>Cursor:</strong></a> A popular AI-powered code editor based on a fork of Vscode that offers features like intelligent code completion and real-time assistance[5].</li>
</ul>

<!-- <video controls src="[/assets/images/blog/nimble_charts/iosdemo.mp4](https://assets.basehub.com/191e7e6d/2c99e8a087f981290dc74d2b621a7192/current-best-for-two-mp4.mp4)" title="Cursor"  width="100%" height="360" style="border-radius: 6px;align-items: center;" alt="Cursor Demo"></video> -->

<ul>
  <li>
    <p><a href="https://github.com/features/copilot"><strong>GitHub Copilot:</strong></a> GitHub’s flagship AI integration product. This VSCode extension recently gained an AI agent.</p>
  </li>
  <li>
    <p><a href="https://cline.bot/"><strong>Cline:</strong></a> Another Vscode extension with similar agent functionality to Cursor. This agent has a pay-as-you-go model but tends to be more accurate than the others, in my experience.</p>
  </li>
</ul>

<h3 id="cli-based-agents">CLI-Based Agents</h3>

<p>CLI agents allow developers to interact with the terminal using natural language. You can give instructions in plain English instructions and the agent will attempt to execute the commands to achieve your goals.</p>

<p><strong>Examples:</strong></p>

<ul>
  <li><a href="https://github.com/Nimblesite/nimble_agent"><strong>Nimble agent:</strong></a> This my own open source porject. It’s not complete, but I am looking for contributors. It’s a LangChain, CLI based AI coding assistant and library aimed at fixing common issues that exist in other AI agents.</li>
</ul>

<video controls="" width="100%" height="360" poster="/assets/images/blog/ai-agent/moviethumbnail.png" style="border-radius: 6px; max-width: 100%; display: block; margin: 20px auto;" title="Nimble Agent Demo">
  <source src="/assets/images/blog/ai-agent/nimble_agent.mov" type="video/quicktime" />
  Your browser does not support the video tag. You can <a href="/assets/images/blog/ai-agent/video/nimble_agent.mov" download="">download the video</a> instead.
</video>

<ul>
  <li>
    <p><a href="https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview"><strong>Claude code:</strong></a> This is a very popular agent from Anthropic, the maker of the Claude model. This has a pay-as-you-go model and uses the Claude model.</p>
  </li>
  <li>
    <p><a href="https://github.com/Aider-AI/aider"><strong>Aider</strong></a> This has emerged as a very popular and stable open-source tool and allows you to BYO API key.</p>
  </li>
</ul>

<p><img src="https://camo.githubusercontent.com/6d2d9a8d839bed3d9dc1bf62d47f0767e19906ce76d369a78ef9805dbfb34609/68747470733a2f2f61696465722e636861742f6173736574732f73637265656e636173742e737667" alt="Aider" /></p>

<h3 id="cloud-based-agents">Cloud-Based Agents</h3>

<p>Cloud-based AI agents operate remotely, offering powerful capabilities without the constraints of local computing resources.</p>

<p><strong>Examples:</strong></p>

<ul>
  <li><a href="https://devin.ai"><strong>Devin.ai:</strong></a> boasts the ability to act as a virtual programmer in the cloud but with a pretty hefty price tag.</li>
</ul>

<iframe width="100%" height="315" src="https://www.youtube.com/embed/fjHtjT7GO1c" title="Devin AI Demo" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="">
</iframe>

<ul>
  <li><strong><a href="https://aws.amazon.com/q/developer/">Amazon Q Developer</a>:</strong> A cloud based AI coding agent from Amazon.</li>
</ul>

<h2 id="agent-frameworks">Agent Frameworks</h2>

<p>You can build your own AI agent. Agents extend basic LLM functionality. This usually involves getting the LLM to produce a plan to achieve the goal and then executing that plan in steps with tools. Surprisingly, it’s not that hard, and you can build one with no framework at all in your favorite language.</p>

<p>However, the game is changing so quickly that it will be almost impossible to keep up with the feature sets of all the other AI agents that you can use for free. So, if you do want to build an agent, check out one of these frameworks. You will be able to leverage code that many large companies are already using and get features like <a href="https://modelcontextprotocol.io">MCP Server</a> integration out of the box.</p>

<ul>
  <li>
    <p><a href="https://www.langchain.com/"><strong>LangChain</strong></a> specializes in creating multi-step language processing workflows. It <a href="https://www.getzep.com/ai-agents/langchain-agents-langgraph">excels in tasks like content generation and language translation by chaining together different language model operations</a>. It’s particularly useful for building simple, linear LLM workflows with strong modularity.</p>
  </li>
  <li>
    <p><a href="https://www.langchain.com/langgraph"><strong>LangGraph</strong></a> takes things further by offering a flexible framework for building stateful applications. It handles complex scenarios involving multiple agents and <a href="https://www.getzep.com/ai-agents/langchain-agents-langgraph">facilitates human-agent collaboration with features like built-in statefulness and human-in-the-loop workflows</a>.</p>
  </li>
</ul>

<h3 id="python-is-king">Python Is King</h3>

<p>Frankly, I’m not really sure why ML and AI engineers have opted for Python, especially when Python uses dynamic typing, and this confuses the hell out of AI agents. But this is where the industry is at. However, it’s not all bad. Python does have a great type system if you extend it with type hints.</p>

<p>When working with AI agent frameworks like LangChain, Python’s type hinting system becomes invaluable. <a href="https://dagster.io/blog/python-type-hinting">Type hints promote clear and reliable code</a> by providing explicit information about the data types your functions expect and return. This makes the code a lot easier for agents to navigate.</p>

<h2 id="conclusion">Conclusion</h2>

<p>AI agents absolutely will change software development. Whether integrated into your IDE, accessible via the command line, or powered by cloud resources, these tools will dramatically increase productivity.</p>

<p>The landscape is rapidly evolving. The competition between established players and newcomers, particularly from China’s tech ecosystem, promises to drive innovation and accessibility in the coming years. There are tonnes of AI agents floating around and they are getting better and better all the time.</p>

<p>Experimenting with these AI agents offers productivity gains today. Your company needs to leverage these tools or they will lose the competitive edge. And you need to learn how to automate some of your jobs because the software engineering profession is changing quickly. Developers won’t be writing the majority of code by hand in five years.</p>]]></content><author><name>Christian Findlay</name></author><category term="software development" /><category term="ai" /><summary type="html"><![CDATA[Compare the best AI coding agents in 2026: Cursor, GitHub Copilot, Claude Code, Aider, and Devin. Covers agent frameworks like LangChain and LangGraph.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.christianfindlay.com/assets/images/blog/ai-agent/header.webp" /><media:content medium="image" url="https://www.christianfindlay.com/assets/images/blog/ai-agent/header.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Mastering Material Design 3: The Complete Guide to Theming in Flutter</title><link href="https://www.christianfindlay.com/blog/flutter-mastering-material-design3" rel="alternate" type="text/html" title="Mastering Material Design 3: The Complete Guide to Theming in Flutter" /><published>2025-02-25T00:00:00+00:00</published><updated>2025-02-25T00:00:00+00:00</updated><id>https://www.christianfindlay.com/blog/flutter-mastering-material-design3</id><content type="html" xml:base="https://www.christianfindlay.com/blog/flutter-mastering-material-design3"><![CDATA[<p>One of the most common questions developers ask when working with Flutter is how to manage themes to create consistent UI styles across their apps. Themes are part of the design system we use. Flutter apps usually use Material Design or Cupertino, but this article focuses on theming with <a href="https://m3.material.io/">Material Design 3</a> (M3) in Flutter. This article details how to create, customize, and apply themes in your Flutter applications.</p>

<h2 id="understanding-flutter-material-design-themes">Understanding Flutter Material Design Themes</h2>

<p>M3 is Google’s latest design system for building apps and websites. Before looking into theming, you should read up about the <a href="https://m3.material.io/">design system</a>. A theme in Flutter is a collection of property-value pairs that dictate the appearance of the app’s widgets. <a href="https://api.flutter.dev/flutter/material/ThemeData-class.html"><code class="language-plaintext highlighter-rouge">ThemeData</code></a> is the class responsible for holding these properties. Let’s first understand the significance of <code class="language-plaintext highlighter-rouge">ThemeData</code> and how it helps in theming.</p>

<h4 id="themedata"><a href="https://api.flutter.dev/flutter/material/ThemeData-class.html">ThemeData</a></h4>

<p>The <code class="language-plaintext highlighter-rouge">ThemeData</code> class encapsulates a Material Design theme’s colors, typography, and shape properties. We typically use it as an argument for the <a href="https://api.flutter.dev/flutter/material/MaterialApp-class.html"><code class="language-plaintext highlighter-rouge">MaterialApp</code></a> widget, which in turn applies the theme to all descendant widgets.</p>

<h4 id="creating-a-custom-theme">Creating a Custom Theme</h4>

<p>Create a <code class="language-plaintext highlighter-rouge">ThemeData</code> instance and assign values to the properties you wish to customize. Let’s create a custom theme and apply it to our Flutter app. You can try this out in <a href="https://dartpad.dev/">Dartpad</a>. Just modify the existing default app there. M3 is now the default design system in Flutter so you don’t need to configure anything. If you are completing a flutter Material 2 to Material 3 migration, you can still explicitly set <code class="language-plaintext highlighter-rouge">useMaterial3</code> to true if you want to make your intentions clear.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>

<span class="n">ThemeData</span> <span class="n">lightTheme</span> <span class="o">=</span> <span class="n">ThemeData</span><span class="p">(</span>
  <span class="nl">useMaterial3:</span> <span class="kc">true</span><span class="p">,</span> <span class="c1">// This is not necessary or recommended anymore.</span>
  <span class="nl">colorScheme:</span> <span class="n">ColorScheme</span><span class="o">.</span><span class="na">fromSeed</span><span class="p">(</span>
    <span class="nl">seedColor:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">pink</span><span class="p">,</span>
    <span class="nl">brightness:</span> <span class="n">Brightness</span><span class="o">.</span><span class="na">light</span><span class="p">,</span>
  <span class="p">),</span>
  <span class="nl">textTheme:</span> <span class="kd">const</span> <span class="n">TextTheme</span><span class="p">(</span>
    <span class="nl">displayLarge:</span> <span class="n">TextStyle</span><span class="p">(</span><span class="nl">fontSize:</span> <span class="mi">32</span><span class="p">,</span> <span class="nl">fontWeight:</span> <span class="n">FontWeight</span><span class="o">.</span><span class="na">bold</span><span class="p">),</span>
    <span class="nl">bodyLarge:</span> <span class="n">TextStyle</span><span class="p">(</span><span class="nl">fontSize:</span> <span class="mi">18</span><span class="p">),</span>
  <span class="p">),</span>
<span class="p">);</span>
</code></pre></div></div>

<p>The most important part of M3 theming is the flutter Material 3 ColorScheme. Instead of setting individual colors, you can use <code class="language-plaintext highlighter-rouge">ColorScheme.fromSeed()</code> to generate a complete, harmonious color scheme from a single seed color, enabling flutter dynamic theming with minimal effort. This ensures your app follows M3 color guidelines and has good contrast ratios. It’s also the easiest way to get up and running without needing to harmonize the colors yourself.</p>

<h4 id="applying-the-themedata-instance">Applying the ThemeData Instance</h4>

<p>Pass the custom theme to the <code class="language-plaintext highlighter-rouge">theme</code> property of the <code class="language-plaintext highlighter-rouge">MaterialApp</code> widget.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="n">runApp</span><span class="p">(</span>
    <span class="n">MaterialApp</span><span class="p">(</span>
      <span class="nl">debugShowCheckedModeBanner:</span> <span class="kc">false</span><span class="p">,</span>
      <span class="nl">title:</span> <span class="s">'Custom Theme Demo'</span><span class="p">,</span>
      <span class="nl">theme:</span> <span class="n">lightTheme</span><span class="p">,</span>
      <span class="nl">home:</span> <span class="n">Scaffold</span><span class="p">(</span>
        <span class="nl">body:</span> <span class="n">Center</span><span class="p">(</span>
          <span class="nl">child:</span> <span class="n">ElevatedButton</span><span class="p">(</span>
            <span class="nl">child:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Hello, Flutter!'</span><span class="p">),</span>
            <span class="nl">onPressed:</span> <span class="p">()</span> <span class="p">{},</span>
          <span class="p">),</span>
        <span class="p">),</span>
      <span class="p">),</span>
    <span class="p">),</span>
  <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="using-the-theme-properties">Using the Theme Properties</h4>

<p>You can access the <code class="language-plaintext highlighter-rouge">ThemeData</code> properties with the <a href="https://api.flutter.dev/flutter/material/Theme/of.html"><code class="language-plaintext highlighter-rouge">Theme.of(context)</code></a> method. Here’s an example of how to use the <code class="language-plaintext highlighter-rouge">headlineMedium</code> named style.  You can read more about M3 Typography and scale <a href="https://m3.material.io/styles/typography/type-scale-tokens#425022ff-21dd-4fbe-9eca-9690d0fc8b16">here</a>.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Text</span><span class="p">(</span>
  <span class="s">'Hello, Flutter!'</span><span class="p">,</span>
  <span class="nl">style:</span> <span class="n">Theme</span><span class="o">.</span><span class="na">of</span><span class="p">(</span><span class="n">context</span><span class="p">)</span><span class="o">.</span><span class="na">textTheme</span><span class="o">.</span><span class="na">headlineMedium</span><span class="p">,</span>
<span class="p">);</span>
</code></pre></div></div>

<h4 id="dark-and-light-themes">Dark and Light Themes</h4>

<p>Flutter allows you to define separate themes for dark and light modes. You can set the <code class="language-plaintext highlighter-rouge">darkTheme</code> property of the <code class="language-plaintext highlighter-rouge">MaterialApp</code> widget. You can use the same seed color for both light and dark themes to maintain color consistency. That means you only need to change the brightness.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ThemeData</span> <span class="n">darkTheme</span> <span class="o">=</span> <span class="n">ThemeData</span><span class="p">(</span>
  <span class="nl">useMaterial3:</span> <span class="kc">true</span><span class="p">,</span>
  <span class="nl">colorScheme:</span> <span class="n">ColorScheme</span><span class="o">.</span><span class="na">fromSeed</span><span class="p">(</span>
    <span class="nl">seedColor:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">pink</span><span class="p">,</span>  <span class="c1">// Same as light theme</span>
    <span class="nl">brightness:</span> <span class="n">Brightness</span><span class="o">.</span><span class="na">dark</span><span class="p">,</span>
  <span class="p">),</span>
  <span class="c1">// These modify the default text styles.</span>
  <span class="nl">textTheme:</span> <span class="kd">const</span> <span class="n">TextTheme</span><span class="p">(</span>
    <span class="nl">displayLarge:</span> <span class="n">TextStyle</span><span class="p">(</span><span class="nl">fontSize:</span> <span class="mi">32</span><span class="p">,</span> <span class="nl">fontWeight:</span> <span class="n">FontWeight</span><span class="o">.</span><span class="na">bold</span><span class="p">),</span>
    <span class="nl">bodyLarge:</span> <span class="n">TextStyle</span><span class="p">(</span><span class="nl">fontSize:</span> <span class="mi">18</span><span class="p">),</span>
  <span class="p">),</span>
<span class="p">);</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">ColorScheme.fromSeed</code> constructor automatically adjusts all colors to be appropriate for dark or light mode, and you’ll have the same color harmony in light or dark mode. This ensures a consistent brand identity across both themes.</p>

<h4 id="applying-dark-themedata-instance">Applying Dark ThemeData Instance</h4>

<p>This example tells Flutter that there are two themes: light and dark. Flutter automatically switches between the two themes based on the device’s brightness settings.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="n">runApp</span><span class="p">(</span>
    <span class="n">MaterialApp</span><span class="p">(</span>
      <span class="nl">debugShowCheckedModeBanner:</span> <span class="kc">false</span><span class="p">,</span>
      <span class="nl">title:</span> <span class="s">'Custom Theme Demo'</span><span class="p">,</span>
      <span class="nl">darkTheme:</span> <span class="n">darkTheme</span><span class="p">,</span>
      <span class="nl">theme:</span> <span class="n">lightTheme</span><span class="p">,</span>
      <span class="nl">home:</span> <span class="n">Scaffold</span><span class="p">(</span>
        <span class="nl">body:</span> <span class="n">Center</span><span class="p">(</span>
          <span class="nl">child:</span> <span class="n">ElevatedButton</span><span class="p">(</span>
            <span class="nl">child:</span> <span class="n">Text</span><span class="p">(</span>
              <span class="s">'Hello, Flutter!'</span><span class="p">,</span>
            <span class="p">),</span>
            <span class="nl">onPressed:</span> <span class="p">()</span> <span class="p">{},</span>
          <span class="p">),</span>
        <span class="p">),</span>
      <span class="p">),</span>
    <span class="p">),</span>
  <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>You can also manually set the theme mode to light or dark using the <code class="language-plaintext highlighter-rouge">themeMode</code> property of the <code class="language-plaintext highlighter-rouge">MaterialApp</code> widget. If you do this, the Flutter app will ignore your device’s brightness setting and use the theme you specify, which is light in this case.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="n">runApp</span><span class="p">(</span>
    <span class="n">MaterialApp</span><span class="p">(</span>
      <span class="nl">themeMode:</span> <span class="n">ThemeMode</span><span class="o">.</span><span class="na">light</span><span class="p">,</span>
      <span class="nl">debugShowCheckedModeBanner:</span> <span class="kc">false</span><span class="p">,</span>
      <span class="nl">title:</span> <span class="s">'Custom Theme Demo'</span><span class="p">,</span>
      <span class="nl">darkTheme:</span> <span class="n">darkTheme</span><span class="p">,</span>
      <span class="nl">theme:</span> <span class="n">lightTheme</span><span class="p">,</span>
      <span class="nl">home:</span> <span class="n">Scaffold</span><span class="p">(</span>
        <span class="nl">body:</span> <span class="n">Center</span><span class="p">(</span>
          <span class="nl">child:</span> <span class="n">ElevatedButton</span><span class="p">(</span>
            <span class="nl">child:</span> <span class="n">Text</span><span class="p">(</span>
              <span class="s">'Hello, Flutter!'</span><span class="p">,</span>
            <span class="p">),</span>
            <span class="nl">onPressed:</span> <span class="p">()</span> <span class="p">{},</span>
          <span class="p">),</span>
        <span class="p">),</span>
      <span class="p">),</span>
    <span class="p">),</span>
  <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="the-importance-of-colorscheme-in-material-design-3">The Importance of ColorScheme in Material Design 3</h2>

<p>Flutter widgets pay particular attention to the <a href="https://api.flutter.dev/flutter/material/ColorScheme-class.html"><code class="language-plaintext highlighter-rouge">ColorScheme</code> class</a> in your theme data. <code class="language-plaintext highlighter-rouge">ColorScheme</code> is the foundation of M3’s color system and determines how your app’s colors work together.</p>

<h4 id="what-is-colorscheme">What is ColorScheme?</h4>

<p>ColorScheme defines the complete set of colors used by M3. Using <code class="language-plaintext highlighter-rouge">ColorScheme</code>s ensure proper contrast ratios and accessibility. Here are the key <a href="https://m3.material.io/styles/color/roles">color roles</a> included in the class:</p>

<ul>
  <li><a href="https://m3.material.io/styles/color/roles#41f55188-5c63-4107-ac41-822ebca8ae1b">Primary colors</a>: Used for key components and actions</li>
  <li><a href="https://m3.material.io/styles/color/roles#290bcc49-b728-414c-8cc5-04336c1c799c">Secondary colors</a>: Used for less prominent components</li>
  <li><a href="https://m3.material.io/styles/color/roles#727a0bf8-c95f-4f83-bc43-290d20f24e8e">Tertiary colors</a>: Used for contrasting accents</li>
  <li><a href="https://m3.material.io/styles/color/roles#47a25970-8a80-43be-8307-c12e0f7a2b43">Error colors</a>: Used for error states</li>
  <li><a href="https://m3.material.io/styles/color/roles#89f972b1-e372-494c-aabc-69aea34ed591">Surface colors</a>:</li>
</ul>

<p><strong>Try our new Seed Color Picker</strong>, which allows you to see how the seed color affects common widgets in your app.
load hfref in new tab
<a href="https://melbournedeveloper.github.io/seed_color_picker/">‍<img src="/assets/images/blog/materialdesign/seed-color-picker.png" alt="Seed Color Picker" width="100%" /></a></p>

<h4 id="default-colors-in-material-design-3">Default colors in Material Design 3</h4>

<p>The primary purpose of a <code class="language-plaintext highlighter-rouge">ColorScheme</code> is to provide a cohesive set of colors that work together. Components automatically use the appropriate colors from your <code class="language-plaintext highlighter-rouge">ColorScheme</code> in M3. This makes it easier to create a consistent color palette while adhering to Material Design guidelines.</p>

<p>You can create a <code class="language-plaintext highlighter-rouge">ThemeData</code> with a custom secondary color in the <code class="language-plaintext highlighter-rouge">ColorScheme</code> like the code below. However, you should exercise caution because it can break the color harmony of the app. If you do specify a secondary color, you will need to work out if it harmonizes with the other default colors.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ThemeData</span><span class="p">(</span>
  <span class="nl">useMaterial3:</span> <span class="kc">true</span><span class="p">,</span>
  <span class="nl">colorScheme:</span> <span class="n">ColorScheme</span><span class="o">.</span><span class="na">fromSeed</span><span class="p">(</span>
    <span class="nl">seedColor:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">blue</span><span class="p">,</span>
    <span class="nl">secondary:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">teal</span><span class="p">,</span>  <span class="c1">// Overrides the secondary color</span>
    <span class="nl">brightness:</span> <span class="n">Brightness</span><span class="o">.</span><span class="na">light</span><span class="p">,</span>
  <span class="p">),</span>
<span class="p">)</span>
</code></pre></div></div>

<p>While you can override specific colors in the <code class="language-plaintext highlighter-rouge">ColorScheme</code>, you’re best of sticking to <code class="language-plaintext highlighter-rouge">ColorScheme.fromSeed()</code> without overrides. This ensures all colors work together harmoniously and meet accessibility guidelines.</p>

<h2 id="identifying-how-widgets-get-their-default-color">Identifying How Widgets Get Their Default Color</h2>

<p>Widgets get their default color from the <code class="language-plaintext highlighter-rouge">ColorScheme</code>. Each widget has a set of specific properties that define the various colors it uses. For example, <code class="language-plaintext highlighter-rouge">TextButton</code> gets the foreground text color from <code class="language-plaintext highlighter-rouge">ColorScheme.primary</code>. In this example, the button’s text displays as green. This example demonstrates how changing the primary color changes the button’s color.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>

<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">runApp</span><span class="p">(</span>
      <span class="n">MaterialApp</span><span class="p">(</span>
        <span class="nl">theme:</span> <span class="n">ThemeData</span><span class="p">(</span>
          <span class="nl">brightness:</span> <span class="n">Brightness</span><span class="o">.</span><span class="na">light</span><span class="p">,</span>
          <span class="nl">colorScheme:</span> <span class="n">ColorScheme</span><span class="o">.</span><span class="na">fromSeed</span><span class="p">(</span>
            <span class="nl">seedColor:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">blue</span><span class="p">,</span>
          <span class="p">)</span><span class="o">.</span><span class="na">copyWith</span><span class="p">(</span>
            <span class="nl">primary:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">green</span><span class="p">,</span>
          <span class="p">),</span>
        <span class="p">),</span>
        <span class="nl">debugShowCheckedModeBanner:</span> <span class="kc">false</span><span class="p">,</span>
        <span class="nl">home:</span> <span class="n">Scaffold</span><span class="p">(</span>
          <span class="nl">body:</span> <span class="n">TextButton</span><span class="p">(</span>
            <span class="nl">onPressed:</span> <span class="p">()</span> <span class="p">{},</span>
            <span class="nl">child:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'test'</span><span class="p">),</span>
          <span class="p">),</span>
        <span class="p">),</span>
      <span class="p">),</span>
    <span class="p">);</span>
</code></pre></div></div>

<p>‍<img src="/assets/images/blog/materialdesign/greenbutton.png" alt="Green Button" width="100%" /></p>

<p>Unfortunately, there is no one-size-fits-all way to determine which property of the <code class="language-plaintext highlighter-rouge">ColorScheme</code> the widget uses because they use different color properties for their default color. The primary sources for learning these are a) the Flutter source code and b) the documentation.</p>

<p>You won’t always find the answer in the documentation, so look at the widget’s source code. Flutter is open-source. You can view the source code for any widget to see exactly how it works. Simply ctrl+click (or cmd+click on macOS) on the widget name in your editor if you are using an IDE like VSCode or Android Studio. This should take you to the definition in the source code.</p>

<p>For example, buttons have a <code class="language-plaintext highlighter-rouge">defaultStyleOf</code> method that returns a <code class="language-plaintext highlighter-rouge">ButtonStyle</code>. This code is from the Flutter source code for <code class="language-plaintext highlighter-rouge">_TextButtonDefaultsM3</code>, which shows you how the <code class="language-plaintext highlighter-rouge">TextButton</code> widget picks up the foreground color from the <code class="language-plaintext highlighter-rouge">ColorScheme.primary</code> property. Notice how the code applies difference opacity values to the colors depending on the state of the button. Also notice how the background color for <code class="language-plaintext highlighter-rouge">TextButton</code> is set to transparent, instead of a M3 color.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">_TextButtonDefaultsM3</span> <span class="kd">extends</span> <span class="n">ButtonStyle</span> <span class="p">{</span>
  <span class="n">_TextButtonDefaultsM3</span><span class="p">(</span><span class="k">this</span><span class="o">.</span><span class="na">context</span><span class="p">)</span>
   <span class="o">:</span> <span class="k">super</span><span class="p">(</span>
       <span class="nl">animationDuration:</span> <span class="n">kThemeChangeDuration</span><span class="p">,</span>
       <span class="nl">enableFeedback:</span> <span class="kc">true</span><span class="p">,</span>
       <span class="nl">alignment:</span> <span class="n">Alignment</span><span class="o">.</span><span class="na">center</span><span class="p">,</span>
     <span class="p">);</span>

  <span class="kd">final</span> <span class="n">BuildContext</span> <span class="n">context</span><span class="p">;</span>
  <span class="kd">late</span> <span class="kd">final</span> <span class="n">ColorScheme</span> <span class="n">_colors</span> <span class="o">=</span> <span class="n">Theme</span><span class="o">.</span><span class="na">of</span><span class="p">(</span><span class="n">context</span><span class="p">)</span><span class="o">.</span><span class="na">colorScheme</span><span class="p">;</span>

  <span class="nd">@override</span>
  <span class="n">MaterialStateProperty</span><span class="p">&lt;</span><span class="n">TextStyle</span><span class="o">?</span><span class="p">&gt;</span> <span class="kd">get</span> <span class="n">textStyle</span> <span class="o">=</span><span class="p">&gt;</span>
    <span class="n">MaterialStatePropertyAll</span><span class="p">&lt;</span><span class="n">TextStyle</span><span class="o">?</span><span class="p">&gt;(</span><span class="n">Theme</span><span class="o">.</span><span class="na">of</span><span class="p">(</span><span class="n">context</span><span class="p">)</span><span class="o">.</span><span class="na">textTheme</span><span class="o">.</span><span class="na">labelLarge</span><span class="p">);</span>

  <span class="nd">@override</span>
  <span class="n">MaterialStateProperty</span><span class="p">&lt;</span><span class="n">Color</span><span class="o">?</span><span class="p">&gt;</span><span class="o">?</span> <span class="kd">get</span> <span class="n">backgroundColor</span> <span class="o">=</span><span class="p">&gt;</span>
    <span class="kd">const</span> <span class="n">MaterialStatePropertyAll</span><span class="p">&lt;</span><span class="n">Color</span><span class="p">&gt;(</span><span class="n">Colors</span><span class="o">.</span><span class="na">transparent</span><span class="p">);</span>

  <span class="nd">@override</span>
  <span class="n">MaterialStateProperty</span><span class="p">&lt;</span><span class="n">Color</span><span class="o">?</span><span class="p">&gt;</span><span class="o">?</span> <span class="kd">get</span> <span class="n">foregroundColor</span> <span class="o">=</span><span class="p">&gt;</span>
    <span class="n">MaterialStateProperty</span><span class="o">.</span><span class="na">resolveWith</span><span class="p">((</span><span class="kt">Set</span><span class="p">&lt;</span><span class="n">MaterialState</span><span class="p">&gt;</span> <span class="n">states</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">states</span><span class="o">.</span><span class="na">contains</span><span class="p">(</span><span class="n">MaterialState</span><span class="o">.</span><span class="na">disabled</span><span class="p">))</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">_colors</span><span class="o">.</span><span class="na">onSurface</span><span class="o">.</span><span class="na">withOpacity</span><span class="p">(</span><span class="mf">0.38</span><span class="p">);</span>
      <span class="p">}</span>
      <span class="k">return</span> <span class="n">_colors</span><span class="o">.</span><span class="na">primary</span><span class="p">;</span>
    <span class="p">});</span>

  <span class="nd">@override</span>
  <span class="n">MaterialStateProperty</span><span class="p">&lt;</span><span class="n">Color</span><span class="o">?</span><span class="p">&gt;</span><span class="o">?</span> <span class="kd">get</span> <span class="n">overlayColor</span> <span class="o">=</span><span class="p">&gt;</span>
    <span class="n">MaterialStateProperty</span><span class="o">.</span><span class="na">resolveWith</span><span class="p">((</span><span class="kt">Set</span><span class="p">&lt;</span><span class="n">MaterialState</span><span class="p">&gt;</span> <span class="n">states</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">states</span><span class="o">.</span><span class="na">contains</span><span class="p">(</span><span class="n">MaterialState</span><span class="o">.</span><span class="na">pressed</span><span class="p">))</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">_colors</span><span class="o">.</span><span class="na">primary</span><span class="o">.</span><span class="na">withOpacity</span><span class="p">(</span><span class="mf">0.1</span><span class="p">);</span>
      <span class="p">}</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">states</span><span class="o">.</span><span class="na">contains</span><span class="p">(</span><span class="n">MaterialState</span><span class="o">.</span><span class="na">hovered</span><span class="p">))</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">_colors</span><span class="o">.</span><span class="na">primary</span><span class="o">.</span><span class="na">withOpacity</span><span class="p">(</span><span class="mf">0.08</span><span class="p">);</span>
      <span class="p">}</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">states</span><span class="o">.</span><span class="na">contains</span><span class="p">(</span><span class="n">MaterialState</span><span class="o">.</span><span class="na">focused</span><span class="p">))</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">_colors</span><span class="o">.</span><span class="na">primary</span><span class="o">.</span><span class="na">withOpacity</span><span class="p">(</span><span class="mf">0.1</span><span class="p">);</span>
      <span class="p">}</span>
      <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
    <span class="p">});</span>

  <span class="nd">@override</span>
  <span class="n">MaterialStateProperty</span><span class="p">&lt;</span><span class="n">Color</span><span class="p">&gt;</span><span class="o">?</span> <span class="kd">get</span> <span class="n">shadowColor</span> <span class="o">=</span><span class="p">&gt;</span>
    <span class="kd">const</span> <span class="n">MaterialStatePropertyAll</span><span class="p">&lt;</span><span class="n">Color</span><span class="p">&gt;(</span><span class="n">Colors</span><span class="o">.</span><span class="na">transparent</span><span class="p">);</span>

  <span class="nd">@override</span>
  <span class="n">MaterialStateProperty</span><span class="p">&lt;</span><span class="n">Color</span><span class="p">&gt;</span><span class="o">?</span> <span class="kd">get</span> <span class="n">surfaceTintColor</span> <span class="o">=</span><span class="p">&gt;</span>
    <span class="kd">const</span> <span class="n">MaterialStatePropertyAll</span><span class="p">&lt;</span><span class="n">Color</span><span class="p">&gt;(</span><span class="n">Colors</span><span class="o">.</span><span class="na">transparent</span><span class="p">);</span>

  <span class="nd">@override</span>
  <span class="n">MaterialStateProperty</span><span class="p">&lt;</span><span class="kt">double</span><span class="p">&gt;</span><span class="o">?</span> <span class="kd">get</span> <span class="n">elevation</span> <span class="o">=</span><span class="p">&gt;</span>
    <span class="kd">const</span> <span class="n">MaterialStatePropertyAll</span><span class="p">&lt;</span><span class="kt">double</span><span class="p">&gt;(</span><span class="mf">0.0</span><span class="p">);</span>

  <span class="nd">@override</span>
  <span class="n">MaterialStateProperty</span><span class="p">&lt;</span><span class="n">EdgeInsetsGeometry</span><span class="p">&gt;</span><span class="o">?</span> <span class="kd">get</span> <span class="n">padding</span> <span class="o">=</span><span class="p">&gt;</span>
    <span class="n">MaterialStatePropertyAll</span><span class="p">&lt;</span><span class="n">EdgeInsetsGeometry</span><span class="p">&gt;(</span><span class="n">_scaledPadding</span><span class="p">(</span><span class="n">context</span><span class="p">));</span>

  <span class="nd">@override</span>
  <span class="n">MaterialStateProperty</span><span class="p">&lt;</span><span class="n">Size</span><span class="p">&gt;</span><span class="o">?</span> <span class="kd">get</span> <span class="n">minimumSize</span> <span class="o">=</span><span class="p">&gt;</span>
    <span class="kd">const</span> <span class="n">MaterialStatePropertyAll</span><span class="p">&lt;</span><span class="n">Size</span><span class="p">&gt;(</span><span class="n">Size</span><span class="p">(</span><span class="mf">64.0</span><span class="p">,</span> <span class="mf">40.0</span><span class="p">));</span>

  <span class="c1">// No default fixedSize</span>

  <span class="nd">@override</span>
  <span class="n">MaterialStateProperty</span><span class="p">&lt;</span><span class="kt">double</span><span class="p">&gt;</span><span class="o">?</span> <span class="kd">get</span> <span class="n">iconSize</span> <span class="o">=</span><span class="p">&gt;</span>
    <span class="kd">const</span> <span class="n">MaterialStatePropertyAll</span><span class="p">&lt;</span><span class="kt">double</span><span class="p">&gt;(</span><span class="mf">18.0</span><span class="p">);</span>

  <span class="nd">@override</span>
  <span class="n">MaterialStateProperty</span><span class="p">&lt;</span><span class="n">Color</span><span class="p">&gt;</span><span class="o">?</span> <span class="kd">get</span> <span class="n">iconColor</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">MaterialStateProperty</span><span class="o">.</span><span class="na">resolveWith</span><span class="p">((</span><span class="kt">Set</span><span class="p">&lt;</span><span class="n">MaterialState</span><span class="p">&gt;</span> <span class="n">states</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">states</span><span class="o">.</span><span class="na">contains</span><span class="p">(</span><span class="n">MaterialState</span><span class="o">.</span><span class="na">disabled</span><span class="p">))</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">_colors</span><span class="o">.</span><span class="na">onSurface</span><span class="o">.</span><span class="na">withOpacity</span><span class="p">(</span><span class="mf">0.38</span><span class="p">);</span>
      <span class="p">}</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">states</span><span class="o">.</span><span class="na">contains</span><span class="p">(</span><span class="n">MaterialState</span><span class="o">.</span><span class="na">pressed</span><span class="p">))</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">_colors</span><span class="o">.</span><span class="na">primary</span><span class="p">;</span>
      <span class="p">}</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">states</span><span class="o">.</span><span class="na">contains</span><span class="p">(</span><span class="n">MaterialState</span><span class="o">.</span><span class="na">hovered</span><span class="p">))</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">_colors</span><span class="o">.</span><span class="na">primary</span><span class="p">;</span>
      <span class="p">}</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">states</span><span class="o">.</span><span class="na">contains</span><span class="p">(</span><span class="n">MaterialState</span><span class="o">.</span><span class="na">focused</span><span class="p">))</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">_colors</span><span class="o">.</span><span class="na">primary</span><span class="p">;</span>
      <span class="p">}</span>
      <span class="k">return</span> <span class="n">_colors</span><span class="o">.</span><span class="na">primary</span><span class="p">;</span>
    <span class="p">});</span>
  <span class="p">}</span>

  <span class="nd">@override</span>
  <span class="n">MaterialStateProperty</span><span class="p">&lt;</span><span class="n">Size</span><span class="p">&gt;</span><span class="o">?</span> <span class="kd">get</span> <span class="n">maximumSize</span> <span class="o">=</span><span class="p">&gt;</span>
    <span class="kd">const</span> <span class="n">MaterialStatePropertyAll</span><span class="p">&lt;</span><span class="n">Size</span><span class="p">&gt;(</span><span class="n">Size</span><span class="o">.</span><span class="na">infinite</span><span class="p">);</span>

  <span class="c1">// No default side</span>

  <span class="nd">@override</span>
  <span class="n">MaterialStateProperty</span><span class="p">&lt;</span><span class="n">OutlinedBorder</span><span class="p">&gt;</span><span class="o">?</span> <span class="kd">get</span> <span class="n">shape</span> <span class="o">=</span><span class="p">&gt;</span>
    <span class="kd">const</span> <span class="n">MaterialStatePropertyAll</span><span class="p">&lt;</span><span class="n">OutlinedBorder</span><span class="p">&gt;(</span><span class="n">StadiumBorder</span><span class="p">());</span>

  <span class="nd">@override</span>
  <span class="n">MaterialStateProperty</span><span class="p">&lt;</span><span class="n">MouseCursor</span><span class="o">?</span><span class="p">&gt;</span><span class="o">?</span> <span class="kd">get</span> <span class="n">mouseCursor</span> <span class="o">=</span><span class="p">&gt;</span>
    <span class="n">MaterialStateProperty</span><span class="o">.</span><span class="na">resolveWith</span><span class="p">((</span><span class="kt">Set</span><span class="p">&lt;</span><span class="n">MaterialState</span><span class="p">&gt;</span> <span class="n">states</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">states</span><span class="o">.</span><span class="na">contains</span><span class="p">(</span><span class="n">MaterialState</span><span class="o">.</span><span class="na">disabled</span><span class="p">))</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">SystemMouseCursors</span><span class="o">.</span><span class="na">basic</span><span class="p">;</span>
      <span class="p">}</span>
      <span class="k">return</span> <span class="n">SystemMouseCursors</span><span class="o">.</span><span class="na">click</span><span class="p">;</span>
    <span class="p">});</span>

  <span class="nd">@override</span>
  <span class="n">VisualDensity</span><span class="o">?</span> <span class="kd">get</span> <span class="n">visualDensity</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">Theme</span><span class="o">.</span><span class="na">of</span><span class="p">(</span><span class="n">context</span><span class="p">)</span><span class="o">.</span><span class="na">visualDensity</span><span class="p">;</span>

  <span class="nd">@override</span>
  <span class="n">MaterialTapTargetSize</span><span class="o">?</span> <span class="kd">get</span> <span class="n">tapTargetSize</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">Theme</span><span class="o">.</span><span class="na">of</span><span class="p">(</span><span class="n">context</span><span class="p">)</span><span class="o">.</span><span class="na">materialTapTargetSize</span><span class="p">;</span>

  <span class="nd">@override</span>
  <span class="n">InteractiveInkFeatureFactory</span><span class="o">?</span> <span class="kd">get</span> <span class="n">splashFactory</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">Theme</span><span class="o">.</span><span class="na">of</span><span class="p">(</span><span class="n">context</span><span class="p">)</span><span class="o">.</span><span class="na">splashFactory</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="override-default-colors-for-widgets">Override Default Colors For Widgets</h4>

<p>You can override colors for a specific widget at the theme level. This can be a shortcut to finding the <code class="language-plaintext highlighter-rouge">ColorScheme</code> default color, but it means you only change the color for a given widget. Widgets that should have the same colors according to M3 may end up with different colors. If you need to override colors, there are three levels where you can do this:</p>

<h4 id="colorscheme-level">ColorScheme Level</h4>

<p>Modify the entire color scheme by adjusting the seed color or overriding specific scheme colors.</p>

<h4 id="widget-theme-level">Widget Theme Level</h4>

<p>Customize colors for all instances of a particular widget type using component themes.</p>

<h4 id="individual-widget-level">Individual Widget Level</h4>

<p>Override colors for specific widget instances.</p>

<p>Tips:</p>
<ul>
  <li>Always start with <code class="language-plaintext highlighter-rouge">ColorScheme.fromSeed()</code> for your base colors</li>
  <li>Try to use the default color assignments before overriding</li>
  <li>When overriding, prefer higher-level overrides (ColorScheme &gt; Component Theme &gt; Individual Widget)</li>
  <li>Consider accessibility and contrast when overriding colors</li>
  <li>Use <a href="https://api.flutter.dev/flutter/widgets/WidgetStateProperty-class.html"><code class="language-plaintext highlighter-rouge">WidgetStateProperty</code></a> (formerly <code class="language-plaintext highlighter-rouge">MaterialStateProperty</code>) for state-dependent colors</li>
</ul>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>

<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">runApp</span><span class="p">(</span>
      <span class="n">MaterialApp</span><span class="p">(</span>
        <span class="nl">debugShowCheckedModeBanner:</span> <span class="kc">false</span><span class="p">,</span>
        <span class="nl">theme:</span> <span class="n">ThemeData</span><span class="p">(</span>
          <span class="c1">// 1. ColorScheme Level: Adjust the entire color scheme</span>
          <span class="nl">colorScheme:</span> <span class="n">ColorScheme</span><span class="o">.</span><span class="na">fromSeed</span><span class="p">(</span>
            <span class="nl">seedColor:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">blue</span><span class="p">,</span>
            <span class="c1">// Override specific scheme colors if needed (not recommended)</span>
            <span class="nl">secondary:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">teal</span><span class="p">,</span>
          <span class="p">),</span>
          <span class="c1">// 2. Component Theme Level: Customize all ElevatedButtons</span>
          <span class="nl">elevatedButtonTheme:</span> <span class="n">ElevatedButtonThemeData</span><span class="p">(</span>
            <span class="nl">style:</span> <span class="n">ButtonStyle</span><span class="p">(</span>
              <span class="nl">backgroundColor:</span> <span class="n">WidgetStateProperty</span><span class="o">.</span><span class="na">resolveWith</span><span class="p">&lt;</span><span class="n">Color</span><span class="o">?</span><span class="p">&gt;(</span>
                <span class="p">(</span><span class="kt">Set</span><span class="p">&lt;</span><span class="n">WidgetState</span><span class="p">&gt;</span> <span class="n">states</span><span class="p">)</span> <span class="p">{</span>
                  <span class="k">if</span> <span class="p">(</span><span class="n">states</span><span class="o">.</span><span class="na">contains</span><span class="p">(</span><span class="n">WidgetState</span><span class="o">.</span><span class="na">disabled</span><span class="p">))</span> <span class="p">{</span>
                    <span class="k">return</span> <span class="n">Colors</span><span class="o">.</span><span class="na">grey</span><span class="o">.</span><span class="na">withValues</span><span class="p">(</span><span class="nl">alpha:</span> <span class="mf">0.5</span><span class="p">);</span>
                  <span class="p">}</span>
                  <span class="k">return</span> <span class="n">Colors</span><span class="o">.</span><span class="na">blue</span><span class="p">;</span>
                <span class="p">},</span>
              <span class="p">),</span>
            <span class="p">),</span>
          <span class="p">),</span>
        <span class="p">),</span>
        <span class="nl">home:</span> <span class="n">Scaffold</span><span class="p">(</span>
          <span class="nl">body:</span> <span class="n">Center</span><span class="p">(</span>
            <span class="nl">child:</span> <span class="n">Column</span><span class="p">(</span>
              <span class="nl">mainAxisAlignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">center</span><span class="p">,</span>
              <span class="nl">children:</span> <span class="p">[</span>
                <span class="c1">// Uses the component theme</span>
                <span class="n">ElevatedButton</span><span class="p">(</span>
                  <span class="nl">onPressed:</span> <span class="p">()</span> <span class="p">{},</span>
                  <span class="nl">child:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Default Theme Button'</span><span class="p">),</span>
                <span class="p">),</span>
                <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">16</span><span class="p">),</span>
                <span class="c1">// 3. Individual Widget Level: Override for this instance</span>
                <span class="n">ElevatedButton</span><span class="p">(</span>
                  <span class="nl">onPressed:</span> <span class="p">()</span> <span class="p">{},</span>
                  <span class="nl">style:</span> <span class="n">ElevatedButton</span><span class="o">.</span><span class="na">styleFrom</span><span class="p">(</span>
                    <span class="nl">backgroundColor:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">green</span><span class="p">,</span>
                  <span class="p">),</span>
                  <span class="nl">child:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Custom Button'</span><span class="p">),</span>
                <span class="p">),</span>
              <span class="p">],</span>
            <span class="p">),</span>
          <span class="p">),</span>
        <span class="p">),</span>
      <span class="p">),</span>
    <span class="p">);</span>
</code></pre></div></div>

<p>‍<img src="/assets/images/blog/materialdesign/buttons.png" alt="Not Red Button" width="100%" /></p>

<h2 id="complete-example">Complete Example</h2>

<p>This example allows you to toggle between the three theme modes to see how the dark and light themes look. You can modify the code to check out how the different colors apply to the widgets.</p>

<figure>
  <iframe style="width:99%;height:400px;" src="https://dartpad.dev/embed-flutter.html?id=940b8910603af83786d34e416bc89901"></iframe>
</figure>

<h2 id="staying-up-to-date">Staying Up To Date</h2>

<p>Flutter’s implementation of M3 is always evolving. Some Flutter releases involve breaking changes to how colors and theming for M3 work. You can stay up to date with the latest changes on the offical Flutter documentation website.</p>

<p><a href="https://docs.flutter.dev/release/breaking-changes/new-color-scheme-roles">Introduce new ColorScheme roles for Material 3</a></p>

<p><a href="https://docs.flutter.dev/release/breaking-changes">Breaking changes and migration guides</a></p>

<p><a href="https://docs.flutter.dev/release/whats-new">What’s new in the docs</a></p>

<h2 id="modifying-typography-with-textstyles">Modifying Typography with TextStyles</h2>

<p><a href="https://m3.material.io/styles/typography">Typography</a> is an important aspect of Material Design, and Flutter’s <code class="language-plaintext highlighter-rouge">ThemeData</code> allows you to customize the typography of your app. This includes adjusting font sizes, weights, and colors for different text styles. The <code class="language-plaintext highlighter-rouge">textTheme</code> property of <code class="language-plaintext highlighter-rouge">ThemeData</code> contains a <a href="https://api.flutter.dev/flutter/material/TextTheme-class.html"><code class="language-plaintext highlighter-rouge">TextTheme</code></a> object, which in turn has a set of predefined <a href="https://api.flutter.dev/flutter/painting/TextStyle-class.html"><code class="language-plaintext highlighter-rouge">TextStyle</code></a> properties. These properties represent different text styles like headlines, body text, captions, etc. You can modify the TextStyle properties to customize the typography for each style. This is an example of setting the font sizes and weights.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ThemeData</span> <span class="n">lightTheme</span> <span class="o">=</span> <span class="n">ThemeData</span><span class="p">(</span>
  <span class="nl">textTheme:</span> <span class="kd">const</span> <span class="n">TextTheme</span><span class="p">(</span>
    <span class="nl">displayLarge:</span> <span class="n">TextStyle</span><span class="p">(</span>
        <span class="nl">fontSize:</span> <span class="mi">96</span><span class="p">,</span> <span class="nl">fontWeight:</span> <span class="n">FontWeight</span><span class="o">.</span><span class="na">w300</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">black</span><span class="p">),</span>
    <span class="nl">displayMedium:</span> <span class="n">TextStyle</span><span class="p">(</span>
        <span class="nl">fontSize:</span> <span class="mi">60</span><span class="p">,</span> <span class="nl">fontWeight:</span> <span class="n">FontWeight</span><span class="o">.</span><span class="na">w400</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">black</span><span class="p">),</span>
    <span class="nl">displaySmall:</span> <span class="n">TextStyle</span><span class="p">(</span>
        <span class="nl">fontSize:</span> <span class="mi">48</span><span class="p">,</span> <span class="nl">fontWeight:</span> <span class="n">FontWeight</span><span class="o">.</span><span class="na">w400</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">black</span><span class="p">),</span>
    <span class="nl">headlineMedium:</span> <span class="n">TextStyle</span><span class="p">(</span>
        <span class="nl">fontSize:</span> <span class="mi">34</span><span class="p">,</span> <span class="nl">fontWeight:</span> <span class="n">FontWeight</span><span class="o">.</span><span class="na">w400</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">black</span><span class="p">),</span>
    <span class="nl">headlineSmall:</span> <span class="n">TextStyle</span><span class="p">(</span>
        <span class="nl">fontSize:</span> <span class="mi">24</span><span class="p">,</span> <span class="nl">fontWeight:</span> <span class="n">FontWeight</span><span class="o">.</span><span class="na">w400</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">black</span><span class="p">),</span>
    <span class="nl">titleLarge:</span> <span class="n">TextStyle</span><span class="p">(</span>
        <span class="nl">fontSize:</span> <span class="mi">20</span><span class="p">,</span> <span class="nl">fontWeight:</span> <span class="n">FontWeight</span><span class="o">.</span><span class="na">w500</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">black</span><span class="p">),</span>
    <span class="nl">bodyLarge:</span> <span class="n">TextStyle</span><span class="p">(</span>
        <span class="nl">fontSize:</span> <span class="mi">16</span><span class="p">,</span> <span class="nl">fontWeight:</span> <span class="n">FontWeight</span><span class="o">.</span><span class="na">w400</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">black87</span><span class="p">),</span>
    <span class="nl">bodyMedium:</span> <span class="n">TextStyle</span><span class="p">(</span>
        <span class="nl">fontSize:</span> <span class="mi">14</span><span class="p">,</span> <span class="nl">fontWeight:</span> <span class="n">FontWeight</span><span class="o">.</span><span class="na">w400</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">black87</span><span class="p">),</span>
    <span class="nl">bodySmall:</span> <span class="n">TextStyle</span><span class="p">(</span>
        <span class="nl">fontSize:</span> <span class="mi">12</span><span class="p">,</span> <span class="nl">fontWeight:</span> <span class="n">FontWeight</span><span class="o">.</span><span class="na">w400</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">black54</span><span class="p">),</span>
    <span class="nl">labelLarge:</span> <span class="n">TextStyle</span><span class="p">(</span>
        <span class="nl">fontSize:</span> <span class="mi">14</span><span class="p">,</span> <span class="nl">fontWeight:</span> <span class="n">FontWeight</span><span class="o">.</span><span class="na">w500</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">white</span><span class="p">),</span>
  <span class="p">),</span>
<span class="p">);</span>
</code></pre></div></div>

<p>You can use these styles in your app with the <a href="https://api.flutter.dev/flutter/material/ThemeData/textTheme.html"><code class="language-plaintext highlighter-rouge">Theme.of(context).textTheme</code></a> property, and the <a href="https://api.flutter.dev/flutter/widgets/Text-class.html"><code class="language-plaintext highlighter-rouge">Text</code></a> widget’s default <code class="language-plaintext highlighter-rouge">TextStyle</code> is <code class="language-plaintext highlighter-rouge">bodyMedium</code>. This example shows how to set the text style, how the <code class="language-plaintext highlighter-rouge">Text</code> widget picks up the default style, and how to use a named <code class="language-plaintext highlighter-rouge">TextStyle</code> from the theme.</p>

<figure>
  <iframe style="width:99%;height:400px;" src="https://dartpad.dev/embed-flutter.html?id=57b9f78da95614eef4b1d922ea2f6593"></iframe>
</figure>

<h2 id="theme-shapes">Theme Shapes</h2>

<p>Defining shapes at the theme level in Flutter ensures your design remains consistent, making your application more intuitive and appealing to users. Shapes in M3 can be simple or complex, adding depth and enhancing the visual hierarchy of the user interface. From subtle round edges to bold, expressive cut corners, shape variations can significantly impact a design’s feel and functionality.</p>

<p>To define shapes at the theme level, specify the <code class="language-plaintext highlighter-rouge">shape</code> parameter of the widget’s theme like this.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ThemeData</span><span class="p">(</span>
  <span class="nl">useMaterial3:</span> <span class="kc">true</span><span class="p">,</span>
  <span class="nl">cardTheme:</span> <span class="n">CardTheme</span><span class="p">(</span>
    <span class="nl">shape:</span> <span class="n">RoundedRectangleBorder</span><span class="p">(</span>
      <span class="nl">borderRadius:</span> <span class="n">BorderRadius</span><span class="o">.</span><span class="na">circular</span><span class="p">(</span><span class="mf">4.0</span><span class="p">),</span>
    <span class="p">),</span>
  <span class="p">),</span>
<span class="p">)</span>
</code></pre></div></div>

<p>The theme sets the default shape for the widget, and you can reference the shape from <code class="language-plaintext highlighter-rouge">Theme</code>.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Card</span><span class="p">(</span>
  <span class="nl">shape:</span> <span class="n">Theme</span><span class="o">.</span><span class="na">of</span><span class="p">(</span><span class="n">context</span><span class="p">)</span><span class="o">.</span><span class="na">cardTheme</span><span class="o">.</span><span class="na">shape</span><span class="p">,</span>
  <span class="nl">child:</span> <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span>
    <span class="nl">width:</span> <span class="mi">100</span><span class="p">,</span>
    <span class="nl">height:</span> <span class="mi">100</span><span class="p">,</span>
  <span class="p">),</span>
<span class="p">),</span>
</code></pre></div></div>

<p>This is a complete example. The first card uses the default shape, and the second uses a custom shape.</p>

<figure>
  <iframe style="width:99%;height:400px;" src="https://dartpad.dev/embed-flutter.html?id=7ec38a9d1049e037c012bec865630dfc"></iframe>
</figure>

<h2 id="conclusion">Conclusion</h2>
<p>Grasping Flutter’s M3 themes is key for modern application design, so spend some time on the <a href="https://m3.material.io/">Material Design website</a> to learn more about the system and how to use it in your apps. Refer back to this guide when you need an overview but remember to take the time to read the official documentation. Also, experiment with themes in Dartpad. This will save you a lot of time. Lastly, remember than you may need to check the actual Flutter code of the widgets to find out where they are getting their default theme values from.</p>

<p><sub>Updated Feburary 2025. Previous versions, such as 2024/01/04 are available on <a href="https://github.com/MelbourneDeveloper/ChristianFindlay.com/blob/main/site/_posts/2000-1-1-flutter-mastering-material-design3.md">GitHub as markdown</a>. </sub></p>]]></content><author><name>Christian Findlay</name></author><category term="flutter" /><category term="material-design" /><summary type="html"><![CDATA[Master Flutter Material 3 ColorScheme and dynamic theming. A complete guide to ThemeData, color palettes, and Material 2 to Material 3 migration.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.christianfindlay.com/assets/images/blog/materialdesign/logo.webp" /><media:content medium="image" url="https://www.christianfindlay.com/assets/images/blog/materialdesign/logo.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Reviving charts_flutter: Introducing nimble_charts for Your Web Dashboards</title><link href="https://www.christianfindlay.com/blog/reviving-charts-flutter" rel="alternate" type="text/html" title="Reviving charts_flutter: Introducing nimble_charts for Your Web Dashboards" /><published>2024-10-02T00:00:00+00:00</published><updated>2024-10-02T00:00:00+00:00</updated><id>https://www.christianfindlay.com/blog/reviving-charts-flutter</id><content type="html" xml:base="https://www.christianfindlay.com/blog/reviving-charts-flutter"><![CDATA[<p>The ability to visualize information effectively is more important than ever. Whether you’re a developer, a data analyst, or a business owner, presenting data clearly and engagingly can unlock insights and drive better decisions. That’s why we’re excited to introduce <a href="https://pub.dev/packages/nimble_charts">nimble_charts</a>, a revitalized charting library (charts_flutter) for Flutter that’s set to transform your web dashboards. It gives you everything you need to build a Flutter web dashboard with WASM support.</p>

<h2 id="the-return-of-a-classic">The Return of a Classic</h2>

<p><a href="https://pub.dev/packages/charts_flutter">charts_flutter</a> was a go-to library for many developers looking to integrate charts into their Flutter apps. However, the Google team responsible for this package decided to discontinue work at the beginning of 2023.</p>

<div style="text-align: center;align-items: center;margin-top: 16px;margin-bottom: 16px;"><img height="260px" src="/assets/images/blog/nimble_charts/original.png" alt="charts_flutter" /></div>

<p>Enter nimble_charts, a fork of the beloved charts_flutter, now maintained by the team at Nimblesite. We’ve taken the solid foundation of the original library, turned up the dial on type safety, and added a tonne of golden tests to ensure its long-term stability.</p>

<h2 id="why-choose-nimble-charts-for-your-projects">Why Choose Nimble Charts for Your Projects?</h2>
<p>charts_flutter was always a winner because of the flexibility, number of chart types, interactive, responsive visualizations, high-level customization, and extensibility. These unique features make it the ideal choice for your data visualization needs. nimble_charts stands out because of the focus on reliability through thorough automated testing. We want to make nimble_charts the go-to package for charts in Flutter, and we plan to do this by making it the most robust Flutter charting package.</p>

<p>The sample app speaks for itself.</p>

<video controls="" src="/assets/images/blog/nimble_charts/iosdemo.mp4" title="iPhone Demo" width="100%" height="360" style="border-radius: 6px;align-items: center;" alt="iPhone Demo Video"></video>

<p>Use it online <a href="https://nimblesite.github.io/nimble_charts/">here</a>.</p>

<h3 id="easy-to-use">Easy To Use</h3>
<p>Integrating Nimble Charts into your Flutter projects is a breeze. You can have stunning charts up and running in no time, making it a user-friendly choice for developers.</p>

<h3 id="interactive-and-responsive-visualizations">Interactive and Responsive Visualizations</h3>
<p>Engage your users with charts that aren’t just static images. nimble_charts supports interactivity, allowing users to hover, click, and explore data points for deeper insights. This feature will surely excite data analysts, as it opens up new possibilities for data exploration.</p>

<h3 id="webassembly-wasm-support">WebAssembly (WASM) Support</h3>
<p>Leverage the power of <a href="https://webassembly.org/">WebAssembly</a> to deliver high-performance web dashboards. Our charts run smoothly in the browser, providing a native-like experience that’s both fast and efficient. Flutter recently added <a href="https://docs.flutter.dev/platform-integration/web/wasm">WASM support</a>, and the live sample is made with WASM.</p>

<video controls="" src="/assets/images/blog/nimble_charts/webdemo.mp4" title="Web WASM Demo" width="100%" height="360" style="border-radius: 6px;align-items: center;" alt="Web WASM Demo Video"></video>

<p>Use it online <a href="https://nimblesite.github.io/nimble_charts/">here</a>.</p>

<h3 id="customizable-and-extensible">Customizable and Extensible</h3>
<p>Every project is unique, and nimble_charts offers the flexibility to customize your charts to match your application’s look and feel. From colors and fonts to animations and behaviors, make your data visualizations truly your own.</p>

<h3 id="actively-maintained">Actively Maintained</h3>
<p>nimble_charts benefits from active maintenance. We’re committed to keeping the library up-to-date alongside the Flutter ecosystem. We will prioritize bug fixes, code quality, and testing over new features, but once we’re happy with the level of stability, we will look at adding new features again.</p>

<h3 id="see-nimble-charts-in-action">See Nimble Charts in Action</h3>
<p>Explore our <a href="https://nimblesite.github.io/nimble_charts/">live demo</a> built with WebAssembly to experience the capabilities of Nimble Charts firsthand. Witness how fluid and responsive your data visualizations can be on the web.</p>

<p>You can select a deselect tags to see the different chart types.</p>

<h3 id="a-nod-from-the-flutter-community">A Nod From The Flutter Community</h3>
<p>The revival of this charting library hasn’t gone unnoticed. We’ve already had several <a href="https://github.com/Nimblesite/nimble_charts/pull/17">pull requests</a> for the library and one influential member of the Flutter community said:</p>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Thank you for bringing back charts!</p>&mdash; Seth Ladd (@sethladd) <a href="https://twitter.com/sethladd/status/1837493581117882672?ref_src=twsrc%5Etfw">September 21, 2024</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<p>This sentiment echoes the excitement and appreciation we’ve received from developers eager to have a reliable charting solution.</p>

<h2 id="building-stunning-web-dashboards">Building Stunning Web Dashboards</h2>
<p>Flutter is the right choice for building web dashboards. Flutter is a great UI toolkit for creating stunning apps, but the addition of WASM support introduces a new level of performance and web-readiness.</p>

<div style="text-align: center;align-items: center;margin-top: 16px;margin-bottom: 16px;"><img height="320px" src="/assets/images/blog/nimble_charts/webdashboard.png" alt="Flutter Web Dashboard" /></div>

<p>Data visualization is more than just displaying numbers—it’s about telling a story. With nimble_charts, you can craft dashboards that not only look impressive but also convey meaningful insights. Whether you’re tracking user engagement, monitoring real-time analytics, or presenting quarterly reports, our library provides the tools to make your data shine.</p>

<h3 id="use-cases">Use Cases</h3>

<p><strong>Business Intelligence Dashboards</strong>: Keep stakeholders informed with up-to-date metrics.</p>

<p><strong>Financial Reporting</strong>: Visualize trends, forecasts, and performance indicators.</p>

<p><strong>Health and Fitness Apps</strong>: Track progress and motivate users with engaging visuals.</p>

<p><strong>Educational Tools</strong>: Simplify complex concepts through interactive charts.</p>

<h2 id="nimblesite-is-dedicated-to-open-source">Nimblesite Is Dedicated to Open Source</h2>

<p>We are dedicated to open source. We build components for the Flutter and .NET communities because it’s a great way to give back, and it allows us to remain experts with these packages so we can offer the best level of support for the apps we build with them.</p>

<h3 id="join-the-nimblesite-community">Join the Nimblesite Community</h3>

<p>We are very active on GitHub and social media. We’d love it if you would check out our work and engage with us on the socials.</p>

<p><strong>Star Our Repos</strong>: Follow our <a href="https://github.com/Nimblesite">GitHub page</a> and show your support by starring our repos.</p>

<p><strong>Share Your Projects</strong>: Showcase how you’re using Nimble Charts in your apps by tagging us on <a href="https://x.com/Nimblesite">X/Twitter</a> with your creations.</p>

<p><strong>Contribute to Development</strong>: To help us improve, contribute code, report issues, or suggest features on the <a href="https://github.com/Nimblesite/nimble_charts">GitHub repo</a>. You can even start a <a href="https://github.com/Nimblesite/nimble_charts/discussions">discussion on GitHub</a>.</p>

<p><strong>Stay Updated</strong>: <a href="https://www.nimblesite.co/#contact">Follow our blog and social media</a> for the latest news and updates.</p>

<h2 id="acknowledgments-and-license">Acknowledgments and License</h2>

<p>nimble_charts is a fork of the original charts_flutter package developed by the Google team. We want to express our gratitude to the original creators for their excellent work, which laid the foundation for this project.</p>

<p>nimble_charts is released under the <a href="https://github.com/Nimblesite/nimble_charts/blob/main/LICENSE">Apache 2.0 license</a>, maintaining the open-source spirit of the original project. We encourage users to review the full license terms on our GitHub repository.</p>

<p>By using and contributing to nimble_charts, you’re not only supporting our efforts but also honoring the work of the original Flutter team at Google who made this possible.</p>

<h2 id="need-a-web-dashboard">Need a Web Dashboard?</h2>

<p>Contact Nimblesite to build a web dashboard for your business: send us an email at <strong>sales at nimblesite.co</strong>, or fill out the <a href="/#contact">contact form</a></p>

<p>This <a href="https://www.nimblesite.co/blog/reviving-charts-flutter/">article</a> was original published on the <a href="https://www.nimblesite.co/">Nimblesite website</a>, and the package/repo belong to them.</p>]]></content><author><name>Christian Findlay</name></author><category term="flutter" /><category term="web" /><category term="cross-platform" /><summary type="html"><![CDATA[Introducing nimble_charts, a revitalized Flutter charting library that brings new life to the discontinued charts_flutter package. With improved type safety, extensive testing, and WebAssembly support, nimble_charts empowers companies to create stunning, interactive web dashboards for data visualization.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.christianfindlay.com/assets/images/blog/nimble_charts/charts.gif" /><media:content medium="image" url="https://www.christianfindlay.com/assets/images/blog/nimble_charts/charts.gif" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Mobile App Success in 2024: 10 Essential Strategies Beyond Development</title><link href="https://www.christianfindlay.com/blog/mobile-app-success-2024" rel="alternate" type="text/html" title="Mobile App Success in 2024: 10 Essential Strategies Beyond Development" /><published>2024-08-24T00:00:00+00:00</published><updated>2024-08-24T00:00:00+00:00</updated><id>https://www.christianfindlay.com/blog/mobile-app-success-2024</id><content type="html" xml:base="https://www.christianfindlay.com/blog/mobile-app-success-2024"><![CDATA[<p>Building a successful app demands more than just a stellar product. It requires a strategic approach to several non-technical facets, such as marketing, networking, and customer engagement. This guide lays out essential strategies that app developers should do in 2024 to foster growth, make users happy and ensure long-term success beyond development.</p>

<p>People often hire us for our technical expertise in building apps, but we also see people making a lot of mistakes that could be hurting their business. You should remember one important thing:</p>

<p><em>Some horrible apps become wildly successful, and some amazing apps never see the light of day</em></p>

<p>Creating a robust app that solves user problems and is enjoyable to use is absolutely the most important part of building app development. But, without marketing, you will never gain users to use it. If you’re building an app, you need to think broadly about other aspects that contribute to the success of your app. This guide lists the ten most important aspects and gives you actionable items you can implement.</p>

<h3 id="1-engage-authentically-on-social-media">1. Engage Authentically on Social Media</h3>

<p>Social media is a powerful tool for growing your SaaS business. It allows you to engage with your users outside the app itself. Share behind-the-scenes content, product updates, and industry insights to build a community around your brand. Engagement should be genuine. Think about creating content that adds value to your audience’s day-to-day lives rather than just broadcasting advertisements.</p>

<h4 id="actionable-advice">Actionable Advice</h4>

<p><strong>Create Accounts on Relevant Platforms</strong>: Focus on platforms where your target audience is most active. For B2B SaaS, <a href="https://www.linkedin.com">LinkedIn</a> is a must. For consumer-focused apps, consider platforms like <a href="https://www.instagram.com/">Instagram</a>, <a href="https://www.tiktok.com/explore">TikTok</a>, and <a href="https://au.pinterest.com/">Pinterest</a>.</p>

<p><strong>Create a Content Calendar</strong>: <a href="https://blog.hootsuite.com/how-to-create-a-social-media-content-calendar/">Plan your posts in advance</a>, including a mix of product updates, industry insights, and engaging content such as polls or user-generated content.</p>

<p><strong>Leverage Analytics</strong>: Use tools like <a href="https://marketingplatform.google.com/about/analytics/">Google Analytics</a> and native social media insights to track engagement and tweak your strategy based on what resonates with your audience.</p>

<h3 id="2-produce-valuable-content-and-implement-smart-seo">2. Produce Valuable Content and Implement Smart SEO</h3>

<p>Starting a blog is about more than just improving your site’s SEO. It’s about establishing your brand. Your content should offer original insights, practical advice, and comprehensive analysis that addresses the pain points and aspirations of your target audience.</p>

<p>But, most importantly, it should be entertaining and engaging. This is what the audience will remember and share, and it’s also what modern algorithms are looking for. Focus primarily on the quality and usefulness of your content to ensure alignment with Google’s emphasis on helpful, people-first content.</p>

<h4 id="actionable-advice-1">Actionable Advice:</h4>

<p><strong>Direct Users To Your App</strong>: put a call-to-action links on your website that link directly to your app in the app stores. This is the most critical point. Your website is the gateway to your app. Put large images of Android and iPhone here so the user picks up the signal that this is how they install your app.</p>

<p><strong>Use Voice Search Optimisation</strong>: Voice search will become far more popular in the next few years, so you <a href="https://www.semrush.com/blog/voice-search-optimization/">need to optimise for this</a>.</p>

<p><strong>Follow Google’s Guidelines</strong>: Pay attention to Google’s <a href="https://developers.google.com/search/docs/fundamentals/seo-starter-guide">Search Engine Optimization (SEO) Starter Guide</a> and follow its advice. Google is the largest search engine, and its traffic can make or break your app’s success.</p>

<p><strong>Keyword Research</strong>: Use tools like <a href="https://ahrefs.com/">Ahrefs</a> or <a href="https://www.semrush.com">SEMrush</a> to find relevant keywords that your target audience is searching for and incorporate them naturally into your content.</p>

<p><strong>Content Depth</strong>: Ensure your articles cover topics thoroughly, using the <a href="https://backlinko.com/skyscraper-technique">“Skyscraper Technique” by Brian Dean</a>, which involves creating the best possible content on a topic.</p>

<h3 id="3-network-effectively">3. Network Effectively</h3>

<p>Networking isn’t just about collecting contacts. It’s about creating meaningful partnerships that can lead to growth opportunities. Attend industry conferences, participate in webinars, and engage in community forums. Share your knowledge, learn from others, and keep an open mind about potential collaborations that can drive your business forward.</p>

<h4 id="actionable-advice-2">Actionable Advice</h4>

<p><strong>LinkedIn Networking</strong>: Regularly post updates and engage with other content in your field. Use LinkedIn’s search to find and connect with potential partners and influencers and read <a href="https://www.linkedin.com/help/linkedin/answer/a541669">LinkedIn’s documentation</a>.</p>

<p><strong>Attend Industry Events</strong>: Whether virtual or in-person, these can be a great way to make connections. Prepare a short introduction and the goals you aim to achieve from each interaction.</p>

<p><strong>Meet Face To Face</strong>: If you can, meet people in person. This can be a great way to build trust and rapport. The online world can become a faceless void, and meeting in person can help to humanize your interactions.</p>

<h3 id="4-fundraising">4. Fundraising</h3>

<p>Fundraising is a critical component of scaling any SaaS business. Prepare clear, concise pitches that not only highlight the unique value proposition of your product but also demonstrate a deep understanding of your market and growth potential. Be transparent about your goals and use data-driven insights to back your assertions.</p>

<h4 id="actionable-advice-3">Actionable Advice</h4>

<p><strong>Develop a Clear Pitch Deck</strong>: Follow this simple <a href="https://articles.sequoiacap.com/writing-a-business-plan">guide</a> to create a pitch deck. Highlight your value proposition, traction, and market opportunity. Tools like <a href="https://pitch.com/">Pitch.com</a> can help streamline this process.</p>

<p><strong>Practice Your Pitch</strong>: Refine your delivery by practising with mentors or peers, and be prepared to answer tough questions investors might ask.</p>

<p><strong>Connect With VCs and Angel Investors</strong>: Use platforms like <a href="https://wellfound.com">Wellfound</a> and <a href="https://crunchbase.com">Crunchbase</a> to find potential investors. Attend pitch events and network with investors to build relationships, particularly in your local area.</p>

<p><strong>Understand Your Metrics</strong>: Be prepared to discuss key metrics like customer acquisition cost (CAC), lifetime value (LTV), and churn rate. Investors want to see that you understand your business and have a clear path to growth.</p>

<h3 id="5-work-on-your-apps-store-ratings">5. Work on Your App’s Store Ratings</h3>

<p>Positive app store ratings can significantly influence potential users’ perceptions and decisions. Encourage satisfied customers to leave a review. Be proactive in addressing negative feedback by providing support and updates that resolve issues. Show potential customers that you are responsive and customer-focused.</p>

<h4 id="actionable-advice-4">Actionable Advice</h4>

<p><strong>Encourage Reviews</strong>: Prompt users to review your app after experiencing key features. Tools like <a href="https://appfollow.io/">AppFollow</a> can automate this process and manage reviews efficiently. Use the Flutter package <a href="https://pub.dev/packages/in_app_review">in_app_review</a> in Flutter apps to prompt users to leave a review.</p>

<p><strong>Address Negative Feedback</strong>: Quickly and professionally respond to negative reviews. This shows potential customers your commitment to customer satisfaction. Try to fix bugs in a timely manner and communicate updates to users.</p>

<p><strong>Leverage Positive Reviews</strong>: Share positive reviews on your website and social media to build credibility and trust with potential users. Share them as testimonials to help you do this.</p>

<p><strong>Monitor Ratings and Reviews</strong>: Regularly check app store ratings and reviews to identify trends and areas for improvement. Show that you are paying attention by responding to feedback and making necessary changes.</p>

<h3 id="6-respond-to-user-feedback">6. Respond to User Feedback</h3>

<p>Engage with your users and solicit feedback through surveys, social media, and direct communications. This demonstrates that you value their input and are committed to evolving your product to meet their needs. Use this feedback to inform product updates and customer service improvements, but remember to quantify data where possible.</p>

<p>Qualitative data like social media posts and complaints can influence the decision-making process, but they are not objective metrics. Data-Driven Decision Making is about making decisions based on objective, quantifiable data. Try to transition your analytics collection to facilitate this.</p>

<h4 id="actionable-advice-5">Actionable Advice</h4>

<p><strong>Utilize Comprehensive Feedback Tools</strong>: Implement tools like <a href="https://www.zonkafeedback.com/">Zonka Feedback</a> and <a href="https://surveysparrow.com">SurveySparrow</a> and other <a href="https://www.typeform.com/blog/product/customer-feedback-tools/">customer feedback tools</a> which offer multi-channel feedback collection and real-time analytics.</p>

<p><strong>Automate Feedback Collection</strong>: Automate the sending of surveys after specific interactions or periods using tools like <a href="https://qualaroo.com/">Qualaroo</a> to trigger surveys based on user behavior.</p>

<p><strong>Incorporate Advanced Survey Features</strong>: Use advanced features like skip logic, branching, and custom themes offered by tools like <a href="https://www.sogolytics.com">SoGoSurvey</a> and Qualaroo to create personalized and targeted surveys.</p>

<p><strong>Leverage AI for Feedback Analysis</strong>: Tools like Qualaroo offer AI-powered feedback analysis, which can help you understand user sentiments and behaviors at a granular level.</p>

<p><strong>Integrate Feedback with CRM</strong>: A CRM system helps you keep track of interaction with your users. Ensure that your feedback tools integrate seamlessly with your CRM system to maintain all customer data in one place.</p>

<p><strong>Optimize Feedback Forms for Mobile Users</strong>: Ensure your feedback forms are mobile-optimized. Tools like Google Forms and JotForm offer user-friendly interfaces that work well on mobile devices</p>

<h3 id="7-utilize-email-marketing-effectively">7. Utilize Email Marketing Effectively</h3>

<p>Email marketing remains a powerful tool for direct communication. Use it to inform subscribers about new features, tips, and company news. Personalize your emails to increase relevance and engagement, and always provide clear value in each communication to keep your audience interested and engaged.</p>

<h4 id="actionable-advice-6">Actionable Advice</h4>

<p><strong>Collect Email Addresses</strong>: User registrations are the primary means of collecting email addresses for marketing purposes. Once you have these, use tools like <a href="https://mailchimp.com/">Mailchimp</a> or <a href="https://brevo.com">Brevo</a> to manage your email campaigns.</p>

<p><strong>Understand Privacy and Ethical Use of Email</strong>: When collecting and using email addresses, ensure you comply with <a href="https://gdpr.eu/">GDPR</a> and other privacy regulations.</p>

<p><strong>Avoid Spamming Your Users</strong>: Be mindful of how often you send emails. Too many emails can lead to unsubscribes and a negative perception of your brand. Focus on sending content that you believe your users will find valuable.</p>

<p><strong>Warm up Your Email Domain</strong>: If you are sending a lot of emails, you <a href="https://www.saleshandy.com/blog/warmup-email-account/">need to warm up your email domain</a> to avoid being marked as spam. This is critical because if your email gets marked as spam, it will be very difficult to get it unmarked.</p>

<p><strong>Personalize Your Emails</strong>: Go beyond just inserting the recipient’s name. Tailor content based on the user’s past interactions with your brand, their preferences, and any data you have that can make the content feel more bespoke.</p>

<p><strong>Regularly Test and Optimize</strong>: <a href="https://emailmastery.org/testing-and-optimizing-email-marketing-campaigns/">Conduct A/B tests</a> on various elements of your emails, such as subject lines, email bodies, call-to-actions (CTAs), and sending times. This helps you understand what works best for your audience.</p>

<p><strong>Provide Clear and Immediate Value</strong>: Each email should offer something valuable to the recipient, whether it’s informative content, a special offer, or an update they’ve requested. This keeps your audience engaged and less likely to perceive your emails as spam.</p>

<p><strong>Use Engaging Subject Lines</strong>: Craft subject lines that are concise, clear, and enticing. Avoid clickbait, but ensure they are compelling enough to make recipients want to open the email.</p>

<h3 id="8-consider-the-role-of-influencers">8. Consider the Role of Influencers</h3>

<p>You cannot ignore the power of influencers. Influencers are people who have a large following on social media and can influence their audience’s purchasing decisions. Sometimes, they accept money or gifts for favourable content on social media, but ethical influencers will always disclose this.</p>

<p>Partnering with influencers in your niche can help extend your reach and credibility. Choose influencers who align with your brand values and have a genuine interest in your product. This partnership should feel natural to their audience and beneficial to both parties.</p>

<p>Pay close attention to how influencers go about their business. If they are not ethical, it can reflect poorly on your brand. If your audience respects the influencer, they will respect your brand more. If they detect</p>

<h4 id="actionable-advice-7">Actionable Advice</h4>

<p><strong>Identify Relevant Influencers</strong>: Use tools like <a href="https://buzzsumo.com/">BuzzSumo</a> or <a href="https://www.traackr.com/">Traackr</a> to <a href="https://contentmarketinginstitute.com/social-media-content/3-steps-to-identify-influencers-in-your-industry">find influencers in your industry</a> who are not only prominent but also align with your brand’s values and audience.</p>

<p><strong>Evaluate Influencer Engagement</strong>: Don’t just look at follower counts. Analyze their engagement rates, audience comments, and the quality of interactions influencers have with their followers.</p>

<p><strong>Establish Clear Collaboration Goals</strong>: Define what you hope to <a href="https://www.aspire.io/blog/how-to-set-and-achieve-your-influencer-marketing-goals">achieve through the partnership</a>, whether it’s increasing brand awareness, driving sales, or something else. Make sure these goals are known and agreed upon by both parties.</p>

<p><strong>Clear Messaging</strong>: Ensure that influencers understand your brand’s messaging and values. Provide them with guidelines and resources to help them create content and ensure that their values align with the messaging you want to convey.</p>

<p><strong>Create Authentic Partnership Strategies</strong>: Perceived <a href="https://influencermarketinghub.com/the-secret-to-gaining-consumer-trust-through-authentic-influencer-relationships/">authenticity is key</a>. Develop content ideas and promotional strategies that feel genuine and resonate with the influencer’s audience. Authenticity is key to the success of influencer campaigns.</p>

<p><strong>Monitor and Measure Results</strong>: <a href="https://www.socialmediaexaminer.com/influencer-marketing-campaigns-5-ways-to-measure/">Monitoring results</a> is critical to success. Set up tracking for metrics such as reach, clicks, conversions, and engagement that result from influencer collaborations. Use this data to assess performance and refine future campaigns.</p>

<h3 id="9-offer-exceptional-customer-support">9. Offer Exceptional Customer Support</h3>

<p>Excellent customer support can be a game-changer for SaaS products. Ensure that your support people are knowledgeable, accessible, and ready to solve problems quickly and efficiently. Consider developing a comprehensive knowledge base and using AI-driven tools to provide timely and effective support.</p>

<h4 id="actionable-advice-8">Actionable Advice</h4>

<p><strong>Train Your Support Team</strong>: Ensure that <a href="https://www.zendesk.com/au/blog/customer-service-training-important/">every team member is thoroughly trained</a> not just on your product and customer service best practices. Regular training sessions help to maintain high standards.</p>

<p><strong>Implement a Multichannel Support System</strong>: <a href="https://www.hubspot.com/products/service/omnichannel-customer-service">Utilize various channels</a> such as email, live chat, phone, and social media to make support accessible. Each channel should be integrated to provide a seamless experience for both customers and support staff.</p>

<p><strong>Make it Easy To Contact You</strong>: Provide clear contact information on your website and app and ensure that users can easily reach out for help when needed. Nothing frustrates users more than not being able to find help when they need it.</p>

<p><strong>Develop a Comprehensive Knowledge Base</strong>: Use a tool like <a href="https://support.freshdesk.com/support/solutions/folders/272620">Freshdesk</a> to create detailed FAQs, how-to guides, and troubleshooting articles. This allows customers to find answers quickly on their own and reduces the workload on your support team.</p>

<p><strong>Leverage AI Tools for Efficiency</strong>: <a href="https://www.zendesk.com/au/blog/ai-customer-service/">Implement AI-driven tools</a>, such as chatbots, to handle initial inquiries and common questions, freeing up human agents for more complex issues. Apps like <a href="https://dontspiral.app">Don’t Spiral</a> demonstrate how AI-driven chat can provide real-time, personalized support to users experiencing relationship anxiety, showing the power of AI beyond traditional customer service.</p>

<p><strong>Regularly Gather Customer Feedback</strong>: <a href="https://www.qualtrics.com/experience-management/customer/collecting-customer-feedback/">Use surveys and direct feedback tools to learn from customer interactions</a>. This can guide improvements in your support services.</p>

<h3 id="10-advertising">10. Advertising</h3>

<p>Advertising supplements all your other success initiatives. Without an app that users enjoy, advertising won’t work, and without social media and other platforms to help users find information about your app, advertising will be ineffective. But, if everything else is in place, advertising will boost your app’s visibility and lead to new installs and sign-ups</p>

<p>Advertising your app effectively on digital stores is critical for driving downloads and user engagement.</p>

<h4 id="actionable-advice-9">Actionable Advice</h4>

<p><strong>Optimize App Store Listings</strong>: Ensure that your app’s title, description, and metadata are optimized with relevant keywords to improve visibility for in-store searches. High-quality screenshots and engaging videos can also help attract potential users.</p>

<p><strong>Leverage Search Ads</strong>: Utilize search <a href="https://developer.apple.com/app-store/promote/">advertising features</a> available in app stores like Google Play Store and Apple App Store. These ads can significantly increase your app’s visibility when users search for related keywords.</p>

<p><strong>Implement ASO (App Store Optimization)</strong>: Focus on improving your app’s ranking and visibility in the app store through optimization strategies that enhance your app’s relevance and appeal to potential users.</p>

<p><strong>Encourage Reviews and Ratings</strong>: Positive reviews and high ratings can improve your app’s credibility and attract more users. Encourage satisfied users to leave feedback.</p>

<p><strong>Use Retargeting Ads</strong>: <a href="https://adespresso.com/blog/facebook-ad-retargeting-strategies-need-try/">Implement retargeting strategies</a> to re-engage users who have visited your app listing but haven’t downloaded your app.</p>

<h3 id="conclusion">Conclusion</h3>

<p>Building a successful mobile app in 2024 is about much more than just development. You need to take a holistic approach to marketing, customer engagement, and community building. Create people-first content and engage genuinely with your audience. This will allow you to build a strong foundation for your app’s success. The goal is to create value that resonates well beyond the digital screens, fostering a community of loyal users and advocates for your brand.</p>

<p>This <a href="https://www.nimblesite.co/blog/mobile-app-success-2024/">article</a> belongs to <a href="https://www.nimblesite.co">Nimblesite</a>. Send them an email at <strong>sales at nimblesite.co</strong>, or fill out the <a href="https://www.nimblesite.co/#contact">contact form</a></p>]]></content><author><name>Christian Findlay</name></author><category term="software development" /><category term="app-store" /><category term="web" /><category term="cross-platform" /><summary type="html"><![CDATA[10 essential strategies for mobile app success in 2024 beyond development: marketing, networking, and customer engagement tips to help your app grow and last.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.christianfindlay.com/assets/images/blog/mobileappsuccess/header.webp" /><media:content medium="image" url="https://www.christianfindlay.com/assets/images/blog/mobileappsuccess/header.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Dart: Algebraic Data Types</title><link href="https://www.christianfindlay.com/blog/dart-algebraic-data-types" rel="alternate" type="text/html" title="Dart: Algebraic Data Types" /><published>2024-07-05T00:00:00+00:00</published><updated>2024-07-05T00:00:00+00:00</updated><id>https://www.christianfindlay.com/blog/dart-algebraic-data-types</id><content type="html" xml:base="https://www.christianfindlay.com/blog/dart-algebraic-data-types"><![CDATA[<p>Algebraic Data Types (ADTs) are a powerful functional programming concept that allows developers to model complex data structures more elegantly than traditional object-oriented classes. They are composite types, meaning that they combine other types. Dart 3.0 introduced Dart sealed class pattern matching, which made ADTs possible in Dart 3. <a href="https://www.christianfindlay.com/blog/dart-switch-expressions">Dart Switch Expressions</a> leverage pattern matching well. This article explains the concept of ADTs, how to use them in Dart, and why using them with pattern matching solves so many traditional code-design problems that OOP languages tend to struggle with.</p>

<h2 id="what-are-algebraic-data-types">What are Algebraic Data Types?</h2>

<p>The official Dart documentation doesn’t explain ADTs, or Dart’s relationship to ADTs very well. It glosses over the concept and only introduces one aspect of ADTs in Dart. This article gives a broader perspective on ADTs outside of Dart and gives some examples in other languages.  If you want some background reading, the <a href="https://en.wikipedia.org/wiki/Algebraic_data_type">Wikipedia article on ADTs</a> is a good place to start.</p>

<p>There are two main categories of ADTs, and it’s important to understand these definitions.</p>

<p><a href="https://en.wikipedia.org/wiki/Tagged_union">Sum types</a> (also known as tagged unions or variants)</p>

<p><a href="https://en.wikipedia.org/wiki/Product_type">Product types</a> (such as tuples or <a href="https://dart.dev/language/records">records</a>)</p>

<p>You can combine these types to create more complex structures, which enables developers to represent data and states in a highly expressive manner.</p>

<h2 id="adts-in-other-languages">ADTs in Other Languages</h2>

<p>Before we look at Dart’s implementation, we should see how other languages known for their strong functional programming support handle ADTs.</p>

<h3 id="f">F#</h3>

<p><a href="https://fsharp.org/">F#</a> is a modern, FP-first, statically-typed language and provides excellent support for ADTs through discriminated unions:</p>

<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="nc">Shape</span> <span class="p">=</span>
    <span class="p">|</span> <span class="nc">Circle</span> <span class="k">of</span> <span class="n">radius</span><span class="p">:</span> <span class="kt">float</span>
    <span class="p">|</span> <span class="nc">Rectangle</span> <span class="k">of</span> <span class="n">width</span><span class="p">:</span> <span class="kt">float</span> <span class="p">*</span> <span class="n">height</span><span class="p">:</span> <span class="kt">float</span>
    <span class="p">|</span> <span class="nc">Triangle</span> <span class="k">of</span> <span class="k">base</span><span class="p">:</span> <span class="kt">float</span> <span class="p">*</span> <span class="n">height</span><span class="p">:</span> <span class="kt">float</span>

<span class="k">let</span> <span class="n">area</span> <span class="p">=</span> <span class="k">function</span>
    <span class="p">|</span> <span class="nc">Circle</span> <span class="n">r</span> <span class="p">-&gt;</span> <span class="nn">Math</span><span class="p">.</span><span class="nc">PI</span> <span class="p">*</span> <span class="n">r</span> <span class="p">*</span> <span class="n">r</span>
    <span class="p">|</span> <span class="nc">Rectangle</span> <span class="p">(</span><span class="n">w</span><span class="p">,</span> <span class="n">h</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="n">w</span> <span class="p">*</span> <span class="n">h</span>
    <span class="p">|</span> <span class="nc">Triangle</span> <span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="n">h</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="mi">0</span><span class="p">.</span><span class="mi">5</span> <span class="p">*</span> <span class="n">b</span> <span class="p">*</span> <span class="n">h</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">Shape</code> type is a discriminated union, which is F#’s implementation of a sum type. It encapsulates three distinct shapes (Circle, Rectangle, and Triangle) within a single type. Each has its own set of parameters. This structure allows for type-safe representation of different shapes without the need for inheritance or interfaces.</p>

<p>The <code class="language-plaintext highlighter-rouge">area</code> function demonstrates pattern matching. Importantly, there are only three possible shapes, which allows the compiler to know ahead of time what branches the code can travel down. It is a key feature of ADTs. This is called <a href="https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/pattern-matching">exhaustiveness checking</a>. It uses a single function to calculate the area for any shape, and the compiler ensures all cases are covered.</p>

<p>If you’re interested, you can read the <a href="https://github.com/dart-lang/language/blob/main/accepted/3.0/patterns/exhaustiveness.md">official Dart specification on this feature</a>.</p>

<h3 id="kotlin">Kotlin</h3>

<p>Kotlin is a modern, hybrid paradigm language like Dart. Like Dart, Kotlin represents ADTs using sealed classes:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">sealed</span> <span class="kd">class</span> <span class="nc">Shape</span> <span class="p">{</span>
 <span class="kd">data class</span> <span class="nc">Circle</span><span class="p">(</span><span class="kd">val</span> <span class="py">radius</span><span class="p">:</span> <span class="nc">Double</span><span class="p">)</span> <span class="p">:</span> <span class="nc">Shape</span><span class="p">()</span>
 <span class="kd">data class</span> <span class="nc">Rectangle</span><span class="p">(</span><span class="kd">val</span> <span class="py">width</span><span class="p">:</span> <span class="nc">Double</span><span class="p">,</span> <span class="kd">val</span> <span class="py">height</span><span class="p">:</span> <span class="nc">Double</span><span class="p">)</span> <span class="p">:</span> <span class="nc">Shape</span><span class="p">()</span>
 <span class="kd">data class</span> <span class="nc">Triangle</span><span class="p">(</span><span class="kd">val</span> <span class="py">base</span><span class="p">:</span> <span class="nc">Double</span><span class="p">,</span> <span class="kd">val</span> <span class="py">height</span><span class="p">:</span> <span class="nc">Double</span><span class="p">)</span> <span class="p">:</span> <span class="nc">Shape</span><span class="p">()</span>
<span class="p">}</span>

<span class="kd">val</span> <span class="py">area</span><span class="p">:</span> <span class="p">(</span><span class="nc">Shape</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="nc">Double</span> <span class="p">=</span> <span class="p">{</span> <span class="n">shape</span> <span class="p">-&gt;</span>
 <span class="k">when</span> <span class="p">(</span><span class="n">shape</span><span class="p">)</span> <span class="p">{</span>
 <span class="k">is</span> <span class="nc">Shape</span><span class="p">.</span><span class="nc">Circle</span> <span class="p">-&gt;</span> <span class="nc">Math</span><span class="p">.</span><span class="nc">PI</span> <span class="p">*</span> <span class="n">shape</span><span class="p">.</span><span class="n">radius</span> <span class="p">*</span> <span class="n">shape</span><span class="p">.</span><span class="n">radius</span>
 <span class="k">is</span> <span class="nc">Shape</span><span class="p">.</span><span class="nc">Rectangle</span> <span class="p">-&gt;</span> <span class="n">shape</span><span class="p">.</span><span class="n">width</span> <span class="p">*</span> <span class="n">shape</span><span class="p">.</span><span class="n">height</span>
 <span class="k">is</span> <span class="nc">Shape</span><span class="p">.</span><span class="nc">Triangle</span> <span class="p">-&gt;</span> <span class="mf">0.5</span> <span class="p">*</span> <span class="n">shape</span><span class="p">.</span><span class="n">base</span> <span class="p">*</span> <span class="n">shape</span><span class="p">.</span><span class="n">height</span>
 <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="the-benefit-of-adts-and-pattern-matching">The Benefit of ADTs and Pattern Matching</h2>

<p>ADTs give you tools to improve your code design.</p>

<p>Type safety: they provide compile-time guarantees about the structure of data and reduce the need for casting</p>

<p>Exhaustiveness checking: compilers can determine whether or not your code handled all cases with pattern matching.</p>

<p>Expressiveness: you can represent complex domain models clearly and concisely.</p>

<p>Immutability: ADTs encourage immutable data structures, which reduces side effects.</p>

<p>Consider this Dart code. At first glance, it has the right data, but there is a problem. None of the values are mutually exclusive, even though the states that the class represents have mutually exclusive pieces of data.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">AuthState</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="kt">String</span><span class="o">?</span> <span class="n">userId</span><span class="p">;</span>
  <span class="kd">final</span> <span class="kt">String</span><span class="o">?</span> <span class="n">errorMessage</span><span class="p">;</span>
  <span class="kd">final</span> <span class="kt">bool</span> <span class="n">isLoading</span><span class="p">;</span>

  <span class="n">AuthState</span><span class="p">({</span><span class="k">this</span><span class="o">.</span><span class="na">userId</span><span class="p">,</span> <span class="k">this</span><span class="o">.</span><span class="na">errorMessage</span><span class="p">,</span> <span class="k">this</span><span class="o">.</span><span class="na">isLoading</span> <span class="o">=</span> <span class="kc">false</span><span class="p">});</span>

  <span class="kt">bool</span> <span class="kd">get</span> <span class="n">isAuthenticated</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">userId</span> <span class="o">!=</span> <span class="kc">null</span><span class="p">;</span>
  <span class="kt">bool</span> <span class="kd">get</span> <span class="n">hasError</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">errorMessage</span> <span class="o">!=</span> <span class="kc">null</span><span class="p">;</span>

  <span class="c1">// This allows creation of invalid states</span>
  <span class="c1">// e.g., AuthState(userId: "123", errorMessage: "Error")</span>
<span class="p">}</span>

<span class="c1">// Usage</span>
<span class="kt">void</span> <span class="nf">handleAuth</span><span class="p">(</span><span class="n">AuthState</span> <span class="n">state</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">state</span><span class="o">.</span><span class="na">isLoading</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">print</span><span class="p">(</span><span class="s">"Loading..."</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">state</span><span class="o">.</span><span class="na">isAuthenticated</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">print</span><span class="p">(</span><span class="s">"Welcome, user </span><span class="si">${state.userId}</span><span class="s">"</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">state</span><span class="o">.</span><span class="na">hasError</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">print</span><span class="p">(</span><span class="s">"Error: </span><span class="si">${state.errorMessage}</span><span class="s">"</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="n">print</span><span class="p">(</span><span class="s">"Please log in"</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>ADTs solve this code-design issue and make accessing this data far safer because you can’t access two pieces of mutually exclusive data at the same time.</p>

<h2 id="adts-in-dart">ADTs in Dart</h2>

<p>Dart 3.0 introduces several new <a href="https://dart.dev/language/class-modifiers">class access modifiers</a>, such as the <a href="https://dart.dev/language/class-modifiers#sealed">sealed modifier</a>. This addition brings the ADT approach to Dart and allows for a form of sum type.</p>

<p>Let’s look at how we can improve upon the previous example with ADTs in Dart. Notice that the user id and error message are now mutually exclusive pieces of data. We cannot create a scenario where there is both a <code class="language-plaintext highlighter-rouge">userId</code> and an <code class="language-plaintext highlighter-rouge">errorMessage</code>. This is the magic of ADTs. We prevent states that shouldn’t exist from occurring in the first place.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">sealed</span> <span class="kd">class</span> <span class="nc">AuthState</span> <span class="p">{}</span>

<span class="kd">final</span> <span class="kd">class</span> <span class="nc">Unauthenticated</span> <span class="kd">extends</span> <span class="n">AuthState</span> <span class="p">{}</span>

<span class="kd">final</span> <span class="kd">class</span> <span class="nc">Authenticating</span> <span class="kd">extends</span> <span class="n">AuthState</span> <span class="p">{}</span>

<span class="kd">final</span> <span class="kd">class</span> <span class="nc">Authenticated</span> <span class="kd">extends</span> <span class="n">AuthState</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="kt">String</span> <span class="n">userId</span><span class="p">;</span>
  <span class="n">Authenticated</span><span class="p">(</span><span class="k">this</span><span class="o">.</span><span class="na">userId</span><span class="p">);</span>
<span class="p">}</span>

<span class="kd">final</span> <span class="kd">class</span> <span class="nc">AuthError</span> <span class="kd">extends</span> <span class="n">AuthState</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="kt">String</span> <span class="n">errorMessage</span><span class="p">;</span>
  <span class="n">AuthError</span><span class="p">(</span><span class="k">this</span><span class="o">.</span><span class="na">errorMessage</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// Usage</span>
<span class="kt">String</span> <span class="nf">handleAuth</span><span class="p">(</span><span class="n">AuthState</span> <span class="n">state</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="k">switch</span><span class="p">(</span><span class="n">state</span><span class="p">)</span> <span class="p">{</span>
  <span class="n">Unauthenticated</span><span class="p">()</span> <span class="o">=</span><span class="p">&gt;</span> <span class="s">"Please log in"</span><span class="p">,</span>
  <span class="n">Authenticating</span><span class="p">()</span> <span class="o">=</span><span class="p">&gt;</span> <span class="s">"Loading..."</span><span class="p">,</span>
  <span class="n">Authenticated</span><span class="p">(</span><span class="o">:</span><span class="kd">final</span> <span class="n">userId</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="s">"Welcome, user </span><span class="si">$userId</span><span class="s">"</span><span class="p">,</span>
  <span class="n">AuthError</span><span class="p">(</span><span class="o">:</span><span class="kd">final</span> <span class="n">errorMessage</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="s">"Error: </span><span class="si">$errorMessage</span><span class="s">"</span>
<span class="p">};</span>

<span class="c1">// Example usage</span>
<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="n">states</span> <span class="o">=</span> <span class="p">[</span>
    <span class="n">Unauthenticated</span><span class="p">(),</span>
    <span class="n">Authenticating</span><span class="p">(),</span>
    <span class="n">Authenticated</span><span class="p">(</span><span class="s">"user123"</span><span class="p">),</span>
    <span class="n">AuthError</span><span class="p">(</span><span class="s">"Invalid credentials"</span><span class="p">),</span>
  <span class="p">];</span>

  <span class="k">for</span> <span class="p">(</span><span class="kd">final</span> <span class="n">state</span> <span class="k">in</span> <span class="n">states</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">print</span><span class="p">(</span><span class="n">handleAuth</span><span class="p">(</span><span class="n">state</span><span class="p">));</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The example above uses a sealed type, <code class="language-plaintext highlighter-rouge">AuthState,</code> to declare a fixed set of types that can derive from it. The compiler knows that these are the only possible types, which means it can do <a href="https://dart.dev/language/branches#exhaustiveness-checking">exhaustiveness checking</a>.</p>

<p>Exhaustiveness checking forces you to handle all possible states. If you don’t, you will get a compilation error. This removes a whole category of potential exceptions from your code. Without exhaustive cases, your code could end up on an unknown branch, and the code would throw an exception. Then, you’d have to handle the exception at a higher level. ADTs allow you to avoid this need.</p>

<p>Notice that the subtypes use the <a href="https://dart.dev/language/class-modifiers#final">final</a> class modifier. This is important if you want to preserve the behavior of the class and prevent other classes from inheriting from this type. Still, not using it doesn’t break exhaustiveness checking.</p>

<p>Here is the shape example we saw earlier, but in Dart:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">sealed</span> <span class="kd">class</span> <span class="nc">Shape</span> <span class="p">{}</span>

<span class="kd">final</span> <span class="kd">class</span> <span class="nc">Circle</span> <span class="kd">extends</span> <span class="n">Shape</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="kt">double</span> <span class="n">radius</span><span class="p">;</span>
  <span class="n">Circle</span><span class="p">(</span><span class="k">this</span><span class="o">.</span><span class="na">radius</span><span class="p">);</span>
<span class="p">}</span>

<span class="kd">final</span> <span class="kd">class</span> <span class="nc">Rectangle</span> <span class="kd">extends</span> <span class="n">Shape</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="kt">double</span> <span class="n">width</span><span class="p">;</span>
  <span class="kd">final</span> <span class="kt">double</span> <span class="n">height</span><span class="p">;</span>
  <span class="n">Rectangle</span><span class="p">(</span><span class="k">this</span><span class="o">.</span><span class="na">width</span><span class="p">,</span> <span class="k">this</span><span class="o">.</span><span class="na">height</span><span class="p">);</span>
<span class="p">}</span>

<span class="kd">final</span> <span class="kd">class</span> <span class="nc">Triangle</span> <span class="kd">extends</span> <span class="n">Shape</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="kt">double</span> <span class="kd">base</span><span class="p">;</span>
  <span class="kd">final</span> <span class="kt">double</span> <span class="n">height</span><span class="p">;</span>
  <span class="n">Triangle</span><span class="p">(</span><span class="k">this</span><span class="o">.</span><span class="na">base</span><span class="p">,</span> <span class="k">this</span><span class="o">.</span><span class="na">height</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">double</span> <span class="nf">area</span><span class="p">(</span><span class="n">Shape</span> <span class="n">shape</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="k">switch</span> <span class="p">(</span><span class="n">shape</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">Circle</span><span class="p">(</span><span class="nl">radius:</span> <span class="kd">final</span> <span class="n">r</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="mf">3.14</span> <span class="o">*</span> <span class="n">r</span> <span class="o">*</span> <span class="n">r</span><span class="p">,</span>
      <span class="n">Rectangle</span><span class="p">(</span><span class="nl">width:</span> <span class="kd">final</span> <span class="n">w</span><span class="p">,</span> <span class="nl">height:</span> <span class="kd">final</span> <span class="n">h</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">w</span> <span class="o">*</span> <span class="n">h</span><span class="p">,</span>
      <span class="n">Triangle</span><span class="p">(</span><span class="kd">base</span><span class="o">:</span> <span class="kd">final</span> <span class="n">b</span><span class="p">,</span> <span class="nl">height:</span> <span class="kd">final</span> <span class="n">h</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="mf">0.5</span> <span class="o">*</span> <span class="n">b</span> <span class="o">*</span> <span class="n">h</span>
 <span class="p">};</span>
</code></pre></div></div>

<h2 id="pattern-matching-in-dart">Pattern Matching in Dart</h2>

<p>Let’s explore some sophisticated <a href="https://dart.dev/language/patterns">pattern-matching</a> examples in Dart to showcase the power of ADTs. See my library <a href="https://pub.dev/packages/nadz">nadz</a> for more complete examples of Result objects as ADTs. This library is also loaded with extension methods to make working with ADTs easier.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">sealed</span> <span class="kd">class</span> <span class="nc">Result</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="n">Result</span><span class="p">();</span>
<span class="p">}</span>

<span class="kd">final</span> <span class="kd">class</span> <span class="nc">Success</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="kd">extends</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="n">T</span> <span class="n">value</span><span class="p">;</span>
  <span class="kd">const</span> <span class="n">Success</span><span class="p">(</span><span class="k">this</span><span class="o">.</span><span class="na">value</span><span class="p">);</span>
<span class="p">}</span>

<span class="kd">final</span> <span class="kd">class</span> <span class="nc">Failure</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="kd">extends</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="kt">String</span> <span class="n">error</span><span class="p">;</span>
  <span class="kd">const</span> <span class="n">Failure</span><span class="p">(</span><span class="k">this</span><span class="o">.</span><span class="na">error</span><span class="p">);</span>
<span class="p">}</span>

<span class="n">T</span> <span class="n">match</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">R</span><span class="p">&gt;(</span>
  <span class="n">Result</span><span class="p">&lt;</span><span class="n">R</span><span class="p">&gt;</span> <span class="n">result</span><span class="p">,</span>
  <span class="n">T</span> <span class="kt">Function</span><span class="p">(</span><span class="n">R</span><span class="p">)</span> <span class="n">onSuccess</span><span class="p">,</span>
  <span class="n">T</span> <span class="kt">Function</span><span class="p">(</span><span class="kt">String</span><span class="p">)</span> <span class="n">onFailure</span><span class="p">,</span>
<span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span>
    <span class="k">switch</span> <span class="p">(</span><span class="n">result</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">Success</span><span class="p">(</span><span class="nl">value:</span> <span class="kd">final</span> <span class="n">v</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">onSuccess</span><span class="p">(</span><span class="n">v</span><span class="p">),</span>
      <span class="n">Failure</span><span class="p">(</span><span class="nl">error:</span> <span class="kd">final</span> <span class="n">e</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">onFailure</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
 <span class="p">};</span>

<span class="c1">// Usage</span>
<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="n">result</span> <span class="o">=</span> <span class="n">Success</span><span class="p">(</span><span class="mi">42</span><span class="p">);</span>
  <span class="kd">final</span> <span class="n">output</span> <span class="o">=</span>
      <span class="n">match</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="s">"Success: </span><span class="si">$value</span><span class="s">"</span><span class="p">,</span> <span class="p">(</span><span class="n">error</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="s">"Failure: </span><span class="si">$error</span><span class="s">"</span><span class="p">);</span>
  <span class="n">print</span><span class="p">(</span><span class="n">output</span><span class="p">);</span> <span class="c1">// Output: Success: 42</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The above is an example of a Result object that can either have a state of <code class="language-plaintext highlighter-rouge">Success</code> or <code class="language-plaintext highlighter-rouge">Failure</code> and allows you to specify what mutually exclusive data these two states have.</p>

<p>Here’s a more complex example using nested patterns:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'dart:math'</span><span class="o">;</span>

<span class="kd">sealed</span> <span class="kd">class</span> <span class="nc">Tree</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="n">Tree</span><span class="p">();</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nc">Leaf</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="kd">extends</span> <span class="n">Tree</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="n">T</span> <span class="n">value</span><span class="p">;</span>
  <span class="kd">const</span> <span class="n">Leaf</span><span class="p">(</span><span class="k">this</span><span class="o">.</span><span class="na">value</span><span class="p">);</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nc">Node</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="kd">extends</span> <span class="n">Tree</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="n">Tree</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">left</span><span class="p">;</span>
  <span class="kd">final</span> <span class="n">Tree</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">right</span><span class="p">;</span>
  <span class="kd">const</span> <span class="n">Node</span><span class="p">(</span><span class="k">this</span><span class="o">.</span><span class="na">left</span><span class="p">,</span> <span class="k">this</span><span class="o">.</span><span class="na">right</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">sum</span><span class="p">(</span><span class="n">Tree</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;</span> <span class="n">tree</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="k">switch</span> <span class="p">(</span><span class="n">tree</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">Leaf</span><span class="p">(</span><span class="nl">value:</span> <span class="kd">final</span> <span class="n">v</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">v</span><span class="p">,</span>
      <span class="n">Node</span><span class="p">(</span><span class="nl">left:</span> <span class="kd">final</span> <span class="n">l</span><span class="p">,</span> <span class="nl">right:</span> <span class="kd">final</span> <span class="n">r</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">sum</span><span class="p">(</span><span class="n">l</span><span class="p">)</span> <span class="o">+</span> <span class="n">sum</span><span class="p">(</span><span class="n">r</span><span class="p">)</span>
 <span class="p">};</span>

<span class="kt">num</span> <span class="n">depth</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">Tree</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">tree</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="k">switch</span> <span class="p">(</span><span class="n">tree</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">Leaf</span><span class="p">()</span> <span class="o">=</span><span class="p">&gt;</span> <span class="mi">0</span><span class="p">,</span>
      <span class="n">Node</span><span class="p">(</span><span class="nl">left:</span> <span class="kd">final</span> <span class="n">l</span><span class="p">,</span> <span class="nl">right:</span> <span class="kd">final</span> <span class="n">r</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="mi">1</span> <span class="o">+</span> <span class="n">max</span><span class="p">(</span><span class="n">depth</span><span class="p">(</span><span class="n">l</span><span class="p">),</span> <span class="n">depth</span><span class="p">(</span><span class="n">r</span><span class="p">))</span>
 <span class="p">};</span>

<span class="c1">// Usage</span>
<span class="kd">final</span> <span class="n">tree</span> <span class="o">=</span>
    <span class="n">Node</span><span class="p">(</span><span class="n">Node</span><span class="p">(</span><span class="n">Leaf</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span> <span class="n">Leaf</span><span class="p">(</span><span class="mi">2</span><span class="p">)),</span> <span class="n">Node</span><span class="p">(</span><span class="n">Leaf</span><span class="p">(</span><span class="mi">3</span><span class="p">),</span> <span class="n">Node</span><span class="p">(</span><span class="n">Leaf</span><span class="p">(</span><span class="mi">4</span><span class="p">),</span> <span class="n">Leaf</span><span class="p">(</span><span class="mi">5</span><span class="p">))));</span>

<span class="n">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="n">print</span><span class="p">(</span><span class="n">sum</span><span class="p">(</span><span class="n">tree</span><span class="p">));</span> <span class="c1">// Output: 15</span>
  <span class="n">print</span><span class="p">(</span><span class="n">depth</span><span class="p">(</span><span class="n">tree</span><span class="p">));</span> <span class="c1">// Output: 3</span>
<span class="p">}</span>
</code></pre></div></div>

<p>ADTs are a great choice for tree structures in Dart because any element could be a <code class="language-plaintext highlighter-rouge">Node</code> or a <code class="language-plaintext highlighter-rouge">Leaf</code>.</p>

<h2 id="avoid-casting-and-unnecessary-assignments">Avoid Casting and Unnecessary Assignments</h2>

<p>FP encourages the use of expressions. Expressions are generally more concise than imperative statements. Instead of writing code in a step-by-step way, expressions return the value of one or more operations. Pattern matching with constructs like Dart switch expressions allow you to write fluid expressions that don’t require unnecessary variable assignments or casting.</p>

<p>While this point is not strictly about ADTs, it illustrates how a shift towards expressions over statements can generally improve your code. ADTs help you move your code in this direction.</p>

<p>Consider these three functions.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'dart:convert'</span><span class="o">;</span>

<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="n">jsonMap</span> <span class="o">=</span> <span class="n">jsonDecode</span><span class="p">(</span><span class="s">'{ "test": 123 }'</span><span class="p">);</span>

  <span class="kd">final</span> <span class="n">one</span> <span class="o">=</span> <span class="n">numberWithCasting</span><span class="p">(</span><span class="n">jsonMap</span> <span class="k">as</span> <span class="kt">Map</span><span class="p">&lt;</span><span class="kt">String</span><span class="p">,</span> <span class="kd">dynamic</span><span class="p">&gt;);</span>
  <span class="kd">final</span> <span class="n">two</span> <span class="o">=</span> <span class="n">numberWithTypePromotion</span><span class="p">(</span><span class="n">jsonMap</span><span class="p">);</span>
  <span class="kd">final</span> <span class="n">three</span> <span class="o">=</span> <span class="n">numberWithSwitch</span><span class="p">(</span><span class="n">jsonMap</span><span class="p">);</span>

  <span class="n">print</span><span class="p">(</span><span class="n">one</span><span class="p">);</span>
  <span class="n">print</span><span class="p">(</span><span class="n">two</span><span class="p">);</span>
  <span class="n">print</span><span class="p">(</span><span class="n">three</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">numberWithCasting</span><span class="p">(</span>
  <span class="kt">Map</span><span class="p">&lt;</span><span class="kt">String</span><span class="p">,</span> <span class="kd">dynamic</span><span class="p">&gt;</span> <span class="n">jsonMap</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">jsonMap</span><span class="p">[</span><span class="s">'test'</span><span class="p">]</span> <span class="k">is</span> <span class="kt">int</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">jsonMap</span><span class="p">[</span><span class="s">'test'</span><span class="p">]</span> <span class="k">as</span> <span class="kt">int</span><span class="p">;</span>
 <span class="p">}</span>
  <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">numberWithTypePromotion</span><span class="p">(</span>
  <span class="kt">Map</span><span class="p">&lt;</span><span class="kt">String</span><span class="p">,</span> <span class="kd">dynamic</span><span class="p">&gt;</span> <span class="n">jsonMap</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="n">test</span> <span class="o">=</span> <span class="n">jsonMap</span><span class="p">[</span><span class="s">'test'</span><span class="p">];</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">test</span> <span class="k">is</span> <span class="kt">int</span><span class="p">)</span> <span class="k">return</span> <span class="n">test</span><span class="p">;</span>
  <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">numberWithSwitch</span><span class="p">(</span>
  <span class="kt">Map</span><span class="p">&lt;</span><span class="kt">String</span><span class="p">,</span> <span class="kd">dynamic</span><span class="p">&gt;</span> <span class="n">jsonMap</span><span class="p">,</span>
<span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span>
    <span class="k">switch</span> <span class="p">(</span><span class="n">jsonMap</span><span class="p">[</span><span class="s">'test'</span><span class="p">])</span> <span class="p">{</span>
      <span class="kd">final</span> <span class="kt">int</span> <span class="n">value</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">value</span><span class="p">,</span>
 <span class="n">_</span> <span class="o">=</span><span class="p">&gt;</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span>
 <span class="p">};</span>
</code></pre></div></div>

<p>Notice that the first two functions require you to either cast the type to <code class="language-plaintext highlighter-rouge">int</code> with <code class="language-plaintext highlighter-rouge">as</code> or make a variable assignment. There is nothing inherently wrong with the extra assignment in <code class="language-plaintext highlighter-rouge">numberWithTypePromotion</code>, but the naming of the variable can detract from the readability of the function and make the code more verbose.</p>

<p>On the other hand, casting is generally bad because it can cause exceptions. Avoiding the <code class="language-plaintext highlighter-rouge">as</code> keyword is generally advisable, even in cases where it obviously does not cause an issue. If someone refactors this code, they may separate the type check from the cast, and then the cast becomes dangerous.</p>

<p>The third function, <code class="language-plaintext highlighter-rouge">numberWithSwitch,</code> addresses both issues by eliminating the need for a variable assignment and combining the type check with the switch. The switch only returns the value if the expected type occurs and allows us to safely deal with other cases.</p>

<h2 id="records">Records</h2>

<p>Records are another recent addition to the Dart language and add further expressiveness to ADTs. Here is a simple example of a record type, which is a product type in Dart.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">typedef</span> <span class="n">Point</span> <span class="o">=</span> <span class="p">(</span><span class="kt">double</span> <span class="n">x</span><span class="p">,</span> <span class="kt">double</span> <span class="n">y</span><span class="p">);</span>

<span class="kt">double</span> <span class="nf">distanceFromOrigin</span><span class="p">((</span><span class="kt">double</span><span class="p">,</span> <span class="kt">double</span><span class="p">)</span> <span class="n">point</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="o">=</span> <span class="n">point</span><span class="p">;</span>
  <span class="k">return</span> <span class="n">sqrt</span><span class="p">(</span><span class="n">x</span> <span class="o">*</span> <span class="n">x</span> <span class="o">+</span> <span class="n">y</span> <span class="o">*</span> <span class="n">y</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="a-complete-example">A Complete Example</h2>

<p>This example combines several different types of data into a record and gives you an example of how you might use it in switch expression with pattern matching</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Sealed class representing different account statuses</span>
<span class="kd">sealed</span> <span class="kd">class</span> <span class="nc">AccountStatus</span> <span class="p">{}</span>

<span class="c1">// Represents an active user account</span>
<span class="kd">class</span> <span class="nc">Active</span> <span class="kd">extends</span> <span class="n">AccountStatus</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="n">DateTime</span> <span class="n">lastActive</span><span class="p">;</span>
  <span class="n">Active</span><span class="p">(</span><span class="k">this</span><span class="o">.</span><span class="na">lastActive</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// Represents a suspended user account</span>
<span class="kd">class</span> <span class="nc">Suspended</span> <span class="kd">extends</span> <span class="n">AccountStatus</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="kt">String</span> <span class="n">reason</span><span class="p">;</span>
  <span class="n">Suspended</span><span class="p">(</span><span class="k">this</span><span class="o">.</span><span class="na">reason</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// Represents a deactivated user account</span>
<span class="kd">class</span> <span class="nc">Deactivated</span> <span class="kd">extends</span> <span class="n">AccountStatus</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="n">DateTime</span> <span class="n">deactivationDate</span><span class="p">;</span>
  <span class="n">Deactivated</span><span class="p">(</span><span class="k">this</span><span class="o">.</span><span class="na">deactivationDate</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// Record type representing a user profile with bio, interests, and account status</span>
<span class="kd">typedef</span> <span class="n">UserProfile</span> <span class="o">=</span> <span class="p">(</span>
  <span class="kt">String</span><span class="o">?</span> <span class="n">bio</span><span class="p">,</span>
  <span class="kt">List</span><span class="p">&lt;</span><span class="kt">String</span><span class="p">&gt;</span> <span class="n">interests</span><span class="p">,</span>
  <span class="n">AccountStatus</span> <span class="n">status</span>
<span class="p">);</span>

<span class="kt">String</span> <span class="nf">analyzeProfile</span><span class="p">(</span><span class="n">UserProfile</span> <span class="n">profile</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="k">switch</span> <span class="p">(</span><span class="n">profile</span><span class="p">)</span> <span class="p">{</span>
      <span class="c1">// New or minimal active profiles</span>
      <span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="p">[],</span> <span class="n">Active</span><span class="p">(</span><span class="nl">lastActive:</span> <span class="kd">final</span> <span class="n">date</span><span class="p">))</span> <span class="o">=</span><span class="p">&gt;</span>
        <span class="s">"New user, last active on </span><span class="si">${date.toLocal()}</span><span class="s">. Needs to complete profile."</span><span class="p">,</span>
      <span class="p">(</span><span class="kt">String</span> <span class="n">b</span><span class="p">,</span> <span class="p">[</span><span class="kd">final</span> <span class="n">single</span><span class="p">],</span> <span class="n">Active</span><span class="p">())</span> <span class="o">=</span><span class="p">&gt;</span>
        <span class="s">"Minimal profile: '</span><span class="si">$b</span><span class="s">'. Only interested in </span><span class="si">$single</span><span class="s">. Very active."</span><span class="p">,</span>

      <span class="c1">// Active coder profiles</span>
      <span class="p">(</span><span class="n">_</span><span class="p">,</span> <span class="p">[</span><span class="s">"coding"</span><span class="p">,</span> <span class="kd">final</span> <span class="n">second</span><span class="p">,</span> <span class="kd">final</span> <span class="n">third</span><span class="p">,</span> <span class="p">...],</span> <span class="n">Active</span><span class="p">())</span> <span class="o">=</span><span class="p">&gt;</span>
        <span class="s">"Active coder also interested in </span><span class="si">$second</span><span class="s"> and </span><span class="si">$third</span><span class="s">."</span><span class="p">,</span>

      <span class="c1">// Other active profiles</span>
      <span class="p">(</span><span class="kt">String</span><span class="p">(),</span> <span class="p">[],</span> <span class="n">Active</span><span class="p">())</span> <span class="o">||</span>
      <span class="p">(</span><span class="kt">String</span><span class="p">(),</span> <span class="p">[</span><span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">],</span> <span class="n">Active</span><span class="p">())</span> <span class="o">||</span>
      <span class="p">(</span><span class="kt">String</span><span class="p">(),</span> <span class="p">[</span><span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="p">...],</span> <span class="n">Active</span><span class="p">())</span> <span class="o">=</span><span class="p">&gt;</span>
        <span class="s">"Active user with varying interests."</span><span class="p">,</span>
      <span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="p">[</span><span class="n">_</span><span class="p">],</span> <span class="n">Active</span><span class="p">())</span> <span class="o">||</span>
      <span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="p">[</span><span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">],</span> <span class="n">Active</span><span class="p">())</span> <span class="o">||</span>
      <span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="p">[</span><span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="p">...],</span> <span class="n">Active</span><span class="p">())</span> <span class="o">=</span><span class="p">&gt;</span>
        <span class="s">"Active user without bio, with varying interests."</span><span class="p">,</span>

      <span class="c1">// Suspended profiles</span>
      <span class="p">(</span><span class="n">_</span><span class="p">,</span> <span class="p">[</span><span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="p">...],</span> <span class="n">Suspended</span><span class="p">(</span><span class="nl">reason:</span> <span class="kd">final</span> <span class="n">r</span><span class="p">))</span> <span class="o">=</span><span class="p">&gt;</span>
        <span class="s">"Suspended account (</span><span class="si">$r</span><span class="s">) with multiple interests."</span><span class="p">,</span>
      <span class="p">(</span><span class="kt">String</span><span class="p">()</span> <span class="o">||</span> <span class="kc">null</span><span class="p">,</span> <span class="p">[],</span> <span class="n">Suspended</span><span class="p">())</span> <span class="o">=</span><span class="p">&gt;</span>
        <span class="s">"Suspended account with no interests."</span><span class="p">,</span>
      <span class="p">(</span><span class="kt">String</span><span class="p">()</span> <span class="o">||</span> <span class="kc">null</span><span class="p">,</span> <span class="p">[</span><span class="n">_</span><span class="p">],</span> <span class="n">Suspended</span><span class="p">())</span> <span class="o">=</span><span class="p">&gt;</span>
        <span class="s">"Suspended account with one interest."</span><span class="p">,</span>

      <span class="c1">// Deactivated profiles</span>
      <span class="p">(</span><span class="kt">String</span> <span class="n">b</span><span class="p">,</span> <span class="kd">final</span> <span class="n">ints</span><span class="p">,</span> <span class="n">Deactivated</span><span class="p">(</span><span class="nl">deactivationDate:</span> <span class="kd">final</span> <span class="n">date</span><span class="p">))</span> <span class="o">=</span><span class="p">&gt;</span>
        <span class="s">"Deactivated on </span><span class="si">$date</span><span class="s">. Bio: '</span><span class="si">$b</span><span class="s">'. Had </span><span class="si">${ints.length}</span><span class="s"> interests."</span><span class="p">,</span>
      <span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="p">[],</span> <span class="n">Deactivated</span><span class="p">())</span> <span class="o">=</span><span class="p">&gt;</span>
        <span class="s">"Deactivated account without bio or interests."</span><span class="p">,</span>
      <span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="p">[</span><span class="n">_</span><span class="p">],</span> <span class="n">Deactivated</span><span class="p">())</span> <span class="o">=</span><span class="p">&gt;</span>
        <span class="s">"Deactivated account without bio, with one interest."</span><span class="p">,</span>
      <span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="p">[</span><span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">],</span> <span class="n">Deactivated</span><span class="p">())</span> <span class="o">=</span><span class="p">&gt;</span>
        <span class="s">"Deactivated account without bio, with two interests."</span><span class="p">,</span>
      <span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="p">[</span><span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="p">...],</span> <span class="n">Deactivated</span><span class="p">())</span> <span class="o">=</span><span class="p">&gt;</span>
        <span class="s">"Deactivated account without bio, with multiple interests."</span><span class="p">,</span>
    <span class="p">};</span>

<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="n">now</span> <span class="o">=</span> <span class="n">DateTime</span><span class="o">.</span><span class="na">now</span><span class="p">();</span>
  <span class="kd">final</span> <span class="n">profiles</span> <span class="o">=</span> <span class="p">&lt;(</span><span class="kt">String</span><span class="o">?</span><span class="p">,</span> <span class="kt">List</span><span class="p">&lt;</span><span class="kt">String</span><span class="p">&gt;,</span> <span class="n">AccountStatus</span><span class="p">)&gt;[</span>
    <span class="c1">// New active user</span>
    <span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="p">&lt;</span><span class="kt">String</span><span class="p">&gt;[],</span> <span class="n">Active</span><span class="p">(</span><span class="n">now</span><span class="p">)),</span>

    <span class="c1">// Minimal active profile</span>
    <span class="p">(</span><span class="s">"Dart lover"</span><span class="p">,</span> <span class="p">[</span><span class="s">"programming"</span><span class="p">],</span> <span class="n">Active</span><span class="p">(</span><span class="n">now</span><span class="p">)),</span>

    <span class="c1">// Active coder profile</span>
    <span class="p">(</span><span class="s">"I code, therefore I am"</span><span class="p">,</span> <span class="p">[</span><span class="s">"coding"</span><span class="p">,</span> <span class="s">"philosophy"</span><span class="p">,</span> <span class="s">"coffee"</span><span class="p">],</span> <span class="n">Active</span><span class="p">(</span><span class="n">now</span><span class="p">)),</span>

    <span class="c1">// Active user with varying interests</span>
    <span class="p">(</span><span class="s">"Eclectic"</span><span class="p">,</span> <span class="p">[</span><span class="s">"music"</span><span class="p">,</span> <span class="s">"sports"</span><span class="p">,</span> <span class="s">"cooking"</span><span class="p">,</span> <span class="s">"travel"</span><span class="p">],</span> <span class="n">Active</span><span class="p">(</span><span class="n">now</span><span class="p">)),</span>

    <span class="c1">// Active user without bio, with varying interests</span>
    <span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="p">[</span><span class="s">"reading"</span><span class="p">,</span> <span class="s">"writing"</span><span class="p">,</span> <span class="s">"arithmetic"</span><span class="p">],</span> <span class="n">Active</span><span class="p">(</span><span class="n">now</span><span class="p">)),</span>

    <span class="c1">// Suspended profile with multiple interests</span>
    <span class="p">(</span><span class="s">"Flutter enthusiast"</span><span class="p">,</span> <span class="p">[</span><span class="s">"mobile"</span><span class="p">,</span> <span class="s">"web"</span><span class="p">,</span> <span class="s">"AI"</span><span class="p">],</span> <span class="n">Suspended</span><span class="p">(</span><span class="s">"Spam"</span><span class="p">)),</span>

    <span class="c1">// Suspended profile with no interests</span>
    <span class="p">(</span><span class="s">"Oops"</span><span class="p">,</span> <span class="p">[],</span> <span class="n">Suspended</span><span class="p">(</span><span class="s">"Violation of terms"</span><span class="p">)),</span>

    <span class="c1">// Suspended profile with one interest</span>
    <span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="p">[</span><span class="s">"trouble"</span><span class="p">],</span> <span class="n">Suspended</span><span class="p">(</span><span class="s">"Inappropriate behavior"</span><span class="p">)),</span>

    <span class="c1">// Deactivated profile with bio and interests</span>
    <span class="p">(</span><span class="s">"Ex-user"</span><span class="p">,</span> <span class="p">[</span><span class="s">"reading"</span><span class="p">,</span> <span class="s">"writing"</span><span class="p">],</span> <span class="n">Deactivated</span><span class="p">(</span><span class="n">DateTime</span><span class="p">(</span><span class="mi">2023</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">31</span><span class="p">))),</span>

    <span class="c1">// Deactivated profile without bio or interests</span>
    <span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="p">[],</span> <span class="n">Deactivated</span><span class="p">(</span><span class="n">DateTime</span><span class="p">(</span><span class="mi">2023</span><span class="p">,</span> <span class="mi">11</span><span class="p">,</span> <span class="mi">15</span><span class="p">))),</span>

    <span class="c1">// Deactivated profile without bio, with one interest</span>
    <span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="p">[</span><span class="s">"gaming"</span><span class="p">],</span> <span class="n">Deactivated</span><span class="p">(</span><span class="n">DateTime</span><span class="p">(</span><span class="mi">2023</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">1</span><span class="p">))),</span>

    <span class="c1">// Deactivated profile without bio, with two interests</span>
    <span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="p">[</span><span class="s">"music"</span><span class="p">,</span> <span class="s">"dance"</span><span class="p">],</span> <span class="n">Deactivated</span><span class="p">(</span><span class="n">DateTime</span><span class="p">(</span><span class="mi">2023</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span> <span class="mi">15</span><span class="p">))),</span>

    <span class="c1">// Deactivated profile without bio, with multiple interests</span>
    <span class="p">(</span>
      <span class="kc">null</span><span class="p">,</span>
      <span class="p">[</span><span class="s">"art"</span><span class="p">,</span> <span class="s">"science"</span><span class="p">,</span> <span class="s">"history"</span><span class="p">,</span> <span class="s">"literature"</span><span class="p">],</span>
      <span class="n">Deactivated</span><span class="p">(</span><span class="n">DateTime</span><span class="p">(</span><span class="mi">2023</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span>
    <span class="p">),</span>
  <span class="p">];</span>

  <span class="c1">// Analyze and print results for each profile</span>
  <span class="k">for</span> <span class="p">(</span><span class="kd">final</span> <span class="n">profile</span> <span class="k">in</span> <span class="n">profiles</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">print</span><span class="p">(</span><span class="n">analyzeProfile</span><span class="p">(</span><span class="n">profile</span><span class="p">));</span>
    <span class="n">print</span><span class="p">(</span><span class="s">'---'</span><span class="p">);</span> <span class="c1">// Separator for readability</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h4 id="output">Output</h4>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Minimal</span> <span class="nl">profile:</span> <span class="s">'Dart lover'</span><span class="p">.</span> <span class="n">Only</span> <span class="n">interested</span> <span class="k">in</span> <span class="n">programming</span><span class="p">.</span> <span class="n">Very</span> <span class="n">active</span><span class="p">.</span>

<span class="n">Active</span> <span class="n">coder</span> <span class="n">also</span> <span class="n">interested</span> <span class="k">in</span> <span class="n">philosophy</span> <span class="n">and</span> <span class="n">coffee</span><span class="p">.</span>

<span class="n">Active</span> <span class="n">user</span> <span class="k">with</span> <span class="n">varying</span> <span class="n">interests</span><span class="p">.</span>

<span class="n">Active</span> <span class="n">user</span> <span class="n">without</span> <span class="n">bio</span><span class="p">,</span> <span class="k">with</span> <span class="n">varying</span> <span class="n">interests</span><span class="p">.</span>

<span class="n">Suspended</span> <span class="nf">account</span> <span class="p">(</span><span class="n">Spam</span><span class="p">)</span> <span class="k">with</span> <span class="n">multiple</span> <span class="n">interests</span><span class="p">.</span>

<span class="n">Suspended</span> <span class="n">account</span> <span class="k">with</span> <span class="n">no</span> <span class="n">interests</span><span class="p">.</span>

<span class="n">Suspended</span> <span class="n">account</span> <span class="k">with</span> <span class="n">one</span> <span class="n">interest</span><span class="p">.</span>

<span class="n">Deactivated</span> <span class="kd">on</span> <span class="mi">2023</span><span class="o">-</span><span class="mi">12</span><span class="o">-</span><span class="mi">31</span> <span class="mi">00</span><span class="o">:</span><span class="mi">00</span><span class="o">:</span><span class="mf">00.000</span><span class="p">.</span> <span class="nl">Bio:</span> <span class="s">'Ex-user'</span><span class="p">.</span> <span class="n">Had</span> <span class="mi">2</span> <span class="n">interests</span><span class="p">.</span>

<span class="n">Deactivated</span> <span class="n">account</span> <span class="n">without</span> <span class="n">bio</span> <span class="n">or</span> <span class="n">interests</span><span class="p">.</span>

<span class="n">Deactivated</span> <span class="n">account</span> <span class="n">without</span> <span class="n">bio</span><span class="p">,</span> <span class="k">with</span> <span class="n">one</span> <span class="n">interest</span><span class="p">.</span>

<span class="n">Deactivated</span> <span class="n">account</span> <span class="n">without</span> <span class="n">bio</span><span class="p">,</span> <span class="k">with</span> <span class="n">two</span> <span class="n">interests</span><span class="p">.</span>

<span class="n">Deactivated</span> <span class="n">account</span> <span class="n">without</span> <span class="n">bio</span><span class="p">,</span> <span class="k">with</span> <span class="n">multiple</span> <span class="n">interests</span><span class="p">.</span>
</code></pre></div></div>

<p>Notice that the switch cases allow you to bind variables. For example <code class="language-plaintext highlighter-rouge">lastActive</code> becomes <code class="language-plaintext highlighter-rouge">date</code>. Also notice that we don’t need to do a null check on <code class="language-plaintext highlighter-rouge">Bio: '$b'</code> because the match already determined that the value is not null.</p>

<h2 id="conclusion">Conclusion</h2>
<p>Dart has taken a significant step towards supporting the functional programming paradigm. These features enable developers to write more expressive, type-safe, and maintainable code and it bridges the gap between Dart and languages traditionally associated with functional programming.</p>

<p>The access modifiers enable powerful ADT features, and if you start thinking in FP style, your code design becomes more natural and fluent. There are fewer opportunities for exceptions to occur. It probably won’t be obvious until you try it a few times for yourself, but this is a game changer for your code design game.</p>]]></content><author><name>Christian Findlay</name></author><category term="flutter" /><category term="dart" /><category term="ADTs" /><category term="functional-programming" /><category term="records" /><category term="fsharp" /><summary type="html"><![CDATA[Explore Algebraic Data Types in Dart 3.0+ with sealed class pattern matching, sum and product types, and exhaustive switch expressions in Flutter.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.christianfindlay.com/assets/images/blog/adts/ADTs.webp" /><media:content medium="image" url="https://www.christianfindlay.com/assets/images/blog/adts/ADTs.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>