Code With AndreaLearn how to become a Flutter Pro and build production-ready apps on mobile and beyond.https://codewithandrea.comenThu, 26 Feb 2026 13:11:30 +0100Thu, 26 Feb 2026 13:11:30 +0100250https://codewithandrea.com/newsletter/february-2026/February 2026: Flutter 3.41, 16 Claudes Build a C Compiler, the OpenClaw Security Crisis, and GPT-5.3 CodexAlso included: why the SDLC is dead, the importance of documentation artifacts for AI coding, and why code is cheap but software isn't.https://codewithandrea.com/newsletter/february-2026/Thu, 26 Feb 2026 01:00:00 +0100What a month for AI coding.

Anthropic got 16 Claude instances to build a C compiler (with some caveats). OpenAI shipped GPT-5.3-Codex, and it's good! And then OpenClaw โ€” a vibe-coded AI agent with 230K GitHub stars โ€” became a full-blown security crisis, with malicious packages showing up on pub.dev too.

There's a new Flutter release as well, so let's start there. ๐Ÿ‘‡

Flutter 3.41 + Dart 3.11

Flutter 3.41 landed earlier this month with 868 commits from 145 contributors. The ongoing work to decouple Material and Cupertino into standalone packages continues, and this release brings several other improvements:

  • Fragment shader improvements: Synchronous image decoding (decodeImageFromPixelsSync) eliminates frame lag, and 128-bit float texture support unlocks GPU-accelerated photo filters.
  • Impeller bounded blur: Fixes color bleeding on translucent widgets using BackdropFilter.
  • Widget Previewer: Better VS Code and IntelliJ integration, plus support for dependencies on platform-specific libraries like dart:ffi and dart:io.
  • New Getting Started Experience: The redesigned Learn section on the Flutter website, powered by Jaspr.
  • 2026 Roadmap: Four stable releases planned. WebAssembly is on track to become the default for Flutter Web (40% faster load times, 30% less runtime memory).

For the full details, read the official blog post:

As for Dart 3.11, this release focuses on tooling improvements rather than new language features. The highlights include faster analyzer plugins (~10s saved on startup), a new dart pub cache gc command to reclaim disk space, enhanced dot shorthand tooling, and pub workspace glob support. The Dart & Flutter MCP Server was also improved. ๐Ÿค–

For all the details:

For more context about Flutter's position in 2026 (including a full timeline of significant milestones over the last year), check out this State of Flutter 2026 article.

AI News

This was a big month for agentic AI coding. Two stories in particular stood out to me.

๐Ÿ“ Building a C Compiler with a Team of Parallel Claudes

Anthropic researcher Nicholas Carlini orchestrated 16 Claude instances to build a C compiler in Rust over nearly 2,000 Claude Code sessions and ~$20K in API costs. The result: a 100,000-line compiler with a 99% pass rate on standard compiler test suites.

The multi-agent engineering appears to be genuinely impressive:

  • Agents claimed work from a shared task queue via lock files, with git handling merges and conflict resolution.
  • Each agent had specialized roles: core compilation, code deduplication, performance optimization, documentation, and architecture.
  • Tests served as the primary feedback mechanism, with comprehensive suites using SQLite, Redis, libjpeg, and the GCC torture tests.

Here's the full write-up:

However, Anthropic's marketing of the project attracted legitimate criticism. ThePrimeagen published a detailed response pointing out that the framing of "from scratch, no human intervention" was misleading:

  • Not "from scratch": Claude was trained on GCC (open source), and used its 37-year-old torture test suite for validation, plus GCC itself as an "online Oracle" to verify output. Starting "from scratch" with the perfect test suite and a reference implementation to test against is not quite the same thing as starting from a blank slate.
  • Not "no human intervention": agents crashed and required restarting during the two weeks.
  • Can't actually boot Linux: the compiler lacks a 16-bit x86 code generator small enough to meet Linux's 32KB real mode limit.
  • Hello World didn't compile: the README example failed because the project lacks a linker.

So what's the real takeaway? As ThePrimeagen himself acknowledges: getting 16 AI agents to run mostly autonomously for two weeks and produce a substantial, functional piece of software is genuinely cool. It shows that models are improving and can handle projects at this scale with the right orchestration. That's the real story โ€” no hype needed.

Chris Lattner (creator of LLVM, Swift, and Mojo) also published a thoughtful analysis, calling it "real progress, a milestone for the industry" while noting that the compiler reproduces decades of established engineering rather than inventing novel abstractions. His core thesis: as coding becomes cheaper, the real challenge becomes choosing the right problems and managing the resulting complexity.

๐Ÿ“ GPT-5.3-Codex: Full Autonomy Has Arrived?

Earlier this month, OpenAI released GPT-5.3-Codex, and the early reviews are eye-opening.

Matt Shumer's hands-on review describes it as the first model where "full autonomy starts feeling operationally real." In practice, this means you can specify an outcome, set up validation criteria, press go, and come back hours later to find the work done โ€” including code changes, GitHub pushes, deployments, and log monitoring.

Key highlights:

  • Can run tasks for 8+ hours without degradation.
  • 25% faster than GPT-5.2-Codex, and tops SWE-Bench Pro and Terminal-Bench 2.0.
  • Self-improvement capabilities: the model debugged its own training run and scaled its own GPU clusters.

I've been testing GPT-5.3-Codex for my own Flutter work over the past few weeks, and I have to say: I'm finding it faster, cheaper, and sharper than Opus 4.6 โ€” often surfacing edge cases and insights that Opus misses. With that said, while "8+ hours without degradation" may be possible, my workflows require frequent human oversight and I care more about output quality than "how long it can run".

The OpenClaw Security Crisis

Now for the other side of the coin. If the Claude compiler pushes the boundaries of AI autonomy, the OpenClaw saga shows what happens when the worst practices collide with rapid adoption.

OpenClaw (formerly Clawdbot/Moltbot) is an open-source AI agent that exploded to 150K+ GitHub stars. It connects to LLMs and can autonomously execute tasks through messaging platforms like WhatsApp, Telegram, and Slack. Sounds cool, right?

The problem: OpenClaw requires broad permissions to function (email, calendars, messaging platforms, file system), and misconfigured or exposed instances quickly became a magnet for attackers. One of OpenClaw's own maintainers warned on Discord: "if you can't understand how to run a command line, this is far too dangerous of a project for you to use safely." Within weeks, the security issues piled up:

  • 40,000+ instances exposed on the public internet because the gateway binds to 0.0.0.0 by default.
  • API keys stored in plaintext markdown and JSON files.
  • 12-20% of ClawHub marketplace skills were malicious, with the ClawHavoc campaign distributing Atomic Stealer to harvest crypto keys, SSH credentials, and browser passwords.
  • CVE-2026-25253 (CVSS 8.8): a one-click remote code execution exploit where visiting a single malicious webpage is enough.
  • Prompt injection attacks already seen in the wild, including crypto wallet drain attempts.

For the full details, here's CrowdStrike's breakdown:

And it gets worse. In a case of meta-irony, Cline CLI was also compromised via a supply chain attack that silently installed OpenClaw on ~4,000 developer machines. The root cause? Prompt injection exploiting AI-assisted GitHub workflows to steal npm publish credentials. An AI coding tool, compromised via an AI-specific attack vector. ๐Ÿ˜ฌ

For entertainment value, here's a video of OpenClaw deleting an entire inbox:

Why Flutter Devs Should Care

This isn't just a general security story. The r/FlutterDev community has been flagging OpenClaw-generated packages appearing on pub.dev โ€” vibe-coded packages that lack proper testing, security review, and sometimes contain hidden dependencies or malicious code.

The broader lesson: AI agents can ship code at unprecedented speed, but that speed makes proper security practices more important, not less. Treat community-generated skills and packages with the same skepticism you'd give a random npm package from a stranger.

AI Articles

Beyond the headline stories, I bookmarked some excellent articles this month that are good food for thought.

๐Ÿ“ The Software Development Lifecycle Is Dead

Boris Tane argues that AI agents haven't just accelerated the SDLC โ€” they've dismantled it. The traditional sequential stages (requirements โ†’ design โ†’ implementation โ†’ testing โ†’ review โ†’ deployment โ†’ monitoring) didn't get faster. They merged into a single, tight feedback loop.

What actually happens when an engineer works with a coding agent
What actually happens when an engineer works with a coding agent

His key insights:

  • Requirements are now fluid and iterative rather than frozen specifications.
  • Code review via PRs becomes a bottleneck when agents generate hundreds of changes daily. Self-verification and second-agent reviews are replacing human code review for routine changes.
  • Observability becomes the primary safety mechanism, with monitoring feeding back to agents for automatic fixes.
  • "Context engineering" replaces process management as the new critical skill.

Read the full article:

I've been using a traditional "spec โ†’ plan โ†’ implement โ†’ review โ†’ ship" cycle in my own work. But I'm starting to notice that if the spec is solid and the agent has its own verification loop (TDD helps greatly here), manual code reviews become less important. The discipline shifts upstream โ€” getting the requirements right matters more than ever.

๐Ÿ“ The Importance of Artifacts in AI-Assisted Programming

Nicholas Zakas makes a compelling case for why documentation isn't optional when coding with AI. His core point: AI has no memory beyond its context window. It can't tell you why it made a decision six months ago that brought down your server today.

His recommended artifacts:

  • Product Requirements Documents (PRDs): Capture the what and why.
  • Architectural Decision Records (ADRs): Immutable records of technical choices and their rationale.
  • Technical Design Documents (TDDs): The how of implementation.
  • Task Lists: Granular work items with dependencies and acceptance criteria.

If you're using AI coding tools and not maintaining these artifacts, this article will change how you think about documentation:

These two articles pair well together: as the SDLC collapses into tight feedback loops, documentation artifacts become the new source of truth that compensates for what both AI and humans forget over time.

I also recommend Code Is Cheap Now. Software Isn't โ€” a thoughtful piece arguing that while LLMs have made code generation nearly free, the barrier to building meaningful software remains unchanged. Engineering value is shifting from syntax mastery toward architectural thinking, taste, and knowing where not to cut corners. This echoes Chris Lattner's thesis perfectly.

Latest from Code with Andrea

I've been quiet on the content front lately, and for good reason: I've been heads-down building an agentic coding toolkit for Flutter โ€” and dogfooding it heavily.

Here are a few Flutter web apps I built entirely with this spec-driven workflow (no manual coding):

If you're curious what the generated code looks like, I've open sourced the Currency Converter on GitHub:

Until Next Time

The toolkit is shaping up well and I'm hoping to launch it soon. I want to get it right โ€” something you can actually use to write quality Flutter code, faster.

Thanks for reading, and happy coding! ๐ŸŽ‰

]]>
https://codewithandrea.com/newsletter/january-2026/January 2026: AI Agents Take Over, Claude Code Workflows, Multi-Agent Orchestration, and OpenCodeAlso included: Gas Town multi-agent orchestration, Ralph loops, Anthropic's ToS crackdown, and my 2025 retrospective.https://codewithandrea.com/newsletter/january-2026/Wed, 28 Jan 2026 01:00:00 +0100Welcome to 2026! This is my 42rd monthly newsletter, which means I've been writing this for 3.5 years now (here's the full archive).

Over the past year, AI coding agents have completely reshaped how we work โ€” from vibe coding, to agentic coding with human review, all the way to fully automated multi-agent orchestration. We're all at different stages of this evolution, and I put this edition together to help you navigate it. ๐Ÿ‘‡

Andrej Karpathy on the State of AI

Andrej Karpathy has been one of the most insightful voices in AI. I featured his 2025 LLM Year in Review in my previous newsletter, and since then he's shared two more posts that are well worth your attention.

๐Ÿ“ "I've never felt this much behind as a programmer"

Earlier this month, Karpathy posted on X that he'd "never felt this much behind as a programmer." The post went viral and resonated deeply across the developer community.

He describes a new "programmable layer of abstraction" involving agents, subagents, prompts, contexts, memory, modes, permissions, tools, plugins, skills, hooks, MCP, and more. In his words:

"Clearly some powerful alien tool was handed around except it comes with no manual and everyone has to figure out how to hold it and operate it, while the resulting magnitude 9 earthquake is rocking the profession."

If even Karpathy feels behind, the rest of us can feel a bit less guilty about struggling to keep up ๐Ÿ˜…

For a counterpoint, Maximilian Schwarzmรผller recorded a reaction video with a reassuring "Relax" message. I think the truth is somewhere in the middle โ€” things are moving fast, but there's no need to panic ๐Ÿ™‚

๐Ÿ“ Random notes from Claude coding

Most recently, Karpathy shared a detailed thread with practical observations from weeks of heavy Claude Code usage. This is the most actionable of the two, and I found myself nodding along to many of his points.

Here are the highlights:

  • Workflow shift: he went from 80% manual coding to 80% agent coding in just a few weeks. "I really am mostly programming in English now."
  • Agent pitfalls: models make wrong assumptions, don't seek clarifications, don't push back when they should, and are "still a little too sycophantic." They'll implement 1,000 lines of bloated code, and when challenged, immediately cut it to 100.
  • Agent swarm hype: "too much for right now" โ€” watch models "like a hawk" if you care about your code.
  • Tenacity: agents never get tired or demoralized. Watching one struggle for 30 minutes and come out victorious is a "feel the AGI" moment.
  • Speedup vs. expansion: the main effect isn't just speed โ€” it's doing things that "wouldn't have been worth coding before."
  • Key tip: "Don't tell it what to do, give it success criteria and watch it go." Shift from imperative to declarative.
  • Fun factor: programming feels more fun โ€” drudgery removed, creative part remains, less blocked/stuck.
  • Atrophy warning: he's already noticing a decline in his ability to write code manually.

His TLDR: "LLM agent capabilities have crossed some kind of threshold of coherence around December 2025 and caused a phase shift in software engineering."

AI Workflows and Tools

So, with the big picture in mind, let's look at what builders are actually doing with these tools.

๐Ÿ“ How the Creator of Claude Code Uses Claude Code

Boris Cherny, the creator of Claude Code, shared his workflow in a viral thread that was covered by VentureBeat, InfoQ, and Fortune.

Here are the key takeaways from his workflow:

  • Runs 5 parallel Claude sessions locally, plus 5-10 on claude.ai
  • Each session uses its own git checkout (not branches or worktrees)
  • Starts in Plan Mode, iterates until the plan is good, then switches to auto-accept mode
  • Maintains a CLAUDE.md file per team to document mistakes and best practices (~2.5K tokens)
  • Uses PostToolUse hooks for auto-formatting
  • Most important tip: "Give Claude a way to verify its work" โ€” tests, browser, simulators

No exotic customization. No clever hacks. He also accepts that 10-20% of sessions simply get abandoned.

For more details, there's even a dedicated site: howborisusesclaudecode.com. Definitely worth a read if you use Claude Code.

๐Ÿ“ Shipping at Inference-Speed

Peter Steinberger, the prominent iOS developer and PSPDFKit founder, takes things even further. His claim: he no longer reads the code his AI agents generate.

Instead, he manages 3-8 simultaneous projects, commits directly to main, and relies on AI agents to iterate and validate. Software development, he argues, is no longer limited by coding ability โ€” but by "inference time and hard thinking."

I always find it fascinating when very experienced devs like Peter claim they no longer read or review the AI generated code on their solo-projects. Yet, many others still advocate that human-in-the-loop is necessary. Who's right? ๐Ÿค”

This tension โ€” between "ship without reading" and "human validation is essential" โ€” is one of the most interesting debates heading into this year.

AI Agent Orchestration

Beyond single-agent workflows, some developers are pushing into multi-agent territory. Here are two approaches at opposite ends of the spectrum โ€” and both are fascinating.

๐Ÿ“ Gas Town: Multi-Agent Orchestration

Steve Yegge's Gas Town is a Go-based orchestration system that lets you coordinate 20-30 parallel Claude Code agents using tmux. It features 7 distinct worker roles and runs on a git-based work tracking system called Beads.

What I find most useful is Yegge's 8-stage maturity model for AI-assisted coding:

Gas Town orchestration system
Gas Town orchestration system
  • Stages 1-2: No or minimal AI (autocomplete, sidebar chat)
  • Stages 3-5: Single agent with increasing trust and automation
  • Stages 6-7: CLI, multi-agent, hand-managed (3-10+ parallel instances)
  • Stage 8: Building your own orchestrator

Most of us (myself included) are somewhere between stages 3-6. Gas Town is for stages 7-8. Fair warning: running heavy sessions can cost $100-200/hour in API fees ๐Ÿ˜…

๐Ÿ“ The Ralph Wiggum Technique: Simple Agent Loops

At the other end of the spectrum, Geoffrey Huntley's Ralph technique is brilliantly simple โ€” just a bash loop that repeatedly feeds Claude a prompt file:

while :; do cat PROMPT.md | claude-code ; done

Each iteration gets fresh context, and memory persists via git history and progress files. The results can be remarkable: a team at a Y Combinator hackathon produced 1,100+ commits across 6 repos overnight for ~$800 in AI costs ๐Ÿคฏ

Anthropic later built an official ralph-wiggum plugin, though Matt Pocock argued that the plugin misses the point: a proper Ralph loop gives bash control over the agent, while the plugin inverts this (letting the agent control the loop, leading to context rot). If you're curious, this article argues why the original bash loop might be preferred.

Anthropic's ToS Crackdown

Earlier this month, Anthropic deployed safeguards that blocked Claude Pro/Max subscription tokens from working outside the official Claude Code CLI. Overnight, third-party tools like OpenCode stopped working โ€” with no warning.

Anthropic's rationale: third-party tools had been spoofing the Claude Code client identity, generating unusual traffic without telemetry, and making debugging and support impossible. This violates their Terms of Service.

There's also an economic angle: the $200/month Max plan provides unlimited tokens through Claude Code, while the same usage via API would cost $1,000+. Third-party tools removed the artificial speed limits, enabling overnight autonomous loops.

The community reaction was strong โ€” subscription cancellations, front-page Hacker News discussion, and criticism from prominent developers, including this entertaining take from Primeagen:

Regardless of where you stand, the takeaway is clear: if you rely on Claude's subscription plans, be aware they're scoped to first-party tools only.

This is a good reminder that we should never be too reliant on a single tool or provider. Having alternatives is always a good idea.

OpenCode: Open-Source Alternative

Speaking of alternatives, if the Anthropic crackdown has you looking around, OpenCode is worth knowing about.

OpenCode is an open-source AI coding agent with a terminal UI that supports 75+ LLM providers โ€” Claude, GPT, Gemini, local models, you name it. It's been growing fast: 56K+ GitHub stars and 450+ contributors.

Key features:

  • Provider flexibility: swap providers or bring your own API keys โ€” no lock-in
  • Privacy-first: no code storage, suitable for regulated environments
  • Client/server architecture: enables remote sessions (Docker containers, mobile control)
  • LSP support: language-aware editing, multi-session, shareable links

Having briefly tried OpenCode myself, I quite like it, especially as it already supports useful features such as custom slash commands and skills, meaning I can reuse many of the workflows I already built for Claude Code.

Check it out here:

Claude Code Resources

If you're using (or getting started with) Claude Code, here are some of the best resources I came across this month:

  • ๐Ÿ“ The Complete Claude Code Tutorial โ€” a viral X thread (4.7M views) by Eyad Khrais. Core message: think before typing. Plan mode outperforms ad-hoc prompting 10 out of 10 times.
  • ๐Ÿ“ A Guide to Claude Code 2.0 โ€” a deep technical guide covering Opus 4.5 workflows, sub-agents, MCP servers, and hooks. Interesting note: the author finds GPT-5.2-Codex superior for code review, while Claude excels at code generation.
  • ๐Ÿ“ Claude Code in Action โ€” free 21-lesson course from Anthropic (also on Coursera). Covers everything from basics to Hooks and the SDK.
  • ๐Ÿ“ Todos โ†’ Tasks in Claude Code 2.1 โ€” the latest Claude Code update introduces session-scoped Tasks (replacing Todos) for complex dependency management and parallel sub-agent coordination.

Latest from Code with Andrea

๐Ÿ“ My 2025 in Review: Freefall and a New Direction

At the start of this month, I published my 2025 retrospective. It was an honest look at a challenging year: reduced content output, declining traffic and revenue, and the broader headwinds hitting coding educators everywhere.

But the article is ultimately forward-looking. I'm pivoting Code with Andrea towards agentic AI coding โ€” using tools like Claude Code to build Flutter apps faster and smarter. I won't be teaching traditional Dart/Flutter tutorials anymore, but I'll continue to cover Flutter in this newsletter and focus my content on AI-assisted development workflows.

If you haven't read it yet, I'd love to hear your thoughts:

Until Next Time

This is by far the most AI-heavy newsletter I've ever written. AI coding agents have crossed a threshold, and I find that learning this new abstraction layer is well worth the effort.

My advice? Start small. Try Claude Code or OpenCode on a side project. Give the agent success criteria instead of step-by-step instructions. And review everything it produces โ€” at least for now ๐Ÿ™‚

I'd love to hear where you are on the AI coding journey! Let me know on X (Twitter), LinkedIn or BlueSky.

Thanks for reading, and happy coding! ๐ŸŽ‰

]]>
https://codewithandrea.com/newsletter/december-2025/December 2025: Flutter GenUI SDK, Build Runner Speedups, 2025 LLM Year in ReviewAlso included: Material/Cupertino decoupling progress, GPT 5.2 release, running AI agents safely in DevContainers, and MCP becoming an Open Standard.https://codewithandrea.com/newsletter/december-2025/Mon, 22 Dec 2025 01:00:00 +0100As we wrap up 2025, it's been interesting to see how much AI is seeping into the Flutter ecosystem. This year brought us the Flutter MCP server, Flutter AI Rules, the GenUI SDK, and the Flutter AI Toolkitโ€”all official tools for building AI-powered apps with Flutter.

Meanwhile, the broader AI landscape has seen big leaps in frontier models (Gemini 3, Opus 4.5, GPT-5.2) and agentic coding tools like Claude Code which, while impressive, are still in their early days.

Let's dive into the latest Flutter videos and AI news from the past month.

Flutter Videos

The Flutter team has been pumping out new videos lately. Here are some highlights that caught my attention.

๐Ÿ“น Getting started with GenUI

Flutter now has an official SDK for building AI-generated user interfaces, and it's called GenUI.

The idea: instead of your LLM responding with walls of text, it can populate UI using a catalog of Flutter widgetsโ€”dynamic carousels, workout cards, or any custom widget, created on demand.

You define a catalog of widgets with JSON schemas, the AI agent generates "surfaces" (UI chunks), and you render them with GenUiSurface widgets. The video walks through a practical example, including Gemini CLI integration and hot reload support.

There's also a follow-up video that goes deeper into building agent-powered apps:

GenUI is still in alpha, so expect API changes. If you're building regular chat-like experiences, consider the Flutter AI Toolkit insteadโ€”it just hit v1.0.0 and offers a more stable set of AI chat widgets.

๐Ÿ“น Strengthening Flutter's core widgets

Earlier this year, the Flutter team decided to decouple the Material and Cupertino libraries from the core frameworkโ€”and this video explains the why, what, and when.

The tight coupling has been causing issues: design updates lead to breaking changes, third-party design libraries are harder to build, and contributions are harder. The fix? Move design-agnostic logic into the widgets library and eventually publish Material/Cupertino as separate packages on pub.dev.

Here's the roadmap:

  • Phase 1 (now): Move common logic into the widgets library; introduce "raw widgets" (e.g., RawRadio) as low-level building blocks
  • Phase 2 (2026): Publish Material/Cupertino on pub.dev; deprecate the old libraries
  • Phase 3 (late 2026): Remove legacy libraries from the framework

For developers, there's no immediate action required. Eventually, you'll be able to version Material and Cupertino independently from the framework, making migrations more predictable:

๐Ÿ“น Accelerating Dart code generation

If you've ever stared at your terminal waiting for build_runner to finish, you know the pain. Good news: a ground-up rewrite of build_runner's transitive import tracking has landed, and it's fast.

How fast? In one test with 3,000 generated libraries, code generation ran twice as fast. Community feedback confirms similar gains in real-world projects using json_serializable, freezed, built_value, and go_router.

To get the speedup, just upgrade to build_runner 2.10.4+. After that, caching kicks inโ€”changing one library only rebuilds that library, while watch mode (dart run build_runner watch) keeps things snappy as you edit.

The video also previews what's coming: augmentations (inject members directly into classes), part imports (generated parts with their own imports), and primary constructors (declare constructor params right after the class name). Less boilerplate ahead!

AI News

If you've been following the AI space, you know there's always something new. Here are some selected reads. ๐Ÿ‘‡

๐Ÿ“ 2025 LLM Year in Review

Karpathy's year-end review identifies six paradigm shifts that defined 2025:

  1. RLVR (Reinforcement Learning from Verifiable Rewards): Instead of humans rating outputs, LLMs now learn from automated verificationโ€”code that compiles, math that checks out. This is why coding models improved so dramatically.
  1. Jagged Intelligence: LLMs are "ghosts," not "animals." They can ace PhD-level physics while failing at basic addition. Understanding this helps set proper expectations.
  1. Cursor: The new LLM app layer emergedโ€”AI-native editors that deeply integrate with codebases, not just chat interfaces bolted onto IDEs.
  1. Claude Code: AI that lives on your computer, browsing files and running commands autonomously. A shift from "AI as tool" to "AI as teammate."
  1. Vibe coding: Programming through natural language. You describe what you want, the AI builds it, and you iterate on vibes rather than syntax.
  1. Nano banana: LLM GUIs are evolving beyond chat. Expect more visual, interactive interfaces that feel less like messaging and more like collaboration.

If you're building with AI (or just trying to keep up), this is essential reading:

๐Ÿ“ MCP Becomes an Open Standard

Earlier this month, Anthropic donated the Model Context Protocol to the Linux Foundation's new Agentic AI Foundation (AAIF). This is a big deal.

If you're not familiar with MCP, it's the protocol that lets AI tools connect to external systemsโ€”databases, APIs, file systems, you name it. What makes this donation significant:

  • Co-founded by competitors: Anthropic, OpenAI, and Block, with support from Google, Microsoft, and AWS. When rivals join forces on infrastructure, you know it matters.
  • Already widely adopted: 10,000+ active MCP servers, 97M+ monthly SDK downloads, used by Claude, ChatGPT, Gemini, Cursor, VS Code, and GitHub Copilot.
  • Solves a real problem: Instead of building separate integrations for each AI tool, you build one MCP server and it works everywhere.

The GitHub blog has a great writeup on what this means for developers. If you're building AI workflows that connect with external data sources, MCP is becoming the infrastructure layer you'll rely on.

๐Ÿ”ฅ GPT-5.2 is Here

After the recent release of Gemini 3 and Opus 4.5, OpenAI was feeling the heat and decided to respond with GPT-5.2.

Here are the "specs":

  • Three variants: Instant (speed), Thinking (complex work), Pro (maximum accuracy)
  • 400K token context window with 128K max output tokens
  • 38% fewer errors than the previous version
  • State-of-the-art on SWE-Bench Pro, the benchmark for software engineering tasks
  • Knowledge cutoff: August 2025

The pricing is higher than GPT-5.1, but if you're doing serious coding work, the improved accuracy might be worth it. It's also available in Cursor and GitHub Copilot now.

For all the details, read the official announcement:

Latest from Code with Andrea

Every week, I read headlines such as this: "Claude CLI deleted my entire home directory"! And it's not just Claude: every AI agent is prone to this kind of security risk, and in my previous newsletter I included a section about understanding Agentic Coding Security Risks.

But talk is cheap! So I decided to figure out how to run my AI agents in isolated environments, and share my solution in public.

Here's what I learned. ๐Ÿ‘‡

๐Ÿ“ How to Safely Run AI Agents Like Cursor and Claude Code Inside a DevContainer

If you've used Claude Code or Cursor, you know the friction: constant permission prompts every time the agent wants to read a file or run a command. It's there for good reason (security!), but it slows things down.

I wrote a guide on using DevContainers to solve this. The idea is to run your AI agent inside an isolated Docker container where it can operate freely without risking your host system.

What you get:

  • Use --dangerously-skip-permissions safely (because the container is sandboxed)
  • Protection from prompt injection attacks
  • Full support for code generation, file modification, Git, and terminal access
  • Step-by-step setup for Docker and the Dev Containers extension

Admittedly, this setup is not perfect since you can't use it to run iOS/Android emulators, and visual UI testing needs to be done separately on the host machine.

But when I want to let the agents rip without baby-sitting permissions, this is my go-to solution:

After publishing this article, someone suggested creating a separate user account (without admin privileges) on my dev machine, and using that in YOLO mode. For some, this might be a reasonable compromise between security and productivity.

Until Next Time

2025 is nearly over. I've spent much of it honing my AI skills and applying them to my app development work. Admittedly, I haven't shared as much content as I would have liked, largely due to my recent move to Italy (and all the logistics involved).

With that said, I have exciting plans for 2026, and I'll be sharing them with you soon. If time allows, I'll also try to share my 2025 retro in the coming weeks (if you're curious, here's the 2024 edition).

But for now, I wish you a happy festive season, and see you in 2026! ๐ŸŽ‰

Happy coding!

Andrea

]]>
https://codewithandrea.com/articles/run-ai-agents-inside-devcontainer/How to Safely Run AI Agents Like Cursor and Claude Code Inside a DevContainerLearn how to bypass AI permission prompts safely by running Claude Code in an isolated Docker container.https://codewithandrea.com/articles/run-ai-agents-inside-devcontainer/Wed, 3 Dec 2025 01:00:00 +0100AI coding agents like Claude Code are powerful, but they ask for permission a lot. Every file read, every bash command, every tool invocation: it's permission prompt after permission prompt.

That's a good thing. These guardrails exist for your safety because AI agents can make mistakes. Another risk is prompt injection attacks, where malicious content tricks the AI into executing harmful commands on your system.

But sometimes you want to hand off a complex task and let Claude Code run autonomously without babysitting every action. Claude Code offers a --dangerously-skip-permissions flag for this, but the name says it all:

Claude dangerously skip permissions

Without permission gates, you're one prompt injection away from a compromised system.

The solution? Run Claude Code inside a Docker container. The official security documentation recommends this approach: use DevContainers to create isolated environments where Claude Code can operate freely without risking your host machine.

Why Containers Work

Containers give you both speed and security:

  • Isolation: AI agents operate in a sandbox. If something goes wrong, only the container is affected.
  • File System Protection: Agents can only access directories you explicitly mount. Personal files, system configs, and sensitive data remain untouched.

What Works Well

  • Code generation and modification: Claude can read, write, and modify files
  • Terminal commands: Run tests, analyzers, linters, package managers
  • Version control: Git operations work normally
  • Basic IDE Integration: You can open your project in Cursor/VSCode, navigate the codebase, and edit files.

What Doesn't Work

  • Simulators/Emulators: iOS and Android emulators require native platform support (macOS for iOS, more complex setup for Android)
  • Visual testing: Can't see app UI running inside the container

In this guide, I'll walk you through setting up a DevContainer for Claude Code with Flutter support. By the end, you'll have a secure sandbox where you can run --dangerously-skip-permissions without the actual danger.

Prerequisites: Install Docker

DevContainers require Docker. Install Docker Desktop for your platform and verify it's running before proceeding:

Docker Desktop running

Step 1: Install the Dev Containers Extension

  • Cursor users: Install the "Cursor Dev Containers" extension by Anysphere:
Cursor Dev Containers extension

To better understand how containers work, read: Developing inside a Container.

Step 2: Add the DevContainer Configuration

Clone one of these repositories to get the .devcontainer folder:

Copy the .devcontainer folder into your project root:

Devcontainer files

Step 3: Open the Project in the Container

Close and reopen your project. You should see "Reopen in Container":

Reopen in Container option

Click it. The container will build and you'll see "Container Claude Code Sandbox":

Claude Code Sandbox container

Verify the setup: Open a terminal and run pwd. You should see /workspace rather than the project root in your local filesystem:

Running pwd in the container workspace

This confirms your project is mounted inside the container. Any commands executed here affect only the container, not your host machine.

Step 4: Set Up Claude Code in the Container

Run claude in the terminal. You'll see the initial setup process:

Claude setup process

That's because the container has its own Claude Code installation and it doesn't share credentials with your host machine. Log in with your Claude subscription or Console account:

Claude login method

Complete the setup steps. You only need to do this once per container. Subsequent runs will go straight to the Claude Code prompt:

Claude Code prompt

Step 5: Run Claude Code Without Permission Prompts

Remember the original goal? We want to safely bypass all permission prompts when running Claude Code.

Run this:

claude --dangerously-skip-permissions

Claude Code will display a warning:

Claude dangerously skip permissions

Accept it. Since you're inside a sandboxed container, Claude Code can only affect the mounted project and your host system remains protected.

Step 6: Flutter Support

If you used the Claude Code + Flutter DevContainer, you'll be set with a minimal Flutter setup inside the container.

To test it, you can run flutter doctor, and you should see something like this:

Doctor summary (to see all details, run flutter doctor -v): [โœ“] Flutter (Channel stable, 3.38.3, on Debian GNU/Linux 12 (bookworm) 6.10.14-linuxkit, locale en_US.UTF-8) [โœ—] Android toolchain - develop for Android devices โœ— Unable to locate Android SDK. Install Android Studio from: https://developer.android.com/studio/index.html On first launch it will assist you in installing the Android SDK components. (or visit https://flutter.dev/to/linux-android-setup for detailed instructions). If the Android SDK has been installed to a custom location, please use `flutter config --android-sdk` to update to that location. [โœ—] Chrome - develop for the web (Cannot find Chrome executable at google-chrome) ! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable. [โœ—] Linux toolchain - develop for Linux desktop โœ— clang++ is required for Linux development. It is likely available from your distribution (e.g.: apt install clang), or can be downloaded from https://releases.llvm.org/ ... [โœ“] Connected device (1 available) [โœ“] Network resources

This means that you can run flutter sub-commands such as analyze, test, pub directly from the terminal or via Claude Code. But you can't use flutter run to run the app from the container (more on this below).

Flutter IDE Integration

The Dart and Flutter extensions that were installed in your local machine won't automatically work inside the container.

To fix this, open the extension panel and install them inside the Container.

Install Dart extensions in the Container

Additionally, hit CMD+SHIFT+P > Flutter: Change SDK and ensure this points to /opt/flutter:

Flutter Change SDK

This ensures you get proper syntax highlighting within the containerized IDE. If this doesn't work right away, hit CMD+SHIFT+P > Dart: Restart Analysis Server.

Running Flutter Apps from the Container?

Unfortunately, you can't use flutter run to run the app from the container, since the provided Dockerfile doesn't install any platform-specific tools like Chrome, Android Studio, or Xcode.

As a workaround, you can open two IDE windows: one for the container and one for your host machine.

  • Container: For running AI agents and bypassing permission prompts
  • Host machine: For running the app manually and testing the UI

Here's an example showing things side-by-side:

Left: Claude code within Cursor (DevContainer mode). Middle: Cursor project on the host machine. Right: iOS Simulator
Left: Claude code within Cursor (DevContainer mode). Middle: Cursor project on the host machine. Right: iOS Simulator

In practice, you'll toggle between the two IDEs as needed, making better use of your screen real estate.

Container vs Host: When to Use Each

Use the Container for:

  • Web research on untrusted sources (mitigate prompt injection)
  • Long-running autonomous tasks (no constant approval needed)
  • New feature development (code generation + file modifications)
  • Running MCP servers (which may have security vulnerabilities)
  • Parallel execution (useful for benchmarking or testing multiple approaches)
  • Installing/updating packages (supply chain attack protection)
  • Exploring third-party codebases (unknown code can't affect host)

Use the Host for:

  • Testing the UI (needs simulators/emulators)
  • Debugging with breakpoints (IDE debugger integration)
  • Hot reload during iteration (faster feedback loop)
  • Quick, targeted fixes (verify changes immediately)

The pattern that emerges: let AI handle the repetitive, high-volume work while you focus on decisions that require human judgmentโ€”UI polish, debugging complex state, and verifying the app behaves correctly.

Bonus: Run Claude Code from a Standalone Terminal

Sometimes you don't need an IDE at all. Maybe you're running Claude Code on a remote VPS, or you just want a lightweight terminal-only workflow for tasks like code generation or refactoring.

You can use the devcontainer CLI to run containers without VS Code or Cursor:

# Install the CLI (once) npm install -g @devcontainers/cli # Start the container (only works if the .devcontainer folder is present) devcontainer up --workspace-folder . # Run Claude Code inside it devcontainer exec --workspace-folder . claude --dangerously-skip-permissions

Or start an interactive shell session:

devcontainer exec --workspace-folder . zsh

Example:

Running Claude Code from a standalone terminal

This is useful for:

  • VPS or cloud servers: Run autonomous AI agents on remote machines
  • CI/CD pipelines: Automate code generation or refactoring tasks
  • Lightweight workflows: Skip the IDE overhead when you only need the terminal

Bonus: Useful aliases

To make development faster, I have included these useful aliases in a file called .zshrc_dev:

# Aliases for Flutter commands alias fclean="flutter clean" alias fpg="flutter pub get" alias fpu="flutter pub upgrade" alias brb="dart run build_runner build -d" alias brw="dart run build_runner watch -d" alias fpgbrb="fpg && brb" alias fpgbrw="fpg && brw" # Aliases for Claude Code alias c-dsp='claude --dangerously-skip-permissions' # Custom prompt or other configurations echo "๐Ÿš€ Flutter Dev Environment Ready!"

To use these aliases in the container, copy the file to your home directory:

cp .zshrc_dev ~/.zshrc_dev

Then, rebuild and reopen the container, and the file will be loaded automatically.

As a result, you'll be able to run the aliases directly:

fpg # same as flutter pub get c-dsp # same as claude --dangerously-skip-permissions

Conclusion

You now have a secure setup for running AI agents without permission prompts:

  1. DevContainer isolation ensures Claude Code can only access your mounted project
  2. --dangerously-skip-permissions lets Claude Code run autonomously
  3. Your host machine remains protected from prompt injection attacks

This approach gives you the productivity benefits of autonomous AI agents while maintaining the security boundaries that matter.

More importantly, it changes how you work. Instead of approving every file read and command, you define the task, let Claude Code execute, and review the results. Your role shifts from supervisor to architectโ€”setting direction, evaluating outcomes, and handling the parts that still require human judgment.

Source Code and Support for Other Agentic IDEs and Tools

You can find my custom .devcontainer files on GitHub:

This enables Dev Containers on Cursor, VSCode and Claude Code within the built-in terminal.

If you'd like to add support for Codex and Gemini, feel free to open a PR. ๐Ÿ™‚

Note: Antigravity uses the Open VSX marketplace, which doesn't have a Dev Containers extension. If you figure out how to use Dev Containers with Antigravity, please let me know! Meanwhile, you should probably stay away from it. ๐Ÿ˜ฑ

Happy coding!

Resources

]]>
https://codewithandrea.com/newsletter/november-2025/November 2025: Flutter 3.38, Dart 3.10, The AI Coding Wars (Gemini 3 vs Claude Opus 4.5)Also included: Google Antigravity IDE, understanding agentic coding security risks, and a different perspective on how AI coding sucks.https://codewithandrea.com/newsletter/november-2025/Fri, 28 Nov 2025 01:00:00 +0100November 2025 was a packed month for both Flutter and AI.

On the Flutter side, we got Flutter 3.38 with Dart 3.10, bringing dot shorthand syntax, 16KB Android page size support, and full iOS 26 compatibility.

But the bigger story this month is what I'm calling "The AI Coding Wars." Google launched Gemini 3 alongside their new Antigravity IDE (a free, agent-first development platform), while Anthropic countered with Claude Opus 4.5 (the most capable coding model yet).

Let's dive in! ๐Ÿ‘‡

Flutter 3.38 & Dart 3.10

Flutter 3.38 dropped earlier this month, bringing Dart 3.10 along with it. This is a significant release that includes some breaking changes you'll need to know about.

If you're maintaining a Flutter app in production, the Android 16KB requirement and Java 17 migration are two things that need your immediate attention. But there's also some great stuff here, like the dot shorthand syntax finally being enabled by default! ๐ŸŽ‰

Here are the highlights:

Dart 3.10 Language Features:

  • Dot shorthand syntax is now enabled by default. You can write .value instead of SomeEnum.value, making your code more concise and readable (similar to Swift's syntax)
  • Build hooks are now stable, making it easier to integrate native code (C++, Rust, Swift) without platform-specific build files
  • New analyzer plugin system for writing custom static analysis rules

Flutter 3.38 Updates:

  • Android 16KB page size support - This is now required for Google Play. If you haven't updated yet, you need to do this now
  • Full iOS 26, Xcode 26, and macOS 26 support with UIScene lifecycle migration
  • Java 17 is now required for Android development (Gradle 8.14 minimum)
  • Fixed a major memory leak that affected all Flutter Android apps since version 3.29.0 (finally! ๐Ÿ™Œ)
  • Web dev config file support for better team consistency
  • DevTools improvements addressing top user pain points
  • New Gemini CLI Extension and MCP support for AI integration

Read the full announcements here:

There's also an official announcement video if you prefer video content.

The AI Coding Wars

November marked a major escalation in AI-powered development tools. Google, Anthropic, and OpenAI made huge announcements within days of each other, and the competition is intensifying fast.

๐Ÿ“ Google Gemini 3

Last week, Google announced Gemini 3, their most capable AI model family yet. This is a massive release that topped the LMArena Leaderboard with a 1492 Elo score.

Here's what stood out to me:

  • PhD-level reasoning: 37.5% on Humanity's Last Exam, 91.9% on GPQA Diamond
  • 1M+ token context window with multimodal understanding (text, images, video, audio, PDFs)
  • Generative UI - can create entire interactive experiences, not just text responses
  • Gemini Agent for multi-step tasks with Calendar and Gmail integration
  • Google claims it's their "best vibe coding model ever" (yes, they actually said that ๐Ÿ˜…)

The launch was coordinated across Google Search, Gemini App, AI Studio, Vertex AI, Gemini CLI, and their new Antigravity IDE.

๐Ÿ“ Google Antigravity IDE

Alongside Gemini 3, Google launched Antigravity, a new IDE forked from VS Code (yes, another one ๐Ÿ˜„). It's free in public preview and available for Mac, Windows, and Linux.

What makes Antigravity different:

  • Agent-first architecture - agents autonomously plan, execute, and verify tasks
  • Agents have dedicated access to Code Editor, Terminal, and Browser
  • Knowledge base system for agents to save and learn from context
  • Supports Gemini 3 Pro, Claude Sonnet 4.5, and GPT models
  • Built by the ex-Windsurf team (Google acquired them for $2.4B in July)

If you want a hands-on perspective, check out this video:

๐Ÿ“ Claude Opus 4.5

Not to be outdone, Anthropic announced Claude Opus 4.5 just days after Gemini 3. They're billing it as "the best model in the world for coding, agents, and computer use."

The numbers are impressive:

  • State-of-the-art on SWE-bench Verified (real-world software engineering tasks)
  • Leads on 7 of 8 programming languages in multilingual benchmarks
  • 15% improvement over Sonnet 4.5 on Terminal Bench for long-horizon tasks
  • Uses 76% fewer output tokens than Sonnet 4.5 while matching performance (this means huge cost savings ๐Ÿ’ฐ)
  • New "effort parameter" for balancing capability vs speed/cost
  • Pricing: $5/$25 per million tokens (input/output)

For those of us using Claude Code daily, this is a big deal. The efficiency improvements mean we can tackle more complex agentic coding tasks without burning through credits as fast.

My take: Frontier models are getting extremely good, but we can only unlock their value by creating truly agentic workflows, and that is a whole skill in itself. Thanks to subagents, skills, MCP servers, and custom slash commands, Claude Code still has a big lead against other agentic coding CLIs, and it's very unlikely I'll switch over unless the competition catches up.

๐Ÿ“ GPT 5.1 & Codex

OpenAI also joined the party with GPT-5.1, the latest in their GPT-5 series. The model dynamically adapts how much time it spends "thinking" based on task complexity, making it faster and more token-efficient for simpler tasks.

But the bigger news for developers is the Codex family:

  • GPT-5.1-Codex and GPT-5.1-Codex-Mini - optimized for long-running, agentic coding tasks
  • GPT-5.1-Codex-Max - the flagship model that can work independently for 24+ hours on a single task

Codex-Max introduces "compaction" - the ability to work coherently across millions of tokens, enabling project-scale refactors and deep debugging sessions. On SWE-Bench Verified, it scored 77.9%.

For GitHub Copilot users, the full GPT-5.1 suite is now available in public preview for Pro, Pro+, Business, and Enterprise plans.

โš ๏ธ Understanding Agentic Coding Security Risks

With all these new agentic coding tools, it's worth understanding the security risks they introduce. Shortly after Antigravity's launch, security researchers discovered serious vulnerabilities that highlight broader concerns with agent-first approaches:

  • Data exfiltration via prompt injection: Attackers can hide malicious instructions in 1-point font on webpages, forcing the AI to bypass file protections and exfiltrate secrets
  • Bypassing .gitignore: AI agents can use system commands to access files that should be protected

These aren't just Antigravity problemsโ€”they're challenges any agentic coding tool must address. GitHub published an excellent breakdown of their agentic security principles, identifying three main threat categories:

  1. Data Exfiltration - Agents with internet access could leak sensitive data, including credentials
  2. Impersonation & Attribution - Unclear accountability for agent actions
  3. Prompt Injection - Malicious instructions hidden in repositories or web pages

Their recommended safeguards include network firewalling, limited data access, reversibility requirements (PRs instead of direct commits), and clear action attribution.

My take: As AI tools gain more autonomy, security becomes critical. At minimum, consider using a sandboxed development container for agentic workflows, as explained here. And always review what permissions you're granting these tools.

The Counter-Argument to AI Coding

With all the AI hype this month, I think it's important to acknowledge the other side of the story. Not everyone is having a great time with AI coding tools, and their concerns are valid.

๐Ÿ“น AI Coding Sucks

Earlier this month, CJ posted a viral rant titled "AI Coding Sucks" that struck a note across developer communities.

The main criticisms:

  • Lost joy of programming - Endless back-and-forth with unpredictable LLMs that take shortcuts
  • Code quality concerns - AI-generated code can be hard to maintain and understand
  • The "skill issue" narrative - Evangelists dismissing legitimate concerns as user error

Matt Pocock, a prominent TypeScript educator, had a thoughtful response, pointing out that careful planning and context management can prevent many of the issues with AI coding.

Latest from Code with Andrea

Following the "AI Coding Sucks" debate, I've been thinking a lot about one fundamental question: when should you write code yourself, and when should you use AI?

๐Ÿ“น When to Code, When to Prompt? My 2x2 Decision Matrix

The result is this video, aiming to help you decide between AI assistance and manual coding for different Flutter development tasks:

The decision matrix is based on comparing prompting effort vs coding effort:

  • Low prompting/High coding effort โ†’ Use AI (boilerplate, tests, refactors)
  • High prompting/Low coding effort โ†’ Code manually (visual issues, tiny fixes)
  • Low/Low โ†’ Either approach works (simple tweaks)
  • High/High โ†’ Collaborative AI approach (complex features, full-stack)

The core principle is simple: compare prompting effort against coding effort. AI offers speed and knowledge, but "accuracy is not guaranteed"โ€”so you need to factor in the cost of reviewing and fixing AI-generated code.

๐Ÿ”ฅ Black Friday Sale 2025

Speaking of AI and productivity... if you've been thinking about leveling up your Flutter skills, now's the time. I'm running my annual Black Friday sale:

These are the best prices you'll see for a while. The sale is live now, so don't wait too long!

Until Next Time

November was a big month for both Flutter and AI development tools. Flutter 3.38 brings some important updates (especially for Android and iOS compatibility), while the AI landscape is evolving faster than ever.

The competition between Google (Gemini 3 + Antigravity), Anthropic (Claude Opus 4.5 + Claude Code), and OpenAI (GPT 5.1 + Codex) is really heating up, and honestly, I think this innovation unlocks more value and increasingly advanced agentic workflows for all of us.

As always, remember that AI is a mutliplier that amplifies both your skills and your mistakes. So, learn to use it well, and don't feel like you need to go all-in. Sometimes the old-fashioned way of writing code manually is still the right call.

What's your take on the AI coding wars? What's your favorite AI coding tool? Let me know on X (Twitter), LinkedIn or BlueSky.

Thanks for reading, and happy coding! ๐ŸŽ‰

]]>
https://codewithandrea.com/videos/when-to-code-vs-prompt-with-ai/When to Code, When to Prompt: My 2x2 Decision MatrixA balanced perspective on when to lean on agentic AI vs coding manually, along with a useful decision matrix for common Flutter app development tasks.https://codewithandrea.com/videos/when-to-code-vs-prompt-with-ai/Wed, 12 Nov 2025 01:00:00 +0100Is your AI coding workflow causing more friction than flow? Do you wonder when to trust AI and when to just write the code yourself?

This video offers a balanced perspective on "When to Code vs. Prompt with AI" and introduces a practical decision matrix to guide your development workflow.

Intro

AI agents are excellent for prototyping, ideation, and boilerplate tasks. But you also know the frustration: AI frequently gets stuck or misunderstands, forcing you to take over and write code manually. While some might dismiss this as a "skill issue," the reality is that AI isn't infallible, and you shouldn't force an AI workflow in every scenario.

So, how do you decide between prompting with AI and traditional coding? This video provides a balanced perspective and a decision matrix to help you choose the right approach. Ultimately, this decision boils down to a simple question: "Which is greater? Your prompting effort, or your coding effort?" The answer, as you'll see, varies significantly by task.

Common Flutter Tasks

Consider this list of common tasks you might encounter during Flutter app development:

  • Complex charting solutions
  • Text rendering issues
  • Boilerplate (crash reporting, analytics...)
  • Full-stack features
  • Small UI/animation tweaks
  • Pull to refresh
  • IAP + entitlements
  • Writing tests
  • Dense business logic
  • Simple refactors
  • Background tasks + iOS/Android specific
  • Localization
  • Theming system
  • Big refactors
  • Offline caching + syncing
  • Codegen
  • Layout errors
  • One-line bug fixes

The Prompt vs. Coding Effort Matrix

This 2x2 matrix shows which tasks fall in which quadrants depending on their coding vs prompting effort:

Prompt vs. Coding Effort Matrix

Low Prompting Effort, High Coding Effort

This quadrant contains tasks that require minimal prompting but generate a significant amount of code. AI excels here because these tasks often involveย pattern recognition and repetitionย orย filling in the blanks. Examples include:

  • Boilerplate code:ย For features like crash reporting or analytics setup.
  • Writing tests:ย AI can effectively analyze existing code and generate comprehensive test cases, covering various edge cases.
  • Localization:ย AI can identify hard-coded strings in the UI and generate correct ARB files for multiple languages, streamlining the setup.
  • Theming systems:ย AI can plan and implement a robust theming system, for instance, by refactoring hard-coded colors and text styles into a consistent theme.
  • Big refactors:ย Large-scale code rearrangements that follow clear patterns.

These tasks are not intrinsically hard but are code-intensive, making them ideal for AI to handle.

High Prompting Effort, Low Coding Effort

This quadrant describes tasks that demand very specific and detailed prompting to get the desired result, but ultimately produce minimal code. In these scenarios, direct coding is often more efficient. Examples include:

  • Text rendering issues:ย Fixing wrapping, ellipsis, or font rendering quirks requires precise explanations of visual problems.
  • Layout errors:ย Issues like overflows or unbounded heights need visual descriptions, screenshots, and desired behavior specified, often for a single-line fix (e.g., wrapping a widget inย Expanded).
  • One-line bug fixes:ย Such as adding a missingย notifyListeners()ย call.
  • Dense business logic:ย Algorithms like binary search, while short in code (less than 20 lines), require verbose and precise natural language prompts to describe, often making the prompt less clear than the code itself.

Note the inherentย ambiguity of natural languageย and theย fidelity gapย that can arise when AI attempts to make precise visual UI changes. If you already understand how to solve the problem, directly editing the code saves time and avoids iterative prompt refinement.

Low Prompting Effort, Low Coding Effort

These tasks are straightforward to describe with minimal prompting and result in a small amount of code. Examples include:

  • Small UI/animation tweaks:ย Adjusting an animation curve or duration.
  • Adding pull-to-refresh:ย Wrapping aย ListViewย with aย RefreshIndicatorย and anย onRefreshย callback.
  • Simple refactors:ย Minor code adjustments you can do quickly.

For problems you're already familiar with, relying on your ownย muscle memoryย is often faster than context-switching to AI, writing a prompt, and waiting. Furthermore, you'll likelyย trust your own codeย more.

I also placeย codegenย in this quadrant. While tools like build_runner might generate a lot of code, your human input is minimal (e.g., adding a new property to a data model), and you trust the output because it's aย deterministic process.

High Prompting Effort, High Coding Effort

This quadrant encompasses tasks that are significantly complex in terms of specification, planning, implementation, and verification. Working with AI here is rarely a "one-shot" prompt, but rather anย iterative and conversational process. Examples include:

  • Offline caching and syncing.
  • Full-stack features.
  • Background tasksย with platform-specific (iOS/Android) code.
  • Complex charting solutions.
  • In-app purchases and entitlements.

To succeed with AI on these tasks, several critical steps are needed:

  • Detailed and specific requirements:ย This is theย cost of providing contextย to AI, and it requires your significantย domain expertise. Without it, AI is likely to miss details and go down the wrong path.
  • Break down complex problems:ย Decompose the main problem into smaller, manageable sub-problems that AI can tackle individually.
  • Ensure maintainability:ย Follow project standards and conventions.
  • Thorough verification:ย This is often the most complex part. Debugging AI-generated code can be particularly challenging because you didn't write it yourself, and AI might have introduced subtle errors.

These tasks have aย high cost of failure. Careful human oversight and robust guardrails are essential, even with AI assistance.

My AI-assisted coding workflow
My AI-assisted coding workflow

Summary

Before coding with AI, always consider whether your prompting effort or coding effort will be greater. The answer is task-dependent.

Additionally, consider yourย cognitive load. A short prompt might still require significant mental effort to formulate. A one-line code change in an unfamiliar system can also be cognitively demanding, where AI can assist you in understanding.

Agentic AI vs. Autocomplete

Your choice of tool also depends on the amount of code you need:

  • For a few lines of code,ย autocomplete and IDE assistsย are more surgical and efficient.
  • For a lot of code or big changes,ย agentic AI workflowsย make more sense. AI can handle heavy lifting (planning, implementation), while you focus on providing good specifications and verifying results.

This mindset is crucial for AI coding in 2025. As AI tools improve, the "high prompting" effort for complex problems will likely decrease, causing the quadrants to shift. What's "high prompt, high code" today might become "low prompt, high code" tomorrow. While AI offers incredibleย knowledgeย andย speed,ย accuracy is not guaranteed.

Your human insights, muscle memory, and direct debugging skills remain indispensable. An important skill in this new era isย knowingย whenย to code andย whenย to prompt, being selective between full agentic AI mode and autocomplete mode to achieve results more quickly and without frustration.

Happy coding!

]]>
https://codewithandrea.com/newsletter/october-2025/October 2025: Flutter & Figma MCP, Platform & UI threads merge, Andrej Karpathy on AGIAlso included: Fluttercon EU videos, Wasm 3.0, my 3-folder system for effective AI coding in Flutter.https://codewithandrea.com/newsletter/october-2025/Fri, 24 Oct 2025 02:00:00 +0200Welcome back to another edition of my Flutter newsletter!

Just like always, I'm excited to share a mix of Flutter and AI-related updates, along with the latest from Code with Andrea.

Letโ€™s dive in! ๐Ÿš€

Flutter Updates

These updates cover the latest from the official Flutter channel, the recent WASM 3.0 release, and I've also included all the FlutterCon EU 2025 videos for you to catch up on.

๐Ÿ“บ Flutter & Figma MCP | Observable Flutter #70

It's been a little while since I've shared an Observable Flutter video, but this one is fantastic! Craig Labenz hosts Muhammad Hamza, the creator of a new MCP server that converts Figma components directly into Flutter widgets.

In this video, Muhammad explains:

  • how the Figma MCP server works its magic
  • how you can import typography, colors, and custom buttons into code for your Flutter apps
  • how to handle images and SVGs, which often require a bit of special attention.

He then goes on to show how to generate entire screens (like login and profile pages) from Figma, and even discusses responsive design support and other exciting upcoming features for the MCP server.

If you're tired of manually creating Flutter widgets from Figma, this tool could be a huge time-saver!

๐Ÿ“บ The great thread merge

If you haven't heard about the Platform & UI threads merge yet (it was first enabled in Flutter 3.32), this video will get you up to speed.

Inside, Craig Labenz breaks down the roles of the UI, Raster, and Platform threads, and explains the reasoning behind the merge. This merge makes it possible to call into native code from Dart synchronously (using ffi), which leads to a much smoother development experience.

๐Ÿ“ Integrating Swift Foundation Models in Flutter Apps with Pigeon

As part of the iOS 26 SDK, Apple released the Foundation Models framework, which lets you use on-device LLMs in your apps.

But how do you call these new APIs from Flutter? The answer is Pigeon! In this tutorial, Pranav Masekar shows you how to build a simple chat app as a proof of concept:

๐Ÿ“ Wasm 3.0 is here

I've been following WASM support in Flutter web with great interest ever since it was first announced back in 2023.

So, I was thrilled to hear that Wasm 3.0 has arrived! It's a significant step forward, bringing exciting features like:

  • 64-bit address space
  • Garbage collection
  • Typed references
  • Tail calls
  • Exception handling

Many of these features are now supported across all major browsers, and I'm really looking forward to seeing what performance improvements this will bring to Flutter web apps.

๐ŸŽฅ FlutterCon EU 2025 Videos

I didn't get a chance to attend FlutterCon this year, so I'm super happy to see that the videos have now been published on YouTube!

Just like in previous years, some of the top Flutter developers delivered fantastic talks (including some advanced topics), and I'm sure you'll find plenty of useful insights.

Here's the full playlist:

AI News

Despite some growing AI-bubble fears, the rollercoaster keeps going, and new tools and updates are popping up every single day. Sonnet 4.5 recently launched, while OpenAI released Sora 2 and a new web browser (though I wouldn't trust it to be secure or private ๐Ÿ˜ฑ).

But what's most relevant for us developers? Here's my top pick from this month. ๐Ÿ‘‡

๐ŸŽง Andrej Karpathy โ€” AGI is still a decade away

Every time Andrej Karpathy speaks publicly, I'm always so impressed by his insights. So, I couldn't miss this new podcast with Dwarkesh Patel, where he shares his thoughts on the evolution of AI and why he believes AGI is still a ways off.

He also discusses recent trends in agentic AI and the three current ways of writing code:

  1. From scratch (he no longer recommends this).
  2. Autocomplete with Cursor.
  3. Agentic: You prompt, and then let the LLM do the work.

Interestingly, Andrej often finds himself using autocomplete as the most optimal balance between speed and control, rather than trying to use agents that tend to perform poorly when writing innovative and "intellect-intensive" code.

The podcast is almost 2 hours and 30 minutes long, but I highly recommend it (a full transcript is also available):

Latest from Code with Andrea

Over the last month, I've started working on a new currency converter app, and I've also published a new video. ๐Ÿ‘‡

๐Ÿ“บ Beyond Prompts: My 3-Folder System for Effective AI Coding in Flutter

The more I work with AI, the more I realize how crucial it is to have a structured workflow and clear guidelines to shape its output.

After many successes and failures, I've settled on a 3-folder system that helps me:

  • Stay organized and follow a consistent workflow.
  • Make it much easier for AI to follow my guidelines.
  • Reduce a lot of friction by letting me reuse battle-tested patterns, commands, and prompts.

And in this video, I cover all the details:

Until Next Time

Over the next few weeks, I'll be ramping up my YouTube content with both long-form and short-form videos, sharing my experiences with AI coding. So stay tuned for more updates!

Happy coding! ๐ŸŽ‰

]]>
https://codewithandrea.com/videos/ai-folders-for-flutter-development/Beyond Prompts: My 3-Folder System for Effective AI Coding in FlutterDive into my personal 3-folder AI strategy for reducing friction, enforcing coding guidelines, and ensuring an organized and consistent workflow.https://codewithandrea.com/videos/ai-folders-for-flutter-development/Mon, 20 Oct 2025 02:00:00 +0200Is there a secret to making your AI agents truly understand your Flutter codebase and massively speed up your development workflow? Well, for me, it's all about theseย 3 essential folders.

  • ai_toolkit: My collection of LLM-friendly commands and patterns.
  • ai_specs: My prompts and plans for ongoing work.
  • ai_docs: My persistent knowledge base for AI agents.

In the video above, I break down exactly what these folders are, how they fit into my AI-assisted workflow, and how you can make the most of them in your AI coding sessions.

My AI-assisted coding workflow
My AI-assisted coding workflow

You see, in the last few months, I've been leaning heavily into AI agents like Claude code and Codex. These powerful tools are creating a fundamental shift in how we develop software. It's less about directly writing every line of code ourselves, and more aboutย orchestrating AI,ย providing precise context, andย carefully verifying its output. To get the most out of them, we need toย rethink our entire workflow.

But here's the thing: even with powerful AI, they won't produceย consistentlyย good results unless you give them the right context.

In fact, it's fair to say thatย the results you get are only as good as your prompts.

So how can you, as an engineer, learn to use AI to its full potential and leverage this context effectively? That's precisely what these 3 essential folders help you do in your Flutter apps.

AI Toolkit: Standardizing AI Interactions

First up is theย ai_toolkitย folder. This is essentially where I keep all my LLM-friendly commands and patterns specifically tailored for Flutter development. I created this because setting up new projects takes time, boilerplates can be too opinionated, and AI agents, while smart, have a high failure rate without proper guardrails.

Thanks to this toolkit, I can do some really cool things:

  • I can prime my AI agents with relevant breaking changes in Dart and Flutter, so they don't generate outdated code.
  • I can quickly run commands I use often.
  • It helps me enforce consistent project guidelines and code style across all my coding sessions, no matter which AI I'm using.
  • And it lets me quickly scaffold new projects with components and functions in a standardized way.

In practice, I "seed context" into my AI sessions using custom commands from this toolkit. This removes ambiguity, making sure the AI follows my specific code patterns and style. If AI ever misses the mark, I can just add a new guideline to my toolkit and include it in the context. This ensures consistent, higher-quality code. Plus, I've created variants that work across different AI agents, and I include commands for things like conventional commits, planning out features, and even letting the AI know about my local shell aliases to reduce friction.

Ultimately, theย ai_toolkitย is all about giving AI the right context and removing friction. I use it as a submodule across my projects, which helps accelerate onboarding, enforces code consistency, and reduces my cognitive load by letting AI handle repetitive decisions. It's a personal tool for now, but I might make it available later!

AI Specs: Structured Planning and Execution

Next, we have theย ai_specsย folder. The main idea here is that while agentic AI can plan, build, and verify, the results are still only as good as my prompts. So, I need guardrails to keep AI agents on track. This folder helps me keep track of all the prompts and plans I use for agentic coding, ensuring I follow a consistent workflow.

My workflow typically involves:

  1. Creating a requirements file:ย This markdown file outlines everything needed for a feature, bug fix, or refactor โ€“ it's the "thinking" part.
  2. Generating a plan:ย I feed this requirements file to my AI agent, asking it to make a detailed plan. I'll iterate on this plan until I'm happy, then save it as a markdown file right here inย ai_specs.
  3. Implementing the plan:ย I then have the AI implement the plan, ideally one stage at a time, so I can easily review and course-correct.
  4. Verification:ย I let the AI run tests to ensure they're green, and I also manually test the app myself.

This process is highly iterative, and things don't always go perfectly. I might discover missing requirements, overly complex implementations, or code duplication. The key is to set a high bar for quality. Since producing code with AI is "cheap," I can afford to iterate, correct course, or even restart to ensure the app is maintainable long-term.

Once everything is complete, tests are green, and the app works, I push changes to GitHub. A neat trick is to instruct the AI to include the completed plan as the Pull Request description. Theย ai_specsย folder then becomes a historical record of all my prompts and plans, almost like a local copy of my GitHub issues and PRs.

AI Docs: Persistent Project Knowledge

Finally, let's talk about theย ai_docsย folder. Think of this as a persistent knowledge base specifically for your AI agents. It's a great place for storing crucial information that AI needs but might not find directly in the code orย ai_toolkit.

This includes things like:

  • API documentation and integrations.
  • Architecture and design documents.
  • Hidden non-code business logic (important rules the AI needs to know).
  • Project-specific patterns that might be unique to this project, even if they differ from the general toolkit.

I use this folder in my projects to outline API specifications and UI functionality. As a project grows, I keep this folder updated with all the critical information my AI agents need. For older projects, I've even used AI to help generate this documentation, feeding these documents to the AI when starting a new session so it can understand the project's architecture and generate more consistent code.

Why These Folders Matter

So, to summarize, I use these three essential folders in my Flutter development workflow:

  • ai_toolkit: My collection of LLM-friendly commands and patterns.
  • ai_specs: My prompts and plans for ongoing work.
  • ai_docs: My persistent knowledge base for AI agents.

The main difference is thatย ai_docsย holds knowledge that evolves over time, like living documentation, whileย ai_specsย is more of a historical record of specific tasks, which might become outdated.

Overall, these folders are incredibly helpful because they:

  • Help me stay organized and follow a consistent workflow.
  • Make it much easier for AI to follow my guidelines and stay on track.
  • Reduce a lot of friction by letting me reuse battle-tested patterns, commands, and prompts.

I really encourage you to create your own similar folders. Just think about repetitive tasks or situations where AI gets it wrong, and then write your own guidelines to steer it. While there's an upfront time investment, it absolutely pays off in the long run, leading to faster development, fewer bugs, and more consistent quality.

By the way, in this video, I haven't even touched on advanced topics like MCP servers or sub-agents. Honestly, I feel like I'm just scratching the surface of what's possible with AI agents. But as I keep exploring and learning, I'll be sharing more videos.

Happy coding!

]]>
https://codewithandrea.com/newsletter/september-2025/September 2025: Riverpod 3.0, Migrating to Flutter, Flutter AI Rules, Best AI AgentsAlso included: Liquid Glass and the cupertino_native package, Flutter vs web wrappers, AI service issues at Anthropic and OpenAI.https://codewithandrea.com/newsletter/september-2025/Fri, 26 Sep 2025 02:00:00 +0200Autumn's here, and so is a fresh edition of my newsletter, packed with the latest in Flutter and AI:

  • Riverpod 3.0
  • The ultimate guide to migrating to Flutter
  • Liquid Glass: what it means for you
  • AI rules for Flutter and Dart
  • Latest from the Flutter community
  • Best AI Coding Agents and my takeaways

Let's dive in!

Riverpod 3.0 is here

The long-awaited Riverpod 3.0 has landed!

This new release brings experimental support for offline persistence and mutations, plus quality-of-life improvements like automatic retry, Ref.mounted, generics support, new testing utilities, and more.

Get a full summary of the changes here:

But what about migrating from Riverpod 2.x? After updating some of my own apps, I found some easy changes (like replacing .valueOrNull with .value). But I also hit unexpected bugs, such as this one.

While the official docs cover migrating from 2.0 to 3.0, it might be wise to hold off until the initial bugs are ironed out.

Migration to Flutter: The Ultimate Guide

Flutter has a strong value proposition: high-quality, multiโ€‘platform apps from a single codebase. But for enterprises with legacy codebases, the path to migration is rarely simple or certain.

This guide by LeanCode is a practical, battleโ€‘tested playbook for navigating that complexity, born from real-world transformations at Virgin Money, Crรฉdit Agricole Bank Polska, Sonova, NOS, and other large-scale programs.

Migration to Flutter: The Ultimate Guide

I did get early access to the guide, and I highly recommend it. You can get your copy here:

Liquid Glass is here: what it means for you

Apple'sย latest device lineupย is out, bringing iOS 26 and its newย Liquid Glass UI.

Liquid Glass UI Design System

But what does this mean for us Flutter developers? And how hard will it be to update our iOS apps to Liquid Glass?

According toย this GitHub issue, official work on the iOS 26 UI will only beginย afterย the core Material and Cupertino libraries areย moved outside the Flutter SDK.

Meanwhile, theย Serverpodย team had an idea: leverage Platform Views and method channels to bring pixel-perfect Liquid Glass UI to Flutter on iOS.

The result is a new package called cupertino_native, which works well and is very performant (note: this was vibe-coded with Codex and GPT-5, so it's not ready for production yet).

AI rules for Flutter and Dart

When building with AI, clear rules and guidelines are crucial for enforcing best practices in code style and design.

Good news: the Flutter team has released an official set ofย AI rules for Flutter and Dart. This includes aย rules.mdย template that you can feed directly to your AI model.

Find all the usage instructions here:

Just like you'd start with a default set ofย lint rulesย for your projects, you can view this as a "sensible defaults" template for Flutter AI coding, which can be further customized to your own project needs.

Latest from the Flutter community

As I write this,ย FlutterCon EUย is taking place in Berlin. I couldn't make it this year, but I'm keen to catch up on the session videos once they're available.

The Flutter community has also been active onย r/FlutterDevย and the officialย Flutter forum. Beyond the usualย Google Play Store complaints, some cool posts can also be found, like this one: ๐Ÿ‘‡

๐Ÿ“ฑ Flutter vs Web Wrappers

Can you spot a web wrapper masquerading as a native mobile app? Common giveaways include slow loading screens, sluggish UX, and poor offline support.

Take the HomeDepot app, for instance โ€“ it's a web wrapper for their official site. While browsingย r/FlutterDev, I found this post a developer who rebuilt it in Flutter. They then shared thisย cool videoย showing the two apps side-by-side. What a difference! ๐Ÿ’ช

Check out the full post:

Latest AI News

The past month in AI has been wild! Here's my rundown of the most relevant news.

๐Ÿค– Best AI Coding Agents with some crazy upsets

We've seen new, high-performant coding LLMs emerge, includingย GPT-5-codex,ย Qwen3 Coder, andย Grok Code Fast 1.

The AI coding tool landscape is also expanding, withย Codex,ย Qwen code,ย Roocode, andย Claude code routerย (which enables selecting different models under the same Claude Code CLI), among many others.

So, which ones should you use for AI-assisted coding? This video offers an excellent summary, complete with benchmarks from real-world evaluations:

Here are some key takeaways, complemented with observations from my own experience:

  • The gap at the top is narrowing; Claude Code is no longer the only game in town.
  • Model choice matters a lot. It's crucial to balanceย speed,ย cost, andย output quality.
  • If you want to get good results consistently, prompting and context engineering remain as important as ever.

Model selection also hinges on another critical factor:ย reliability. ๐Ÿ‘‡

โš ๏ธ AI Service Issues

While leading AI companies battle for model and tool supremacy, they've also been experiencing significant service issues, as confirmed by theย OpenAI statusย andย Claude statusย pages.

Anthropic's situation has been particularly problematic, leading them to share this recent postmortem:

This is the current reality: models are improving, demand is soaring, and AI companies are struggling to guarantee high uptime. Many users on monthly subscriptions have already switched vendors, and I expect this trend to continue.

While uptime guarantees may be less concerning during development, they're absolutely critical when AI is built into production systems (think automotive or medical applications). It will be interesting to see how companies will account for and mitigate downtime.

Until Next Time

Having recently moved countries, some things are taking longer than expected, so I'm not yet back at full speed.

That said, I've started someย new projects, which are proving to be a great way to experiment with the latest AI tools.

Despite their unreliability, I'm using these tools for increasingly complex tasks, including planning, refactoring, test generation, and more.

Keep an eye on my YouTube channel; I'll be sharing some agentic AI coding videos soon!

Happy coding!

]]>
https://codewithandrea.com/tips/relative-absolute-imports-dart/Relative & Absolute Imports in DartYou can use absolute imports for reusable Dart files that are copy-pasted across projects.https://codewithandrea.com/tips/relative-absolute-imports-dart/Tue, 23 Sep 2025 02:00:00 +0200Did you know?

You can use absolute imports for reusable Dart files that are copy-pasted across projects.

This way, they are always imported correctly (as long as they live in the same location relative to the project root).

Relative & Absolute Imports in Dart

Happy coding!

]]>
https://codewithandrea.com/tips/matrix4-vector3/Deprecated APIs in Matrix4Matrix4 APIs such as translate and scale have been deprecated in Flutter 3.35. Here's how to update your code.https://codewithandrea.com/tips/matrix4-vector3/Wed, 3 Sep 2025 03:00:00 +0200Did you know?

Some Matrix4 APIs such as translate and scale have been deprecated in the latest Flutter 3.35 release.

To update them:

  • Import vector_math explicitly
  • Use the new APIs which take a Vector3 argument
Deprecated APIs in Matrix4

Happy coding!

]]>
https://codewithandrea.com/newsletter/august-2025/August 2025: Flutter 3.35, Widget Previews, Flutter MCP Server, Latest AI NewsAlso included: Dart 3.9, Decoupling Material & Cupertino from the core SDK, new Package of the Week videos, recent studies about AI.https://codewithandrea.com/newsletter/august-2025/Wed, 27 Aug 2025 02:00:00 +0200My long summer break is over, and Iโ€™m back in business! This month, we're diving into the new Flutter 3.35 release, the stable Flutter & Dart MCP Server, the significant move to decouple Material & Cupertino, and of course, the ever-evolving world of AI. Let's get into it!

Flutter 3.35 & Dart 3.9 Highlights

Flutter 3.35 brings two long-awaited features:

  • Stateful hot reload on the web: First introduced in Flutter 3.32, now available as a stable release.
  • Experimental Widget Previews: This allows you to see your widgets render in real-time, separate from a full app, in your Chrome browser.

I initially doubted the utility of a Flutter Widget Previewer, but after using it, I can see its value, especially for applications with many custom and reusable UI widgets. Check out this guide to get started.

Beyond these, the release includes:

  • More control and polish in the Material and Cupertino libraries
  • Various framework, engine, and DevTools improvements

Dart 3.9 also ships alongside Flutter 3.35, though it's a fairly minor update.

For the complete breakdown:

But hold on, there's more!

๐Ÿค– Dart and Flutter MCP server

The Flutter MCP (model context protocol) server is now stable and helps AI agents to better understand your code context and perform actions, such as:

  • Analyze and fix code errors
  • Resolve symbols, fetch documentation, and signature info
  • Introspect and interact with you running app
  • Search pub.dev for packages
  • Manage pubspec.yaml dependencies
  • Run tests and analyze the results
  • Format code with the same formatter and config as dart format

To learn how to set it up with popular AI tools, read the official guide:

Tools like Claude Code already perform some of the tasks above without extra setup. Now that the Flutter MCP server is stable, I'll do some experiments with and without it, compare the differences, and share my findings.

๐ŸŽจ Decoupling Design in Flutter

The Flutter team is embarking on a significant architectural change: moving theย materialย andย cupertinoย libraries out of the core Flutter SDK intoย standalone packages. This will make the framework lighter, more flexible, and allow for faster iteration on design libraries.

What does this mean for you?

  • Faster Updates: Critical bug fixes and design updates for Material and Cupertino will no longer be tied to Flutter's quarterly stable release cycle, reaching developers much faster.
  • More Control & Flexibility: Developers gain finer control over their design system versions and a leaner, un-opinionated core framework for custom UIs, fostering a more diverse Flutter ecosystem.

This is a substantial undertaking, with the transition expected to complete in 2026. You can track the progress and learn more about the rationale in these resources:

๐Ÿงฑ Featured Packages

If your app does some form of audio recording or playback, you might want to check out the record and flutter_soloud packages, which were recently featured in the Package of the Week playlist:

Latest AI News

LLMs and AI tools continue to evolve rapidly. A few weeks ago, both GPT-5 and Claude Opus 4.1 were released, claiming the top spots in popular benchmarks and leaderboards like WebDev Arena.

But let's not get carried away by the hype: GPT-5 still managed to fail on a kindergarten worksheet, and Steve Yegge's recent attempt to use Claude in production ended with catastrophic results.

Moreover, a recent MIT report pointed out that 95% of generative AI pilots at companies are failing, and another study found that experienced open-source developers took 19% longer to complete various tasks when using AI tools (note: the developers in the study used Cursor Pro with Sonnet 3.5/3.7, which is no longer a frontier model).

So, what does this all mean for you?

  • Benchmarks don't tell the real story: Real world usage is what really matters, and there isn't a single tool or LLM that works best for all use cases (even Andrey Karpathy said so in this recent and insightful post).
  • AI tools can't be trusted: Make sure you remain in control and put guardrails in place.

My takeaway is that AI tools can still unlock big productivity gains. I've experienced this firsthand, but not before spending a fair chunk of time learning how to extract value from them (and to be honest, I still have a lot to learn and share).

Until Next Time

I've been a bit quiet recently as I was busy moving back to my home country ๐Ÿ‡ฎ๐Ÿ‡น. Now that (almost) everything is settled, I look forward to getting back to work, and sharing my journey with Flutter, AI, and more!

So stay tuned and keep an eye out on my YouTube, socials, and this newsletter.

Happy coding!

]]>
https://codewithandrea.com/newsletter/june-2025/June 2025: Apple Liquid Glass, Software 3.0, how to use LLMs, AI-Assisted coding, Claude Code Crash CourseAlso included: 12 lessons from AI pair programming, and my code review of an open-source Flutter app with 100K+ lines of code.https://codewithandrea.com/newsletter/june-2025/Fri, 27 Jun 2025 02:00:00 +0200This is my 36th monthly newsletter, and this means that I've been writing this Flutter newsletter for 3 years now (here's the full archive).

Much has changed since then, and while I was collecting links for this edition, I realized most of my bookmarks from this month were about AI, rather than Flutter.

Indeed, agentic AI tools have been dramatically changing my development workflow (in a good way), and the overall trend is too hard to ignore by now.

So, in this edition, I decided to include some relevant AI-related content that will help you expand your knowledge and stay up to date.

But before that, we need to talk about Apple Liquid Glass! ๐Ÿ˜…

Apple Liquid Glass

Apple announced all their latest software and developer SDK updates at WWDC 2025. Their keynote was focused on Liquid Glass, a visual refresh of their design system, based on a glass-like UI.

Preview of Apple Liquid Glass on iOS 26 beta 1
Preview of Apple Liquid Glass on iOS 26 beta 1

Initial reactions were mixed, with some people praising the new design system, while others criticized it for its poor contrast and accessibility (which was much improved in the second beta).

But what does this mean for us as Flutter developers?

Since Flutter draws every pixel (no native UI), apps using the existing Cupertino widgets risk appearing visually outdated on the upcoming iOS 26 release.

This triggered a new wave of "Flutter is dead" and "Flutter can't do this" comments, which were quickly followed by open source demos (such as this and this) showing how to achieve a very similar effect using shaders:

Liquid Glass effect with Flutter shaders (demo by @reNotANumber)
Liquid Glass effect with Flutter shaders (demo by @reNotANumber)

The whole conversation is very much in flux, and this umbrella issue was created to track Liquid Glass support in Flutter:

For now, you don't need to do anything. iOS 26 will be released in September, and many things are still subject to change.

AI Videos

This is my first AI-related newsletter, so I decided to share some good introductory videos that helped me improve my mindset about LLMs and AI in general. ๐Ÿ‘‡

๐Ÿ“น Andrej Karpathy: Software Is Changing (Again)

Andrej Karpathy is an elite AI researcher and engineer, having worked as director of AI at Tesla and founding member of OpenAI.

In this video, he talks about the evolution of software development paradigms:

  • Software 1.0 is written as traditional code
  • Software 2.0 is written as neural networks, able to solve specific tasks (e.g. language translation) better than traditional code does
  • Software 3.0 is written by LLMs programmed with natural language prompts

In his talk, Andrej likens LLMs to operating systems, and English prompts to programs.

He also describes LLMs as "stochastic simulations of people" with encyclopedic knowledge but also cognitive deficits like hallucination and "jagged intelligence".

As a result, he believes partial autonomy with human verification is the way forward, and, as a professional developer, I completely agree with his assessment.

If you have to watch only one video from this newsletter, make it this one:

๐Ÿ“น Karpathy vs. McKinsey: The Truth About AI Agents (Software 3.0)

I chose to feature this video because it's all about "war at the heart of AI" between business consultants (like McKinsey) and builders (like Andrej Karpathy).

Here are the two contrasting views:

  • Andrej encourages viewing AI as a design problem. He emphasizes that current AI agents need significant human supervision, and that software should be designed with humans as validators, where AI generates and humans validate.
  • MkKinsey focuses on "agentic mesh" and a world where agents and data can plug in like USB ports. The video criticises this as "fiction" that misleads CEOs and leads to failed enterprise AI projects.

Overall, consultants should be honest about the complexity and challenges of building AI systems. It is possible to take Andrej's Software 3.0 vision and tell good business stories, without oversimplifying things to the level of "USB plug and play".

More realism and less hype is the way to go:

๐Ÿ“น How I use LLMs

Yet another video from Andrej, this time about how he uses LLMs.

While this is intended for a general audience, it's a great overview about how LLMs work, along with the extra features they gained over the recent years.

Here's what's covered:

  • Explanation of the concept of tokens
  • Context window as working memory
  • Pre-training and post-training
  • Tool use (internet search, deep search, Python interpreter)
  • Multimodality (text, images, audio, video)

Many examples are included, showing how you might use all these different features in your daily life (and not strictly related to coding).

If you have the time, I highly recommend it (maybe even share it with less technical friends & family ๐Ÿ™‚):

I loved the top comment about this video: "Just canceled my wedding this weekend to make time to watch this" ๐Ÿ˜…

AI Articles

If you have digested the videos above and want to get your hands dirty with AI coding, these two articles are a great starting point.

๐Ÿ“ AI-assisted coding for teams that can't get away with vibes

Here's a very good article with tips for building high-quality software faster with AI.

Here are the main takeaways:

  • AI is a multiplier: To make AI good, get good yourself. If you are a small coefficient, you wonโ€™t see much gain. If you are a negative coefficient, expect negative gains.
  • What helps the human helps the AI: Software engineering is the art and science of maintaining a large body of well-defined mental models that achieve a business or economic need. A rich environment and context helps the AI work better.
  • Tools and techniques in the editor: Use the best frontier models (don't cheap out), be excellent at providing context, implementing new features, refactoring, and debugging.
  • Tools and techniques outside the editor: Use AI to grow your skills and knowledge, create extensive documentation, microfriction lubricants, code review, debugging and monitoring live applications, performance optimisations.

Read on for all the details:

๐Ÿ“ What Actually Works: 12 Lessons from AI Pair Programming

Another good article with practical tips and techniques that move the needle, from someone who spent 6 months pair-programming with AI.

I used most of these techniques in my own work, and can confirm they work:

Top tip: did you know that you can replace github.com with gitingest.com on any GitHub repo URL, and get a single text digest of the codebase? Super useful for feeding a codebase into a LLM. Try it here: gitingest.com

Latest from Code with Andrea

A couple of years ago, my opinion about AI was along the lines of "this is cute and somewhat useful". But only recently I started to see gains along the lines of "it just one-shotted a complex feature I couldn't tackle myself ๐Ÿคฏ".

I haven't been this excited about coding for a long time, so much so that I decided to start making YouTube videos again, and share some of my experiences with AI. ๐Ÿ‘‡

๐Ÿ“น Build Flutter Apps FASTER with Claude Code Opus 4

Here's a full crash course on how to use Claude Code to build a voice-activated timer app.

Inside, I share my dev workflow and tips for using Claude Code effectively, while showing you how to build the app from scratch, using AI to generate all the code.

Watch the video for a real glimpse of modern agentic AI coding:

๐Ÿ“น Code Review of Cashew App: Lessons from a Flutter App with 100k+ Downloads

By popular request, I also started a new video series where I dig into popular open-source Flutter apps to see how theyโ€™re really built.

The first video is about Cashew, a popular budget-tracking app with 100k+ downloads, and 100K+ lines of code, too! Inside, I offer a breakdown of an appโ€™s architecture, patterns, code style, and so on.

If you've ever needed to make yourself familiar with a new codebase, or simply want to learn how big OSS apps are built, you'll like this format:

Until Next Time

That's a wrap up for my first AI-heavy newsletter. In this edition, I wanted to include some foundational content about AI, as well as practical tips, and share my first-hand experience building a Flutter app with Claude Code.

I have a ton of ideas for AI-related content in the future. But for now, I'd like to hear your opinion: did you like this new newsletter format? Is there anything specific you'd like me to cover? Or should I go back to the "Flutter-only" format?

Let me know on X (Twitter), LinkedIn or BlueSky.

Thanks for reading, and happy coding! ๐ŸŽ‰

]]>
https://codewithandrea.com/videos/build-flutter-apps-faster-claude-code-opus4/Build Flutter Apps FASTER with Claude Code Opus 4A crash course on using Claude Code to build a non-trivial Flutter app involving native integrations, including speech recognition and permissions.https://codewithandrea.com/videos/build-flutter-apps-faster-claude-code-opus4/Wed, 25 Jun 2025 02:00:00 +0200This is an AI transcript/summary of the video above, where show you how I used Claude Code to build a voice-activated timer app from scratch. This isn't a step-by-step tutorial, but a real-world test to see how Claude Code performs on a non-trivial Flutter project involving native integrations like speech recognition and permissions.

The app idea stemmed from my calisthenics training, where it's impractical to manually control a timer during exercises like the frog stand. A voice-activated timer that responds to "start" and "stop" commands solves this perfectly. This project was an excellent candidate to test Claude Code's ability to handle both UI and underlying logic, especially involving platform-specific features and permissions.

Voice Timer App Screenshots
Voice Timer App Screenshots

Having used Claude Code (specifically the Max plan with access to Opus 4 and Sonnet 4) for a few weeks, I've been impressed. In this crash course, I'll recap the key aspects of my workflow, highlight where Claude Code shined, where it struggled, and share my overall conclusions and tips for using it effectively.

Useful links

Getting Started with Claude Code

(Based on 02:21 - 04:24)

If you're looking to try Claude Code yourself, the setup is straightforward. It's a command-line tool that integrates well with your existing IDE (like Cursor, which I use).

  1. Installation: You install it globally using npm:
    npm install -g @anthropic-ai/claude-code
    1. Running: Navigate to your project directory in the terminal and simply run claude.
    2. Authentication: You'll need to authenticate. The best value, especially if you plan to use it regularly and leverage the most powerful models like Opus 4, is a paid subscription ($20/month for Pro, $100/month for Max). Alternatively, you can use pay-as-you-go billing via an Anthropic API key. As I mentioned in the video, if you code daily and can afford it, the $100/month Max plan is absolutely worth it for Opus 4 access.
    3. IDE Integration: Running claude from your IDE's built-in terminal (like in Cursor) is ideal. It allows Claude Code to interact with your project files, while you can easily switch to your editor to review and manually adjust code when needed.

    Claude Code supports different models. On the Max plan, you often have access to Opus 4 for a portion of your usage, after which it might fall back to Sonnet 4. This is important to note, as model performance can vary significantly.

    Getting Started with the Flutter App & Initial Requirements

    (Based on 04:24 - 07:34)

    I started with a completely empty Flutter project โ€“ no extra dependencies, just the default "hello world" app.

    Before writing any code, my most important piece of advice when using AI coding tools is this: Always start by writing detailed and specific instructions. This dramatically increases the chances that the AI will produce the code you want and significantly reduces later refactoring.

    For this app, I created a specs folder with an initial-requirements.md file. This document outlined both functional and non-functional requirements:

    Functional Requirements:

    • Single-page iOS app built with Flutter.
    • Voice-activated timer for calisthenics.
    • Basic UI: Timer display showing elapsed time, Start/Stop button, Reset button (like the iOS stopwatch). Use a Stopwatch object.
    • Voice Mode: Request microphone and speech recognition permissions on startup. Disable features if denied.
    • Voice Commands: Respond to "start" and "stop". Start/continue timer on "start", stop on "stop".
    • Audio Feedback: Play a beep sound when a voice command is recognized (initially planned as a TODO).

    Non-Functional Requirements:

    • Dark mode only, theming done in MaterialApp (no hardcoded colors/sizes).
    • Proper separation of concerns (suitable folder structure like features, shared, core).
    • Prefer small, composable widgets.
    • Prefer flex values over hardcoded sizes for responsive UI.
    • Use log from dart:developer for logging instead of print or debugPrint.

    These opinionated requirements are crucial. Without them, Claude might produce code that functions but doesn't align with your preferred architecture, styling, or conventions, leading to tedious refactoring down the line. For larger projects, I'd add even more detail on linting, dependency injection, testing practices, etc.

    Structuring the Workflow with Claude Code

    (Based on 07:34 - 20:15)

    Claude Code offers features that help structure your AI-assisted development workflow.

    1. /init and CLAUDE.md: Running /init analyzes your project and generates a CLAUDE.md file. This file acts as a permanent context for Claude across sessions. It captures project overview, key requirements it inferred, proposed architecture, coding guidelines, and detected assets. It's a good idea to review and edit this file to ensure it accurately reflects your project's state and guidelines. While Claude might initially be a bit overzealous with bash commands during /init, you can guide it to focus on generating the .md file itself.
    2. Plan Mode: Accessible by pressing Shift + Tab twice, Plan Mode is specifically designed for reasoning and breaking down requirements into actionable steps. I used it, referencing my initial-requirements.md, to generate an implementation plan.
      Think hard about how to implement all the requirements described in @specs/initial-requirements.md. Make a plan with all the proposed tasks and subtasks, focusing on the UI first. Write all the proposed tasks and subtasks to PLAN.md.
      1. Iterating on the Plan: Claude generated a detailed plan with phases, tasks, and subtasks in PLAN.md. I reviewed it and provided feedback to adjust the order, specify technical details (like using a Ticker for UI updates, formatting time as SS.S, using a Stopwatch directly in the UI, removing the "lap" button), and ensure the plan allowed for testing at each step. This iterative planning phase in Plan Mode is invaluable before writing any code.
      2. PLAN.md as Master Plan: Once the plan was satisfactory, I explicitly told Claude to write it to a file (PLAN.md). This document becomes your single source of truth for the project's implementation steps. Adding a line referencing this file in CLAUDE.md ensures Claude always has access to it.
      3. Custom Commands: A powerful feature is the ability to create custom, project-specific commands by adding files to the .claude/commands folder. I created an update-plan-commit command that automatically updates PLAN.md with completed tasks, stages all changes, and creates a clear Git commit message. This command streamlines the process of keeping the plan and version control in sync after completing each step.
        1. Executing the Plan: With the plan in place, you tell Claude to proceed step-by-step, stopping after each one for review.
        2. Using @ References: When giving instructions or asking Claude to modify a specific file, using the @ symbol followed by the file path (e.g., @lib/theme/app_theme.dart) is highly effective. It focuses Claude's attention, uses fewer tokens, and generally leads to better results than letting it guess which file you mean.
        3. Managing Context with /compact: Long conversation histories can consume tokens. The /compact command shrinks the existing chat history while retaining a summary of key information in the context, helping manage token usage without losing important project knowledge. Alternatively, use the /clear command which resets the context completely; as long as CLAUDE.md and other referenced files like PLAN.md are present, Claude will pick up the project context.

        This structured workflow, leveraging planning, context management, and automation, is key to getting consistent results from Claude Code.

        Building the Core Timer UI

        (Based on 20:15 - 41:37)

        Following the plan, Claude Code started implementing the core UI:

        • Project Setup & Theming: It correctly set up the project structure, added the specified font to pubspec.yaml, and configured the basic dark theme in MaterialApp and app_theme.dart, following the non-functional requirements. It even caught a linter warning about a deprecated property and fixed it, demonstrating its ability to understand and act on analyzer feedback.
        • Timer Display & Page: Claude created the TimerDisplay widget, correctly using a Ticker for smooth updates based on the screen refresh rate and implementing the requested SS.S time formatting. It also created the TimerPage widget to hold the Stopwatch and pass it down to the TimerDisplay. This initial UI implementation was one-shotted perfectly based on the plan.
        • Timer Controls (Buttons): It then implemented the Start/Stop and Reset buttons. Claude correctly used widget composition, creating a reusable TimerControlButton and then specific StartStopButton and ResetButton widgets that wrapped it. This demonstrated good code structure and saved manual refactoring.

        At each step, I reviewed the generated code (often using the source control diff view in Cursor) and committed the changes using my custom update-plan-commit command.

        Using Screenshots for UI Design (28:27):

        Wanting a more visually appealing UI, I decided to give Claude Code a screenshot of the desired design from the production app. This is a fantastic feature โ€“ you can drag and drop an image into the prompt.

        Using the provided screenshot as inspiration, update the styling and design of the timer display, start and stop button, reset button, and voice indicator widget (we'll use this later). Note the start and reset button should still appear side by side inside the row. Keep as much styling as possible in the top level theme, declaring constants with meaningful names in a separate file rather than hard coding values in the widget themselves.
        • First Attempt (with Sonnet 4): Claude Code attempted to match the design. It created an app_constants.dart file for colors and sizes, updated the theme, and modified the UI widgets. However, the result wasn't perfect โ€“ it added an unwanted linear gradient, some constants were too widget-specific for a global file, the app bar title didn't use the correct font, and there was a layout overflow issue. This attempt ran after Opus 4 usage limit was reached, potentially impacting the result.
        • Decision Point: I had a choice: manually refactor or discard and try again with a better prompt (and hopefully Opus 4). I chose the latter to see if Claude Code could nail it with clearer instructions.
        • Second Attempt (with Opus 4): I discarded the changes, explicitly added a file with my desired AppColors, and modified the prompt. I emphasized using the provided colors, ensuring all text used the correct font, and adjusting layout factors. This time, running on Opus 4, the result was much better. Claude correctly used the provided colors, applied the font consistently via the theme, and produced a layout much closer to the screenshot, fixing the overflow issue. While some minor tweaks were still needed (like adding button labels or adjusting the voice indicator border), the result was good enough to proceed or make final adjustments manually.
          Using the updated screenshot as inspiration, update the styling and design on the timer display, start stop button, reset button, and voice indicator widget. Keep as much styling as possible in the top level theme and ensure all text widgets use the Babas new font. When styling the UI, use the colors in @lib/constants/app_colors.dart.

          This demonstrated that providing clear guidance, managing context (by providing the color file), and leveraging the best model (Opus 4) significantly improves the quality of the output, especially for complex tasks like UI design from an image.

          Implementing Permissions

          (Based on 41:37 - 54:48)

          Next, we moved on to integrating native permissions for microphone and speech recognition:

          • Adding Dependencies & Platform Setup: Claude correctly identified the need for the permission_handler package and added it to pubspec.yaml. Crucially, it also figured out the necessary platform-specific configuration for iOS, adding the privacy usage descriptions to Info.plist and the required build configurations to the Podfile based on the package's README. This ability to read documentation and apply platform setup is a major strength.
          • Requesting Permissions: Claude created a PermissionService class to encapsulate the permission logic. It correctly used the permission_handler API to request both microphone and speech recognition permissions simultaneously, returning true only if both are granted.
          • Integrating into UI Lifecycle: Initially, Claude placed the permission request logic in main.dart. I guided it to move this logic to the initState method of the TimerPage widget, which made more sense for this single-page app and kept UI-related state management within the page widget. Claude successfully refactored the code as requested.
          • Handling Denials with Alerts: The requirements specified showing an alert if permissions are denied. Claude implemented this, but initially used a standard Material AlertDialog. To match the native iOS look, I introduced my own show_alert_dialog.dart helper file (which uses showAdaptiveDialog) and instructed Claude to use it instead. Claude successfully integrated my helper function, including wiring up the "Open Settings" button action.

          Debugging the permission flow required running on a real device (simulator behavior can differ). We encountered an issue where the native permission dialog wasn't appearing on first launch. Claude Code, with some prompting ("Is some more platform-specific configuration needed?"), correctly identified the missing Podfile configuration requirement from the permission_handler README, which resolved the issue. This again showcased its strength in understanding documentation and platform-specific setup.

          Building Voice Recognition

          (Based on 54:48 - 1:10:52)

          Implementing the continuous voice recognition loop was the most complex part and where Claude Code faced the biggest challenge in the live demo.

          • Detailed Requirements: Given the complexity, I created a specific voice-recognition.md document outlining the desired behavior: initialize the service if permissions are granted, start a continuous listening loop, stop listening as soon as a command ("start" or "stop") is recognized, process the command, play a beep (TODO), and restart the listening loop. It also included guidelines for simple, stateless implementation and graceful error recovery.
          • Planning the Feature: Using Plan Mode and referencing the new requirement document, Claude generated a plan for implementing the voice recognition feature, including adding the necessary package (speech_to_text), creating a VoiceCommandService, integrating it with the TimerPage, and updating the voice indicator UI. Ensuring Claude took the detailed voice-recognition.md into account required a specific prompt in Plan Mode.
          • Initial Implementation: Claude added the speech_to_text package and created the VoiceCommandService. The initial code had some issues: it held state variables (isListening, isInitialized) that ideally belonged in the UI layer, used non-idiomatic Dart (setCallbacks), and the continuous listening logic was complex, involving Future.delayed calls and recursive method calls (startListeningLoop). The onSpeechResult handling had duplicate logic.
          • Iterating for Improvements: Recognizing the code wasn't ideal, I created a detailed voice-recognition-improvements.md document listing specific suggestions: make the service stateless, remove setCallbacks and Future.delayed, use an enum for commands, improve the startListening API to return a Future<TimerCommand>, use a Completer to signal command recognition and stop listening before completing, and remove unnecessary helper methods.
          • Second Attempt: I provided this document to Claude Code. It made significant changes, resulting in a much simpler and cleaner VoiceCommandService that used a Completer as suggested. It successfully made the service stateless and updated the TimerPage to manage the state and the listening loop logic.
          • Testing Challenges: Testing on a real device confirmed that voice recognition worked for the first command ("start" or "stop"), but the listening loop would get stuck if any other speech was detected. While Claude Code's code was much improved, this specific bug in the continuous listening loop logic was complex and proved difficult to resolve within the demo session, requiring manual trial and error later in the production app.

          This experience highlighted that while Claude Code is excellent at understanding requirements and generating complex patterns (like using a Completer or generating tests, as seen later), intricate state management and lifecycle issues (like a robust continuous listening loop) can still be challenging for it to perfect without extensive guidance or manual debugging.

          Beyond the Demo: Production App Examples

          (Based on 1:11:38 - 1:14:19)

          While the live coding session showed the process and some challenges, the completed production app (available on the App Store) demonstrates Claude Code's ability to handle even more:

          • Custom UI (_StadiumBorderPainter): The animated circling border around the voice indicator is a custom painter. I showed Claude a screenshot of a similar effect and described what I needed. It generated the entire _StadiumBorderPainter class with animation logic, saving significant time on complex drawing code.
          • Info Button & Action Sheet: Implementing the info button that brings up a native-looking action sheet with voice commands was a one-shot success based on a single prompt.
          • Advanced Time Formatting & Unit Tests: The ability to display and speak the time in different formats was a complex task. I defined a TimeFormat enum with formatTime (for UI) and formatTimeForSpeech methods. Generating the nuanced speech formatting logic for each format based on examples I provided was impressive. Even more so, Claude Code generated a comprehensive suite of unit tests for this logic and used its agentic capabilities to run them, find failures, and help me fix bugs in the generated code. This combination of complex logic generation and test-driven bug fixing was a major win.

          These production examples show that when properly guided, Claude Code can generate complex UI elements, handle nuanced logic, and even write tests, significantly accelerating development.

          Key Takeaways and Tips for Effective Use

          (Based on 1:14:00 - 1:16:40)

          So, what are the key lessons learned from using Claude Code to build this app? It boils down to this: Claude Code is a powerful skill multiplier, but it's not a magic wand. The value you get is directly proportional to how well you use it.

          Here are my top tips for effective AI-assisted development with Claude Code:

          • Be Specific & Write Detailed Requirements: Forget "vibe coding." Invest time upfront to write clear, detailed requirements covering functional needs, UI details, desired behavior, and crucial non-functional preferences like code style (in a spec document like @docs/initial-requirements.md). The more precise you are, the better Claude will understand your intent and the less refactoring you'll need later.
          • Use Planning & Structure Your Workflow: Break down the project into smaller, manageable tasks using Claude's Plan Mode (PLAN.md). Keep the plan updated and follow it step-by-step. This structured approach, combined with reviewing and committing changes after each task, maintains a working state and makes it easy to revert if needed. Consider asking Claude to ensure your app is testable at each step.
          • Actively Guide & Review Code Ruthlessly: Don't blindly accept Claude's output. Read the code, make sure you understand it, and be critical. If it's not what you want โ€“ maybe it's overly complex or doesn't fit your patterns โ€“ be prepared to discard the changes and provide more specific guidance or a better prompt. Like when it first attempted the VoiceCommandService, I had to provide specific feedback to steer it in the right direction. Remember: Generating code is cheap; maintaining bad code is expensive.
          • Optimize Context & Use @ References: Be specific in your prompts and use @ references to relevant files (@docs/initial-requirements.md, @PLAN.md, specific code files) to ensure Claude has the necessary context without wasting tokens.
          • Leverage Opus 4: Use the most powerful model available. I found Opus 4 significantly better than Sonnet 4 for planning, complex problem-solving, and generating more accurate code upfront.
          • Automate Repetitive Tasks: Identify recurring actions (like updating the plan, committing, or running specific checks) and create custom commands for them to streamline your workflow.
          • Speed Up Verification with Tests: Use Claude to help write unit tests for the code it generates. Since Claude is agentic, it can then run these tests after making changes, automating part of the verification process and helping you catch bugs faster.

          Applying these principles makes Claude Code a powerful skill multiplier. It can handle boilerplate, figure out API usage from documentation, generate complex logic and tests, and write most of the code for you, which honestly feels quite magical sometimes. This structured, deliberate approach makes all the difference between "Vibe coding" (which often only produces sloppy products) and "AI-assisted software development" (which helps you build robust and maintainable apps). I'd much rather be in that second category!

          From Prototype to Production-Ready App

          (Based on 1:16:40 - 1:17:33)

          As we saw, the app built during the video was a working prototype, and a pretty good one thanks to Claude Code. However, a truly production-ready app requires more: polish, robust error handling, analytics, potentially backend integrations, continuous integration, etc.

          The final app I published includes crucial features like analytics, user feedback mechanisms, and error monitoring with Sentry. Many of these are standard boilerplate or patterns I already had battle-tested in my other apps. For those, it was often quicker for me to copy-paste and adapt my existing code rather than asking Claude to implement them from scratch.

          So, while Claude Code is fantastic for building core features and tackling new problems, don't hesitate to reuse your own proven code for standard infrastructure pieces. You could even explore writing custom Claude commands to help integrate your pre-existing code patterns more easily into new apps.

          Conclusion

          (Based on 1:17:33 - 1:18:42)

          Ultimately, Claude Code can do a lot of the heavy lifting for you and significantly accelerate development. So, my advice is to give it a go. If you're a professional software developer and you can afford it, the Max plan is absolutely worth the investment, and Opus 4 is a big step forward compared to other models available in different tools.

          Now, you might be thinking: What about Cursor? Should you still use it, or is it better to use Claude Code for everything? As you've seen, I've been using Claude Code extensively for planning, generating code, and tackling complex tasks. However, I still find Cursor incredibly useful for its deep IDE integration โ€“ things like seamless tab-autocompletion directly in the editor, and for making quick, small inline changes or refactors manually. So, at the moment, I'm actually still paying for both tools. This hybrid approach allows me to leverage Claude Code's powerful model and agentic capabilities for larger tasks and planning, while still benefiting from Cursor's tight IDE features for day-to-day coding and manual adjustments. Using them both strategically lets me maximize my productivity.

          On a final note, we've only scratched the surface of what Claude Code can do in this video. If you want to learn more about its features or dive deeper into AI-assisted development, you can find some useful links and resources below.

          Additional Resources

          ]]>
          https://codewithandrea.com/tips/multiple-flutter-versions-puro/Use Multiple Flutter Versions with PuroPuro is a powerful tool that lets you install multiple versions of Flutter and easily switch between them in your projects.https://codewithandrea.com/tips/multiple-flutter-versions-puro/Mon, 9 Jun 2025 03:00:00 +0200Did you know?

          Puro is a powerful tool that lets you install multiple versions of Flutter and easily switch between them in your projects.

          It's very fast and can automatically configure your IDE to use your desired Flutter version.

          Use multiple Flutter versions with Puro

          Before Puro, I was using FVM.

          But after trying it, I have converted:

          • Faster downloads
          • Better IDE integration
          • Nicer CLI interface

          Learn more and install here:

          Happy coding!

          ]]>
          https://codewithandrea.com/videos/code-review-cashew-app/Code Review of Cashew App: Lessons from a Flutter App with 100k+ DownloadsAn in-depth code review of Cashew, a budget and expense tracking app with 4.9 star ratings, 100,000+ downloads, and just as many lines of code.https://codewithandrea.com/videos/code-review-cashew-app/Mon, 9 Jun 2025 02:00:00 +0200This video provides a comprehensive analysis of the Cashew budgeting app, an open-source Flutter application with over 100,000 downloads and exceptional user reviews. While the app demonstrates remarkable business success, the codebase reveals critical lessons about technical debt and software engineering practices.

          Below is an AI summary of the video. Here's the GitHub repository.

          App Overview and Success Metrics

          Cashew stands as a testament to what a single developer can achieve. The finance budgeting app boasts:

          • Over 100,000 downloads on Google Play
          • 4.9-star rating from 15,700+ reviews
          • Similar high ratings on the App Store
          • Featured multiple times on YouTube channels with substantial followings
          • Cross-platform availability (iOS, Android, Web)

          The app was actively developed from September 2021 to October 2024, spanning over three years of continuous developmentโ€”far exceeding the typical lifespan of side projects. However, the last release occurred in July 2024, suggesting potential development challenges.

          Getting the App Running: Version Compatibility Challenges

          The first hurdle in reviewing this codebase was simply getting it to run. The project's extensive dependencies created immediate compatibility issues with current Flutter versions. Rather than upgrading dozens of packages individually, the solution involved downgrading to Flutter 3.22 using tools like Puro for version management.

          This approach highlights a critical lesson: when inheriting legacy codebases, sometimes working backward to a compatible environment is more efficient than forcing everything forward. The experience underscores the importance of version management tools for developers working across multiple projects.

          Android setup required manual Gradle script updates, while iOS proved more straightforward after removing the old Podfile.lock and running pod install. Interestingly, the repository includes Firebase configuration filesโ€”a security concern for open-source projects that should be addressed through proper backend security measures.

          Architecture and Dependencies Analysis

          Package Dependencies

          The pubspec.yaml reveals a complex dependency structure:

          • Database: Drift with SQLite for local storage
          • Authentication: Google Sign-In integration
          • Backend: Firebase packages for cloud synchronization
          • State Management: Provider (notably absent: dedicated solutions like BLoC or Riverpod)
          • Development: Build Runner for code generation

          The absence of dedicated state management frameworks suggests custom-built solutions, which can work effectively when implemented properly but require careful architectural planning.

          Codebase Scale

          Using the cloc tool reveals staggering numbers:

          • 103,000 lines of Dart code (excluding empty lines and generated files)
          • Largest files include database tables and schema versions
          • Individual pages reaching nearly 5,000 lines of code
          • The addTransactionsPage.dart file alone contains massive complexity

          Testing Infrastructure

          Perhaps the most concerning discovery: the project contains no meaningful tests beyond the default Flutter project template. For a production application of this scale serving thousands of users, the absence of automated testing represents significant risk. Any code changes could potentially break existing functionality without detection.

          Code Architecture Deep Dive

          Global State Management Issues

          The application relies heavily on global variables for dependency management, declared in files like databaseGlobal.dart. Dependencies such as SharedPreferences, database instances, and notification payloads are initialized as global variables rather than through proper dependency injection systems.

          This approach creates several problems:

          • Unclear initialization order: Functions like loadCurrencyJSON set global variables without explicit return values
          • Mutable global state: Variables can be modified from anywhere in the application
          • Testing difficulties: Global state makes unit testing nearly impossible
          • Maintenance challenges: Dependencies between components become opaque

          Navigation and State Updates

          The app employs a complex system of global navigation keys defined in navigationFramework.dart. These keys enable widgets to force rebuilds of other widgets throughout the application using patterns like:

          homepageStateKey.currentState.refreshState();

          This approach creates problematic interdependencies where widgets can trigger rebuilds of seemingly unrelated components. The refreshState method typically contains empty setState() calls that force complete widget rebuildsโ€”a brute-force approach to UI updates that can cause performance issues.

          Main Application Structure

          The main.dart file demonstrates inconsistent initialization patterns:

          • Some dependencies initialized as global variables in the main method
          • Others handled through inherited widgets at the widget tree's root
          • Error handling relies on runZonedGuarded primarily for development logging
          • No integration with production error monitoring services like Crashlytics or Sentry

          UI Implementation Analysis

          Homepage Complexity

          The homepage widget exemplifies the architectural challenges throughout the codebase:

          • Build method spans nearly 200 lines
          • Complex conditional logic based on global app settings
          • Dynamic widget mapping using string keys
          • For loops within the build method creating widgets based on context

          This monolithic approach means any state change potentially rebuilds the entire homepage, creating performance bottlenecks and making the code difficult to reason about.

          The 5,000-Line Monster: AddTransactionsPage

          The addTransactionsPage.dart file represents the extreme end of the complexity spectrum:

          • Over 5,000 lines in a single file
          • 44 setState() calls throughout the file
          • Massive build method spanning from line 1,050 to 2,218
          • Business logic, UI code, database operations, and animations all intertwined

          The addTransactionLocked method demonstrates the architectural problems:

          1. Opens bottom sheets for user input
          2. Performs data validation
          3. Creates transaction objects
          4. Handles conditional logic for related transactions
          5. Updates the database
          6. Manages animations
          7. Sets notifications
          8. Updates global app settings
          9. Handles error cases

          This single method mixing UI, business logic, database operations, and side effects makes testing and maintenance extremely challenging.

          Database Implementation

          Schema Complexity

          The database implementation in tables.dart spans over 7,000 lines, containing:

          • Table definitions with extensive column specifications
          • Complex converter classes for data transformation
          • Migration strategies across multiple app versions
          • Intricate SQL queries for financial calculations

          Migration Strategy Evolution

          The migration code reveals evolving development practices:

          • Early migrations: Simple await calls without error handling
          • Later migrations: Try-catch blocks with print statements for debugging
          • Complex multi-step migrations for schema changes
          • Inconsistent error handling patterns

          Query Complexity

          Database queries demonstrate sophisticated financial calculations but raise maintainability concerns:

          • Methods like watchTotalNetBeforeStartsDateTransactionCategoryWithDay perform complex aggregations
          • Some queries depend on global app settings variables
          • Commented-out code suggests ongoing experimentation
          • Query optimization appears secondary to functionality

          Folder Structure and Organization

          The project's organization reflects organic growth rather than planned architecture:

          • struct folder: Contains miscellaneous utilities (data formatting, animations, Firebase auth, biometrics)
          • widgets/util folder: Another catch-all containing app links, debouncing, animations, mixins, file I/O
          • Inconsistent naming conventions
          • No clear separation between layers (presentation, domain, data)

          This structure makes navigation difficult and suggests code was placed wherever convenient rather than following established architectural patterns.

          Performance and Maintainability Concerns

          State Management Issues

          The combination of global mutable state, massive widget classes, and forced rebuilds creates several performance risks:

          • Unnecessary widget rebuilds due to global state changes
          • Complex interdependencies making optimization difficult
          • Memory leaks potential from global state retention
          • Difficult debugging due to unclear state flow

          Code Maintainability

          Several factors compound maintenance difficulties:

          • Massive files requiring extensive scrolling and mental mapping
          • Mixed concerns within single classes
          • Global dependencies making isolated testing impossible
          • Inconsistent architectural patterns throughout the codebase

          Learning Opportunities and Best Practices

          What Went Right

          Despite architectural challenges, the Cashew app demonstrates several positive aspects:

          • Successful product delivery: The app serves real users effectively
          • Feature completeness: Comprehensive budgeting functionality
          • User satisfaction: Exceptional ratings indicate strong user experience
          • Open source contribution: Provides learning opportunities for the community
          • Solo developer achievement: Remarkable accomplishment for a single person

          Critical Issues Identified

          1. No error monitoring

          • Problem: Errors are silenced or only logged to console
          • Solution: Implement Crashlytics or Sentry for production error tracking

          2. No automated tests

          • Problem: No automated tests for a 103,000-line codebase
          • Solution: Start with integration tests for critical user flows before major refactoring

          3. No linter tool

          • Problem: No linting or static analysis
          • Solution: Implement Flutter lints package, consider DCM (Dart Code Metrics) for advanced analysis

          4. Poor separation of concerns

          • Problem: Business logic, UI, and data access mixed throughout
          • Solution: Implement layered architecture (presentation, domain, data layers)

          5. Lack of dependency injection system / service locator

          • Problem: Global variables for dependency injection
          • Solution: Adopt proper DI system (Riverpod, GetIt) for dependency management

          6. Global mutable state

          • Problem: App settings and other state accessible/modifiable globally
          • Solution: Implement proper state management (Riverpod, BLoC) with immutable state

          7. Widget rebuilds via global navigator keys

          • Problem: Widgets forcing other widgets to rebuild by directly accessing their navigator keys and resetting their state
          • Solution: Move to reactive state management solutions for better control of widget rebuilds

          8. Massive widget classes

          • Problem: Massive widget classes with complex interdependencies
          • Solution: Break down into smaller, focused widgets with clear responsibilities

          Refactoring Strategy

          For a codebase of this scale, refactoring must be incremental:

          1. Establish safety nets: Add integration tests for critical paths
          2. Implement error monitoring: Gain visibility into production issues
          3. Add code quality tools: Enable automated detection of issues
          4. Gradual architectural improvements: Apply Boy Scout Ruleโ€”leave code better than found
          5. Dependency injection migration: Slowly replace global variables with proper DI
          6. State management evolution: Introduce reactive state management incrementally
          7. Widget decomposition: Break large widgets into smaller, testable components

          Technical Debt Impact

          The Cashew app illustrates how technical debt accumulates in successful projects:

          • Initial velocity: Quick development enabled rapid feature delivery
          • Growing complexity: Each new feature became harder to implement
          • Maintenance burden: Changes risk breaking existing functionality
          • Developer fatigue: Complex codebase becomes overwhelming to maintain

          The lack of recent releases (since July 2024) may indicate the technical debt has reached a tipping point where continued development becomes prohibitively difficult.

          Architectural Recommendations

          Immediate Priorities

          1. Error monitoring implementation: Essential for production app stability
          2. Critical path testing: Integration tests for core user journeys
          3. Dependency injection: Replace global variables with proper DI container
          4. State management: Introduce reactive patterns to reduce forced rebuilds

          Long-term Improvements

          1. Layered architecture: Separate presentation, domain, and data concerns
          2. Widget decomposition: Break monolithic widgets into manageable components
          3. Code organization: Restructure folders following architectural principles
          4. Performance optimization: Address unnecessary rebuilds and memory issues

          Development Process Improvements

          1. Code review practices: Implement standards for future changes
          2. Automated testing: Build comprehensive test suite incrementally
          3. Continuous integration: Automate quality checks and testing
          4. Documentation: Create architectural decision records and coding standards

          Lessons for Flutter Developers

          For Solo Developers

          • Balance speed with quality: Technical debt compounds quickly in successful projects
          • Invest in testing early: Manual testing doesn't scale with codebase growth
          • Establish architectural patterns: Consistency prevents complexity explosion
          • Plan for success: Consider maintenance burden as the app grows

          For Teams

          • Code review importance: Multiple perspectives prevent architectural drift
          • Shared standards: Team conventions prevent inconsistent patterns
          • Refactoring time: Budget time for technical debt reduction
          • Knowledge sharing: Prevent single points of failure in understanding

          Universal Principles

          • Separation of concerns: Keep UI, business logic, and data access distinct
          • Dependency management: Avoid global state; use proper injection patterns
          • Error handling: Plan for failure scenarios from the beginning
          • Performance considerations: Design for scale, even in early stages

          Conclusion

          The Cashew app represents a fascinating case study in Flutter development. While the business success is undeniableโ€”serving over 100,000 users with exceptional satisfactionโ€”the technical implementation reveals the challenges of scaling a codebase without proper architectural foundations.

          The developer's achievement in creating a successful app single-handedly deserves recognition. However, the codebase serves as a cautionary tale about technical debt accumulation and the importance of sustainable development practices.

          Key takeaways for the Flutter community:

          1. Technical debt is real: It accumulates silently and can eventually halt development
          2. Architecture matters: Even solo projects benefit from proper structural planning
          3. Testing is essential: Manual testing doesn't scale with complexity
          4. Refactoring is investment: Regular code improvement prevents future paralysis
          5. Tools help: Linting, error monitoring, and quality metrics provide early warnings

          The open-source nature of Cashew provides invaluable learning opportunities. By examining both its successes and challenges, developers can make more informed decisions about their own projects, balancing delivery speed with long-term maintainability.

          For developers facing similar technical debt situations, the path forward involves incremental improvement, establishing safety nets through testing, and gradually introducing better architectural patterns. The Boy Scout Ruleโ€”leaving code better than you found itโ€”becomes essential for managing legacy codebases while continuing to deliver value to users.

          The Cashew app ultimately demonstrates that while technical perfection isn't required for business success, sustainable development practices become crucial as projects scale and mature. The challenge lies in finding the right balance between shipping features and maintaining code qualityโ€”a balance that becomes increasingly important as applications grow in complexity and user base.

          ]]>
          https://codewithandrea.com/newsletter/may-2025/May 2025: Flutter 3.32, Dart 3.8, Material 3 Expressive, Local-First Apps with PowerSyncAlso included: How Flutter works (video series), the definitive guide to Navigator 2.0, and the latest from Code with Andrea.https://codewithandrea.com/newsletter/may-2025/Wed, 21 May 2025 06:00:00 +0200Google I/O is taking place this week, and while all the new AI stuff is grabbing the headlines, three Flutter sessions are also planned.

          As usual, this newsletter is all about the latest from the Flutterverse:

          • The new Flutter 3.32 and Dart 3.8 releases
          • Flutterโ€™s path towards seamless interop
          • A peek at Material 3 Expressive
          • New videos and articles from the community
          • All the latest from Code with Andrea

          Let's dive in!

          ๐Ÿ“ What's new in Flutter 3.32

          Flutter 3.32 is here, unlocking new features like web hot reload, Cupertino squircles for native fidelity, and new AI integrations with Firebase. The release addresses various improvements across web, framework, Cupertino, Material, accessibility, text input, desktop, iOS, Android, and engine components.

          Here are some highlights:

          • Web Hot Reload (Experimental): Now available with the --web-experimental-hot-reload flag, as explained here.
          • Cupertino Squircles: Added rounded superellipse shape for iOS fidelity in CupertinoAlertDialog and CupertinoActionSheet.
          • Progress on multi-window support on desktop by Canonical.
          • Flutter Property Editor: New tool for easily editing widget properties and reading documentation within the IDE (here's a preview).
          • Accessibility: Introduced a new SemanticsRole API for precise control over how UI elements are interpreted by assistive technologies.

          Read on for all the details:

          Detailed release notes for Flutter 3.32 are available here.

          ๐Ÿ“ Flutterโ€™s path towards seamless interop

          Flutter is launching an early access program for plugin authors to test and provide feedback on FFIgen and JNIgen, codegen solutions that aim to simplify access to native platform APIs by directly bridging Dart and native code.

          These tools seek to replace method channels, which are time-consuming to implement and maintain, with a more efficient and seamless developer experience. For all the details and how to apply, read on:

          ๐Ÿ“ Announcing Dart 3.8

          Flutter 3.32 includes Dart 3.8, which brings some welcome improvements:

          Read the announcement for all the details:

          ๐Ÿ“น Decoding Flutter: How Flutter Works (Video Series)

          The Flutter team shared an excellent 6-part video series that takes you on a deep dive into Flutter's internals. From high-level architecture to low-level engine details, this comprehensive series explains how Flutter actually works under the hood.

          The series starts with Flutter's overall architecture and gradually builds up your understanding through the widget system, state management, rendering pipeline, and platform integration. You'll learn about the three trees (Widget, Element, and RenderObject), how state management really works, what happens during layout and painting, and how Flutter interfaces with native platforms.

          It's a great watch if you want to understand the framework at a deeper level:

          Here are all the episodes:

          The official docs also include a high-level overview of the architecture of Flutter, including the core principles and concepts that form its design. Learn more here: Flutter architectural overview.

          Upcoming: Material 3 Expressive

          Have you heard? Material 3 Expressive was announced this month, and it's expected to ship with the upcoming Android 16 later this year:

          Material 3 Expressive preview
          Material 3 Expressive preview

          According to this umbrella issue on the Flutter repo, the team is not actively developing Material 3 Expressive at this stage, but I'm sure we'll hear more about it in the coming months, so keep an eye out.

          Community Articles

          This month, two useful articles that caught my attention. ๐Ÿ‘‡

          ๐Ÿ“ Building Local-First Flutter Apps with Riverpod, Drift, and PowerSync

          If you want to build offline-ready apps that stay fast and responsive, even without internet, you'll need to shift from the traditional online-first approach to a local-first model.

          This article by Dinko Marinac offers a great overview of key challenges like change tracking, conflict resolution, and error handling, and shows how PowerSync solves them. A practical implementation is included, showing how to integrate Riverpod, Drift, and PowerSync in a sample TODO app:

          ๐Ÿ“ The Definitive Guide to Navigator 2.0 in Flutter

          Flutterโ€™s navigation system has had a bit of a troubled past:

          • Navigator 1.0 is easy, but breaks down on the web and in complex apps.
          • Navigator 2.0 is powerful but under-documented and hard to reason about.
          • GoRouter tries to simplify it, but brings its own set of bugs and edge cases.

          Given GoRouter's recent troubles, I decided to use Navigator 2.0 directly when implementing URL-navigation in my Flutter Tips app.

          While I'm happy with the result, it was challenging to make it work, and this latest article by Tadas Petra has helped me get a better conceptual understanding of all the moving parts:

          Latest from Code with Andrea

          Over the last month, I shared a bunch of new tips and published two new articles.

          ๐Ÿ“ How to Update Your Android Gradle Files to the Kotlin DSL

          Since Flutter 3.29, new Flutter projects use the new Kotlin DSL for Gradle files by default. This has some implications for projects that rely on custom Gradle configurations, such as flavors, code signing, and more.

          If you need help with migrating to the new Kotlin DSL, this article breaks down what changed, how it affects you, and how to avoid common pitfalls:

          ๐Ÿ“ Flutter App Analytics: Scalable Architecture & Firebase Setup

          App analytics are very useful because they help you make product decisions based on data, rather than guesswork.

          My latest article shows how to track analytics in your Flutter app, including:

          • What to track: Choosing the right events.
          • How to structure it: Simple and scalable architectures for event tracking.
          • Firebase Analytics setup: How to wire everything up in a real app.

          Read on for all the details:

          I've also updated a previous article, explaining how to configure the updated code formatter in Dart 3.8.

          Until Next Time

          Lately, my brain has already been churning new content ideas, including a new video series where I'll be reviewing popular OSS Flutter apps to see how they're built.

          I'll be on vacation over the next week, but I'll be recording the first episode as soon as I get back, so stay tuned!

          Happy coding!

          ]]>
          https://codewithandrea.com/tips/preserve-trailing-commas-dart-3.8/Preserve Trailing Commas in Dart 3.8Dart 3.8 introduces a new formatting option to preserve trailing commas, overriding the new formatting style introduced in Dart 3.7.https://codewithandrea.com/tips/preserve-trailing-commas-dart-3.8/Wed, 21 May 2025 05:00:00 +0200Did you know?

          Dart 3.8 introduces a new formatting option to preserve trailing commas.

          Enable this to override the default formatter behaviour (introduced in Dart 3.7), which is to remove trailing commas when parameter lists fit within the max page width.

          Preserve trailing commas in Dart 3.8

          Some other formatter configuration options are available.

          For all the details, read this updated article:

          Happy coding!

          ]]>
          https://codewithandrea.com/tips/null-aware-elements-dart-3.8/Null-aware elements in Dart 3.8Null-aware elements allow you to add nullable elements to a collection with a single character (?).https://codewithandrea.com/tips/null-aware-elements-dart-3.8/Wed, 21 May 2025 03:00:00 +0200Did you know?

          Dart 3.8 introduces null-aware elements!

          This allows you to add nullable elements to a collection with a single character (?).

          Much shorter than using the collection-if syntax. ๐Ÿ‘

          Null-aware elements in Dart 3.8

          Here are some more examples (from the docs) showing how you might use this:

          Null-aware elements (more examples)

          More details on the feature specification:

          Happy coding!

          ]]>
          https://codewithandrea.com/tips/go-router-delegate-listener/GoRouter Delegate Listener for Screen TrackingIf your app uses GoRouter with shell routes, tracking screen views reliably requires some workarounds. Here's how to make it work.https://codewithandrea.com/tips/go-router-delegate-listener/Thu, 8 May 2025 02:00:00 +0200Did you know?

          If your app uses GoRouter with shell routes, tracking screen views reliably requires some workarounds.

          Here's how I do it (using Riverpod):

          1. Declare GoRouter inside a Riverpod provider
          2. Create a stateful GoRouter listener to track screen views
          3. Return it inside MaterialApp.builder
          GoRouter Delegate Listener for Screen Tracking

          Here's a PR showing how I implemented this in my Time Tracker app:

          And here's the original GoRouter issue showing that NavigatorObserver doesn't work with shell routes:

          Happy coding!

          ]]>
          https://codewithandrea.com/tips/button-styles-material3/Button Styles in Material 3Material 3 supports 5 types of buttons: elevated, filled, filled tonal, outlined, and text. Here's how to use them in Flutter.https://codewithandrea.com/tips/button-styles-material3/Wed, 7 May 2025 02:00:00 +0200Did you know?

          Material 3 supports 5 types of buttons:

          • Elevated
          • Filled
          • Filled tonal
          • Outlined
          • Text

          Here's how to use them in Flutter ๐Ÿ‘‡

          Button Styles in Material 3

          Example with source code:

          More complex example with custom icons and LTR/RTL support:

          Happy coding!

          ]]>
          https://codewithandrea.com/tips/themedata-platform/Test your UI with ThemeData.platformBy overriding ThemeData.platform inside MaterialApp, you can quickly test all your adaptive widgets and any conditional code that checks the platform.https://codewithandrea.com/tips/themedata-platform/Tue, 6 May 2025 02:00:00 +0200Did you know?

          By overriding ThemeData.platform inside MaterialApp, you can quickly test all your adaptive UI code, such as:

          • all adaptive widgets
          • any conditional code that checks the platform

          Much faster than choosing a different emulator/device and running from scratch ๐Ÿ‘

          Test your UI with ThemeData.platform

          Happy coding!

          ]]>
          https://codewithandrea.com/articles/flutter-app-analytics/Flutter App Analytics: Scalable Architecture & Firebase SetupA complete guide to adding analytics to Flutter apps using Firebase. Track custom events with a maintainable architecture.https://codewithandrea.com/articles/flutter-app-analytics/Fri, 2 May 2025 02:00:00 +0200Picture this: You've told your friends and family about your amazing new app (๐Ÿ˜„). Maybe youโ€™ve even built a waitlist of eager users. Youโ€™re excitedโ€”and ready to hit โ€œPublish.โ€

          But waitโ€ฆ

          How will you know whatโ€™s working?

          • How many users complete onboarding?
          • How many sign up and create an account?
          • How many reach the paywallโ€”and convert?

          Without analytics, youโ€™re flying blind.

          Youโ€™re making product decisions based on vibes, not data. Thatโ€™s a fast way to stall growth, waste dev time, or worseโ€”lose your users.

          The Importance of Analytics

          Analytics give you the data you need to grow. They help you:

          • Measure engagement: Whoโ€™s using your app, how often, and for how long?
          • Understand retention: Where are users dropping offโ€”and why?
          • Track feature usage: Whatโ€™s popular? Whatโ€™s ignored?
          • Optimize revenue: Monitor purchases, subscriptions, and churn.
          • Know your users: Demographics, devices, platforms.
          • Follow the journey: Map user flows across the app and spot friction points.

          With this info, you can stop guessing and start making product decisions that actually improve your app.

          Example Mixpanel dashboard showing primary user metrics for my Flutter Tips app
          Example Mixpanel dashboard showing primary user metrics for my Flutter Tips app

          What You'll Learn

          In this article, Iโ€™ll show you how to track analytics in your Flutter appโ€”from basic event logging to a scalable architecture that works with multiple providers like Firebase and Mixpanel.

          Hereโ€™s what weโ€™ll cover:

          • What to track: Choosing the right eventsโ€”and why they matter.
          • How to structure it: Simple and scalable architectures for event tracking.
          • Firebase Analytics setup: How to wire everything up in a real app.

          Letโ€™s dive in. ๐Ÿ‘‡

          This article will guide you through the fundamentals. If you want to go deeper, check out my Flutter in Production course, which contains an entire module about app analytics.

          Introduction to Event Tracking

          Ask five developers how they implemented analytics, and youโ€™ll likely get five different answers (I know because I did).

          As usual with software engineering, thereโ€™s no one-size-fits-all solutionโ€”just tradeoffs.

          So before we dive into the code, letโ€™s zoom out and answer a few key questions:

          • What events should we track?
          • How should we track them?
          • What requirements should our solution meet?

          Once weโ€™ve nailed that down, weโ€™ll pick a suitable architecture.

          What Events Do We Need to Track?

          In simple terms, events representย interactions between the user and your app.

          Take my Flutter Ship appย as an example:

          Flutter Ship App Demo

          The app helps you "tick all the boxes" before releasing your Flutter apps. Itโ€™s basically a pre-filled TODO list.

          Flutter Ship App web demo. Open in a separate window

          If you wanted to add analytics to this app, what would you track?

          Focus on events that:

          • Help the user succeedย (e.g. completing tasks)
          • Help your business succeedย (e.g. user signups, retention, monetization)

          For this app, meaningful events are:

          1. Create a new app
          2. Edit an existing app
          3. Delete an app
          4. Complete a task

          Everything else is optional. Donโ€™t track for the sake of tracking.


          Tracking Events with Firebase Analytics and Mixpanel

          Now, suppose you want to track these events in your Flutter app.

          If you're using the firebase_analytics package, you can do it like this:

          final analytics = FirebaseAnalytics.instance; analytics.logEvent('app_created'); analytics.logEvent('app_updated'); analytics.logEvent('app_deleted'); analytics.logEvent('task_completed', parameters: {'count': count});

          Or, if you're using the mixpanel_flutter package, this is the way:

          final mixpanel = await Mixpanel.init( Env.mixpanelProjectToken, trackAutomaticEvents: true, ); mixpanel.track('App Created'); mixpanel.track('App Updated'); mixpanel.track('App Deleted'); mixpanel.track('Task Completed', properties: {'count': count});

          You can already spot the problem:ย different APIs, different syntax.

          โŒ Scattering these calls all over your codebase is a bad idea. โŒ

          Here's why:

          • You lock yourself into specific SDKs
          • You duplicate event names everywhereย (easy to mistype, hard to refactor)
          • You lose type safety and IDE support
          • You canโ€™t reuse logic or dispatch to multiple vendors
          • You mix business logic with implementation details

          We can do much better by defining a clear interfaceโ€”one that separates event tracking from everything else.

          Letโ€™s formalize some requirements next.

          App Analytics: Requirements

          Before jumping into implementation, letโ€™s define what we actually need from an analytics system.

          Here are the key requirements:

          1. Separation of concerns: App code should never call vendor SDKs (like Firebase or Mixpanel) directly. Instead, weโ€™ll route all event tracking through a clear interface.
          1. Support for multiple clients: We should be able to send events to more than one provider. For example, you might want to log to both Firebase and Mixpanel at the same time.
          1. Debug vs Release mode: In development, we only want to log events to the console. In release mode, real analytics providers should be usedโ€”with no changes to app logic.

          Other things worth thinking about:

          • Screen view tracking
          • Opt-in/out togglesย (e.g. for privacy/GDPR)
          • User identificationย (after login)
          • Multiple build flavorsย (dev/staging/prod)

          But for now, weโ€™ll focus on the two most important ones:

          • Keeping analytics isolated from app logic
          • Supporting multiple analytics providers

          Next, let's walk through two possible architectures and break down their tradeoffs. ๐Ÿ‘‡

          Super-Simple Analytics Architecture

          This setup is adapted from a suggestion I receivedย on X (Twitter):

          import 'dart:developer'; import 'package:flutter/foundation.dart'; import 'package:mixpanel_flutter/mixpanel_flutter.dart'; import 'package:firebase_analytics/firebase_analytics.dart'; class AnalyticsClient { const AnalyticsClient(this._analytics, this._mixpanel); final FirebaseAnalytics _analytics; final Mixpanel _mixpanel; Future<void> track( String name, { Map<String, dynamic> params = const {}, }) async { if (kReleaseMode) { await _analytics.logEvent(name: name, parameters: params); await _mixpanel.track(name, properties: params); } else { log('$name $params', name: 'Event'); } } }

          This is about as simple as it gets. It gives you a shared interface for tracking events:

          • Inย release mode, it logs to both Firebase and Mixpanel
          • Inย debug mode, it just prints to the console

          If you're using Riverpod, tracking events looks like this:

          final analytics = ref.read(analyticsClientProvider); analytics.track('app_created'); analytics.track('app_updated'); analytics.track('app_deleted'); analytics.track('task_completed', parameters: {'count': count});

          As long as your event names follow theย Firebase Analytics guidelinesย (1โ€“40 alphanumeric characters or underscores), youโ€™re good to go.

          This approach is surprisingly flexible for how little code it requires. You can swap out analytics providers by updating a single class, and the rest of your app doesnโ€™t care. ๐Ÿ‘

          But It Has Drawbacks

          This architecture starts to break down as your app grows:

          • No conditional tracking: You canโ€™t log some events to Mixpanel but not Firebase. This is a problem if you're watching your event volume (and costs).
            • Hardcoded strings everywhere: Event names end up duplicated across the codebase. Easy to mistype. Hard to refactor. A big footgun on teams.
              • No single source of truth:Thereโ€™s no centralized list of events or their expected parameters.
                • No autocomplete: You lose IDE support. If you had a type-safe API, you'd get helpful autocompletion:
                  Analytics methods auto-completion in VSCode
                  Analytics methods auto-completion in VSCode

                  This setup may be good enough for solo devs or small apps where you want minimal friction and full control.

                  But if youโ€™re working in a larger codebase or with a team, itโ€™s worth investing in something a bit more robust. Letโ€™s look at a more scalable alternative. ๐Ÿ‘‡

                  More Complex Analytics Architecture

                  If you want aย type-safeย analytics API with autocomplete and clear separation of concerns, hereโ€™s the approach I recommend.

                  Start by defining an abstractย AnalyticsClientย interface. For theย Flutter Ship app, it might look like this:

                  abstract class AnalyticsClient { Future<void> trackAppCreated(); Future<void> trackAppUpdated(); Future<void> trackAppDeleted(); Future<void> trackTaskCompleted(int completedCount); }

                  This way, all the events are defined in a single place, avoiding duplication and potential mistakes.

                  But how do we support multiple clients?

                  Hereโ€™s one way to structure it:

                  App Analytics Architecture
                  App Analytics Architecture

                  How this works:

                  • You define all your events in an abstractย AnalyticsClientย interface.
                  • Each concrete implementation (e.g.ย LoggerAnalyticsClient,ย FirebaseAnalyticsClient, etc.) handles how those events are logged.
                  • Anย AnalyticsFacadeย class implementsย AnalyticsClientย tooโ€”but just dispatches to all registered clients.
                  • Finally, the app uses aย analyticsFacadeProviderย to access the facade and call the appropriate tracking methods.

                  Now, from your app code, tracking an event is as simple as:

                  final analytics = ref.read(analyticsFacadeProvider); analytics.trackEditApp();

                  And since each event is a dedicated method, you get autocomplete and type safety out of the box:

                  Analytics methods auto-completion in VSCode
                  Analytics methods auto-completion in VSCode

                  How Do We Implement This?

                  This is just the high-level architecture. To make it work, weโ€™ll need to answer:

                  • How do we register multiple clients inย AnalyticsFacade?
                  • How are events dispatched to each client?
                  • What do concreteย AnalyticsClientย subclasses look like?

                  Letโ€™s walk through the implementation details next. ๐Ÿ‘‡

                  App Analytics: Implementation Details

                  To implement the architecture we just discussed, weโ€™ll need a few key building blocks:

                  1. Anย AnalyticsClientย interface that defines all the events we want to track.
                  2. Anย AnalyticsFacadeย that implementsย AnalyticsClient, and delegates calls to multiple clients.
                  3. Aย LoggerAnalyticsClientย for local devโ€”logs events to the console.
                  4. Additional clients likeย FirebaseAnalyticsClient,ย MixpanelAnalyticsClient, etc., that wrap vendor SDKs.

                  To keep things organized, I recommend putting all of this in aย monitoringย folder:

                  โ€ฃ lib โ€ฃ src โ€ฃ monitoring โ€ฃ analytics_client.dart โ€ฃ analytics_facade.dart โ€ฃ logger_analytics_client.dart โ€ฃ firebase_analytics_client.dart

                  Hereโ€™s how each part works:

                  1. Theย AnalyticsClientย Cnterface

                  This is the contract. It defines all the events your app supports.

                  abstract class AnalyticsClient { // Custom events for the Flutter Ship app. // TODO: Replace with your own events. Future<void> trackNewAppOnboarding(); Future<void> trackNewAppHome(); Future<void> trackAppCreated(); Future<void> trackAppUpdated(); Future<void> trackAppDeleted(); Future<void> trackTaskCompleted(int completedCount); }

                  Since this class isย abstract, all methods are declarations onlyโ€”no implementations.

                  2. Theย LoggerAnalyticsClientย Class

                  This oneโ€™s simple: it logs events to the console. Useful for local dev and tests before wiring up a real analytics backend:

                  import 'dart:async'; import 'dart:developer'; import 'package:flutter_ship_app/src/monitoring/analytics_client.dart'; class LoggerAnalyticsClient implements AnalyticsClient { const LoggerAnalyticsClient(); static const _name = 'Event'; @override Future<void> trackNewAppHome() async { log('trackNewAppHome', name: _name); } @override Future<void> trackNewAppOnboarding() async { log('trackNewAppOnboarding', name: _name); } @override Future<void> trackAppCreated() async { log('trackAppCreated', name: _name); } @override Future<void> trackAppUpdated() async { log('trackAppUpdated', name: _name); } @override Future<void> trackAppDeleted() async { log('trackAppDeleted', name: _name); } @override Future<void> trackTaskCompleted(int completedCount) async { log('trackTaskCompleted(completedCount: $completedCount)', name: _name); } }

                  Weโ€™ll add aย FirebaseAnalyticsClientย later. For now, focus on what you want to trackโ€”not how itโ€™s sent.

                  Is There a Better Way to Define Events?

                  This approach worksโ€”but itโ€™s not DRY. Youโ€™re copy-pasting a lot when adding new events:

                  // copy-paste when creating new events @override Future<void> trackAppCreated() async { log('trackAppCreated', name: _name); }

                  Alternatives worth considering:

                  • Enums: Define an AppEvent enum and list all the possible events as values. This doesn't work if different events need different arguments.
                  • Sealed classes: Use a baseย AppEventย sealed class and create a subclass every time we need a new event. More flexible, but verbose.
                  • Freezed union types: Terse syntax and pattern matchingโ€”but requires codegen.

                  Each option has tradeoffs. For now, we'll stick with our initial approach.

                  3. Theย AnalyticsFacadeย Class

                  Theย AnalyticsFacadeย implements theย AnalyticsClientย interface and forwards each method call to all registered clients.

                  import 'package:flutter/foundation.dart'; import 'package:flutter_ship_app/src/monitoring/analytics_client.dart'; import 'package:flutter_ship_app/src/monitoring/logger_analytics_client.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'analytics_facade.g.dart'; // https://refactoring.guru/design-patterns/facade class AnalyticsFacade implements AnalyticsClient { const AnalyticsFacade(this.clients); final List<AnalyticsClient> clients; @override Future<void> trackAppOpened() => _dispatch( (c) => c.trackAppOpened(), ); @override Future<void> trackNewAppHome() => _dispatch( (c) => c.trackNewAppHome(), ); @override Future<void> trackNewAppOnboarding() => _dispatch( (c) => c.trackNewAppOnboarding(), ); @override Future<void> trackAppCreated() => _dispatch( (c) => c.trackAppCreated(), ); @override Future<void> trackAppUpdated() => _dispatch( (c) => c.trackAppUpdated(), ); @override Future<void> trackAppDeleted() => _dispatch( (c) => c.trackAppDeleted(), ); @override Future<void> trackTaskCompleted(int completedCount) => _dispatch( (c) => c.trackTaskCompleted(completedCount), ); Future<void> _dispatch( Future<void> Function(AnalyticsClient client) work, ) async { for (final client in clients) { await work(client); } } }

                  Theย _dispatchย method removes duplication and broadcasts each event to all the registered clients.

                  Setting Up the Provider

                  Hereโ€™s how to expose theย AnalyticsFacadeย via Riverpod:

                  @Riverpod(keepAlive: true) AnalyticsFacade analyticsFacade(Ref ref) { return const AnalyticsFacade([ if (!kReleaseMode) LoggerAnalyticsClient(), ]); }

                  In debug builds, you get console logging. In release builds, you can add real clients likeย FirebaseAnalyticsClient (more on this later).

                  This setup gives you a clear, modular, and scalable way to track analytics eventsโ€”without coupling your app logic to any vendor-specific SDK.

                  Which Architecture Is Better?

                  The architecture we just built gives you:

                  • A type-safe event tracking API
                  • Autocomplete and IDE support
                  • Clear separation of concerns
                  • Support for multiple analytics backends

                  Hereโ€™s a visual recap:

                  App Analytics Architecture
                  App Analytics Architecture

                  Itโ€™s flexible, scalable, and production-ready.

                  But it comes at a cost.

                  Every time you add a new event, you need to:

                  • Add a method to theย AnalyticsClientย interface
                  • Implement it in theย AnalyticsFacade
                  • Implement it in every concrete client (e.g. Logger, Firebase, Mixpanel)

                  When the Simple Approach Is Enough

                  For small apps or quick MVPs, the simple approach may be enough:

                  import 'dart:developer'; import 'package:flutter/foundation.dart'; import 'package:mixpanel_flutter/mixpanel_flutter.dart'; import 'package:firebase_analytics/firebase_analytics.dart'; class AnalyticsClient { const AnalyticsClient(this._analytics, this._mixpanel); final FirebaseAnalytics _analytics; final Mixpanel _mixpanel; Future<void> track( String name, { Map<String, dynamic> params = const {}, }) async { if (kReleaseMode) { await _analytics.logEvent(name: name, parameters: params); await _mixpanel.track(name, properties: params); } else { log('$name $params', name: 'Event'); } } }

                  This gets the job doneโ€”fast. But you lose:

                  • Autocomplete
                  • Type safety
                  • Centralized event definitions
                  • Vendor-level control and filtering
                  Analytics methods auto-completion in VSCode
                  Analytics methods auto-completion in VSCode

                  Bottom line: For anything beyond a throwaway prototype, go with the more structured architecture. Itโ€™s more verbose up front, but it pays off in maintainability, testability, and developer experience.

                  Now letโ€™s see how to actually call the analyticsFacadeProvider to track events in real app code.

                  Tracking Custom Events

                  Weโ€™ve defined an interface like this:

                  abstract class AnalyticsClient { Future<void> trackNewAppOnboarding(); Future<void> trackNewAppHome(); Future<void> trackAppCreated(); Future<void> trackAppUpdated(); Future<void> trackAppDeleted(); Future<void> trackTaskCompleted(int completedCount); }

                  But unless you actually call these methods, nothing gets tracked.

                  So where should you put these calls?

                  • In widgets?
                  • In controllers?
                  • Somewhere else?

                  Letโ€™s look at both options.

                  Tracking Events in Widgets and Controllers

                  Hereโ€™s a simple exampleโ€”aย +ย button on the home page:

                  New app buttons on the home page
                  New app buttons on the home page

                  The event tracking code looks like this:

                  IconButton( onPressed: () { // event tracking code unawaited(ref.read(analyticsFacadeProvider).trackNewAppHome()); // navigation code Navigator.of(context).pushNamed(AppRoutes.createApp); }, icon: Icon(Icons.add), )

                  In this case, the analytics call is placed directly in theย onPressedย callback.

                  ๐Ÿ’ก Theย unawaitedย function is used here to fire the event without blocking. These are fire-and-forget callsโ€”you donโ€™t need to wait for them. More on that here:ย Use unawaited for your analytics calls.

                  But if your logic is more complex, itโ€™s better to move analytics calls into a controller.

                  Hereโ€™s an example fromย a custom controller class:

                  /// This class holds the business logic for creating, editing, and deleting apps /// using the underlying AppDatabase class for data persistence. /// More info here: https://codewithandrea.com/articles/flutter-presentation-layer/ @riverpod class CreateEditAppController extends _$CreateEditAppController { @override void build() { // no-op } Future<void> createOrEditApp(App? existingApp, String newName) async { final db = ref.read(appDatabaseProvider); // * Update the DB if (existingApp != null) { await db.editAppName(appId: existingApp.id, newName: newName); } else { await db.createNewApp(name: newName); } // * Analytics code if (existingApp != null) { unawaited(ref.read(analyticsFacadeProvider).trackAppUpdated()); } else { unawaited(ref.read(analyticsFacadeProvider).trackAppCreated()); } } Future<void> deleteAppById(int appId) async { await ref.read(appDatabaseProvider).deleteAppById(appId); ref.read(analyticsFacadeProvider).trackAppDeleted(); } }

                  This controller handles mutations and database interactionsโ€”so itโ€™s the perfect place to also track related analytics events.

                  Key Guidelines for Tracking Events

                  • โŒย Never track events inย build(). It can be called dozens of times per second during animations. Same goes forย initState()ย and other lifecycle methods.
                  • โœ…ย Track in widget callbacksย likeย onPressed. If the logic is complex, move it out into a controller or service class.
                  • โœ…ย Tracking in controllers is ideal. Theyโ€™re UI-free, easier to test, and already house the logic that triggers the events.
                  • โŒ Avoid tracking events in the data/networking layer. Events should be tracked at the source (UI layer) for better accuracy.
                  • โœ…ย Useย unawaited()ย for all analytics calls. Donโ€™t block UI or care about resultsโ€”just fire and forget.

                  Firebase Analytics Integration

                  So far, weโ€™ve been using theย LoggerAnalyticsClientย to print events to the console.

                  But for production apps, youโ€™ll want to plug in a real analytics backendโ€”like Firebase Analytics, Mixpanel, or PostHog.

                  Thanks to ourย AnalyticsFacade, you can do this without changing your app code. Just add a new client, register it, and youโ€™re done. Thatโ€™s the power of separation of concerns.

                  Letโ€™s walk through integrating Firebase Analytics.

                  Adding Firebase to your Flutter app

                  Follow the official guide toย add Firebase to your Flutter app.

                  If your app uses multiple flavors (e.g. dev, staging, prod), the setup is more involved. I cover that in detail here:

                  Creating theย FirebaseAnalyticsClientย Class

                  This is a straightforward implementation of theย AnalyticsClientย interface using the Firebase SDK:

                  import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:flutter_ship_app/src/monitoring/analytics_client.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'firebase_analytics_client.g.dart'; class FirebaseAnalyticsClient implements AnalyticsClient { const FirebaseAnalyticsClient(this._analytics); final FirebaseAnalytics _analytics; @override Future<void> trackNewAppHome() async { await _analytics.logEvent(name: 'new_app_home'); } @override Future<void> trackNewAppOnboarding() async { await _analytics.logEvent(name: 'new_app_onboarding'); } @override Future<void> trackAppCreated() async { await _analytics.logEvent(name: 'app_created'); } @override Future<void> trackAppUpdated() async { await _analytics.logEvent(name: 'app_updated'); } @override Future<void> trackAppDeleted() async { await _analytics.logEvent(name: 'app_deleted'); } @override Future<void> trackTaskCompleted(int completedCount) async { await _analytics.logEvent( name: 'task_completed', parameters: {'count': completedCount}, ); } } @Riverpod(keepAlive: true) FirebaseAnalyticsClient firebaseAnalyticsClient(Ref ref) { return FirebaseAnalyticsClient(FirebaseAnalytics.instance); }

                  Registering the Client with the Facade

                  Update yourย analyticsFacadeProviderย to include the Firebase client:

                  @Riverpod(keepAlive: true) AnalyticsFacade analyticsFacade(Ref ref) { final firebaseAnalyticsClient = ref.watch(firebaseAnalyticsClientProvider); return AnalyticsFacade([ firebaseAnalyticsClient, if (!kReleaseMode) const LoggerAnalyticsClient(), ]); }

                  And just like thatโ€”no changes to the rest of your appโ€”your events are now being sent to Firebase Analytics.

                  Youโ€™ll see them show up in theย Firebase Console:

                  First active user showing in the Firebase Analytics dashboard
                  First active user showing in the Firebase Analytics dashboard

                  Hereโ€™s what it looks like in myย Flutter Tips app:

                  Firebase Analytics dashboard for the Flutter Tips app
                  Firebase Analytics dashboard for the Flutter Tips app

                  Flutter App Analytics: Summary

                  In this article, we covered everything you need to get started with production-grade analytics in your Flutter app:

                  • โœ… Why analytics is essential for shipping and growing with confidence
                  • โœ… How to think about and choose the right events to track
                  • โœ… A simple architecture for quick wins
                  • โœ… A scalable, type-safe architecture for larger apps and teams
                  • โœ… How to track events from widgets and controllers
                  • โœ… How to integrate Firebase Analytics with zero impact on app logic

                  But hold onโ€”thereโ€™s more to shipping analytics in real-world apps.

                  Before you hit publish, consider:

                  • Tracking screen views: Use a navigation observer to capture screen transitions automatically.
                  • Opt-in/out analytics: Let users disable tracking if required (e.g. for GDPR compliance).
                  • User identification: Link events to logged-in users for cross-device tracking and funnel analysis.
                  • Mixpanel or PostHog integration: Firebase is free, but tools like Mixpanel offer powerful filtering and reportingโ€”and are much more privacy-friendly.

                  To go deeper into these topicsโ€”and way beyond analyticsโ€”check out my latest course. ๐Ÿ‘‡

                  Flutter in Production

                  When it comes to shipping and maintaining apps in production, there are many important aspects to consider:

                  • Preparing for release: splash screens, flavors, environments, error reporting, analytics, force update, privacy, T&Cs.
                  • App Submissions: app store metadata & screenshots, compliance, testing vs distribution tracks, dealing with rejections.
                  • Release automation: CI workflows, environment variables, custom build steps, code signing, uploading to the stores.
                  • Post-release: error monitoring, bug fixes, addressing user feedback, over-the-air updates, feature flags & A/B testing.

                  My latest course will help you get your app to the stores faster and with fewer headaches.

                  If youโ€™re interested, you can learn more and enroll here. ๐Ÿ‘‡

                  ]]>
                  https://codewithandrea.com/tips/adaptive-alert-dialog/Adaptive Alert Dialog (Material, Cupertino)A simple alert dialog that supports adaptive mode, default and cancel actions, destructive style, and dismissible mode.https://codewithandrea.com/tips/adaptive-alert-dialog/Thu, 1 May 2025 02:00:00 +0200In many of my projects, I use a simple alert dialog that supports:

                  • Adaptive mode (Material / Cupertino)
                  • Default and cancel actions
                  • Destructive UI style
                  • Dismissible mode

                  Super useful for Yes/No, Cancel/Delete scenarios. ๐Ÿ‘

                  Adaptive Alert Dialog

                  Here's a gist with the source code:

                  Feel free to tweak and reuse it in your projects. ๐Ÿ™‚

                  Happy coding!

                  ]]>
                  https://codewithandrea.com/articles/flutter-android-gradle-kts/Kotlin DSL in Flutter 3.29: How to Update Your Android Gradle FilesNew Kotlin DSL in Flutter 3.29? Learn how to update your Gradle files, configure code signing, flavors, and more, in minutes.https://codewithandrea.com/articles/flutter-android-gradle-kts/Wed, 30 Apr 2025 02:00:00 +0200The recent Flutter 3.29 release introduced many new updates to Impeller, Cupertino widgets, DevTools and more. But one big change flew under the radar: new Flutter projects now use the Kotlin DSL for Gradle files by default.

                  This has some implications for projects that rely on custom Gradle configurations, such as flavors, code signing, and more.

                  This article breaks down what changed, how it affects you, and how to avoid common pitfalls.

                  What's changed?

                  New projects generated with Flutter 3.29 now have .kts files instead of .gradle:

                  • android/build.gradle โ†’ android/build.gradle.kts
                  • android/settings.gradle โ†’ android/settings.gradle.kts
                  • android/app/build.gradle โ†’ android/app/build.gradle.kts
                  Android project folder generated with Flutter 3.27 (left) and Flutter 3.29 (right)
                  Android project folder generated with Flutter 3.27 (left) and Flutter 3.29 (right)

                  Before you panic: both Groovy (old) and Kotlin (new) files are fully supported.

                  If your project is using Groovy .gradle files, you don't need to migrate. Everything will still work with Flutter 3.29.

                  Only brand-new projects start with .kts files.

                  That said, if you are working with .kts, there are a few things youโ€™ll want to know. ๐Ÿ‘‡

                  The New Kotlin DSL Syntax

                  Letโ€™s play spot-the-difference between these two snippets:

                  // Example 1 - is this Groovy or Kotlin? pluginManagement { def flutterSdkPath = { def properties = new Properties() file("local.properties").withInputStream { properties.load(it) } def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" return flutterSdkPath }() includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") repositories { google() mavenCentral() gradlePluginPortal() } } plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version "8.1.0" apply false id "org.jetbrains.kotlin.android" version "1.8.22" apply false } include ":app"
                  // Example 2 - is this Groovy or Kotlin? pluginManagement { val flutterSdkPath = run { val properties = java.util.Properties() file("local.properties").inputStream().use { properties.load(it) } val flutterSdkPath = properties.getProperty("flutter.sdk") require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } flutterSdkPath } includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") repositories { google() mavenCentral() gradlePluginPortal() } } plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("com.android.application") version "8.7.0" apply false id("org.jetbrains.kotlin.android") version "1.8.22" apply false } include(":app")

                  Both snippets do the same thing, but there are key differences:

                  • Groovy uses def for variables; Kotlin uses val.
                  • Groovy lets you skip parentheses in method calls; Kotlin requires them.
                  • Groovy assertions use assert; Kotlin uses require.

                  There are plenty more differences, all neatly documented here:

                  But what does this actually mean for your project?

                  Common Gradle Setup Tasks

                  Here are the typical things youโ€™ll need to adjust:

                  1. Set Android NDK, minSdk, targetSdk, Java version
                  2. Configure code signing
                  3. Add the com.google.gms.google-services plugin for apps using Firebase
                  4. Set up multiple flavors

                  Old tutorials and Stack Overflow answers? Mostly outdated now. Here's how to handle it.

                  1. Setting the Android NDK, minSdk, targetSdk, Java Version

                  Same settings as beforeโ€”just with the correct Kotlin syntax (assignment operator, double quotes for string literals):

                  ... android { namespace = "com.codewithandrea.flutter_ship_app" compileSdk = flutter.compileSdkVersion ndkVersion = "27.0.12077973" // Updated compileOptions { sourceCompatibility = JavaVersion.VERSION_17 // Updated targetCompatibility = JavaVersion.VERSION_17 // Updated } kotlinOptions { jvmTarget = JavaVersion.VERSION_17.toString() // Updated } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId = "com.codewithandrea.flutter_ship_app" minSdk = 21 // Updated targetSdk = 34 // Updated versionCode = flutter.versionCode versionName = flutter.versionName } ... } ...

                  Pro Tip: updating all these values manually is a pain, and I do it so often that I created a script to automate the process. Here's how it works:

                  Download it here and add it to your project:

                  2. Configuring Code Signing

                  The official Flutter docs about building and releasing an Android app are already updated for the new Kotlin DSL.

                  Key steps:

                  Here's a sample code showing the required changes to the android/app/build.gradle.kts file:

                  import java.io.FileInputStream import java.util.Properties plugins { ... } val keystoreProperties = Properties() val keystorePropertiesFile = rootProject.file("key.properties") if (keystorePropertiesFile.exists()) { keystoreProperties.load(FileInputStream(keystorePropertiesFile)) } android { // ... signingConfigs { create("release") { keyAlias = keystoreProperties["keyAlias"] as String keyPassword = keystoreProperties["keyPassword"] as String storeFile = keystoreProperties["storeFile"]?.let { file(it) } storePassword = keystoreProperties["storePassword"] as String } } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, // so `flutter run --release` works. signingConfig = signingConfigs.getByName("debug") signingConfig = signingConfigs.getByName("release") } } ... }

                  3. Adding the com.google.gms.google-services plugin (Firebase)

                  This is handled automatically when you add Firebase to your app with the FlutterFire CLI.

                  Resulting settings.gradle.kts:

                  plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("com.android.application") version "8.7.0" apply false // START: FlutterFire Configuration id("com.google.gms.google-services") version("4.3.15") apply false // END: FlutterFire Configuration id("org.jetbrains.kotlin.android") version "1.8.22" apply false }

                  The same plugin will also be listed in android/app/build.gradle.kts:

                  plugins { id("com.android.application") // START: FlutterFire Configuration id("com.google.gms.google-services") // END: FlutterFire Configuration id("kotlin-android") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id("dev.flutter.flutter-gradle-plugin") }

                  4. Adding Support for Multiple Flavors

                  This one is a bit tricky as it requires defining separate product flavors for your app.

                  The Flutter Flavorizr plugin already handles this for you when you run these processors:

                  dart run flutter_flavorizr -p android:buildGradle,android:flavorizrGradle,android:androidManifest

                  If you're doing it manually, open android/app/build.gradle.kts and add this line at the very bottom:

                  apply { from("flavors.gradle.kts") }

                  Then, create a android/app/flavors.gradle.kts file with these contents:

                  import com.android.build.gradle.AppExtension val android = project.extensions.getByType(AppExtension::class.java) android.apply { flavorDimensions("flavor-type") productFlavors { create("dev") { dimension = "flavor-type" applicationId = "com.codewithandrea.flutter_ship_app.dev" resValue(type = "string", name = "app_name", value = "Flutter Ship Dev") } create("stg") { dimension = "flavor-type" applicationId = "com.codewithandrea.flutter_ship_app.stg" resValue(type = "string", name = "app_name", value = "Flutter Ship Stg") } create("prod") { dimension = "flavor-type" applicationId = "com.codewithandrea.flutter_ship_app" resValue(type = "string", name = "app_name", value = "Flutter Ship") } } }

                  Notes:

                  • Make sure the flavor names match your flutter run --flavor [name] command.
                  • Update the applicationId and app name for each flavor.
                  • In AndroidManifest.xml, set the app label dynamically in the android:label attribute:
                  <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application android:label="@string/app_name" <-- updated android:name="${applicationName}" android:icon="@mipmap/ic_launcher"> ... </application> ... </manifest>

                  There's much more to adding flavors than what I've shown here. For a full guide, check out my Flutter in Production course.

                  What If You Need to Recreate Your Android Project?

                  Old Flutter apps sometimes break on new Android tooling. If youโ€™re stuck, itโ€™s often easier to nuke and recreate the Android folder:

                  # Commit to git before making any changes git add . && git commit -m "Working copy" # Delete android folder rm -rf android # Create it again with the Flutter CLI flutter create . --platforms android --org com.yourorgname # See what's changed git diff android # Reapply previous settings, following steps above # Run again flutter run # All good? Commit to git git add . && git commit -m "Updated Android project"

                  When you recreate the Android project from scratch, any old settings you had applied will also be lost. Always run git diff android to see what's changed, and selectively restore any custom settings as needed.

                  Conclusion

                  Starting with Flutter 3.29, new projects use the Kotlin DSL for Gradle.

                  Honestly? I see little real benefit over Groovy. Yet, our tech overlords have decided this is the way, and we have to keep up and learn a new syntax. ๐Ÿคทโ€โ™‚๏ธ

                  My advice:

                  • If you're not forced to migrate, stick with Groovy.
                  • If you start fresh or need Kotlin DSL, this guide has you covered.

                  Need more help shipping your app? Keep reading.

                  Flutter in Production

                  Shipping and maintaining apps in production takes more than just writing code:

                  • Preparing for release: splash screens, flavors, environments, error reporting, analytics, force update, privacy, T&Cs.
                  • App Submissions: metadata, screenshots, compliance, testing vs distribution tracks, dealing with rejections.
                  • Release automation: CI workflows, environment variables, custom build steps, code signing, uploading to the stores.
                  • Post-release: monitoring crashes, fixing bugs, OTA updates, feature flags & A/B testing.

                  My latest course will help you ship faster and with fewer headaches. Learn more here. ๐Ÿ‘‡

                  ]]>
                  https://codewithandrea.com/tips/firebase-remote-config-init/How to Initialize Firebase Remote ConfigA battle tested solution for loading Firebase Remote Config in Flutter, avoiding common pitfalls.https://codewithandrea.com/tips/firebase-remote-config-init/Wed, 30 Apr 2025 02:00:00 +0200Firebase Remote Config is great, but it's a bit tricky to configure it correctly.

                  Here's my battle-tested loading strategy:

                  1. setup fetch interval (flavor-dependent)
                  2. set default values
                  3. activate previous values
                  4. fetch new values (unawaited)
                  5. add a realtime listener (which is not subject to the minimumFetchInterval)
                  How to Initialize Firebase Remote Config

                  Want to use this in your apps? Grab it from this gist:

                  For more info about common loading strategies, read:

                  Flutter in Production

                  My latest course shows how to use Firebase Remote Config in useful scenarios such as force updates and A/B tests.

                  Learn more here ๐Ÿ‘‡

                  ]]>
                  https://codewithandrea.com/tips/disable-impeller-android/How to Disable Impeller on AndroiddebugRepaintRainbowEnabled helps you discover widgets/areas that unexpectedly repaint in your app. Here's how to use it.https://codewithandrea.com/tips/disable-impeller-android/Tue, 29 Apr 2025 02:00:00 +0200Did you know?

                  Since Flutter 3.29, Impeller is enabled by default on Android, but it can cause some rendering issues on some devices.

                  If needed, here's how to disable it and revert to the old Skia renderer. ๐Ÿ‘‡

                  How to disable Impeller on Android

                  See this changelog for some Impeller issues that were recently fixed:

                  Happy coding!

                  ]]>
                  https://codewithandrea.com/newsletter/april-2025/April 2025: Flutter Roadmap Update, New Beta Release, Latest Community ArticlesAlso included: upcoming changes to the formatter in Dart 3.8, common mistakes in Flutter and Dart development, OWASP Mobile Top 10 series.https://codewithandrea.com/newsletter/april-2025/Thu, 24 Apr 2025 02:00:00 +0200Spring has officially arrived ๐ŸŒธโ€”and while weโ€™re likely waiting until Google I/O for the next stable Flutter release, thereโ€™s already plenty happening in the ecosystem.

                  This monthโ€™s newsletter covers:

                  • Flutterโ€™s updated 2025 roadmap
                  • Whatโ€™s new in the latest beta release
                  • Upcoming formatter changes and new IDE assists in Dart 3.8
                  • Hand-picked articles about common Flutter mistakes, app security, and more

                  Letโ€™s dive in! ๐Ÿš€

                  ๐Ÿงญ Flutter 2025 Roadmap Update

                  The Flutter team has shared their official 2025 roadmap, outlining key focus areas for the year.

                  Highlights include:

                  • Interoperability on iOS & Android: Continue experimental work to support direct calls from Dart to Swift/Obj-C (iOS) and Kotlin/Java (Android). This includes calling APIs that can only be invoked on the main OS/platform thread.
                  • Web Platform: Hot reload support, better performance, improved accessibility, and the removal of legacy HTML/JS libraries.
                  • Core Framework: Investigate changes to reduce verbosity in Flutter widget code and streamline development.
                  • Dart Language Improvements: Enhancements to code generation, new language features, and a refactored analyzer for better performance and tooling.

                  Personally, Iโ€™m most excited about hot reload for Flutter web (finally!), as well as new Dart language features and build_runner improvements.

                  Learn more here:

                  ๐Ÿ†• Wanna help Flutter? Try out the beta!

                  The last stable releaseโ€”Flutter 3.29โ€”introduced some big changes (like Impeller on Android) and left many developers frustrated with regressions and waiting for hotfixes.

                  The Flutter team is now encouraging developers to test the latest beta and report bugs early. If you want to help shape the next stable release, nowโ€™s a great time to get involved:

                  I think this will help iron out potential issues before the next stable release. It also means we can already try out some of the upcoming changes. ๐Ÿ‘‡

                  โŒจ๏ธ Dart 3.8: Formatter Improvements

                  The new formatting style introduced in Dart 3.7 was quite controversial, with many people against the decision to automatically remove trailing commas.

                  The good news? Dart 3.8 will introduce an opt-in formatter config to preserve trailing commas.

                  To try it out, upgrade to the latest beta and add the following to your analysis_options.yaml:

                  formatter: trailing_commas: preserve

                  More info:

                  โšก๏ธ New IDE Assists Coming to Dart 3.8

                  Dart 3.8 also brings a bunch of new code assists and quality-of-life improvements:

                  • Smarter code completion
                  • Quick wraps withย FutureBuilder and ValueListenableBuilder
                  • Rename closure parameters
                  • Better show/hide combinator assists for imports

                  For the full list, check the changelog:

                  Shoutout to @FMorschel for driving many of these improvements!

                  โš ๏ธ Hot Restart Bug on iOS (Flutter 3.29)

                  Some devs (myself included) have hit an annoying issue where hot restart hangs on iOS.

                  Thankfully, Luke Pighetti filed an issue, and the Flutter team already confirmed that it's fixed on master. If you're affected, read this comment for a temporary workaround.

                  For more details, here's the full issue:

                  ๐Ÿ“š Articles from the Community

                  Here are some standout reads from the past month:

                  ๐Ÿ“ 15 Common Mistakes in Flutter and Dart Development (and How to Avoid Them)

                  From memory leaks and rebuild traps to architectural missteps, Majid Hajian shares hard-earned lessons from 7 years of Flutter development.

                  This article covers:

                  • Real-world mistakes in large Flutter apps
                  • How to catch issues with tools like DCM
                  • Strategies for better maintainability and performance

                  Read on to explore common pitfalls and learn how to avoid them:

                  ๐Ÿ›ก๏ธ OWASP Top 10 for Flutter (Security Series)

                  Majid is also behind this new series on Flutter security, applying the OWASP Mobile Top 10 to real-world Flutter apps.

                  So far, three parts are out:

                  If youโ€™re building apps that handle sensitive data, this is essential reading.

                  ๐Ÿ“ TypeSet: WhatsApp-Style Text Formatting in Flutter

                  TypeSet is a small but powerful package that lets you style inline text with a WhatsApp-style syntax:

                  TypeSet('Hello *bold* _italic_ ~strikethrough~ #underlined# `monospace` text');

                  Perfect for chat apps or UIs that need rich text formatting without complexity.

                  To learn more, check out the package on pub.dev or read the full article:

                  ๐Ÿ“ข Latest from Code with Andrea

                  No new articles from me this monthโ€”Iโ€™ve been focused on finishing my Flutter in Production course.

                  The good news? Itโ€™s now complete with over 200 lessons! ๐ŸŽ‰

                  To celebrate this, Iโ€™m running a Spring Sale next week. If youโ€™ve been thinking about enrolling, here are all the details about the upcoming sale:

                  Until Next Time

                  I recently stumbled across a new cool site called Is Flutter dead?โ€”glad to see it still says no! ๐Ÿ˜…

                  As the Flutter ecosystem continues to grow, Iโ€™m already brainstorming new content and ideas for future courses!

                  So stay tuned, and as always, happy coding!

                  ]]>
                  https://codewithandrea.com/tips/debug-repaint-rainbow-enabled/How to use debugRepaintRainbowEnableddebugRepaintRainbowEnabled helps you discover widgets/areas that unexpectedly repaint in your app. Here's how to use it.https://codewithandrea.com/tips/debug-repaint-rainbow-enabled/Thu, 24 Apr 2025 02:00:00 +0200Did you know?

                  debugRepaintRainbowEnabled helps you discover widgets/areas that unexpectedly repaint in your app.

                  Here's how to set it ๐Ÿ‘‡

                  How to use debugRepaintRainbowEnabled

                  If you discover widgets that repaint when they shouldn't, wrap them with RepaintBoundary.

                  This can improve rendering performance, but only use it when necessary (RepaintBoundary has some extra CPU/memory cost).

                  Learn more here:

                  Happy coding!

                  ]]>
                  https://codewithandrea.com/tips/text-form-field-numeric-inputs/TextFormField Setup for Numeric InputsWhen working with forms in Flutter, numeric inputs need special attention. To improve the user experience, set the appropriate keyboardType and inputFormatters.https://codewithandrea.com/tips/text-form-field-numeric-inputs/Tue, 15 Apr 2025 02:00:00 +0200Did you know?

                  When working with forms in Flutter, numeric inputs need special attention.

                  To improve the user experience:

                  1. Add keyboardType: TextInputType.number
                  2. Add FilteringTextInputFormatter.digitsOnly as an inputFormatter

                  Your users will thank you!

                  TextFormField Setup for Numeric Inputs

                  Happy coding!

                  ]]>
                  https://codewithandrea.com/tips/show-flutter-web-url-text-span/Showing URLs on Flutter web with TextSpanThe TextSpan class lets you set a custom mouse cursor style, along with a tap gesture recognizer for opening your URL links on Flutter web.https://codewithandrea.com/tips/show-flutter-web-url-text-span/Wed, 9 Apr 2025 02:00:00 +0200Did you know?

                  You can show native-looking URL links on Flutter web using TextSpan:

                  • Create a Text.rich or SelectableText.rich widget
                  • Make it look like a web link with a custom text style
                  • Add a gesture recognizer to open the web link
                  • Customize the mouse cursor style
                  Showing URLs on Flutter web with TextSpan

                  As an alternative, wrap the whole thing with a Link widget (from the url_launcher package):

                  Alternative with Link widget (url_launcher)

                  Here's a complete URLTextWidget that implements the whole thing, in 40 lines of code:

                  Happy coding!

                  ]]>
                  https://codewithandrea.com/tips/ab-testing-flutter/A/B Testing in FlutterA/B tests help you make data-driven decisions and increase conversions in your app. Here's how they work.https://codewithandrea.com/tips/ab-testing-flutter/Tue, 8 Apr 2025 02:00:00 +0200Did you know?

                  A/B tests help you make data-driven decisions and increase conversions in your app.

                  To run an A/B test: 1. Choose a metric to improve 2. Formulate a hypothesis 3. Split the audience into cohorts 4. Run an experiment, analyze the results, and roll out the winner ๐Ÿฅ‡

                  A/B Testing in Flutter

                  Tools like Firebase Remote Config and PostHog make it easy to run A/B tests in your apps.

                  For example, here's a report from an A/B test I ran on my Flutter Tips app:

                  A/B Testing with Firebase Remote Config

                  But how do you implement this in practice?

                  My latest course includes an entire module about Feature Toggles and A/B Tests.

                  Learn more here:

                  ]]>
                  https://codewithandrea.com/tips/release-toggles-dart-define/Release Toggles with Dart DefinesStatic release toggles let you release unfinished code without activating it in production. Here's how to use --dart-define to manage them.https://codewithandrea.com/tips/release-toggles-dart-define/Wed, 2 Apr 2025 02:00:00 +0200Did you know?

                  Static release toggles let you release unfinished code without activating it in production.

                  This is common with large projects that practice trunk-based deployment and continuous delivery.

                  For more flexibility, you can implement this with --dart-define. ๐Ÿ‘‡

                  Release Toggles with Dart Defines

                  Static release toggles are easy to use, but some use cases require more flexibility:

                  • Experiment by showing different variants to separate user cohorts (A/B and multivariate testing)
                  • Gradually roll out a feature to users, rather than releasing it to everyone at once

                  Complex use cases require dynamic toggles and some dedicated infrastructure to manage them, and my latest course covers this topic in much more detail.

                  Learn more here. ๐Ÿ‘‡

                  ]]>
                  https://codewithandrea.com/tips/int-bool-from-environment/int.fromEnvironment and bool.fromEnvironmentWhen reading variables from .env files, you can use int.fromEnvironment and bool.fromEnvironment to read integers and booleans.https://codewithandrea.com/tips/int-bool-from-environment/Fri, 28 Mar 2025 01:00:00 +0100Did you know?

                  In addition to String.fromEnvironment, Dart also supports:

                  Very handy when reading environment variables from your .env files:

                  int.fromEnvironment and bool.fromEnvironment

                  To learn more about environment variables and best practices for storing API keys, read:

                  Happy coding!

                  ]]>
                  https://codewithandrea.com/newsletter/march-2025/March 2025: Hot-reload on Flutter web, Practical Architecture, Unified Riverpod SyntaxAlso included: Lesser-known Dart and Flutter functionalities, latest from Code with Andrea, and some thoughts on vibe coding with AI.https://codewithandrea.com/newsletter/march-2025/Tue, 25 Mar 2025 01:00:00 +0100After theย Flutter 3.29ย release (donโ€™t forget to update to the latestย hotfix), things have been relatively quiet this month.

                  Still, there are some exciting updates worth sharing:

                  • Hot reload finally comes to Flutter web (3.31 beta)
                  • Practical Flutter Architecture
                  • Lesser-known Dart & Flutter featuresย you should be using
                  • Preview of the unified provider syntax for the next Riverpod release
                  • Latest from Code with Andrea
                  • Some thoughts on the "vibe coding" trend with AI

                  Letโ€™s dive in! ๐Ÿš€

                  Flutter News and Articles

                  โšก๏ธย Hot Reload on Flutter Web (Beta)

                  Hot reload is one of Flutterโ€™sย biggest strengths, making development incredibly fast. But until now,ย Flutter Web didnโ€™t support itโ€”despite being theย #2 most upvoted feature request.

                  Thatโ€™s finally changing withย Flutter 3.31 beta! ๐ŸŽ‰ You can nowย try hot reload on Flutter Webย before it hits stable.

                  To enable it, follow these steps:

                  For all the details, check out the original thread on Reddit:

                  ๐Ÿ“ Practical Flutter Architecture

                  App architecture remains one of theย hottest topics in Flutter developmentโ€”a good structure can make or break your app in the long run.

                  Existing resources include:

                  Most recently, Thomas Burkhart also decided to share his own take on the topic.

                  His article reiterates the benefits of layered architecture, offers a summary of approaches such as MVVM, MVC, MVU (none of which are particularly suited for Flutter), and introduces a pragmatic approach that has worked well for him.

                  Whether you're a seasoned Flutter developer or just starting out, this is well worth a read:

                  Bonus:ย He also forked theย Flutter compass appย and refactored it using his approach:

                  Like Thomas, I also wasnโ€™t fully satisfied with the official compass app and I've been meaning to refactor it for some time. Hopefully, I'll be able to revisit this soon!

                  ๐Ÿ“ย 10 Lesser-Known Dart & Flutter Features You Should Use

                  As you advance in Flutter development, you'll want to make the most of the language and framework features.

                  In this article,ย Majid Hajianย sharesย 10 advanced APIsย that help various aspects of your code, including:

                  • Better async handling
                  • Improved error handling and debugging
                  • Attaching metadata to objects that canโ€™t be subclassed

                  Each technique is backed byย real-world use cases, making it easy to apply in your projects:

                  ๐Ÿ’ก New Unified Provider Syntax for Riverpod

                  While Riverpod is a very popular package, not everyone is happy with the current API:

                  • โŒย Too many provider types
                  • โŒย Code generation is slow for large apps

                  Sinceย Dart macros were canceledย (details here),ย Rรฉmi Rousseletย went back to the drawing board and proposed aย new, simplified provider syntax that eliminates code-gen.

                  My first impression?ย This is a very positive step forward.

                  If you use Riverpod,ย you can find the proposal here and share your feedback:

                  While the proposal is still in the early stages, it is very detailed and gives us a glimpse of what Riverpod 3.0 might look like.

                  Latest from Code with Andrea

                  Since last month, I have published:

                  ๐Ÿ“ย Why You Should Refactor Before Adding New Features

                  Refactoring is aboutย making future changes easier. When done right:

                  • New features integrate smoothlyย instead of feeling bolted on.
                  • Tech debt is reduced, making maintenance less painful.
                  • Code reviews are faster, since changes are smaller and more focused.

                  In this article, I share a real-world example of how refactoring has helped me ship a new feature while keeping my codebase flexible and maintainable:

                  Some Thoughts on Vibe Coding with AI

                  The termย vibe codingย was recentlyย coined by Andrej Karpathy (a leading AI researcher and engineer), and it now keeps popping up in my feeds.

                  The idea?

                  AI-driven developmentย where LLM-based IDEs (likeย Cursor) do all the work for you, to an extent where:

                  "You fully give in to the vibes, embrace exponentials, and forget that the code even exists."ย โ€” Andrej Karpathy

                  At first, I assumed that in the hands of inexperienced devs,ย this would lead to catastrophic security flaws. But I also see the appealโ€”especially forย rapid prototyping and non-production apps.

                  If youโ€™re curious, here are some thought-provoking takes on vibe coding:

                  Have you triedย vibe coding with Flutter? Iโ€™d love to hear your thoughts!

                  Until Next Time

                  With myย Flutter in Productionย course nearing completion, Iโ€™m excited to experiment withย new ideas and projects.

                  Who knowsโ€”maybe Iโ€™ll even tryย vibe coding myselfย and see how far I can take it before everything falls apart. ๐Ÿ˜…

                  Stay tunedโ€”and as always, happy coding! ๐ŸŽ‰

                  ]]>
                  https://codewithandrea.com/tips/hot-reload-flutter-web/Hot Reload on Flutter web (3.32)To enable this, switch to Flutter 3.32 and run your app with --web-experimental-hot-reloadhttps://codewithandrea.com/tips/hot-reload-flutter-web/Thu, 20 Mar 2025 01:00:00 +0100Did you know?

                  Hot-reload is available on Flutter web! โšก๏ธ

                  To enable it:

                  • Upgrade to Flutter 3.32 stable
                  • Run your app with --web-experimental-hot-reload

                  For the best experience in VSCode:

                  • Enable hot reload on save
                  • Add a web launch configuration ๐Ÿ‘‡
                  Hot Reload on Flutter web

                  Check this post for all the details:

                  Happy coding!

                  ]]>
                  https://codewithandrea.com/tips/uploading-screenshots-fastlane/Uploading Screenshots with FastlaneInstead of taking screenshots manually for each device & language, you can automate it with Maestro! Here's how.https://codewithandrea.com/tips/uploading-screenshots-fastlane/Wed, 19 Mar 2025 01:00:00 +0100Did you know?

                  Rather than uploading your app store screenshots manually, you can automate the process with Fastlane!

                  Here's how ๐Ÿ‘‡

                  Uploading Screenshots with Fastlane

                  Before uploading to the App Store, fastlane will show a preview so you can check if everything looks good.

                  Fastlane App Store preview showing the metadata and screenshots

                  You can skip this by passing force: true to the upload_to_app_store lane (useful for non-interactive CI workflows).


                  Video Preview

                  Here's a preview showing how to export screenshots from Figma and upload them with Fastlane, in less than one minute:


                  Flutter in Production

                  My latest course includes a whole module about screenshot automation, covering:

                  • โœ… Tips for better screenshots
                  • โœ… Capturing screenshots with Maestro
                  • โœ… Editing them with Figma
                  • โœ… Uploading them with Fastlane (locally & on CI)

                  Learn more here:

                  ]]>
                  https://codewithandrea.com/tips/semantics-identifiers-ui-testing/Using Semantics Identifiers for UI TestingInstead of taking screenshots manually for each device & language, you can automate it with Maestro! Here's how.https://codewithandrea.com/tips/semantics-identifiers-ui-testing/Tue, 18 Mar 2025 01:00:00 +0100Did you know?

                  UI testing frameworks like Maestro and Appium rely on Flutterโ€™s semantics tree to interact with your app UI.

                  If your app is localized, you can make your UI tests more robust by adding a semantic identifier to your widgets:

                  Here's how. ๐Ÿ‘‡

                  Using Semantics Identifiers for UI Testing

                  If you want to dig deeper, here's an excellent article explaining how Flutter's semantics tree maps with the native accessibility tree:

                  To learn more about UI testing, screenshot generation, and more, check the latest module in my Flutter in Production course:

                  ]]>
                  https://codewithandrea.com/tips/automated-screenshot-generation-maestro/Automated Screenshot Generation with MaestroInstead of taking screenshots manually for each device & language, you can automate it with Maestro! Here's how.https://codewithandrea.com/tips/automated-screenshot-generation-maestro/Mon, 17 Mar 2025 01:00:00 +0100Did you know?

                  Instead of taking screenshots manually for each device & language, you can automate it with Maestro!

                  Here's a video preview:

                  How to use this in practice?

                  Simply write a YAML file to define how Maestro should interact with your app UI and call takeScreenshot as needed.

                  Super easyโ€”no test harness required! ๐Ÿš€

                  Automated Screenshot Generation with Maestro

                  Getting Started

                  To get started, check the official docs:

                  Flutter in Production course

                  My latest course includes a whole module about screenshot automation, covering:

                  • โœ… Tips for better screenshots
                  • โœ… Capturing screenshots with Maestro
                  • โœ… Editing them with Figma
                  • โœ… Uploading to the stores with Fastlane (locally & on CI)

                  Learn more here:

                  ]]>
                  https://codewithandrea.com/tips/android-demo-mode-for-screenshots/Android Demo Mode for Better ScreenshotsHere's a handy command to override the Android status bar UI before taking screenshots for your Play Store listings.https://codewithandrea.com/tips/android-demo-mode-for-screenshots/Fri, 14 Mar 2025 01:00:00 +0100Did you know?

                  If you want to take better screenshots for your Play Store listings, you can enable Demo Mode in the Android settings.

                  Here's how to enable this. ๐Ÿ‘‡

                  Android Demo Mode for Better Screenshots

                  Happy coding!

                  ]]>
                  https://codewithandrea.com/tips/ios-status-bar-for-screenshots/iOS Status Bar Tip for Better ScreenshotsHere's a handy command to override the iOS status bar UI before taking screenshots.https://codewithandrea.com/tips/ios-status-bar-for-screenshots/Thu, 13 Mar 2025 01:00:00 +0100Did you know?

                  You can use this command to clean up the iOS status bar before taking screenshots:

                  xcrun simctl status_bar booted override --time 09:41 --batteryState charged --batteryLevel 100 --cellularBars 4
                  iOS Status Bar tip for Better Screenshots

                  Then, you can take screenshots manually (CMD+S on macOS), or automate the process with Maestro.

                  Once you're done, reset the status bar like this:

                  xcrun simctl status_bar booted clear

                  Happy coding!

                  ]]>
                  https://codewithandrea.com/tips/gradle-kotlin-dsl/Gradle Kotlin DSL (Flutter 3.29)Apps created with Flutter 3.29 use the new Gradle Kotlin DSL on Android. Some CLI tools don't support the new syntax, yet.https://codewithandrea.com/tips/gradle-kotlin-dsl/Mon, 10 Mar 2025 01:00:00 +0100Did you know?

                  New apps created with Flutter 3.29 use the new Gradle Kotlin DSL on Android.

                  This may affect you if you rely on CLI tools that don't support the new syntax.

                  Here's what you need to know. ๐Ÿ‘‡

                  Gradle Kotlin DSL (Flutter 3.29)

                  Happy coding!

                  ]]>
                  https://codewithandrea.com/tips/flutter-run-route/The flutter run --route argumentPub.dev has a new chart that lets you see package download counts by version (major, minor, patch). Here's where to find it.https://codewithandrea.com/tips/flutter-run-route/Tue, 4 Mar 2025 01:00:00 +0100Did you know?

                  You can run flutter run --route /path/to/route to start your Flutter app from a specific route.

                  This works with named routes (nav 1.0) and the router APIs (nav 2.0).

                  Super useful for debugging nested routes when you need to hot-restart.

                  The flutter run --route argument

                  Happy coding!

                  ]]>
                  https://codewithandrea.com/articles/why-refactor-before-new-features/Why You Should Refactor Before Adding New FeaturesA real-world case study about ephemeral vs application state, navigation, scroll controllers, and how refactoring can help you ship new features smoothly.https://codewithandrea.com/articles/why-refactor-before-new-features/Fri, 28 Feb 2025 02:00:00 +0100We've all been there: you're excited to build a new feature, but as soon as you dive in, you realize the existing code is a mess.

                  At this point, you have two choices:

                  1. Charge aheadโ€”make it work by building on top of the existing code.
                  2. Take a step backโ€”clean things up first, then implement the feature with confidence.

                  Option 1 might feel faster, but it usually leads to disaster:

                  • Your new code worksโ€ฆ kind of. But it doesn'tย feelย rightโ€”hello, tech debt.
                  • Youโ€™re unsure if everything plays nicely together, and subtle bugs creep in.
                  • Your PR balloons in scope, making reviews painful and frustrating.

                  Meanwhile, your boss (who is very easily impressed by those wonderful AI agents) is breathing down your neck and questioning your abilities. ๐Ÿ˜ญ๐Ÿ˜ฑ

                  The boss from Office Space (movie)

                  Before you quit tech and become a farmer (no shame in that), let me share a story about refactoringโ€”one that helped me ship a feature smoothly instead of wrestling with broken code.

                  As part of this, we'll explore a real-world example of:

                  • ephemeral vs application state in Flutter
                  • how to manage navigation and communication across screens
                  • how to blend reactive updates with imperative APIs (as needed by ScrollControllers)

                  A Tale About Refactoring

                  Recently, I decided to add wide-screen support to my Flutter tips app:

                  Flutter tips app preview
                  Flutter tips app preview

                  That is, given these two screens that were initially designed for mobile:

                  Flutter tips app on mobile, with two screens for content and tips navigation
                  Flutter tips app on mobile, with two screens for content and tips navigation

                  I wanted to show them side by side and enable seamless navigation on iPad:

                  Flutter tips app on iPad, with content and navigation side by side
                  Flutter tips app on iPad, with content and navigation side by side

                  Making this UI adaptive is quite easy. All you need is:

                  • A responsive layout that uses a split-view on wide screens, and regular push/pop navigation on mobile.
                  • Some conditional logic based on MediaQuery.sizeOf(context) or LayoutBuilder to choose between the split-view and single-pane layout depending on a layout breakpoint.

                  When given a task like this, it's tempting to just add the new feature as a single PR.

                  But, as we're about to find out, there's more than meets the eye.

                  Ephemeral State, Application State, and Navigation

                  To set the stage, let's revisit the mobile-only version of the app:

                  Flutter tips app on mobile, with two screens for content and tips navigation

                  Based on the screenshots above, can you guess how I was keeping track of the currently selected tip index?

                  Of all possible options, I decided to go with the good old setState approach.

                  That is, I had a _MarkdownTipsPageViewState class that I was using for:

                  • storing the _currentTipIndex as ephemeral state
                  • updating this state via the _updateTipIndex method
                  class _MarkdownTipsPageViewState extends ConsumerState<MarkdownTipsPageView> { // both initialized in initState late PageController _pageController; late int _currentTipIndex; void _updateTipIndex(int tipIndex) { setState(() { _currentTipIndex = tipIndex; // Used to store the tip index in shared preferences (but not too relevant here) ref.read(currentTipIndexStoreProvider.notifier).setTipIndex(tipIndex); }); } ... }

                  In the build method, I had an IconButton with a callback for pushing the tipsList route, and updating the state if a new tip index was returned:

                  IconButton( icon: const Icon(Icons.search), tooltip: 'Search tips', onPressed: () async { // Push the "tips list" route and wait for the result final newIndex = await Navigator.of(context).pushNamed<int>( AppRoutes.tipsList, arguments: _currentTipIndex, ); // If a new tip index is returned if (newIndex != null) { // Update the state and jump to the new page _updateTipIndex(newIndex); _pageController.jumpToPage(currentPage); } }, )

                  Accordingly, my TipsListViewScreen had an onSelected callback that was used to return the selected tip index via Navigator.pop:

                  TipsListViewScreen( flutterTips: tips, initialTipIndex: initialTipIndex, // Pop the index so the parent route can update the state onSelected: (index) => Navigator.of(context).pop(index), )

                  Here's a diagram showing the interaction between the two screens:

                  Interaction between the page view and list view screens
                  Interaction between the page view and list view screens

                  In summary:

                  • The _currentTipIndex variable is the main source of truth that determines the currently selected tip.
                  • It is stored in the _MarkdownTipsPageViewState class, but it is also passed to the TipsListViewScreen via the initialTipIndex argument and updated via the onSelected callback.

                  This works just fine on mobile, but let's now consider the wide-screen version of the app:

                  Flutter tips app on iPad, with content and navigation side by side

                  With the setup described above, the app would behave like this:

                  • When swiping between pages, the _currentTipIndex would be updated in the page view screen, but this change would not be reflected in the list view screen.
                  • Calling Navigator.pop from the list view would break the navigation completely (in split view mode, we're already at the root of the navigation stack).

                  Such inferior and broken UX is absolutely unacceptable. So how did I fix it?

                  From Ephemeral to Application State

                  The core issue was that my _currentTipIndex lived as ephemeral state inside a widget, making cross-screen updates clunky.

                  Here's a better approach:

                  • Promote the current tip index to application state
                  • Use a ValueNotifier or Riverpod's own Notifier class to store it

                  This way, both pages can mutate the notifier and update the UI when needed. Here's an interactive demo of the solution in action:

                  Page View - List View syncing demo (looks best on desktop)

                  It works like this:

                  • When you swipe between pages, the correct item is highlighted and centered in the list.
                  • When you select an item from the list, the page view immediately jumps to the corresponding tip.

                  Much better!

                  You can make this work by using ValueNotifier or Riverpod's own Notifier class. I won't cover all the details here, but you can take this challenge as an exercise if you want: Page View / List View syncing challenge

                  What About Navigation?

                  On mobile, we can use the search icon to navigate to the tips list screen:

                  Flutter tips app on mobile, with two screens for content and tips navigation

                  But on iPad, this icon is hidden since both pages are visible side by side:

                  Flutter tips app on iPad, with content and navigation side by side

                  To achieve this, I ended up using a small LayoutBreakpoints helper class:

                  if (!LayoutBreakpoints.isSplitView(screenSize)) IconButton( icon: const Icon(Icons.search), tooltip: 'Search tips', // Push the tips list screen (application state is updated elsewhere) onPressed: () => Navigator.of(context).pushNamed(AppRoutes.tipsList), ),

                  Note how the onPressed callback is no longer used to update the tip index.

                  In fact, we can now do that directly on the onSelected callback:

                  TipsListViewScreen( flutterTips: tips, currentTipIndex: currentTipIndex, onSelected: (index) { // Update the tip index ref .read(tipUpdateEventNotifierProvider.notifier) .updateTipSelectedFromList(index); // On mobile, go back to the tips page view if (!LayoutBreakpoints.isSplitView(screenSize)) { Navigator.of(context).pop(); } }, )

                  The fundamental idea is that neither screen owns the application state, but both screens can mutate it and rebuild / update their UI when it changes:

                  Interaction between the page view, list view, and current tip index notifier
                  Interaction between the page view, list view, and current tip index notifier

                  Considerations About ScrollControllers

                  Promoting the tip index to application state is a good step forward, but it's not enough:

                  • On the page view screen, the currently selected page is controlled by a PageController, so an explicit call to jumpToPage is needed to ensure it stays in sync with the tip index.
                  • On the list view screen, the currect offset is controlled by a AutoScrollController (from the scroll_to_index package). This controller needs to be updated explicitly when the tip index changes.

                  Implementing state updates and ensuring these were correctly reflected in the UI required some fine-tuning. But eventually, I managed to get the app to work as expected.

                  Hint: you can register a ValueNotifier listener to update the PageController or AutoScrollController when the tip index changes, as explained here: Side Effects with ValueNotifier.

                  Time for some advice about refactoring code. ๐Ÿ‘‡

                  Refactor First, Then Add Features

                  My initial goal was to add responsive support to my Flutter tips app:

                  Flutter tips app on iPad, with content and navigation side by side

                  Rather than tackling everything at once, I took a step back and did a first round of refactoring:

                  • moved from ephemeral to application state (via notifier)
                  • used the notifier to trigger state updates rather than relying on push/pop navigation
                  • prepared the ground for the new feature

                  After verifying that the updates worked as expected, I landed this change.

                  Then, in a second PR, I implemented the actual feature:

                  • wide-screen support via split view
                  • a proper event system for syncing the scroll position between screens
                  • final tweaks

                  By refactoring first, I was able to revisit the initial assumptions and choose a more suitable state management approach. But if I had tried to do everything at once, I would have ended up with workarounds and ugly code.

                  The Case for Refactoring

                  Refactoring is aboutย making future changes easier. When done right:

                  • New features integrate smoothlyย instead of feeling bolted on.
                  • Tech debt is reduced, making maintenance less painful.
                  • Code reviews are faster, since changes are smaller and more focused.

                  Some even argue that refactoring should be part of your definition of done for every feature.

                  But let's face it: as software evolves, requirements change and your initial assumptions may no longer hold. When that happens, refactoring is a good way to improve the existing foundations and make way for new code.

                  Of course, refactoring can be done with more confidence if you have automated tests that help you catch regressions. This is especially important when you work on a shared codebase with other developers.

                  But for today, here's the key takeaway:

                  โ†’ If you're fighting against existing code when making changes, stop and refactor first.

                  Youโ€™ll save time, avoid frustration, and ship with confidence.

                  Conclusion

                  Adding new features to a messy codebase is like building a house on a shaky foundationโ€”it might stand for a while, but cracks will show. Refactoring first ensures that your code remains flexible, maintainable, and ready for future improvements.

                  So next time you're about to add a feature, ask yourself:ย Is my code ready for this?ย If not, take a step back, clean it up, and then move forward. Your future self (and your team) will thank you.

                  P.S. You can try the updated Flutter Tips app with responsive layouts here:

                  Happy coding!

                  ]]>
                  https://codewithandrea.com/tips/download-counts-by-version/Downloads Count by Version on Pub.devPub.dev has a new chart that lets you see package download counts by version (major, minor, patch). Here's where to find it.https://codewithandrea.com/tips/download-counts-by-version/Fri, 28 Feb 2025 01:00:00 +0100Did you know?

                  An improved weekly downloads chart was recently added on pub.dev.

                  This lets you see package download counts by version (major, minor, patch).

                  To view it, click on the Scores tab and scroll to the bottom (took me a while to find it).

                  Downloads Count by Version on Pub.dev

                  Happy coding!

                  ]]>
                  https://codewithandrea.com/tips/side-effects-value-notifier/Side Effects with ValueNotifierBy registering a ValueNotifier listener, you can perform side effects such as updating controllers, showing dialogs, and performing navigation.https://codewithandrea.com/tips/side-effects-value-notifier/Thu, 27 Feb 2025 01:00:00 +0100Did you know?

                  By registering a ValueNotifier listener, you can perform various side effects such as:

                  • Updating a ScrollController, PageController, AnimationController, etc.
                  • Showing a dialog
                  • Navigating to another route

                  Note: This works with ChangeNotifier, too. ๐Ÿ’ก

                  Side Effects with ValueNotifier

                  Important: side effects should never be performed from the build method.

                  To learn more and avoid common mistakes, check this article:

                  Happy coding!

                  ]]>
                  https://codewithandrea.com/newsletter/february-2025/February 2025: Flutter 3.29, Dart 3.7, Shorebird & Jaspr Updates, New Formatting Style, TextFormField MistakesAlso included: Flutter rendering changes, discontinued official packages, Dart macros update, new Flutter UI challenges from Code with Andrea.https://codewithandrea.com/newsletter/february-2025/Mon, 24 Feb 2025 01:00:00 +0100February is a Flutter release month, and there's a lot to cover! In this edition, we'll explore:

                  • The latest Flutter 3.29 and Dart 3.7 releases
                  • Flutter rendering changes (moving to the platform's main thread)
                  • The new Dart formatting style and how it affects your projects
                  • Why Dart macros are no longer comingโ€”and what else is in focus
                  • Official Flutter packages being discontinued
                  • Key updates from Shorebird & Jaspr
                  • Common TextFormField mistakes in Flutter apps
                  • The latest from Code with Andrea

                  Let's dive in!

                  Whatโ€™s new in Flutter 3.29

                  Every Flutter release brings many improvements, and 3.29 is no exception. Expect refinements to Cupertino and Material widgets, along with DevTools enhancements and Impeller engine optimizations.

                  Read the official announcement for the full details:

                  Here are some important highlights. ๐Ÿ‘‡

                  โš ๏ธ Flutter rendering moves to the platform's main thread

                  Previously, Flutter ran Dart code on a separate UI thread, which made it harder to call into platform code. Flutter now runs Dart code on the platform's main thread on Android and iOS. This enables more efficient platform interop through direct synchronous calls.

                  To learn more about this change, check out this detailed issue about merging the platform and UI thread, along with the most recent comments by Eric Seidel and Matej Knopp.

                  But does this change affect existing apps? An issue about slowdowns and jank during scrolling was already reported and fixed on the master channel. To avoid surprises, consider waiting for a hotfix before upgrading to Flutter 3.29 in production.

                  โš ๏ธ Breaking changes and deprecations

                  In an effort to focus on the core framework, the Flutter team is discontinuing support for several official packages on April 30th, and the community is encouraged to fork and maintain these.

                  Details are in this umbrella issue:

                  Announcing Dart 3.7

                  Here's what's new in Dart:

                  • New Formatting Style: If you opt in to Dart 3.7 in your pubspec.yaml file, a new formatting style will be applied to your code. Since this affects all developers, I've written a detailed article to help you navigate the changes.
                  • Wildcard variables: The _ symbol is now a proper wildcard, meaning you can use it multiple times when declaring unused parameters, rather than writing _, __, ___ to avoid name collisions.
                  • Deprecated JS legacy libraries:
                    • These libraries are now deprecated: dart:html, dart:indexed_db, dart:js, dart:js_util, dart:web_audio, dart:web_gl.
                    • Instead, use dart:js_interop and package:web going forward.
                  • New pub.dev features: Pub.dev has been updated with dark mode, a new weekly downloads chart (located under the "Scores" tab), and a new topics page.

                  For all the details, check out the official announcement:

                  Update on Macros

                  Back in January, the Dart team announced that work on the experimental macros feature has stopped due to several major technical hurdles.

                  While this is not the news many hoped for, it means the team can focus on shipping build_runner improvements and augmentations, bringing tangible benefits to all Flutter developers.

                  For more details, check out this announcement:

                  I also recommend reading Eric Seidel's take on the topic:

                  Wondering if Flutter is dead already? Here's the answer. ๐Ÿ˜‰

                  Flutter Videos and Articles

                  ๐Ÿ“น The runtime that makes Dart tick - Slava Egorov

                  Slava Egorov labels himself as the "dreamer" in the Dart team, and in this talk he dives into low-level performance issues and how to apply optimisations at the compiler level.

                  He also goes through some little-known features of the Dart runtime, including interoperability with other languages through the dart:ffi interface.

                  While this talk may not be immediately relevant for app developers, it offers fascinating insights into the Dart runtime:

                  ๐Ÿ“ Common mistakes with TextFormFields in Flutter

                  This article highlights common mistakes in Flutter form design that can hurt user experience and conversion rates. It covers best practices like setting textInputAction for smooth field navigation, using onFieldSubmitted for efficient form submission, choosing the right keyboardType, applying TextCapitalization, using TextInputFormatter for input validation, and enabling autofill with AutofillHints and AutofillGroup.

                  If your forms are not up to par, this article will help you fix them:

                  Flutter Ecosystem Updates

                  As Flutter grows, it's great to see tools in the wider ecosystem are getting better and more capable. This month, Shorebird and Jaspr have caught my attention.

                  ๐Ÿฆ Shorebird

                  If you want to bypass the app store review process and deploy app updates instantly, Shorebird is the right tool for you.

                  Most recently, the team has shipped two big features:

                  With the new integration, you can deploy your Shorebird-enabled Flutter app to Codemagic in 10 minutes:

                  While I only started using Shorebird recently, I'm already a big fan and I'll be adding a new Shorebird module to my [Flutter in Production] (courses/flutter-in-production/) course this week! ๐Ÿš€

                  ๐Ÿถ Jaspr

                  Have you heard of Jaspr yet? It's a Dart web framework that renders actual HTML and CSS, making it ideal for websites that need to be SEO-friendly and load fast!

                  As of today, Jaspr supports both static sites and server-side rendering. It can also integrate with your favorite state management solution and custom backend, and it even supports Tailwind CSS!

                  To learn more, visit the official website:

                  Personally, I prefer Astro for my web development needs, and I don't mind working directly with HTML and JS/TS (there, I said it! ๐Ÿ˜…). But if your heart is set on the Dart ecosystem, Jaspr might be the right choice for you.

                  Latest from Code with Andrea

                  Since the last edition, I've been busy:

                  ๐Ÿ“ New Formatting Style in Dart 3.7

                  This is worth repeating: there's a new formatting style in Dart 3.7. If you opt-in, the formatter will automatically decide whether to add or remove trailing commas (no more arguing about formatting during code reviews. ๐ŸŽฏ)

                  This change will affect a lot of existing code. If you want to migrate while keeping your sanity, I've got you covered:

                  Updated Flutter Tips App

                  This app is my little side project and I like to polish it from time to time. With the latest changes, I've added proper responsive support and the app now looks great on all devices:

                  Preview of the Flutter Tips app on Flutter web
                  Preview of the Flutter Tips app on Flutter web

                  The update is already available on the app stores, and the web version even supports left/right arrow navigation. ๐ŸŽฏ

                  New UI Challenges

                  Building UIs in Flutter is fun, but it can be challenging, too!

                  So I created this collection of UI challenges to help you practice:

                  Preview of the Flutter UI Challenges
                  Preview of the Flutter UI Challenges

                  Each challenge contains a web demo, a starter project, some goals, and the complete solution.

                  I've shared 8 challenges to date, including new ones about PageView/ListView synchronization, and building a simple quiz app. Try them for free here:

                  Until Next Time

                  After 10 months of work, I'm getting closer to finishing my Flutter in Production course, and I hope to wrap up the remaining modules over the next month or two.

                  Some new stuff is on the pipeline, too, and I can't wait to share more details soon!

                  So stay tunedโ€”and as always, happy coding! ๐ŸŽ‰

                  ]]>
                  https://codewithandrea.com/tips/new-formatting-style-dart-3-7/New Formatting Style in Dart 3.7Dart 3.7 introduces a new formatter that automatically adds or removes trailing commas, based on the max line length.https://codewithandrea.com/tips/new-formatting-style-dart-3-7/Thu, 13 Feb 2025 02:00:00 +0100Did you know?

                  Dart 3.7 introduces a new formatter that automatically adds or removes trailing commas, based on the max line length.

                  This means you no longer decide how to format your code! The tool now does it for you.

                  New Formatting Style in Dart 3.7

                  The new change affects all Flutter developers.

                  So I created this guide to help you handle it smoothly in your projects. ๐Ÿ‘‡

                  Happy coding!

                  ]]>
                  https://codewithandrea.com/articles/updated-formatter-dart-3-8/How to Configure the Updated Code Formatter in Dart 3.8Dart 3.8 introduced an updated formatter with various configuration options. Learn whatโ€™s new and how to prepare your codebase.https://codewithandrea.com/articles/updated-formatter-dart-3-8/Thu, 13 Feb 2025 01:00:00 +0100Flutter 3.29ย andย Dart 3.7ย dropped in February 2025, bringing a bunch of new features and improvements.

                  While some changes might not impact you immediately,ย one update will affect every Flutter developer:ย the new Dart formatter.

                  This change isย big and unless you explicitly opt-out, you'll getย a tonย of formatting changes in your codebase. This article guides you through the new changes and all the configuration options, so you can handle things smoothly.

                  Let's dive in!

                  Update: Dart 3.8 (part of Flutter 3.32) adds a new formatter option to preserve trailing commas. This is significant because it means you can upgrade your SDK but still opt-out if you don't like the new style. More on this below.

                  How Did The Old Formatter Work?

                  Before Dart 3.7, you had control over trailing commas. Consider this widget constructor:

                  Manual formatting with or without trailing commas (applied on save)
                  Manual formatting with or without trailing commas (applied on save)

                  Here's how the old formatter behaved:

                  • Addย a trailing comma โ†’ formatterย splitsย it into multiple lines.
                  • Removeย the trailing comma โ†’ formatterย inlinesย it (if it fits within the page width).

                  While consistent, this approach requiredย you to decide whether to add or remove trailing commas for all parameter lists and widgets in your codebase (and argue about it during code reviews ๐Ÿ˜ž).

                  To fix this, Dart 3.7 introduces aย new formatting style.

                  How Does The New Formatter Work?

                  Now, the formatterย automatically decidesย whether to add or remove trailing commas.

                  • If a parameter listย fits within the max page width, the formatter removes trailing commas and keeps it inline:
                  New formatter removes trailing commas when we don't exceed the max page width
                  New formatter removes trailing commas when we don't exceed the max page width
                  • If a parameter listย exceedsย the max page width, the formatterย addsย a trailing comma and splits it into multiple lines:
                    New formatter adds trailing commas when the code exceeds the page width
                    New formatter adds trailing commas when the code exceeds the page width

                    Summary: Old vs New Formatter

                    • Old formatter
                      • Add trailing comma โ†’ split into multiple lines
                      • Remove trailing comma โ†’ inline code
                    • New formatter
                      • Code fits on one line โ†’ remove trailing comma and inline code
                      • Code exceeds page width โ†’ add trailing comma and split into multiple lines

                    What Does This Mean for You?

                    If your pubspec.yaml still uses Dart 3.6 or below, nothing changes yet:

                    environment: sdk: ^3.6.0

                    But as soon as you explicitly upgrade to Dart 3.7 or above, the new formatter kicks in:

                    environment: sdk: ^3.7.0

                    And when that happens...ย expect a massive diff in your codebase.

                    โš ๏ธ Important: Formatting Applies on Save

                    As soon as you save a file, the new formatting style applies, leading toย large diffs:

                    Code diff for a single file after applying the new formatter
                    Code diff for a single file after applying the new formatter

                    To avoid chaos,ย apply the new formatter across your entire codebase in one go:

                    # Run from the root of your project dart format .

                    This ensures all formatting changes are applied consistently. I recommend to push this as a single PRย and merge it before making other changes:

                    # Push all changes at once git add . git commit -m "Apply the new formatter" git push

                    For reference, when I applied the new formatter to my Flutter Tips App, I ended up with a PR with 1200+ changes, which is about 25% of the total line count:

                    PR with 1200+ changes (25% of the total line count) on my Flutter Tips app
                    PR with 1200+ changes (25% of the total line count) on my Flutter Tips app

                    What About Page Width?

                    Let's recall that the new formatter decides how to format the code based on the maximum page width (a.k.a line length).

                    This is configurable in your analysis_options.yaml file:

                    formatter: page_width: 120

                    Changing this and runningย dart formatย again will reformat your entire codebase accordingly.

                    Bottom line:ย Pick a page width that works for your project and team, and stick with it. ๐Ÿ‘

                    What About Code-Generated Files?

                    When you run dart format ., it applies to all Dart files in your project, including generated files.

                    But there's a catch: build_runner doesnโ€™t respect your page_width setting. Most code generators format output with a fixed 80-character line width, which means generated files may not match your project's formatting.

                    What this means for you:

                    • If your page_width is 80 (default), everything will align without issues.
                    • If you use a custom page width, generated files will revert to 80-character line breaks whenever you run build_runner.

                    This isnโ€™t a major problem, since you only need to run the formatter once for the entire project (as suggested above).

                    Future Improvement: According to this comment, a future Dart formatter update will allow excluding specific files from formatting, which could help with this issue.

                    What About the Updated Formatter in Dart 3.8?

                    When the new formatter was first discussed in 2023, some raised concerns about inconsistent formatting because line breaks don't respect the semantic meaning of the code.

                    After more discussions, the Dart team agreed to add a new formatter option in Dart 3.8 to preserve trailing commas.

                    This means you can upgrade to Dart 3.8:

                    # pubspec.yaml environment: sdk: ^3.8.0

                    Then, you have to choose between two options:

                    1. Preserve trailing commas

                    Enable this option in your analysis_options.yaml file:

                    # analysis_options.yaml formatter: trailing_commas: preserve

                    Effectively, this disables the new formatter behaviour and gives you back control over trailing commas.

                    2. Don't preserve trailing commas (default formatting style)

                    As discussed above, the new formatting style will strip trailing commas on code that fits within the page width, so expect a large diff in your codebase.

                    To handle this smoothly, follow these steps:

                    • Set a customย page_widthย inย analysis_options.yaml (optional but recommended):
                    # analysis_options.yaml formatter: page_width: 120
                    • Apply the formatter to your entire codebase:
                    # Run from the root of your project dart format .
                    • Run build_runner again to update the generated files:
                    # Update code-generated files dart run build_runner watch -d
                    • Push the changesย as a single PR, then merge it before continuing development.
                    # Push all changes at once git add . git commit -m "Apply the new formatter" git push

                    While the new formatter is safe to use, Flutter 3.32 isย brand newโ€”bugs may still surface. If you're not in a rush, consider waiting for a hotfix release before upgrading.

                    Additional Resources

                    All the configuration options are documented here:

                    Here's the original proposal and discussion about the new formatter:

                    ]]>
                    https://codewithandrea.com/tips/wildcard-variables-dart-3-7/Wildcard Variables in Dart 3.7Since Dart 3.7, the _ character is a wildcard variable and you can use it more than once in your code, without causing name collisions.https://codewithandrea.com/tips/wildcard-variables-dart-3-7/Thu, 13 Feb 2025 01:00:00 +0100Did you know?

                    Since Dart 3.7, the _ character is a wildcard variable.

                    This means that:

                    • You can use it more than once in your code (e.g. inside parameter lists), without causing name collisions.
                    • You can't use it as an actual variable (it's only a placeholder).
                    Wildcard Variables in Dart 3.7

                    Happy coding!

                    ]]>
                    https://codewithandrea.com/tips/hotkeys-callback-shortcuts/Hotkeys with CallbackShortcutsThe CallbackShortcuts widget makes it easy to add keyboard shortcuts to your Flutter app. Here's how to use it.https://codewithandrea.com/tips/hotkeys-callback-shortcuts/Tue, 11 Feb 2025 01:00:00 +0100Did you know?

                    The CallbackShortcuts widget makes it easy to add keyboard shortcuts to your Flutter app.

                    To use it:

                    1. Add one or more key bindings using SingleActivator
                    2. Add a Focus widget to capture the desired events
                    3. Use callbacks to perform your desired actions
                    Hotkeys with CallbackShortcuts

                    Example Code

                    // * If another widget captures the focus (e.g. a text field), call // * hotkeysFocusNode.requestFocus() in the onEditingComplete callback. static final hotkeysFocusNode = FocusNode(); // in the build method: return CallbackShortcuts( bindings: { const SingleActivator(LogicalKeyboardKey.arrowLeft): () { onPrevious(); }, const SingleActivator(LogicalKeyboardKey.arrowRight): () { onNext(); }, }, child: Focus( focusNode: hotkeysFocusNode, autofocus: true, descendantsAreFocusable: false, descendantsAreTraversable: false, child: Row( children: [ IconButton( tooltip: 'Hotkey: โฌ…๏ธ', icon: const Icon(Icons.arrow_back), onPressed: onPrevious, ), IconButton( tooltip: 'Hotkey: โžก๏ธ', icon: const Icon(Icons.arrow_forward), onPressed: onNext, ), ], ), ), );

                    Note: the activators will only be triggered when there's a focused child widget.

                    The easiest way to do this is to wrap your child widget in a Focus widget with autofocus: true.

                    If other widgets also rely on active focus, you can explicitly request focus with a FocusNode.


                    For more details about CallbackShortcuts, check this video:


                    To dive deeper and learn what to do when things don't work as you expect, watch this three-part series. ๐Ÿ‘‡


                    If you want to see this technique in action, check my Flutter Tips app (web version):

                    This supports three different keyboard shortcuts:

                    • Left arrow: go to the previous tip
                    • Space: favorite/unfavorite a tip
                    • Right arrow: go to the next tip

                    Happy coding!

                    ]]>
                    https://codewithandrea.com/tips/responsive-split-view-flutter/Responsive Split View in FlutterHere's how to create a responsive split-view widget that works on mobile, desktop and web.https://codewithandrea.com/tips/responsive-split-view-flutter/Mon, 10 Feb 2025 01:00:00 +0100Did you know?

                    In Flutter, you can easily create a responsive split-view widget that works on mobile, desktop and web.

                    You can do this in 30 lines of code, without any 3rd party packages: ๐Ÿ‘‡

                    Responsive Split View in Flutter

                    Example Code

                    class SplitView extends StatelessWidget { const SplitView({super.key, required this.navigationBuilder, required this.contentBuilder, this.breakpoint = 600, this.navigationWidth = 300}); final WidgetBuilder navigationBuilder; final WidgetBuilder contentBuilder; final double breakpoint; final double navigationWidth; @override Widget build(BuildContext context) { final screenWidth = MediaQuery.sizeOf(context).width; if (screenWidth >= breakpoint) { // * wide screen: navigation on the left, content on the right return Row( children: [ SizedBox( width: navigationWidth, child: navigationBuilder(context), ), // if you want, add a divider here Expanded(child: contentBuilder(context)), ], ); } else { // * show content only (handle navigation with a drawer or similar) return contentBuilder(context); } } }

                    For a more complete tutorial, read this article (slightly outdated, but the main principles still apply):

                    Happy coding!

                    ]]>
                    https://codewithandrea.com/tips/github-self-hosted-runners/GitHub Self-Hosted RunnersIf you're using GitHub Actions for your CI/CD needs, you can setup a self-hosted runner to cut your build times and save money.https://codewithandrea.com/tips/github-self-hosted-runners/Thu, 6 Feb 2025 01:00:00 +0100Did you know?

                    If you're using GitHub Actions for your CI/CD needs, you can setup a self-hosted runner to cut your build times and save money. ๐ŸŽฏ

                    This is very easy to setup, and if you have an organization account, you can share one or more runners across all your repos. ๐Ÿ‘

                    GitHub Self-Hosted Runners

                    My latest course includes a whole module about GitHub Actions, showing how to make your pipelines faster, more reliable, and easier to manage.

                    A complete workflow for Android & iOS is also included as a reference.

                    To learn more, check the module intro:

                    Happy coding!

                    ]]>
                    https://codewithandrea.com/newsletter/january-2025/January 2025: Flutter vs React Native, Hard Truths About AI, Pub Workspaces, Less-Known WidgetsAlso included: GoRouter in maintenance mode, Dart Serialization Proposal, localizing an app into 55 languages, Cupertino and Material Updates, and more.https://codewithandrea.com/newsletter/january-2025/Thu, 23 Jan 2025 01:00:00 +0100Welcome to the first edition of my Flutter newsletter for 2025! ๐ŸŽ‰

                    This month, weโ€™re diving into these topics:

                    • Outlook for Flutter vs React Native in 2025
                    • Hard truths about AI
                    • GoRouter entering maintenance mode (and what it means for you)
                    • A new serialization protocol for Dart (proposal)
                    • Selected articles from the community
                    • All the latest from Code With Andrea

                    Letโ€™s dive in! ๐Ÿš€

                    Flutter News

                    Letโ€™s kick-off with some trends that will shape Flutter and the mobile app development landscape in 2025.

                    ๐Ÿ“ Cross-platform mobile development

                    Gergely Orosz recently published a deep dive into the most popular frameworks: React Native, Flutter, native-first, and web-based technologies, and how to pick the right approach.

                    Key takeaways:

                    • Flutter and React Native dominate the cross-platform space andย are both here to stay.
                    • Developer preference often depends on location.
                    • While Flutter powers more apps, React Native apps generate slightly more revenue.

                    Read-on for the full breakdown:

                    Clarification: During the Flutter In Production livestream, it was claimed that 28% of new iOS apps on the App Store use Flutter (source: Apptopia), but this figure was based on free apps only. The latest app intelligence stats from App Figures (which take into account paid apps, too) show that Flutter sits at 13% on the App Store and 19% on the Play Store.

                    ๐Ÿ“ย Hard Truths About AI

                    AI is revolutionizing how we build softwareโ€”and Flutter apps are no exception. I find myself using AI tools more and more, and thisย recent articleย about AI-assisted coding really resonated with me.

                    Highlights include:

                    • The Knowledge Paradox: Seniors use AI to speed up tasks they already know, while Juniors use AI to learn new thingsโ€”with very different results.
                    • Practical Patterns: things like "AI first draft", "constant conversation", and "trust but verify" are emerging as effective ways to work with AI.
                    • Software as a Craft: While AI makes it easier to build software quickly, creating polished, high-quality experiences is still crucial if you want to build products that stand out.

                    Check out the full post for a pragmatic take on how AI is reshaping engineering:

                    Curious about how other Flutter devs feel about AI? Here are some recent posts on Reddit.

                    โš ๏ธ GoRouter Enters Maintenance Mode

                    The Flutter team has been maintaining GoRouter for many years, but many long-standing P1 and P2 issues have still not been addressed.

                    Most recently, this notice was added at the bottom of the package README:

                    This package has entered a maintenance phase. The Flutter team's primary focus will be on addressing bug fixes and ensuring stability. While active feature development is not currently planned, we welcome and encourage community contributions to expand the package's functionality.

                    If this means that the team can focus more on core parts of the Flutter framework, I welcome this. But itโ€™s clear that developers are frustrated with all the GoRouter bugs.

                    Hereโ€™s my take:

                    • If your app already uses GoRouter and you can work around its issues, keep using it.
                    • If youโ€™re starting a new project and the navigator 1.0 APIs are not enough for you, consider moving to alternatives like auto_route (fully-featured) or navigation_utils (lightweight).

                    ๐Ÿงช RFC: New Serialization Protocol for Dart

                    Serialization is a critical aspect of application development, yet the Dart ecosystem currently lacks a unified, efficient approach to tackle it. Existing solutions like json_serializable focus primarily on JSON and rely on intermediate Map objects, which limit performance and flexibility.

                    This proposal aims to introduce a modular, universally usable, and performant serialization protocol. It emphasizes format-agnostic functionality, allowing seamless support for JSON, CSV, YAML, MessagePack, and more, while also enabling developers to avoid redundant code when switching between data formats.

                    Even if you are happily using json_serializable or jsonEncode/jsonDecode today, I recommend reading the proposal and understanding the issues it aims to solve:

                    Flutter Articles from the Community

                    Here are my top picks from the Flutterverse this month.

                    ๐Ÿ“ How to localize the app into 55 languages and not go crazy?

                    If youโ€™ve ever wondered what it takes to build a language-learning app, this article is for you.

                    From internationalization challenges like RTL support, to handling remote localizations, to choosing which flag to show for multi-country languages, itโ€™s all covered here. ๐Ÿ‘‡

                    ๐Ÿ“ 10 Flutter Widgets You Probably Havenโ€™t Heard Of (But Should Be Using!)

                    In this article, Majid Hajian uncovers hidden gems like AnimatedModalBarrier, RepaintBoundary, and SemanticsDebugger, along with some of their implementation details.

                    ๐Ÿ“ Exploring Cupertino and Material Updates in Flutter 3.27.0

                    Flutter 3.27.0 shipped with a range of updates to the Cupertino and Material widget catalogs.

                    This article explores the key updates with code samples and illustrations, so you can more easily use the new APIs in your apps:

                    ๐Ÿ“ Modern Monorepo Management with Pub Workspaces and Melos in Dart

                    Monorepos are great when you want to store multiple apps and/or packages in a single repository. And thanks to the latest pub workspace feature in Dart 3.6, itโ€™s easier than ever to manage them.

                    Key benefits include:

                    • Centralized Dependency Management: All projects in the workspace share a single version of each dependency.
                    • Unified Upgrades: Running dart pub upgrade applies updates across all packages in the workspace.
                    • Improved Dart Analyzer Performance: Previous issues with monorepos causing degraded analyzer performance are now gone.

                    Read on to learn how to setup a pub workspace and take things further with Melos:

                    ๐Ÿ“ All I Know about State Management

                    If you ever wondered what happens when you call setState in Flutter, this article is for you. Inside, you'll learn:

                    • How Flutter internally marks certain elements as "dirty" and ensure they're rebuild on the next frame.
                    • How InheritedWidget and ValueListenableBuilder work.
                    • How 3rd party solutions like flutter_hooks, signals, and flutter_riverpod build upon core Flutter APIs like markNeedsBuild() under the hood.

                    Read on for all the details:

                    Latest from Code with Andrea

                    Since the last edition, I added a new module about CI/CD with Codemagic to my Flutter in Production course, published my 2024 retro, and shared a new article. Hereโ€™s a recap.

                    ๐Ÿ“ My 2024 in Review: Ups and Downs

                    Every year, I take a step back to reflect on my journey as a content creator.

                    This 2024 retrospective dives into my highlights and achievements from the past year, how AI, SEO, and social media are reshaping my strategy, the launch of latest course, and more:

                    ๐Ÿ“ How to Release Your Flutter App on the Google Play Store

                    If you're new to the Google Play Store, there's a lot to consider, from signing up for a developer account (and understanding the strict testing policies for new accounts), to preparing and submitting your app for review.

                    This guide covers all the essential steps, so you can publish your app with more confidence:

                    Site Update: As my collection of articles continues to grow, I decided to re-arrange them by topic for easier discovery. You can explore them at this link.

                    Until Next Time

                    This year, Iโ€™ll continue to publish new Flutter tips, articles, and courses, and I hope they'll help you succeed as a software engineer.

                    Stay tunedโ€”and as always, happy coding! ๐ŸŽ‰

                    ]]>
                    https://codewithandrea.com/tips/debug-fill-properties/The debugFillProperties MethodIf you add a debugFillProperties() method to your custom widget classes, all your custom widget properties will show in the DevTools.https://codewithandrea.com/tips/debug-fill-properties/Thu, 23 Jan 2025 01:00:00 +0100Did you know?

                    If you add a debugFillProperties() method to your widget classes, all your custom properties will show in the DevTools.

                    This information is useful for debugging purposes, and is stripped from release builds (no performance impact).

                    The debugFillProperties Method

                    To learn more about all the supported property types and variants, read the official docs:

                    Happy coding!

                    ]]>
                    https://codewithandrea.com/tips/upload-source-maps-sentry/Uploading the Source Maps to SentryIf you enable code obfuscation in your Flutter release builds, the stack traces will be unreadable in the Sentry crash reports. Here's how to fix it.https://codewithandrea.com/tips/upload-source-maps-sentry/Mon, 20 Jan 2025 01:00:00 +0100Did you know?

                    If you enable code obfuscation in your Flutter release builds, the stack traces will be unreadable in the Sentry crash reports.

                    To fix this:

                    • Generate the source maps with --split-debug-info
                    • Use the sentry_dart_plugin package to upload them
                    • Automate the process with CI/CD (optional but highly recommended)
                    Uploading the Source Maps to Sentry

                    Learn more

                    To learn more about code obfuscation and how to upload the source maps to Sentry, check these links:

                    For a more complete guide showing how everything fits together (error monitoring, app releases, and CI/CD automation), check my latest course:

                    ]]>
                    https://codewithandrea.com/tips/ssh-access-codemagic/SSH Access on Codemagic BuildsIf your Codemagic builds are failing, you can enable SSH access and login to the build runner to diagnose the issue.https://codewithandrea.com/tips/ssh-access-codemagic/Thu, 16 Jan 2025 01:00:00 +0100Did you know?

                    If your Codemagic builds are failing and you can't figure out why, you can enable SSH access. ๐Ÿ”‘

                    Use this to login to the build runner, where you can inspect the environment variables and project files, and see if something is missing. ๐Ÿ’ก

                    SSH Access on Codemagic Builds

                    My Flutter in Production course contains a whole module about CI/CD automation with Codemagic.

                    If you want to check it out, here's the intro:

                    Happy coding!

                    ]]>
                    https://codewithandrea.com/articles/how-to-release-flutter-google-play-store/How to Release Your Flutter App on the Google Play StoreA reference guide for releasing your Flutter app on the Play Store, including app content, store listing, Android project settings, and code signing.https://codewithandrea.com/articles/how-to-release-flutter-google-play-store/Mon, 13 Jan 2025 01:00:00 +0100Your Flutter app is ready, and youโ€™ve completed all the importantย pre-release steps. Now itโ€™s time to publish it on the Google Play Store. But where do you start?

                    The Flutter documentationย already provides a technical guide for building and releasing an Android app. But if you're new to the Google Play Store, there's a lot more to consider, from signing up for a developer account (and understanding the strict testing policies for new accounts), to preparing and submitting your app for review.

                    If youโ€™re new to this, donโ€™t worry. This article will provide a high-level roadmap for releasing your Flutter app on the Google Play Store. Even if youโ€™re familiar with this process, you may discover a trick or two along the way. ๐Ÿ‘

                    This article is about releasing your app on the Google Play Store. A companion article about How to Release Your Flutter App on the iOS App Store is also available.

                    What Youโ€™ll Learn:

                    • How toย sign up for a Google Play Developer Account.
                    • How toย create a new app on the Google Play Console.
                    • How toย prepare your app for review by filling all the app content, data safety, and store listing info.
                    • How to review the Android project settings, including code signing.
                    • How toย build, upload, and submitย your app for review.

                    Want a more detailed, hands-on guide? Myย Flutter in Productionย course includes a complete module with 17 lessons that cover each step in depthโ€”from account setup to submission and beyond.

                    Letโ€™s dive in! ๐Ÿš€

                    Should You Sell Apps on the Google Play Store?

                    It's well known that Apple users are far more willing to pay for apps compared to Android users (itโ€™sย not even close), especially in premium markets like the United States.

                    The Play Store comes with some other drawbacks, too:

                    • Strict testing requirements: Individual developers face additional friction due to mandatory closed testing requirements introduced in 2023.
                    • Device fragmentation: Unlike Appleโ€™s tightly controlled ecosystem, Android runs on a vast number of devices with varying screen sizes, hardware capabilities, and operating system versions. This leads to higher testing effort and increased maintenance overhead.
                    • Privacy Concerns: For individual developers, personal information such as your legal name, address, and email may be displayed publicly on the Play Store. This raises privacy concerns, especially for solo developers who may not want their personal details exposed.

                    With that said, Google's Play Store is still a great platform if:

                    • Your target audience is located in regions where Android has a larger market share than iOS (e.g., Asia, Africa, Latin America).
                    • Youโ€™re building apps for markets where affordability and accessibility are key factors, as Android dominates mid-range and low-cost device segments.

                    Bottom line:

                    • Individuals: If youโ€™re starting out as a solo developer, consider prioritizing the Apple App Store. It offers better monetization potential and access to a premium audience, making it a more lucrative starting point.
                    • Companies: If your business needs a presence on both platforms, publishing on both the App Store and Play Store is essential.

                    1. Sign Up for a Google Play Developer Account

                    To publish apps on the Google Play Store, youโ€™ll need to create a Play Console developer account.

                    Key info:

                    • Cost: one time $25 fee.
                    • Who can enroll: Individuals or organizations.
                    • How to enroll: Start the process atย play.google.com/console/signup.
                    • Fees: Google takes a 15% cut on your first $1M (USD) of yearly earnings after you enroll in an Account Group. Beyond $1M, the fee increases to 30%.

                    Invididual or Organization account?

                    The first step on the Google Play Console Signup page is to choose between enrolling as an organization or as an individual.

                    Before you choose, beware of the strict app testing requirements for new personal developer accounts. Quoting:

                    If you have a newly created personal developer account, you must run a closed test for your app with a minimum of 12 testers who have been opted-in for at least the last 14 days continuously. When you meet these criteria, you can apply for production access on the Dashboard in Play Console so that you can ultimately distribute your app on Google Play.

                    These requirements don't apply if you choose an organization account, or if your personal account was created before 13 November 2023.

                    Privacy considerations

                    When creating a developer account, be aware that yourย legal name,ย address, andย contact detailsย may be displayed publicly on Google Play (source).

                    • Individual Accounts: Google displays yourย legal name,ย country, andย developer email address. If you monetize your app, yourย full addressย will also be shown.
                      • Organization Accounts: Google displays your organizationโ€™sย legal name,ย legal address,ย email, andย phone numberย to improve transparency and user safety.

                      For individual developers who want to monetize their apps, this raises significant privacy concerns. To protect your personal information, consider registering aย virtual business address using a service like iPostal1. However, this option comes with additional costs.

                      How to Sign Up for a Google Play Developer Account

                      To get started, visit this page to sign up for an organization or individual account.

                      Then, complete all the steps:

                      • Enter your Developer Name
                      • Link a Payments Profile to verify your identity
                      • Enter your email address and consent for your data to be shown on your public developer profile
                      • Tell Google about you, your website, and your app publishing and monetization plans
                      • Share your private contact details so Google can contact you

                      If you're signing up as an organization, you'll also need to obtain a D-U-N-S Number. This process can take 1 to 30 business days, so apply as early as possible.

                      Once you've agreed to the terms and purchased your membership, you'll need to complete the verification process and submit some official proof of ID and/or business registration.

                      2. Create a New App on the Google Play Console

                      Once your developer account is ready, you can sign in to the Google Play Console. After choosing your developer account, you'll be taken to the main dashboard:

                      Google Play Console dashboard

                      How to Create a New App

                      Select the Home tab on the left panel, then click on Create app:

                      Create a new app

                      On the next page, enter all the required details:

                      • App name
                      • Default language for your store listing
                      • Select App or Game
                      • Select Free or Paid

                      Then, tick the declaration boxes and click Create app.

                      The App Dashboard

                      After creating the app, you'll be taken to your app's dashboard:

                      App dashboard

                      The App Dashboard is your central hub for managing your app. It highlights important tasks like:

                      • Setting up your Store Listing: Add your app's name, description, and media assets.
                      • Testing and Releasing: Manage internal testing, open testing, and production releases.
                      • Compliance: Ensure your app meets Google Play policies and regulatory requirements.

                      3. Prepare your App For Review (App Content)

                      Preparing an app for review on the Play Store is a long process and you'll need to fill in multiple pages. Let's start with the App Content and Data Safety sections.

                      Set up your app (App Content)

                      From your appโ€™s dashboard, scroll down to theย Set up your appย section and expand the tasks:

                      Dashboard - Set up your app

                      Here's how to tackle them:

                      • Privacy Policy: Enter your privacy policy URL.
                      • App Access: If parts of your app are restricted (e.g., authentication required), provide instructions for the Google Play review team to access those areas.
                      • Ads: Indicate whether your app contains ads.
                      • Content Rating: Complete the content rating questionnaire to generate appropriate ratings for your app in different regions.
                      • Target Audience: Specify the target age group for your app. If your app targets children, youโ€™ll need to meet additional compliance requirements.
                      • Address Specific Use Cases: Some tasks only apply to apps with specific use cases (e.g. news apps, government apps, financial features, health features). Complete the relevant sections as needed.

                      Data Safety

                      You'll also need to fill the data safety section, which includes 5 separate steps:

                      Data safety

                      Let's tackle them:

                      1. Overview: The first page provides an overview of what you need to disclose.
                      1. Data collection and security: Here, youโ€™ll answer questions about how your app collects and handles user data. Answer these questions accurately based on your appโ€™s functionality and policies.
                      1. Data Types: Here, youโ€™ll declare all the data types your app collects or shares. These are grouped into categories such as, Location, Crash logs, Device or other IDs, App activity, Personal info, Financial info, and others.
                      1. Data usage and handling: For each data type you selected, youโ€™ll need to answer questions about:
                        • Purpose: Why is the data collected (e.g., analytics, diagnostics)?
                        • Sharing: Is the data shared with third parties?
                        • User Choice: Can users opt out of data collection?
                        • Data Retention: Is the data processed ephemerally, retained temporarily, or stored long-term?
                      1. Preview: The final step is to review the summary that will appear on your appโ€™s Play Store listing. This summary helps users understand how your app collects and handles their data.

                      4. Prepare your App For Review (Store Listing)

                      Navigate to your appโ€™s dashboard and locate the Manage how your app is organized and presented section under Set up your app:

                      Set up your app

                      Click on Select an app category and provide contact details to begin.

                      Store Settings

                      In the Store settings page, youโ€™ll set up your App category and Store Listing contact details.

                      • App Category:
                        • Click Edit, then select App or Game and the appropriate category.
                        • Click Manage tags to add up to five relevant tags that describe your appโ€™s content and functionality. Tags improve your appโ€™s discoverability in search results and recommendations.
                      • Store Listing Contact Details:
                        • Click Edit and enter your email address (required), phone number (optional), and website (optional).

                      Store Listing

                      The Store Listing is the public-facing page where users will discover, download, and interact with your app on the Google Play Store.

                      From the app dashboard, click Set up your Store Listing and follow the steps below:

                      • Listing assets
                        • App name: The name displayed on the Play Store.
                        • Short description: A brief, engaging snippet that appears in search results.
                        • Full description: A detailed explanation of your appโ€™s features and benefits. Use clear, concise, and engaging language.
                      • App Icon and Feature Graphic: Upload your app icon (512x512 px) and feature graphic (1024x500 px). These visuals are essential for branding and grabbing usersโ€™ attention.
                      • Phone and Tablet Screenshots: Upload 2 to 8 screenshots (16:9 or 9:16 aspect ratio).

                      Select Countries and Regions

                      Before moving on, letโ€™s complete one more important step so you wonโ€™t forget later: selecting countries and regions where your app will be available.

                      Scroll down to the Create and publish a release section at the very bottom of your appโ€™s dashboard:

                      Select countries and regions

                      Click Select countries and regions to choose where your app will be available.

                      5. Review the Android Project settings

                      Before releasing your app, itโ€™s essential to review your Android project settings to ensure compatibility with Google Play requirements and avoid potential errors.

                      Here's a handy checklist:

                      • App Icons and Launch Screen: Ensure your app has a launcher icon and a splash screen tailored for Android. The flutter_launcher_icons and flutter_native_splash packages can help with this.
                      • Flavors Configuration (Optional): If youโ€™re using flavors to manage multiple app versions (e.g., free vs. paid), verify your Android flavor settings. My Flutter in Production course includes an entire module about flavors.
                      • App Manifest File: Review your AndroidManifest.xml file for required permissions and settings. For example, add <uses-permission android:name="android.permission.INTERNET"/> if your app requires internet access, and verify plugin-specific requirements (e.g., url_launcher).
                      • Gradle Build Configuration: Update your android/app/build.gradle file to meet Google Playโ€™s API level requirements:
                        • Set compileSdkVersion and targetSdkVersion to 34 (Android 14).
                        • Confirm minSdkVersion is set to 21 to avoid multidex requirements.

                      To more easily update the Gradle settings, use this: Script to Update the Android Project Settings.

                      Code signing

                      Before you can release your app on the Google Play Store, it needs to be signed with a digital certificate. Code signing ensures that your app is authentic and hasnโ€™t been tampered with after being published.

                      To enable code signing, you need to:

                      1. Create an upload keystore.
                      2. Reference the keystore in your project.
                      3. Configure Gradle for signing release builds.

                      These steps are all explained in detail in the official docs:

                      6. Build, Upload, and Submit your App to the Google Play Store

                      After configuring code signing, itโ€™s time to build a release version of your app and upload it to Google Play.

                      Here, we'll focus on how to publish your app on the production track, so that users can download and install from the Play Store. Internal, closed, and open-testing tracks also exist, and you can use them to distribute your app to different groups of users before going live. These are covered in my Flutter in Production course.

                      Step 1: Update the Appโ€™s Version Number

                      Before building your app, double check the version number in the pubspec.yaml file:

                      version: 0.3.4+18

                      What Does This Mean?

                      • 0.3.4: The version name (major.minor.patch).
                      • 18: The build number (used for versioning in Google Play).

                      When you build the app, these values will update the versionName and versionCode in the local.properties file on Android.

                      You can also override the version name and build number during with this command:

                      flutter build appbundle --build-name=0.3.4 --build-number=18

                      This is useful if youโ€™re using multiple release tracks (different app builds need to be uploaded for each testing or production track).

                      Step 2: Build Your App for Release

                      Google Play supports two release formats: App Bundle (AAB) (preferred) and APK.

                      To build an app bundle, run:

                      flutter build appbundle --release

                      If successful, the app bundle will be generated at:

                      [project]/build/app/outputs/bundle/release/app.aab

                      To protect your Dart code from reverse-engineering, consider adding the --obfuscate and --split-debug-info flags. Learn more in the Dart Obfuscation Guide.

                      Step 3: Upload to Google Play

                      Select the Production track, then click Create new release:

                      Production, create new release

                      Drag and drop your app bundle (app.aab) into the App bundles box:

                      Create production release

                      Once uploaded, the bundle will be optimised for distribution (this may take a minute or so).

                      Then, it will appear above the Release details section, and the Release name will be pre-filled with the build version:

                      Uploaded app bundle

                      Update the Release notes (e.g. "Initial release"), then click Next.

                      After completing all required steps, Google Play will display any errors or warnings. For example, you may see a warning about missing debug symbols:

                      Errors, warnings and messages

                      The warning above refers to uploading debug symbols for your Android native code (Kotlin or Java) and can be safely ignored. When building a Flutter app, most of your crashes are likely to happen in your Dart code. You only need to upload debug symbols for your Flutter app if you used the --obfuscate flag when building your app bundle.

                      Once you click Save, a popup will appear. Click Go to overview:

                      Go to Publishing overview?

                      Step 4: Submit for Review

                      At this stage, Google Play will perform some automated checks on your app bundle:

                      Running quick checks for commonly found issues

                      If all checks pass, click on the button to Send changes for review:

                      Once the checks are done, the page will update and if there are no issues, you'll be able to send all the changes for review:

                      Changes not yet sent for review

                      After submission, your app will enter the review process, which can take several days.

                      Changes in review

                      Congratulations! You've now submitted the first version of your app to the Play Store! ๐ŸŽ‰

                      At the top of the Publishing overview, you can find an option about Managed publishing. Turn on this option if you want control when approved changes are published on Google Play.

                      What Happens Next?

                      Once youโ€™ve submitted your app, it enters the App Review Process, where Google Play evaluates your app for compliance with its Developer Policies.

                      While you wait, keep an eye on your Inbox in the Google Play Console. This is the best place to track important updates and communications from Google Play:

                      Google Play Console Inbox

                      To ensure you donโ€™t miss critical notifications, turn on Email notifications for Publishing updates.

                      What if your app is rejected?

                      Rejections happen, even to experienced developers. If your app is rejected, donโ€™t panicโ€”follow these steps to resolve the issue:

                      • Review the Rejection Reasons: Google Play will send you an email outlining the reasons for the rejection. This email usually includes links to the Developer Policy Center to help you understand the specific guidelines you violated.
                      • Fix the Issues: Make the necessary changes to your app to address the rejection reasons. This might involve updating your app metadata, fixing technical issues like crashes or bugs, or adjusting your appโ€™s functionality to comply with policies.
                      • Resubmit Your App: Once youโ€™ve resolved the issues, upload a new build of your app, update the metadata if necessary, and resubmit your app for review.
                      • Appeal If Necessary: If you believe your app was unfairly rejected, you can submit an appeal. Be sure to provide clear and concise reasoning, along with evidence supporting your case.

                      7. Submitting App Updates to the Google Play Store

                      Keeping your app updated is essential for maintaining user satisfaction and staying competitive on the Play Store. Regular updates allow you to:

                      • Fix bugs to improve user experience and app stability.
                      • Introduce new features to keep your app relevant and engaging.
                      • Comply with new Google Play policies or platform changes (e.g., updated API level requirements).

                      Once you're ready, follow these steps to submit an app update:

                      • Update the Version and Build Number: If you forget to do this, the upload will fail with an error, and you'll have to make a new build.
                      • Build a New App Bundle: Run the flutter build appbundle --release command (with any other flags that are needed by your app).
                      • Create a New Production Release: In the Google Play Console, go to the Production track and click Create new Release.
                      • Upload the App Bundle: Drag and drop your updated app bundle (app-prod-release.aab) into the App bundles box.
                      • Update the Release Notes: Scroll down to the Release notes section, where you can describe whatโ€™s new in this update.
                      • Review and Save: Google Play will check for errors or warnings. If there are no errors, click Save.
                      • Submit for Review: Once everything is ready, go to the Publishing overview and click Send change for review.

                      Your app will enter the review process, just like when you first published it.

                      Wrapping Up

                      This article presented a clear roadmap for releasing your app on the Play Store. From account setup to submission and updates, you now have a clear guide for navigating the process successfully.

                      Note that making releases by hand can be time consuming, especially if you release often. Here's a solution. ๐Ÿ‘‡

                      Scaling with CI/CD Pipelines

                      If you work as part of a team or release updates often, consider setting up CI/CD pipelines to automate your app builds, testing, and releases. CI/CD enables you to:

                      • Save time by automating repetitive tasks.
                      • Run specific workflows (e.g. run tests) when certain events are triggered (e.g. when pushing a branch).
                      • Distribute apps more efficiently via TestFlight or the App Store.

                      To dive deeper into these topics, and many more, you can explore my new course. ๐Ÿ‘‡

                      New Course: Flutter in Production

                      When it comes to shipping and maintaining apps in production, there are many important aspects to consider:

                      • Preparing for release: splash screens, flavors, environments, error reporting, analytics, force update, privacy, T&Cs.
                      • App Submissions: app store metadata & screenshots, compliance, testing vs distribution tracks, dealing with rejections.
                      • Release automation: CI workflows, environment variables, custom build steps, code signing, uploading to the stores.
                      • Post-release: error monitoring, bug fixes, addressing user feedback, over-the-air updates, feature flags & A/B testing.

                      My latest course will help you get your app to the stores faster and with fewer headaches.

                      If youโ€™re interested, you can learn more and enroll here. ๐Ÿ‘‡

                      ]]>
                      https://codewithandrea.com/tips/move-to-file-vscode-assist/Move Declaration to File (VSCode assist)With VSCode, you can easily move your Dart classes and functions to a different file (this is very useful when refactoring).https://codewithandrea.com/tips/move-to-file-vscode-assist/Wed, 8 Jan 2025 01:00:00 +0100Did you know?

                      With VSCode, you can easily move your Dart classes and functions to a different file.

                      To use this, select any declaration name and press CMD+., then use the Move option.

                      The desired file will be created with all the required imports. ๐Ÿ‘

                      Move Declaration to File (VSCode assist)

                      Happy coding!

                      ]]>
                      https://codewithandrea.com/tips/stack-fractionally-sized-box/Using Stack and FractionallySizedBoxHere's how to overlay multiple widgets inside a Stack and constrain their size and position with Positioned and FractionallySizedBox.https://codewithandrea.com/tips/stack-fractionally-sized-box/Tue, 7 Jan 2025 01:00:00 +0100Did you know?

                      Flutter offers many ways to create custom layouts that canโ€™t be expressed with Row and Column. ๐Ÿ‘

                      For example, here's how to overlay multiple widgets inside a Stack and constrain their size and position with Positioned and FractionallySizedBox. ๐Ÿ‘‡

                      Using Stack and FractionallySizedBox

                      Example Code

                      const n = 4; // * Aspect ratio = 1 yields a square return AspectRatio( aspectRatio: 1, // * Stack the squares on top of each other child: Stack( // * Generate n squares children: List.generate(n, (index) { // * Material colors have shades from 100 to 900 final color = Colors.indigo[(index + 2) * 100]!; // * Fill the entire surface return Positioned.fill( // * Size child to a fraction of the available space child: FractionallySizedBox( // * Pick width and height between 0 and 1 widthFactor: 1 - index / n, heightFactor: 1 - index / n, // * Choose the alignment of the child alignment: Alignment.topRight, // * Just a colored box child: ColoredBox(color: color), ), ); }), ), );

                      Challenge

                      Bonus: Try to implement a chess board layout using Stack and FractionallySizedBox by completing this challenge:

                      Happy coding!

                      ]]>
                      https://codewithandrea.com/newsletter/december-2024/December 2024: Flutter 3.27 Release, Architecture Case Study, Dart Microbenchmarks, and Union TypesAlso included: Dart 3.6, Flutter AI Toolkit, Flutter in Production livestream, and the latest from Code with Andrea.https://codewithandrea.com/newsletter/december-2024/Mon, 23 Dec 2024 01:00:00 +0100Welcome to my final Flutter newsletter of 2024!

                      This edition is packed with exciting updates:

                      • The mighty and much-awaited Flutter 3.27 release
                      • Flutter's Production Era (livestream recap)
                      • A new official architecture case study
                      • Deep-dive articles about benchmarks and union types
                      • Latest updates from Code with Andrea

                      So grab a drink, relax, and enjoy the read! ๐Ÿท

                      Flutter 3.27 Release

                      Flutter 3.27 is a big release that brings many updates across the framework, engine, and ecosystem. Highlights include:

                      Read the full announcement:

                      ๐Ÿ“ Announcing Dart 3.6

                      Released alongside Flutter 3.27, Dart 3.6 introducesย pub workspaces. This allows the Flutter analyzer to process all of the packages in a pub workspace in a single analysis context. For large repositories, this can significantly reduce the memory used by the Dart language server, improving IDE performance.

                      Another nice addition is the support for digit separators, for better readability of large numbers. Learn more here:

                      ๐Ÿ“ Announcing Flutter AI Toolkit

                      The Flutter team also announced the Flutter AI Toolkit, a collection of ready-to-use AI chat widgets designed to seamlessly integrate into your Flutter projects (a sample Flutter AI chat app is also available).

                      The toolkit provides features like rich text display, voice input, multimedia attachments, and more, making it easier to add an AI chat experience to your app. The toolkit also offers pluggable LLMs, meaning you can use it with different AI models.

                      Here's the full announcement:

                      Note: the flutter_ai_toolkit package uses Firebase under the hood. If you don't want to use Firebase, it may not be the best fit for your project.

                      Flutter in Production Event

                      Last weekโ€™sย #FlutterInProductionย livestream celebrated Flutterโ€™s 10-year journey. Some stats stood out:

                      • 1M+ monthly active Flutter developers ๐Ÿ“ˆ
                      • 28% of new iOS apps on the App Store use Flutter ๐Ÿคฏ

                      The livestream also offered a peek into some experiments and planned features, including:

                      After the livestream, Matt Carroll expressed strong opinions and concerns about decorators. As a follow up, Remi Rousselet made an interesting proposal that uses a pipe operator to reduce nesting. For now, keep in mind that decorators are only an experimentโ€”time will tell if something will come out of this.

                      If you missed the livestream, you can watch it on YouTube:

                      A written recap is also available:

                      โœจ Flutter is Now a Monorepo!

                      Hereโ€™s a big change that flew under the radar: the Flutter engine has been merged into the main Flutter repository! ๐ŸŽ‰

                      This brings some clear benefits:

                      • Framework and engine code are now built, tested, and shipped together
                      • Improved efficiency and collaboration for the Flutter team and external contributors

                      Architecture Case Study

                      Last month, I've mentioned that the Flutter docs have a new guide about app architecture.

                      Since then, this architecture case study has been added, showing how the official compass sample app was built.

                      The guide covers:

                      For all the details, check the full case study:

                      In my opinion, the guide does a good job at explaining important architectural concepts and offers a good overview of how the UI layer and data layer interact with each other. While implementation details such as using ChangeNotifier and the provider package may work well for the compass sample app, they may fall a bit short for more complex apps. Bottom line: use the principles as guidance and find the most suitable implementation based on your preferences and project requirements.

                      Flutter Articles

                      This month, I'm sharing two very good articles about Dart microbenchmarks and Union Types.

                      ๐Ÿ“ Microbenchmarks are experiments

                      Lately, someone posted this pretty visualization comparing the performance of various programming languages on a meaningless benchmark.

                      While such posts are good for engagement farming, the devil is in the details. So I was extremely pleased when Slava Egorov (tech lead of the Dart Language) posted this take:

                      Sigh. 2025 is almost around the corner and people still seem to think that the goal of a benchmark is to produce a number which by itself reveals some hidden truth about the world. You write a loop in Dart, you write a loop in C. You run it three times (statistics!). You compare the running time and, lo and behold, these two numbers reveal you all you need to know about Dart and C performance. The rest of the day can be spent browsing numerological magazines for the meaning of your date of birthโ€ฆ

                      He then proceeded with a detailed breakdown of the benchmark, including a low-level performance comparison between JavaScript and Dart (yay, assembly code!).

                      The full post is very much worth a read:

                      Ironically, OpenAI recently announced their latest o3 model, showing that o3 achieved a 87.5% score on the ARC-AGI benchmark and implying that it is close to AGI. As it turns out, the model itself was specifically trained on the benchmark ๐Ÿคฆโ€โ™‚๏ธ. Bottom line: things aren't always as they seem, and some good old critical thinking is required to discern between hype and reality.

                      ๐Ÿ“ Demystifying Union Types in Dart, Tagged vs. Untagged, Once and For All

                      Union types are a concept that appears in many languages, but they are often misunderstood.

                      This article by Majid Hajian is a deep-dive into Union Types (untagged and tagged unions)โ€”what they are, why theyโ€™re helpful, where they fall short, and why, in strongly typed languages like Dart, you might want to reconsider their use.

                      Read on to get a better conceptual understanding of union types, so you can choose the right language features or constructs (e.g. sealed classes) depending on your use case:

                      Latest from Code with Andrea

                      Since the last newsletter, I've been quite busy:

                      If you missed them, here's a summary of the latest articles:

                      ๐Ÿ“ How to Release Your Flutter App on the iOS App Store

                      The Flutter documentation already provides a good technical guide for building and releasing an iOS app. But publishing your first app involves more than just building and uploading. There are many steps to follow, and they arenโ€™t always obvious.

                      This article will provide a high-level roadmap for releasing your Flutter app on the App Store. Even if youโ€™re familiar with this process, you may discover a trick or two along the way.

                      ๐Ÿ“ How to Build a Robust Flutter App Initialization Flow with Riverpod

                      Earlier this year, I shared an article about stateful app initialization with Riverpod.

                      Since then, I discovered a flaw that was breaking URL-based navigation in my original setup. So, after finding the solution, I've rewritten the article entirely. ๐Ÿ‘‡

                      I've also updated my Starter Architecture repo to use the new technique:

                      Until Next Time

                      2024 is nearly over and this year, I've shared 12 new articles and 81 new tips about Flutter app development.

                      Over the next week, I'll take some time to reflect on my journey as a Flutter educator and share my 2024 retro (if you're curious, hereโ€™s the 2023 edition).

                      But for now, I want to thank you for reading and supporting my work this year, and I wish you a happy festive season! ๐ŸŽ‰

                      Happy coding!

                      Andrea

                      ]]>
                      https://codewithandrea.com/tips/list-wheel-scroll-view/The ListWheelScrollView WidgetIf you need to select between a small list of values but have limited vertical space, you can use a ListWheelScrollView.https://codewithandrea.com/tips/list-wheel-scroll-view/Fri, 20 Dec 2024 01:00:00 +0100Did you know?

                      If you need to select between a small list of values but have limited vertical space, you can use a ListWheelScrollView. ๐ŸŽฏ

                      Pro Tip: use FixedExtentScrollPhysics to snap to the nearest item when the user stops scrolling. ๐Ÿ‘

                      The ListWheelScrollView Widget

                      How to use it

                      • call the regular ListWheelScrollView constructor and pass a list of children.
                      • call ListWheelScrollView.useDelegate and pass a ListWheelChildDelegate.

                      For all the details, check the official docs:

                      Happy coding!

                      ]]>
                      https://codewithandrea.com/tips/color-deprecations-flutter-3-27/Color API Deprecations in Flutter 3.27To support the latest wide gamut color spaces, Flutter 3.27 has deprecated some properties and methods in the Color class.https://codewithandrea.com/tips/color-deprecations-flutter-3-27/Wed, 18 Dec 2024 01:00:00 +0100Did you know?

                      To support the latest wide gamut color spaces, Flutter 3.27 has deprecated some properties and methods in the Color class. ๐ŸŒˆ

                      โš ๏ธ No Dart fix command is available for this breaking change, so you'll need to migrate your code manually.

                      Color API Deprecations in Flutter 3.27

                      Learn more about this breaking change here:

                      Happy coding!

                      ]]>
                      https://codewithandrea.com/tips/text-style-tabular-figures/Text Style with Tabular FiguresIf you want to render fixed width (monospaced) digits, set FontFeature.tabularFigures() inside your TextStyle.https://codewithandrea.com/tips/text-style-tabular-figures/Tue, 17 Dec 2024 01:00:00 +0100Did you know?

                      If you want to render fixed width (monospaced) digits, set FontFeature.tabularFigures() inside your TextStyle. ๐ŸŽฏ

                      This works great when showing numbers and dates that should align vertically or update in realtime! ๐Ÿ”ฅ

                      Text Style with Tabular Figures

                      Happy coding!

                      ]]>
                      https://codewithandrea.com/tips/digits-separators-dart-3-6/Digits Separators in Dart 3.6Since Dart 3.6 (Flutter 3.27), you can use _ as a digits separator. This works with integers and floats, as well as custom formats (hex, scientific).https://codewithandrea.com/tips/digits-separators-dart-3-6/Fri, 13 Dec 2024 01:00:00 +0100Did you know?

                      Since Dart 3.6 (Flutter 3.27), you can use _ as a digits separator. ๐ŸŽฏ

                      This works with integers and floats, as well as custom formats (hex, scientific).

                      To enable this, bump the Dart SDK to 3.6 in your pubspec.yaml.

                      Digits Separators in Dart 3.6

                      Happy coding!

                      ]]>
                      https://codewithandrea.com/tips/spacing-row-column/New Spacing Argument in Row/Column (Flutter 3.27)Since Flutter 3.27, you can pass a spacing argument to your Row and Column widgets (rather than using SizedBox).https://codewithandrea.com/tips/spacing-row-column/Thu, 12 Dec 2024 01:00:00 +0100Did you know?

                      Since Flutter 3.27, you can pass a spacing argument to your Row and Column widgets. โœ…

                      This means you no longer need a SizedBox to add fixed spacing between each child. ๐Ÿš€

                      New Spacing Argument in Row/Column

                      Combining spacing and flex

                      If you want, you can combine spacing and flex together:

                      Combining spacing and flex

                      This makes it easier to mix fixed and proportional spacing when laying out the children.

                      Happy coding!

                      ]]>
                      https://codewithandrea.com/tips/banner-widget/The Banner WidgetYou can use the Banner widget to place a small diagonal banner over a child widget. For more custom styling, build your own using a custom painter.https://codewithandrea.com/tips/banner-widget/Tue, 10 Dec 2024 01:00:00 +0100Did you know?

                      You can use the Banner widget to place a small diagonal banner over a child widget.

                      The Banner Widget

                      Note that Banner only offers limited customization options. If you need more custom styling, you have two options:

                      • Build your own using a custom painter.
                      • Use the super_banners package.

                      Banner is very closely related to CheckedModeBanner, which shows inside your MaterialApp in debug mode. For more info, check the official docs: Banner class.

                      Happy coding!

                      ]]>
                      https://codewithandrea.com/tips/cursor-edit-mode/Improve your code with Cursor Edit ModeYou can use Cursor edit mode to sweep through your code and make changes. Works best with imperative code.https://codewithandrea.com/tips/cursor-edit-mode/Thu, 5 Dec 2024 01:00:00 +0100Did you know?

                      The Cursor IDE has a powerful edit mode (hit CMD+K to enable it).

                      I find it quite useful for finding edge cases in my imperative code.

                      It doesn't always get it right, though! So make sure you tweak the output as needed and discard any unwanted changes. ๐Ÿ™‡โ€โ™‚๏ธ

                      Improve your code with Cursor Edit Mode

                      Happy coding!

                      ]]>
                      https://codewithandrea.com/tips/fixing-version-solving-failed-errors/Fixing Version Solving Failed ErrorsA few suggestions for solving the "version solving failed" error when updating your Flutter dependencies.https://codewithandrea.com/tips/fixing-version-solving-failed-errors/Wed, 4 Dec 2024 01:00:00 +0100When you get a "version solving failed" error, how can you fix it?

                      Some useful tips:

                      1. Use flutter pub upgrade, don't change versions manually ๐Ÿ’ก
                      2. Read the error logs ๐Ÿง
                      3. Remove all version constraints ๐Ÿ‘ป
                      4. Update your Podfile ๐Ÿ
                      5. Update the Android project settings ๐Ÿค–
                      Fixing Version Solving Failed Errors

                      Some more details:

                      1. flutter pub upgrade is your friend. It will try to resolve all dependencies without causing conflicts
                      2. Be persistent. Inspect the error log closely and see if you can figure it out
                      3. Remove version constraints from conflicting packages and try again
                      Fixing Version Solving Failed Errors

                      Here's some more info about the flutter pub upgrade command:

                      If you need to update the Android project settings, this script may help:

                      Happy coding!

                      ]]>
                      https://codewithandrea.com/articles/how-to-release-flutter-ios-app-store/How to Release Your Flutter App on the iOS App StoreA step-by-step guide on how to publish your Flutter app, including metadata, compliance, privacy manifests, Xcode settings, and building your IPA file.https://codewithandrea.com/articles/how-to-release-flutter-ios-app-store/Tue, 3 Dec 2024 01:00:00 +0100Your Flutter app is ready, and youโ€™ve completed all the importantย pre-release steps. Now itโ€™s time for the final step: publishing it on the App Store and sharing it with the world. But where do you start?

                      Sure, theย Flutter documentationย provides a technical guide for building and releasing an iOS app. But publishing your first app involves more than just building and uploadingโ€”itโ€™s a process filled withย red tape,ย guidelines, andย steps that arenโ€™t always obvious.

                      If youโ€™re new to this, donโ€™t worry. This article will provide a high-level roadmap for releasing your Flutter app on the App Store. Even if youโ€™re familiar with this process, you may discover a trick or two along the way. ๐Ÿ‘

                      What Youโ€™ll Learn:

                      1. How toย enroll in the Apple Developer Program.
                      2. How toย register your App ID in the Apple Developer Portal.
                      3. How to create your app inย App Store Connect.
                      4. How toย prepare your app for review, including metadata, privacy, and compliance.
                      5. How to create a Privacy Manifest in Xcode.
                      6. How to update the Xcode project settings, including code signing.
                      7. How toย build, upload, and submitย your app for App Store review.

                      Want a more detailed, hands-on guide? Myย Flutter in Productionย course includes a complete module with 20 lessons that cover each step in depthโ€”from account setup to submission and beyond.

                      Letโ€™s dive in! ๐Ÿš€

                      Should You Sell Apps on the App Store?

                      For many developers, theย App Storeย is the ultimate platform for reaching a high-quality audience. Apple users are far more willing to pay for apps compared to Android users (itโ€™sย not even close). And with payments seamlessly linked to their Apple ID, purchasing is frictionless.

                      However, joining Appleโ€™sย walled gardenย comes with trade-offs:

                      • 15% revenue cut: Even with theย small business program, Apple takes a significant cut of your earnings.
                      • No direct customer ownership: Apple controls the relationship with your customers, making it harder to connect with them or market additional products.
                      • App review delays: Theย app review processย can take hours or daysโ€”and even longer if your app is rejected, causing delays in your release timeline.

                      The App Store offers real opportunities, but remember that Apple stands between you and your users. If your app fits Appleโ€™s ecosystem and youโ€™re prepared to work within its constraints, the App Store can be a powerful platform to reach millions of users worldwide.

                      1. Enroll in the Apple Developer Program

                      To publish apps on the App Store, youโ€™ll need to join theย Apple Developer Program:

                      Apple Developer Program: enrollment page
                      Apple Developer Program: enrollment page

                      Enrollment Steps

                      The enrollment process is fairly simple and involves the following steps:

                      • Start your Enrollment
                      • Sign in with your Apple ID (or create an account)
                      • Agree to the Apple Developer Agreement
                      • Confirm your personal information
                      • Select your entity type (individual, organization, or others)
                      • Obtain a D-U-N-S Number using this service (organizations only - can take up to 30 business days, or up to 8 business days for the expedited service)
                      • Agree to the legal terms
                      • Purchase your membership
                      • Complete the verification process

                      What Happens Next?

                      Once youโ€™ve submitted all the required information:

                      • Verification: Apple may take a few days to verify your enrollment.
                      • Activation: Once verified, youโ€™ll receive an activation email.
                      • Access to Resources: With your membership, youโ€™ll have access to your Apple Developer Account, documentation, and additional resources.

                      2. Register Your App ID

                      After enrolling in the Apple Developer Program, you'll get access to your Apple Developer Account:

                      Apple Developer Account page
                      Apple Developer Account page

                      The next step is to register your App ID, which uniquely identifies your app within Appleโ€™s ecosystem.

                      How to Register Your App ID

                      1. Open the identifiers page of your Apple Developer account.
                      2. Click + to register a new App ID.
                      3. Enter an app name, select Explicit App ID, and enter the Bundle ID (e.g., com.yourcompany.yourapp).
                      4. Select the capabilities your app requires (e.g., Push Notifications, In-App Purchases), then click Continue.
                      5. Click Register to confirm your App ID.

                      Note that the Bundle ID must match the Bundle Identifier property in Xcode under Runner > General > Identity:

                      Where to find the Bundle Identifier in Xcode
                      Where to find the Bundle Identifier in Xcode

                      3. Create your app in App Store Connect

                      After registering your App ID, the next step is to create your app in App Store Connect.

                      How to Create a New App in App Store Connect

                      Go to App Store Connect > Apps, then add a new app:

                      To create a new app, click the
                      To create a new app, click the "+" button

                      Fill in the required details:

                      Adding the new app details
                      Adding the new app details

                      These include:

                      • Platforms: Ensureย iOSย is selected.
                      • Name: The app name displayed to users on the App Store.
                      • Primary Language: The default language for your appโ€™s metadata.
                      • Bundle ID: Select the App ID you registered in theย previous step.
                      • SKU: A unique internal ID for your app (not visible to users).
                      • User Access: Decide which App Store Connect users can manage the app.

                      When you're done, click Create to finalize the setup.

                      4. Prepare your App For Review

                      After clicking Create, youโ€™ll land on the appโ€™s overview page:

                      Main page for a new app in App Store Connect
                      Main page for a new app in App Store Connect

                      Note that entering all the information is long process and you'll need to fill multiple pages:

                      Fill all the data in
                      Fill all the data in "App Information", "App Privacy", "Pricing and Availability", before adding your app for review

                      Hereโ€™s what youโ€™ll need to provide and where to find it.

                      Sections to Complete

                      • Main Page
                        • Preview and Screenshots: up to 3 app previews (optional), and up to 10 screenshots
                        • App Metadata: description, keywords, support URL, marketing URL, version, copyright
                        • App Review Information: Provide:
                          • Sign-in Information: If your app requires login credentials for testing, create a test account and share them here.
                          • Contact Information: Provide details for Apple to contact you during the review process.
                          • Notes: Add any additional context for the App Review team.
                      • App Information
                        • Localizable Information: your app's name and subtitle
                        • General Information: bundle ID, primary and secondary category, content rights information
                        • Age Rating: a questionnaire with multiple selections and yes/no questions
                        • App Encryption Documentation: specify whether your app uses non-exempt encryption (see tip below for most cases).
                      • Pricing and Availability
                        • Price Schedule: starting price for your app (free or paid)
                        • App Availability: countries where your app will be available to purchase or download
                      • App Privacy
                        • Privacy Policy: a link to your appโ€™s Privacy Policy (required).
                        • Data Collection: Specify your appโ€™s data collection and usage practices. This is a multi-step process where youโ€™ll need to disclose:
                          • Types of data collected (e.g., location, identifiers).
                          • How the data is used (e.g., analytics, advertising).
                          • Whether the data is linked to user identity or used for tracking.

                      Tip: If your app doesnโ€™t use non-exempt encryption (this applies to most apps), add a ITSAppUsesNonExemptEncryption key and set the value to NO in your Info.plist file in Xcode. This will prevent extra encryption documentation steps when uploading your builds to App Store Connect. Read this tip for more details.

                      Before Submitting for Review

                      Make sure youโ€™ve entered all the required app details. If any information is missing, youโ€™ll see an error when you click Add for Review:

                      Warning about required items before the app can be added for review
                      Warning about required items before the app can be added for review

                      5. Create a Privacy Manifest in Xcode

                      When you specify your data collection practices in App Store Connect, Apple generates privacy labels for your app, which are displayed on your App Store listing. For example:

                      App Store privacy labels
                      App Store privacy labels

                      Starting November 12, 2024, Apple also requires you to add a Privacy Manifest to your app before submitting it to App Store Connect.

                      What is a Privacy Manifest?

                      A Privacy Manifest is a file named PrivacyInfo.xcprivacy that documents your app's data collection practices and API usage. This file helps Apple understand what data your app collects, how itโ€™s used, and why.

                      How to Create a Privacy Manifest in Xcode

                      Follow these steps to create a Privacy Manifest for your app:

                      • Open the ios/Runner.xcworkspace project in Xcode.
                      • Right-click the Runner folder and select New File from Template...
                      • Scroll down to the Resource section, choose App Privacy, and click Next.
                      • Create the file with the default name PrivacyInfo.

                      What to Add to the Privacy Manifest

                      Once the file is created, add the following values to the App Privacy Configuration section:

                      • NSPrivacyTracking: Indicates whether your app uses tracking, as defined by Appleโ€™s App Tracking Transparency framework.
                      • NSPrivacyTrackingDomains: Lists internet domains your app uses for tracking purposes.
                      • NSPrivacyCollectedDataTypes: Details the types of data collected by your app (e.g., location, identifiers).
                      • NSPrivacyAccessedAPITypes: Describes the APIs your app accesses and the reasons for their use (e.g., User Defaults for local storage).

                      These should match the data collection types you declared in App Store Connect.

                      Once all the information is complete, your PrivacyInfo file should look similar to this:

                      Xcode privacy manifest file
                      Xcode privacy manifest file

                      Example File

                      Here's an example of the completed PrivacyInfo.xcprivacy for one of my apps:

                      To learn more, read the official Apple docs about User privacy and data use and Privacy manifest files.

                      6. Update the Xcode project settings

                      Before building and submitting your app to App Store Connect, review your Xcode project settings to ensure everything is configured correctly. You can find these settings in Runner > General:

                      Xcode project settings (General tab)
                      Xcode project settings (General tab)

                      Key Xcode project settings

                      Here are the key settings you need to review and update:

                      • Supported Destinations: In addition to iPhone, decide if your app should support iPad, Mac, and Apple Vision
                      • Minimum Deployment Version: Targeting an iOS version that is 2 years old ensures your app will be compatible with over 90% of devices (source).
                      • Identity: Double-check the App Category, as it impacts your appโ€™s discoverability on the App Store. Other fields like Display Name, Bundle Identifier, Version, and Build Number are derived from your pubspec.yaml, file, so there should be no need to change them.
                      • Deployment Info: Review and update the supported orientations based on your appโ€™s design.
                      • App Icons and Launch Screen: Verify that they look as intended.

                      Tip: use packages like flutter_launcher_icons and flutter_native_splash to generate consistent app icons and launch screens for all platforms.

                      Code Signing

                      In addition to the general settings, youโ€™ll need to review the Signing & Capabilities tab:

                      Signing & Capabilities tab in Xcode
                      Signing & Capabilities tab in Xcode
                      • Enable Automatic Signing: Ensure Automatically manage signing is enabled.
                      • Select Your Team: In the dropdown, choose your Team. This allows Xcode to automatically generate the necessary certificate and provisioning profile (if they donโ€™t already exist).

                      By default, a development certificate is created (rather than a distribution one). This is normal and wonโ€™t prevent you from uploading your app to App Store Connect. You can find all your certificates in the Apple Developer Portal.

                      App Version and Build Number

                      In Flutter, theย versionย andย build numberย are defined in theย pubspec.yamlย file under theย versionย key:

                      version: 0.3.4+18

                      Inย App Store Connect, youโ€™ll need to manually set the app version number to match the version in yourย pubspec.yaml. Hereโ€™s how:

                      1. Go toย App Store Connectย >ย My Appsย >ย Your App.
                      2. Scroll down to the metadata and set the correctย version numberย to match whatโ€™s in yourย pubspec.yaml:
                      Setting the version number in App Store Connect
                      Setting the version number in App Store Connect

                      7. Build, upload, and submit your app to App Store Connect

                      Once youโ€™ve verified your Xcode settings and versioning, itโ€™s time to build your app bundle and upload it to App Store Connect for review.

                      Building the iOS App Bundle

                      To build the iOS app bundle, run the following command:

                      flutter build ipa

                      Depending on your appโ€™s configuration, you might need to pass additional flags. To see all supported arguments, run: flutter build ipa --help.

                      If your app supports multiple flavors, specify the flavor using the --flavor flag. For example:

                      # To build the prod flavor flutter build ipa --flavor prod -t lib/main_prod.dart --dart-define-from-file=.env.prod

                      This command will generate an IPA file (iOS App Archive) in the build/ios/ipa/ directory. The IPA is the format used to distribute iOS apps.

                      Uploading the App Bundle to App Store Connect

                      There are multiple ways to upload your app bundle, but for first-time uploads, I recommend using the Apple Transporter app. Transporter simplifies the upload process by handling both the upload and validation for you.

                      How to Use Transporter:

                      • Open the Apple Transporter app and sign in with your Apple ID.
                      • Drag and drop the build/ios/ipa/*.ipa file into the app.
                      • Click Deliver to upload your app bundle:
                      How to deliver the IPA with the Transporter app

                      After a few minutes, Transporter will show the app as Ready for Internal Testing:

                      Transporter showing that the app is ready for internal testing

                      Submitting Your App for Review

                      Go to your app in App Store Connect, scroll down to Build, click the + icon, select your build, and click Done:

                      Selecting the build in App Store Connect

                      Then, click Add for Review at the top of the page, then Submit to App Review:

                      Saving the changes and adding the app for review

                      What Happens Next?

                      Your app will now enter Appleโ€™s review queue. Appleโ€™s review team will assess your app for compliance with their guidelines. If everything checks out, youโ€™ll receive a notification, and your app will be ready for release!

                      Wrapping Up

                      Congratulations! Youโ€™ve successfully submitted your iOS app to App Store Connectโ€”the final step before Apple reviews it for release on the App Store. ๐ŸŽ‰

                      Whatโ€™s Next?

                      While waiting for App Review, itโ€™s a good idea to get familiar with Appleโ€™s review guidelines and resources to ensure a smooth approval process. Start with the App Review page, which includes helpful documentation like:

                      Submitting App Updates to App Store Connect

                      Once your app is approved, youโ€™ll likely submit updates to add new features and fix bugs. While using the Transporter app works for occasional uploads, it can quickly become tedious if you update your app frequently.

                      A better solution? Automate the process with a build script that uses the xcrun command to automatically build and upload your app to App Store Connect.

                      To learn more about this, read:

                      Scaling with CI/CD Pipelines

                      If you work as part of a team or release updates often, consider setting up CI/CD pipelines to automate your app builds, testing, and releases. CI/CD enables you to:

                      • Save time by automating repetitive tasks.
                      • Run specific workflows (e.g. run tests) when certain events are triggered (e.g. when pushing a branch).
                      • Distribute apps more efficiently via TestFlight or the App Store.

                      To dive deeper into these topics, and many more, you can explore my new course. ๐Ÿ‘‡

                      New Course: Flutter in Production

                      When it comes to shipping and maintaining apps in production, there are many important aspects to consider:

                      • Preparing for release: splash screens, flavors, environments, error reporting, analytics, force update, privacy, T&Cs.
                      • App Submissions: app store metadata & screenshots, compliance, testing vs distribution tracks, dealing with rejections.
                      • Release automation: CI workflows, environment variables, custom build steps, code signing, uploading to the stores.
                      • Post-release: error monitoring, bug fixes, addressing user feedback, over-the-air updates, feature flags & A/B testing.

                      My latest course will help you get your app to the stores faster and with fewer headaches.

                      If youโ€™re interested, you can learn more and enroll here. ๐Ÿ‘‡

                      ]]>
                      https://codewithandrea.com/tips/xcode-privact-manifest/Adding a Privacy Manifest in XcodeStarting November 12, 2024, apps that donโ€™t include a Privacy Manifest canโ€™t be submitted for review in App Store Connect.https://codewithandrea.com/tips/xcode-privact-manifest/Mon, 2 Dec 2024 02:00:00 +0100Did you know?

                      Starting November 12, 2024, apps that donโ€™t include a Privacy Manifest canโ€™t be submitted for review in App Store Connect.

                      To address this, add a privacy manifest to the Runner project inside Xcode.

                      Adding a Privacy Manifest in Xcode

                      To learn more, read:

                      For instructions about what to put in your privacy manifest, read:


                      My latest course, Flutter in Production, covers the entire iOS app release process in detailโ€”from joining the Apple Developer Program to submitting your app for review.

                      Hereโ€™s a free lesson to get started:

                      Happy coding!

                      ]]>
                      https://codewithandrea.com/tips/fix-missing-compliance-warning/Fix for Missing Compliance Warning in App Store ConnectIf your app does not use Non-Exempt Encryption, set ITSAppUsesNonExemptEncryption to NO in your Info.plist file in Xcode.https://codewithandrea.com/tips/fix-missing-compliance-warning/Fri, 29 Nov 2024 02:00:00 +0100Tired of seeing the "Missing Compliance" warning when uploading builds to App Store Connect?

                      If your app does not use Non-Exempt Encryption, set ITSAppUsesNonExemptEncryption to NO in your Info.plist file in Xcode.

                      Upload your next build, and the warning will disappear. ๐Ÿ‘

                      Fix for Missing Compliance Warning in App Store Connect

                      For more details, read:


                      My latest course, Flutter in Production, covers the entire iOS app release process in detailโ€”from joining the Apple Developer Program to submitting your app for review.

                      Hereโ€™s a free lesson to get started:

                      Happy coding!

                      ]]>
                      https://codewithandrea.com/tips/material-icons-theme-vscode-extension/Material Icons Theme (VSCode Extension)Upgrade all your file icons with the Material Icons Theme. File nesting is properly supported, too, and the icons will be correctly left-aligned.https://codewithandrea.com/tips/material-icons-theme-vscode-extension/Wed, 27 Nov 2024 02:00:00 +0100Tired of the old, boring VSCode default theme?

                      Then, install the Material Icons Theme and upgrade all your file icons! ๐Ÿคฉ

                      File nesting is properly supported, too, and the icons will be correctly left-aligned! ๐ŸŽ‰

                      Material Icons Theme (VSCode Extension)

                      Happy coding!

                      ]]>
                      https://codewithandrea.com/tips/build-upload-ios-script/iOS App Store: Build and Upload ScriptA simple script to build and upload your iOS app to App Store Connect. You can run this locally, no CI/CD needed!https://codewithandrea.com/tips/build-upload-ios-script/Tue, 26 Nov 2024 02:00:00 +0100Did you know?

                      With this simple script, you can build and upload your iOS app to App Store Connect.

                      The best thing? You can run it from your local machine (no CI/CD needed!)

                      Read below for instructions on how to get it working. ๐Ÿงต

                      iOS App Store: Build and Upload Script

                      The script uses the xcrun command line tool to upload the IPA to App Store Connect.

                      Youโ€™ll need to authenticate with one of these methods:

                      • Username and Password
                      • App Store Connect API Key (recommended)

                      This guide explains how to obtain an API key:


                      Once you have obtained your Key ID and Issuer ID, set them as environment variables in your system (I like to store mine in ~/.zshrc for convenience.)

                      Then, simply download the script from here and use it:


                      My latest course, Flutter in Production, covers the entire iOS app release process in detailโ€”from joining the Apple Developer Program to submitting your app for review.

                      Hereโ€™s a free lesson to get started:

                      Happy coding!

                      ]]>
                      https://codewithandrea.com/tips/api-keys-client-server/API keys storage: Client or Server?Some guidelines to help you decide which API keys belong on the client, and which belong to the server.https://codewithandrea.com/tips/api-keys-client-server/Mon, 25 Nov 2024 02:00:00 +0100When building mobile or web apps, security is paramount.

                      Some API keys belong on the client, others on the serverโ€”but do you know which is which? ๐Ÿค”

                      Here are some guidelines to help you decide.

                      API keys storage: Client or Server?

                      Additional Resources

                      For more in-depth guidance about securing API keys, read this article:

                      Some API keys must be stored on the server and never transmitted to the client. Dart Shelf works great in this scenario, and this article covers all the details:

                      If you work with Firebase Cloud Functions and want to learn about best practices for securing your server-side keys, this guide has you covered:

                      Happy coding!

                      ]]>
                      https://codewithandrea.com/tips/downloads-count-pub-dev/How to Show the Downloads Count on pub.devHow to enable dark mode and see downloads count for your favourite packages on pub.dev.https://codewithandrea.com/tips/downloads-count-pub-dev/Thu, 21 Nov 2024 02:00:00 +0100Did you know?

                      You can now see the downloads count for your favourite packages on pub.dev.

                      To enable this, go to pub.dev/experimental, and toggle "Download count metrics".

                      Dark mode is also supported. ๐ŸŒš

                      Downloads Count on pub.dev

                      Happy coding!

                      ]]>
                      https://codewithandrea.com/newsletter/november-2024/November 2024: Architecting Flutter Apps, Flutter Forum, Image Filters, Meshes and GradientsAlso included: Flutter Community on Bluesky, Flock (Flutter Fork), and the latest from Code with Andrea.https://codewithandrea.com/newsletter/november-2024/Thu, 21 Nov 2024 01:00:00 +0100November is a Flutter release month, but as I write this, the new stable version hasnโ€™t landed just yet. No need to worryโ€”itโ€™s likely just around the corner.

                      In the meantime, I have a bunch of exciting news and updates from the Flutter community. Letโ€™s dive right in! ๐Ÿš€

                      Flutter News

                      This month, there have been some very interesting developments in the Flutter ecosystem. Here are the top stories. ๐Ÿ‘‡

                      ๐Ÿ’™ Architecting Flutter Apps

                      Building scalable and maintainable apps starts with a solidย app architecture, and Iโ€™ve written extensively on the topic (hereย andย here). So Iโ€™m very happy to see the Flutter team address this with new officialย docs on app architecture.

                      These docs introduce concepts likeย separation of concerns,ย unidirectional data flow, and theย MVVM design pattern. While thereโ€™s room for further expansion, theyโ€™re a great starting point for developers looking to build robust and scalable apps.

                      The Flutter team also released a well-structured sample app to demonstrate best practices, including multiple environments, brand-specific styling, and high test coverage. Check it out here:

                      ๐Ÿ’ฌ Flutter Forum

                      The brand-newย Flutter Forumย has quickly become one of my favorite places to hang out.

                      It features high-quality discussions, like these onย full-stack Flutter architectures,ย responsiveness in Flutter, andย lesser-known Dart classes, and many experienced Flutter devs have already joined.

                      Compared to Reddit, Discord, and all the other channels, this forum has a much better signal-to-noise ratio. Best of all, itโ€™s free and public! ๐Ÿ™Œ

                      ๐Ÿฆ‹ Flutter Community on Bluesky

                      On a related note, many Flutter developers areย migrating from X to Bluesky. While Iโ€™m still posting on both platforms, Bluesky is becoming a great space for focused Flutter discussions, without the usual noise.

                      If youโ€™re exploring Bluesky, I recommend these curated lists to get started:

                      ๐Ÿ“ Weโ€™re Forking Flutter. This is Why.

                      A few weeks ago, Matt Carroll published this controversial post, announcing his decision to fork the Flutter framework, with the stated goal of โ€œexpanding Flutter's available labor, and accelerate developmentโ€.

                      The post talks about the โ€œFlutter team's labor shortageโ€, why that's a problem, and how the community can help by contributing to a new Flutter fork called Flock.

                      While the concept has sparked debate, some developers are skeptical about its feasibility. For example, inย this forum discussion, one commenter noted:

                      I donโ€™t understand why you would consider swapping a fully funded team working on a very complicated project with what I can tell currently appears to be a team of two people mostly.

                      Personally, I have mixed feelings about the whole thing, and donโ€™t see a realistic scenario where Flock can live up to its claims, but time will tell.

                      For now, you can read the original post here:

                      Flutter Videos

                      I havenโ€™t shared any Observable Flutter episodes in a while, but two recent videos stood out to me, coveringย Image Filtersย andย Mesh Gradients. If youโ€™re into graphics and shaders, these are must-watches.

                      ๐Ÿ“น Image Filters

                      In this episode, Raouf Rahiche explores Flutterโ€™s built-in image filters, including:

                      • Implementing Androidโ€™s overscroll effect using shaders
                      • Examples of shaders ported from Metal and Skia to GLSL
                      • Blur filters, tile modes, and color filters (grayscale, sepia, etc.)

                      The episode also features a live coding session to create a "selective focus" effect, highlighting specific areas of an image. If you want to dive deeper into filters and shaders, give it a watch:

                      ๐Ÿ“น Meshes and Gradients

                      In this episode, Renan Araujo introducesย Oโ€™Mesh, the mesh gradient library he used to power this playground.

                      He explains how mesh gradients differ from traditional gradients, how to overcome shader limitations, and how Bรฉzier curves are used to create distortions. A live coding session is also included, showing how to use the library in your projects:

                      Latest from Code with Andrea

                      Hereโ€™s what Iโ€™ve been up to this past month:

                      One More Thing

                      My Black Friday Sale will start on Monday 25th.

                      If youโ€™ve been planning to get my courses, wait until then, as I'll be offering a big discount.

                      Other than that, Iโ€™ll continue to share new content as usual, including many new tips about the upcoming Flutter 3.27 release! ๐Ÿ—“๏ธ

                      Thanks for reading, and happy coding! ๐ŸŽ‰

                      ]]>
                      https://codewithandrea.com/tips/update-android-project-script/Script to Update the Android Project SettingsUse this script to update the Gradle, Java, NDK version and other settings in your Android project.https://codewithandrea.com/tips/update-android-project-script/Mon, 18 Nov 2024 02:00:00 +0100Tired of dealing with Gradle and other build errors on Android? ๐Ÿคฎ

                      Me too! So, I built a script to fix it all at once:

                      • Gradle version
                      • Java version
                      • NDK version
                      • Min SDK
                      • Target SDK

                      The result? Faster updates and fewer headaches. ๐Ÿ‘

                      Script to Update the Android Project Settings

                      You can grab the script here:

                      To use it:

                      • Download the script and add it to a folder in your system PATH
                      • Give it execution access: chmod +x update-android-project.sh
                      • Tweak the versions if needed
                      • Run it from the root of your Flutter project

                      Some notes:

                      • The script may not work perfectly for older Android projects. If your Android project is very old, the best fix is to nuke it and create it again with the Flutter CLI, as described here: Fixing Build Issues - Nuclear Option

                      Happy coding!

                      ]]>
                      https://codewithandrea.com/videos/how-to-design-flutter-app-icons-figma/How to Design Your Flutter App Icons in FigmaA video showing how to create a custom app icon from scratch in Figma, even if you're new to design.https://codewithandrea.com/videos/how-to-design-flutter-app-icons-figma/Fri, 15 Nov 2024 01:00:00 +0100The app icon and app store screenshots are the first things people notice when browsing the app store.

                      A well-designed icon grabs attention and makes your app stand out. ๐Ÿ’ก

                      But letโ€™s be real. If youโ€™re a busy developer, designing the app icon is probably the last thing on your mind, and your home screen might look like this:

                      Every Flutter developer's iOS home screen

                      Good news: it doesnโ€™t have to be this way. ๐Ÿ‘‡

                      How to Design a Flutter App Icon in Figma

                      While having design skills is helpful, you donโ€™t need to be a pro to create a simple app icon.

                      In this video, Iโ€™ll show you how to design a custom app icon from scratchโ€”even if you're new to design.

                      Summary

                      To help you follow along, hereโ€™s the app icon template I shared in the video.

                      Hereโ€™s a quick recap of the steps:

                      • Start with a 1000x1000px square for your icon background.
                      • Design the foreground by combining shapes. Group them and center them.
                      • For iOS icons, scale the foreground content to 75% of the total size.
                      • For Android icons, scale the foreground content to 50% of the total size.
                      • Android icons require separate foreground and background layers.
                      • Export your icons with 1024w as the size selector.

                      Once you're done, add the icons as assets to your Flutter project and generate the launcher icon using the flutter_launcher_icons package.

                      Extra tip: Importing FlatIcon icons into Figma

                      Want to save time? Search for icons on FlatIcon.

                      Once you find a suitable icon, export it as PNG or SVG and import it into Figma (remember to check the license!).

                      I recommend using SVG since it allows you to edit individual layers in Figma, though note that SVG export is a paid feature.

                      Wrap Up

                      As weโ€™ve seen, creating a simple icon in Figma is fairly straightforward.

                      If youโ€™d like to reuse my design, you can grab my template here.

                      Even better - you can use the Expo App Icon & Splash community template, which follows Appleโ€™s and Androidโ€™s official design guidelines.

                      New course: Flutter in Production

                      Designing app icons is a small but important step in the app development process.

                      But when it comes to shipping and monitoring apps in production, there are many more things to consider:

                      • Preparing for release: splash screens, flavors, environments, error reporting, analytics, force update, privacy, T&Cs
                      • App Submissions: app store metadata & screenshots, compliance, testing vs distribution tracks, dealing with rejections
                      • Release automation: CI workflows, environment variables, custom build steps, code signing, uploading to the stores
                      • Post-release: error monitoring, bug fixes, addressing user feedback, adding new features, over-the-air updates

                      My latest course will help you get your app to the stores faster and with fewer headaches.

                      If youโ€™re interested, you can learn more and enroll here. ๐Ÿ‘‡

                      ]]>
                      https://codewithandrea.com/tips/error-throw-with-stack-trace/Error.throwWithStackTraceWith Error.throwWithStackTrace, you can throw custom exceptions while keeping the original stack trace intact.https://codewithandrea.com/tips/error-throw-with-stack-trace/Tue, 12 Nov 2024 02:00:00 +0100Did you know?

                      Using domain-specific exceptions makes your code easier to test and maintain.

                      But donโ€™t lose the original stack trace for debugging!

                      With Error.throwWithStackTrace, you can throw custom exceptions while keeping the original stack trace intact. ๐Ÿ‘‡

                      Error.throwWithStackTrace

                      To learn more, read the official docs:

                      Happy coding!

                      ]]>
                      https://codewithandrea.com/tips/apple-small-business-program/Apple Small Business ProgramIf your app sales are less than $1M/year, you can apply to the Small Business Program and slash your fees to 15%!https://codewithandrea.com/tips/apple-small-business-program/Thu, 7 Nov 2024 02:00:00 +0100Did you know?

                      If you sell apps on the iOS App Store, Apple will take a big cut:

                      • paid apps & IAPs: 30%
                      • subscriptions: 30% in the first year, 15% after

                        But if you make less than $1M/year, you can apply to the Small Business Program and slash your fees to 15%!

                        That's a no-brainer! ๐Ÿ’ฐ

                        Apple Small Business Program

                        Read this page to learn more and enroll:

                        Happy coding!

                        ]]>
                        https://codewithandrea.com/tips/riverpod-prodivers-with-ref/Declaring Riverpod Providers with RefSince Riverpod 2.6.0, all generated providers can be declared with a Ref argument. Here's how to migrate to the new syntax.https://codewithandrea.com/tips/riverpod-prodivers-with-ref/Mon, 4 Nov 2024 02:00:00 +0100Did you know?

                        Since Riverpod 2.6.0, all generated providers can be declared with a Ref argument.

                        The old [ProviderName]Ref syntax is deprecated.

                        To upgrade existing projects, simply run: dart run custom_lint --fix. ๐Ÿ‘

                        Declaring Riverpod Providers with Ref

                        Note: in order to update all providers in your codebase, custom_lint needs to be installed and configured:

                        # pubspec.yaml dev_dependencies: custom_lint: 0.7.0 riverpod_lint: 2.6.2
                        # analysis_options.yaml analyzer: plugins: - custom_lint

                        Once this is done, run: dart run custom_lint --fix.

                        Happy coding!

                        ]]>
                        https://codewithandrea.com/newsletter/october-2024/October 2024: Flutter Fundamentals, Memory Leaks, Offline-First Apps, In-App Payments, New UI/UX PackagesAlso included: getting a Flutter job, auto stop services (Firebase extension), animated Flutter widgets, and the latest from Code with Andrea.https://codewithandrea.com/newsletter/october-2024/Thu, 24 Oct 2024 02:00:00 +0200Welcome to another edition of my Flutter newsletter!

                        This month, weโ€™re diving into some essential reads, including articles on memory leaks, offline-first apps, and in-app payments with RevenueCat. Plus, Iโ€™ve got a fresh batch of packages and resources to help you with your Flutter projects.

                        Letโ€™s jump right in! ๐Ÿš€

                        Flutter Fundamentals

                        A recent addition to the Flutter docs, Flutter Fundamentals is a must-read for anyone new to Flutter. If you've already completed your first codelab, this guide will take you further by covering essential topics like widgets, layout, state management, and handling user input.

                        If you missed this update, now may be the perfect time to explore these core concepts and better understand how Flutter works.

                        Check it out here:

                        Flutter Articles

                        Here are some curated reads from the Flutter community this month.

                        ๐Ÿ“ Getting a Flutter Job

                        Landing a Flutter job isnโ€™t just about searching for โ€œFlutterโ€ in job listings. Eric Seidel, former founder/lead of Flutter, emphasizes the importance of focusing on broader mobile jobsโ€”many companies donโ€™t even realize they need Flutter yet!

                        He also suggests targeting smaller companies and startups, or even approaching companies directly with projects that showcase your skills. This article is packed with practical tips on networking, building a portfolio, and even cold outreach.

                        If youโ€™re serious about finding a Flutter role, this is a must-read:

                        ๐Ÿ“ Letโ€™s Talk About Memory Leaks In Dart And Flutter

                        Memory leaks are tricky to detect and can cause performance issues in long-running Flutter apps. In this article, Majid Hajian discusses common causes of memory leaks in Dart and Flutter, focusing on the challenges introduced by asynchronous programming and Streams.

                        He also reviews tools like DevTools, Leak Tracker, and DCM's static analysis to help identify and prevent leaks early in development.

                        Check out the full article to improve your appโ€™s performance:

                        ๐Ÿ“ How to Add In-App Payments With RevenueCat in Flutter

                        Monetizing your Flutter app is crucial, but setting up payments can be a headache.ย So here's a comprehensive guide on integrating RevenueCat into your Flutter app to handle in-app purchases smoothly.

                        The guide shows how to create a RevenueCat account, link Google Play Console to RevenueCat, configure products, entitlements, and offerings, and use the RevenueCat plugin to display products, manage purchases, and handle cancellations.

                        If you're planning to add payments to your app, check it out:

                        ๐Ÿ“ Building Offline-First Mobile Apps with Supabase, Flutter and Brick

                        Brick is a powerful data manager for Flutter that simplifies syncing and caching data between Supabase and local storage like SQLite, making it ideal for building offline-first apps.

                        This article explains how Brick ensures your app works seamlessly without an internet connection, while also speeding up performance through local caching. If you want to build robust mobile apps that can handle both offline and online scenarios, this guide walks you through setting up Brick with Supabase step by step.

                        Check out the full guide here:

                        Flutter Packages and Tools

                        Here are some packages, tools, and open source examples you can use to improve your Flutter apps.

                        ๐Ÿ”ฅ Auto Stop Services (Firebase Extension)

                        Worried about unexpected Firebase costs? The Auto Stop Services extension helps you avoid cost overruns by automatically disabling Firebase and Google Cloud services once your project reaches a predefined budget threshold.

                        You can either stop all services by removing the billing account, or selectively disable specific services. This tool helps you keep your projectโ€™s budget in check, supporting different use cases for production and non-production environments:

                        ๐Ÿ™ Flutterfx Widget - Animated Flutter Widgets

                        Looking to add some flair to your Flutter app? Flutterfx Widget offers a growing collection of animated widgets, with new additions every week.

                        Each animation is implemented as a separate widget, making it easy to understand and integrate into your own projects:

                        ๐Ÿงฑ Packages to Improve UI/UX of your App

                        Recently, thereโ€™s been a surge of new UI/UX packages, helping you customize the look and feel of your Flutter apps.

                        This Reddit thread contains some good suggestions:

                        In addition, here are a few standout packages Iโ€™ve discovered on pub.dev:

                        • pretty_animated_text: Easily add beautiful, customizable animated text widgets to your project.
                        • soft_edge_blur: Apply smooth, soft blur effects to your widgets for a polished, modern look.
                        • forui: a UI library for Flutter that provides a set of minimalistic widgets heavily inspired by shadcn/ui.

                        You might find some gems here to add some extra shine to your appโ€™s UI and UX. ๐Ÿ’Ž

                        Latest from Code with Andrea

                        After months of hard work, I finally launched Flutter in Production! The course already includes nine modules, each with a free introductory lesson. If you want a sneak peek, you can check the course introduction.

                        On top of that, Iโ€™ve shared a personal record of 14 new Flutter tipsโ€”so thereโ€™s plenty to explore. ๐Ÿ™‚

                        But thatโ€™s not all. Iโ€™ve also published three new articles this month. ๐Ÿ‘‡

                        ๐Ÿ“ 6 Key Steps to Take Before Releasing Your Next Flutter App

                        In this article, I break down six important steps you need to follow before hitting "publish". From setting up flavors and environments to keep your development and production separate, to implementing error monitoring and analytics for tracking performance and user behavior, these steps will help ensure a smooth release.

                        Plus, I cover strategies for handling force updates, collecting user feedback, and prompting in-app reviews:

                        ๐Ÿ“ How to Setup Flutter & Firebase with Multiple Flavors Using the FlutterFire CLI

                        If your Flutter app supports multiple flavors (like development, staging, and production), you'll need to set up separate Firebase environments for each. In this guide, I break down how to use the FlutterFire CLI to manage Firebase configurations across different flavors, ensuring a clear separation between environments.

                        From configuring Firebase projects to streamlining the process with shell scripts, this article will save you time and prevent setup headaches:

                        ๐Ÿ“ How to Ask for In-App Reviews in Your Flutter App

                        App reviews play an important role in the success of your apps. In this article, I walk you through using the in_app_review package to prompt users for reviews at the perfect momentโ€”when theyโ€™re most engaged and satisfied.

                        From setting up the package to timing the prompt properly (without overwhelming users), this guide helps you boost your appโ€™s rating and visibility in the store:

                        Until Next Time

                        The past few months have been quite intense, so Iโ€™m taking a much-needed break. ๐Ÿ–๏ธ

                        Once Iโ€™m back, Iโ€™ll be diving into more articles, tips, and course content to keep you up to speed with all things Flutter.

                        Also, a heads-up: my Black Friday sale is approaching. If youโ€™ve been waiting to get my courses, youโ€™ll be able to grab them with a big discount next month. (Note: Flutter in Production is already 40% off, so it won't receive a further discount.)

                        Thanks for reading, and happy coding!

                        ]]>
                        https://codewithandrea.com/tips/flutter-pub-upgrade/What does flutter pub upgrade do?If you want to upgrade all dependencies to the latest non-major version, ignoring the pubspec.lock file, use flutter pub upgrade.https://codewithandrea.com/tips/flutter-pub-upgrade/Wed, 23 Oct 2024 03:00:00 +0200Did you know?

                        There's a subtle difference between pub get and pub upgrade:

                        • pub get will get all dependencies, keeping the versions inside pubspec.lock.
                        • pub upgrade will upgrade all dependencies to the latest non-major version, ignoring the pubspec.lock file.
                        What does flutter pub upgrade do?

                        Also note:

                        • If pubspec.lock doesn't exist yet, both commands behave identically.
                        • Neither command updates any dependencies that are locked to a specific version (no caret syntax).

                        To learn more, read these resources:

                        Happy coding!

                        ]]>
                        https://codewithandrea.com/tips/firebase-init-multiple-flavors/Firebase Initialization with Multiple Flavors in DartAn overview of two different strategies for initializing Firebase inside a Flutter app with multiple flavors.https://codewithandrea.com/tips/firebase-init-multiple-flavors/Fri, 18 Oct 2024 03:00:00 +0200Did you know?

                        If your Flutter app has multiple flavors, you can put all the Firebase initialization logic in one file and switch based on the appFlavor.

                        Firebase Init with Multiple Flavors (switch expression)

                        Note: when you do this, all the Firebase config files are bundled in the final app, which is not ideal.

                        A better solution is to create three entry points that load the corresponding config file and pass it to the function that performs the actual initialization.

                        When running, you can use the -t flag to specify the entry point:

                        Firebase Init with Multiple Flavors (multiple entry points)

                        This requires a bit more work but is more secure. ๐Ÿ‘


                        My latest course covers flavors and environments in great depth.

                        To learn more, check it out here:

                        ]]>
                        https://codewithandrea.com/articles/flutter-firebase-multiple-flavors-flutterfire-cli/How to Setup Flutter & Firebase with Multiple Flavors using the FlutterFire CLILearn how to set up Firebase for multiple flavors in your Flutter app using the FlutterFire CLI. This guide covers iOS, Android, and web configurations.https://codewithandrea.com/articles/flutter-firebase-multiple-flavors-flutterfire-cli/Fri, 18 Oct 2024 02:00:00 +0200If your Flutter app supports multiple flavors and connects to Firebase, you need some extra setup to ensure each flavor corresponds to a different Firebase environment.

                        The best approach is to create a separate Firebase project for each flavor. This keeps your development, staging, and production environments separate.

                        Each Flutter flavor maps to a separate Firebase project

                        When using custom backends or Dart SDKs like Supabase, you can connect to the correct environment by switching the URL and API key based on the flavor. But Firebase does not offer a Dart SDK and requires some platform-specific setup, making the flavoring process more complex.

                        Thankfully, the FlutterFire CLI comes to the rescue. I'll walk you through using it to flavor your Flutter & Firebase apps without losing your mind. ๐Ÿ˜…

                        Here's what we will cover:

                        • Why do we need FlutterFire?
                        • Installing the Firebase and FlutterFire CLI
                        • FlutterFire Config Syntax for Multiple Flavors
                        • Easier Setup with a Shell Script
                        • Initializing Firebase during App Startup (iOS, Android, and web)

                        By the end, you'll be able to confidently integrate Firebase into your multi-flavor Flutter app, saving time and avoiding common setup headaches.

                        Prerequisites

                        Note: This guide assumes you already have a Flutter app that runs correctly with dev, stg, and prod flavors on iOS and Android:

                        flutter run --flavor dev flutter run --flavor stg flutter run --flavor prod

                        You'll also need three Firebase projects, like in this example:

                        Three Firebase projects for the Flutter app

                        You can use Flutter Flavorizr to add flavors to your app. For more guidance, check out my Flutter in Production course.

                        Ready? Let's go! ๐Ÿš€

                        Why do we need FlutterFire?

                        Adding Firebase to a Flutter app used to be a tedious process. Youโ€™d have to manually download configuration files for each platform (likeย GoogleService-Info.plistย for iOS andย google-services.jsonย for Android).

                        Now, the process isย much simpler. You just runย flutterfire configureย and follow some interactive prompts. Once finished, these files are automatically added to your project:

                        • lib/firebase_options.dart
                        • ios/Runner/GoogleService-Info.plist
                        • android/app/google-services.json

                        However, when working with multiple flavors, it gets trickier. Youโ€™ll needย separate versionsย of these files for each flavor, stored in different locations to avoid overwriting them during the configuration process.

                        Luckily,ย FlutterFire 1.0.0 added support for multiple flavors. Let's explore how to use it.

                        Installing the Firebase and FlutterFire CLI

                        The official docs cover all the steps for installing the Firebase and FlutterFire CLIs.

                        The Firebase CLI can be installed as a standalone library or via npm. Iโ€™ve found the npm approach to be the most reliable:

                        npm install -g firebase-tools

                        To check your installation, run: firebase --version.

                        Next, log in by runningย firebase login. Youโ€™ll see this prompt:

                        ? Allow Firebase to collect CLI and Emulator Suite usage and error reporting information? Yes

                        This opens a browser window where you can sign in with Google. After selecting your account, youโ€™ll see this:

                        Firebase CLI wants to access your Google Account

                        Click "Allow" and close the window. Now, the Firebase CLI is logged in.

                        Note: Make sure you use the Google account linked to the Firebase projects you want to work with. If youโ€™re logged in with the wrong account, runย firebase logoutย and firebase login again.

                        Installing the FlutterFire CLI

                        To install the FlutterFire CLI, run:

                        dart pub global activate flutterfire_cli

                        Then, check that youโ€™re on versionย 1.0.0ย or above by runningย flutterfire --version.

                        FlutterFire Config Syntax with Multiple Flavors

                        Let's consider this command, which generates all the config files for theย devย flavor:

                        flutterfire config \ --project=flutter-ship-dev \ --out=lib/firebase_options_dev.dart \ --ios-bundle-id=com.codewithandrea.flutterShipApp.dev \ --ios-out=ios/flavors/dev/GoogleService-Info.plist \ --android-package-name=com.codewithandrea.flutter_ship_app.dev \ --android-out=android/app/src/dev/google-services.json

                        Hereโ€™s what each argument does:

                        • --project: The Firebase project to use (note: pass the project ID, not the alias).
                        • --out: Output path for the Firebase config file.
                        • --ios-bundle-id: iOS appโ€™s bundle ID. Find it in Xcode underย Runnerย >ย Generalย >ย Identityย > Bundle Identifier.
                        • --ios-out: Output path for the iOSย GoogleService-Info.plist.
                        • --android-package-name: Android appโ€™s package name (found asย applicationIdย inย android/app/build.gradle.kts).
                        • --android-out: Output path for the Androidย google-services.json.

                        To learn about all the available options, run flutterfire config --help.

                        To use this command, you can:

                        1. Copy it into your terminal.
                        2. Update theย project,ย ios-bundle-id, andย android-package-nameย for your app.
                        3. Run it and follow the interactive prompts (weโ€™ll cover these in a moment).

                        But youโ€™ll need to repeat this for theย stgย andย prodย flavors. Thatโ€™s time-consuming and error prone.

                        Let's see if we can automate the process. ๐Ÿ‘‡

                        Easier Setup with a Shell Script

                        Whileย flutterfire configย handles most of the work, you still need to run it for each flavor with different arguments.

                        To streamline this, create aย flutterfire-config.shย script and save it at the root of your project:

                        #!/bin/bash # Script to generate Firebase configuration files for different environments/flavors # Feel free to reuse and adapt this script for your own projects if [[ $# -eq 0 ]]; then echo "Error: No environment specified. Use 'dev', 'stg', or 'prod'." exit 1 fi case $1 in dev) flutterfire config \ --project=flutter-ship-dev \ --out=lib/firebase_options_dev.dart \ --ios-bundle-id=com.codewithandrea.flutterShipApp.dev \ --ios-out=ios/flavors/dev/GoogleService-Info.plist \ --android-package-name=com.codewithandrea.flutter_ship_app.dev \ --android-out=android/app/src/dev/google-services.json ;; stg) flutterfire config \ --project=flutter-ship-stg \ --out=lib/firebase_options_stg.dart \ --ios-bundle-id=com.codewithandrea.flutterShipApp.stg \ --ios-out=ios/flavors/stg/GoogleService-Info.plist \ --android-package-name=com.codewithandrea.flutter_ship_app.stg \ --android-out=android/app/src/stg/google-services.json ;; prod) flutterfire config \ --project=flutter-ship-prod \ --out=lib/firebase_options_prod.dart \ --ios-bundle-id=com.codewithandrea.flutterShipApp \ --ios-out=ios/flavors/prod/GoogleService-Info.plist \ --android-package-name=com.codewithandrea.flutter_ship_app \ --android-out=android/app/src/prod/google-services.json ;; *) echo "Error: Invalid environment specified. Use 'dev', 'stg', or 'prod'." exit 1 ;; esac

                        With this script, you still need to set the correct arguments for your project, but you only need to do this once.

                        Then, generating all the Firebase config files becomes a breezeโ€”no need to remember each argument.

                        Time to take take the script for a ride. ๐Ÿ‘‡

                        Running the FlutterFire Script for each Flavor

                        To configure the dev flavor, run:

                        ./flutterfire-config.sh dev

                        When prompted, select "Build configuration":

                        ? You have to choose a configuration type. Either build configuration (most likely choice) or a target set up. โ€บ โฏ Build configuration Target

                        Then, choose theย Debug-devย build configuration:

                        ? Please choose one of the following build configurations โ€บ Debug Release Profile โฏ Debug-dev Profile-dev Release-dev Debug-stg Profile-stg Release-stg Debug-prod Profile-prod Release-prod

                        Note: If you encounter aย "Failed to list Firebase projects"ย error, runย firebase logout, thenย firebase login, and try again.

                        Next, choose the platforms you want to configure:

                        ? Which platforms should your configuration support (use arrow keys & space to select)? โ€บ โœ” android โœ” ios macos โœ” web windows

                        This step may take some time as the CLI registers the necessary apps with Firebase. If successful, youโ€™ll see a confirmation similar to this:

                        โœ” You have to choose a configuration type. Either build configuration (most likely choice) or a target set up. ยท Build configuration โœ” Please choose one of the following build configurations ยท Debug-dev i Found 40 Firebase projects. Selecting project flutter-ship-dev. โœ” Which platforms should your configuration support (use arrow keys & space to select)? ยท android, ios, web i Firebase android app com.codewithandrea.flutter_ship_app.dev is not registered on Firebase project flutter-ship-dev. i Registered a new Firebase android app on Firebase project flutter-ship-dev. i Firebase ios app com.codewithandrea.flutterShipApp.dev is not registered on Firebase project flutter-ship-dev. i Registered a new Firebase ios app on Firebase project flutter-ship-dev. i Firebase web app flutter_ship_app (web) is not registered on Firebase project flutter-ship-dev. i Registered a new Firebase web app on Firebase project flutter-ship-dev. Firebase configuration file lib/firebase_options_dev.dart generated successfully with the following Firebase apps: Platform Firebase App Id web 1:424176442589:web:c86e231d1eeaba0e90cf34 android 1:424176442589:android:c5841ba53606b4c490cf34 ios 1:424176442589:ios:592b56a800affa4e90cf34 Learn more about using this file and next steps from the documentation: > https://firebase.google.com/docs/flutter/setup

                        Next, repeat the same steps for theย stgย flavor by running:

                        ./flutterfire-config.sh stg

                        And again for theย prodย flavor:

                        ./flutterfire-config.sh prod

                        Once complete, your project will have these new files:

                        lib/firebase_options_dev.dart lib/firebase_options_stg.dart lib/firebase_options_prod.dart ios/flavors/dev/GoogleService-Info.plist ios/flavors/stg/GoogleService-Info.plist ios/flavors/prod/GoogleService-Info.plist android/app/src/dev/google-services.json android/app/src/stg/google-services.json android/app/src/prod/google-services.json

                        Should the Firebase config files be added to Git?

                        The files above donโ€™t contain sensitive information, so itโ€™s safe to commit them to Git.

                        However, in my open-source projects, I prefer to add them toย .gitignore:

                        # Ignore Firebase configuration files lib/firebase_options*.dart ios/Runner/GoogleService-Info.plist ios/flavors/*/GoogleService-Info.plist macos/Runner/GoogleService-Info.plist macos/flavors/*/GoogleService-Info.plist android/app/google-services.json android/app/src/*/google-services.json

                        This has two implications:

                        • For a fresh checkout, youโ€™ll need to runย flutterfire-config.shย again for each flavor.
                        • On CI, you can store these files as environment secrets and add a pre-build step to restore them to their correct locations.

                        FlutterFire setup complete โœ…

                        If you followed all the steps above without errors, all the Firebase configuration files should now be in your project.

                        Before running the app with Firebase, there are a few more steps to tackle:

                        • Install the firebase_core package and verify the app runs on Android and iOS
                        • Initialize Firebase when the app starts

                        Letโ€™s walk through them. ๐Ÿ‘‡

                        Installing the firebase_core package

                        To addย firebase_core, run this in your terminal:

                        flutter pub add firebase_core flutter pub get

                        Running the app on Android

                        If your Android app was configured with FlutterFire CLI 1.1.0 or above, it should run without errors.

                        But if your FlutterFire setup is incorrect, you may encounter this error:

                        Plugin [id: 'com.google.gms.google-services'] was not found in any of the following sources: - Gradle Core Plugins (plugin is not in 'org.gradle' namespace) - Included Builds (No included builds contain this plugin) - Plugin Repositories (plugin dependency must include a version number for this source)

                        To fix this, openย android/settings.gradle.ktsย and ensure com.google.gms.google-servicesย is added as a plugin:

                        plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("com.android.application") version "8.7.0" apply false // START: FlutterFire Configuration id("com.google.gms.google-services") version("4.3.15") apply false // END: FlutterFire Configuration id("org.jetbrains.kotlin.android") version "1.8.22" apply false }

                        The same plugin should also be listed in the plugins block in android/app/build.gradle.kts:

                        plugins { id("com.android.application") // START: FlutterFire Configuration id("com.google.gms.google-services") // END: FlutterFire Configuration id("kotlin-android") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id("dev.flutter.flutter-gradle-plugin") }

                        After applying this fix, the Android app should run correctly.

                        Note: you can find the latest version of com.google.gms.google-services in Google's Maven Repository.

                        Running the app on iOS

                        Before running the iOS app, openย ios/Podfileย and ensure the platform version is set toย 13.0ย or higher:

                        # Uncomment this line to define a global platform for your project platform :ios, '13.0'

                        Runย pod install, and you should be able to run the app on iOS.

                        Firebase Initialization During App Startup

                        According to theย official docs, you should add the Firebase initialization code toย lib/main.dart:

                        import 'package:flutter_ship_app/firebase_options.dart'; // inside main() await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, );

                        However, this default setup wonโ€™t work for us because we have separate configuration files for each flavor:

                        The Firebase options files

                        So, how can we handle this?

                        Option 1: Centralize the Firebase Initialization logic

                        One option is to create a firebase.dartย file with the following code:

                        // firebase.dart import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_ship_app/firebase_options_prod.dart' as prod; import 'package:flutter_ship_app/firebase_options_stg.dart' as stg; import 'package:flutter_ship_app/firebase_options_dev.dart' as dev; Future<void> initializeFirebaseApp() async { // Determine which Firebase options to use based on the flavor final firebaseOptions = switch (appFlavor) { 'prod' => prod.DefaultFirebaseOptions.currentPlatform, 'stg' => stg.DefaultFirebaseOptions.currentPlatform, 'dev' => dev.DefaultFirebaseOptions.currentPlatform, _ => throw UnsupportedError('Invalid flavor: $flavor'), }; await Firebase.initializeApp(options: firebaseOptions); }

                        This works by switching on theย appFlavorย constant to return the correctย FirebaseOptionsย object based on the flavor.

                        Note: When running on Flutter web with the --flavor option, you'll get a warning you that flavors are not fully supported. But the appFlavor constant will still return the correct value.

                        Now, you can simply callย await initializeFirebaseApp()ย inย lib/main.dart, which remains theย singleย entry point for the app:

                        import 'firebase.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await initializeFirebaseApp(); runApp(const MainApp()); }

                        With this setup, the Flutter app will initialize and connect to the correct Firebase project, depending on the flavor.

                        However, there's one issue. ๐Ÿ‘‡

                        All the Firebase Config Files are Bundled (No Tree Shaking)

                        If you look closely atย firebase.dart, youโ€™ll notice that although the correct Firebase config is selected based on the flavor,ย all threeย firebase_options_*.dartย files are still imported:

                        // firebase.dart import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; // Note: all three files are imported import 'package:flutter_ship_app/firebase_options_prod.dart' as prod; import 'package:flutter_ship_app/firebase_options_stg.dart' as stg; import 'package:flutter_ship_app/firebase_options_dev.dart' as dev; Future<void> initializeFirebaseApp() async { // Determine which Firebase options to use based on the flavor final firebaseOptions = switch (appFlavor) { 'prod' => prod.DefaultFirebaseOptions.currentPlatform, 'stg' => stg.DefaultFirebaseOptions.currentPlatform, 'dev' => dev.DefaultFirebaseOptions.currentPlatform, _ => throw UnsupportedError('Invalid flavor: $flavor'), }; await Firebase.initializeApp(options: firebaseOptions); }

                        This means thatย all three files are compiled and bundledย during the build process because tree-shaking doesn't work here (the switch happens at runtime).

                        In theory, this could expose your development or staging environment details (which may be less secure than production) if someone reverse engineers your app.

                        While this might not be a big issue for apps that donโ€™t handle sensitive data, itโ€™s still a potential risk. If you want to mitigate this entirely, consider a more secure approach. ๐Ÿ‘‡

                        Option 2: Use Multiple Entry Points

                        As we've seen, this code can be problematic:

                        import 'package:flutter_ship_app/firebase_options_prod.dart' as prod; import 'package:flutter_ship_app/firebase_options_stg.dart' as stg; import 'package:flutter_ship_app/firebase_options_dev.dart' as dev;

                        A more secure approach is to create three separate entry pointsโ€”main_dev.dart, main_stg.dart, and main_prod.dartโ€”which look like this:

                        // main_dev.dart import 'package:flutter_ship_app/firebase_options_dev.dart'; import 'main.dart'; void main() async { runMainApp(DefaultFirebaseOptions.currentPlatform); }

                        These files should do one thing only: import the correct firebase_options_*.dart file and pass the config as an argument to a function inside main.dart that performs the actual initialization. Hereโ€™s an example:

                        // main.dart import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; void runMainApp(FirebaseOptions firebaseOptions) async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(options: firebaseOptions); runApp(const MainApp()); }

                        This approach ensures that only the required Firebase configuration file is bundled, making it a secure and efficient solution for managing multiple flavors.

                        But how do you run the app with the right flavor? ๐Ÿ‘‡

                        Running the App with a Specific Flavor

                        If you use the second option outlined above, you'll have four files:

                        • main_dev.dart: Entry point for dev
                        • main_stg.dart: Entry point for stg
                        • main_prod.dart: Entry point for prod
                        • main.dart: Contains the app initialization code

                        As a result, you can run the app with a specific flavor using these commands:

                        flutter run --flavor dev -t lib/main_dev.dart flutter run --flavor stg -t lib/main_stg.dart flutter run --flavor prod -t lib/main_prod.dart

                        This way, the correct entry point is used for each flavor, ensuring that the right Firebase environment is connected when the app launches.

                        If you follow this approach, make sure to update your local configuration (e.g., .vscode/launch.json) and CI/CD scripts to reflect these changes.

                        Which Option Should you Choose?

                        Both options have their pros and cons, and the right choice depends on your projectโ€™s needs:

                        • Option 1: Centralized Firebase Initialization. This approach is easier and quicker to implement. It allows you to use a single main.dart file and handle flavor-specific Firebase options dynamically at runtime. However, because all Firebase configuration files are bundled in the final app (even if theyโ€™re not used), it's not the best choice for security reasons.
                        • Option 2: Multiple Entry Points for Each Flavor. This option requires a bit more setup because youโ€™ll need to create separate entry points for each flavor (main_dev.dart, main_stg.dart, main_prod.dart). However, it only bundles the necessary Firebase configuration file for each build, making it a more secure solution, since an attacker wonโ€™t have access to the environment details of other flavors.

                        While option 2 takes a bit more work, I recommend it for multi-flavor Flutter apps that use Firebase. ๐Ÿ‘

                        Option 1 works just fine for non-Firebase apps, since you can use --dart-define-from-file and define environment variables inside separate files (e.g. .env.dev, .env.stg, .env.prod) for each flavor. To learn more, read: How to Store API Keys in Flutter: --dart-define vs .env files.

                        Conclusion

                        By leveragingย FlutterFireย alongside a simple shell script, weโ€™ve streamlined what used to be a complex and error-prone flavoring process. Instead of manually configuring Firebase for each flavor, you can now generate the necessary files for all environments with a single script, saving time and reducing the chance of mistakes.

                        The centralized Firebase initialization option offers a quick and simple way to connect your app to the correct Firebase project at startup, using a single main.dart file and flavor-specific logic. However, this approach bundles all Firebase configurations, which may not be ideal for security-sensitive apps.

                        For more secure setups, the multiple entry points strategy ensures that only the necessary Firebase configuration is included in each build, making it a better choice when handling sensitive data or production-grade apps.

                        With either approach, your Flutter app will automatically connect to the appropriate Firebase environmentโ€”whether thatโ€™s dev, stg, or prodโ€”ensuring your app behaves as expected in every stage of development and production.

                        Each Flutter flavor maps to a separate Firebase project

                        This setup makes it easier to manage multiple flavors in your Flutter & Firebase apps, and I've been happily using it in production for my own apps. โœ…

                        New Course: Flutter in Production

                        When it comes to shipping and maintaining apps in production, there are many important aspects to consider:

                        • Preparing for release: splash screens, flavors, environments, error reporting, analytics, force update, privacy, T&Cs
                        • App Submissions: app store metadata & screenshots, compliance, testing vs distribution tracks, dealing with rejections
                        • Release automation: CI workflows, environment variables, custom build steps, code signing, uploading to the stores
                        • Post-release: error monitoring, bug fixes, addressing user feedback, adding new features, over-the-air updates

                        My latest course will help you get your app to the stores faster and with fewer headaches.

                        If youโ€™re interested, you can learn more and enroll here. ๐Ÿ‘‡

                        ]]>
                        https://codewithandrea.com/tips/fixing-build-issues-nuclear-option/Fixing Build Issues - Nuclear OptionIf you have a Flutter project that no longer builds on a specific platform, you can delete the whole folder and generate it again.https://codewithandrea.com/tips/fixing-build-issues-nuclear-option/Wed, 16 Oct 2024 02:00:00 +0200Did you know?

                        If you have a Flutter project that no longer builds on a specific platform, you can try this:

                        • delete the whole folder
                        • use the Flutter CLI to generate it again
                        • discard any unwanted changes

                        When it works, this can save you hours of frustration. ๐Ÿ˜Œ

                        Fixing Build Issues - Nuclear Option

                        Here are the steps:

                        # Commit to git before making any changes git add . && git commit -m "Working copy" # Delete android folder rm -rf android # Create it again with the Flutter CLI flutter create . --platforms android # See what's changed, reapply previous settings git diff # Run again flutter run # All good? Commit to git git add . && git commit -m "Updated Android project"

                        To learn more about effective techniques for shipping your apps in production, check out my latest course:

                        ]]>
                        https://codewithandrea.com/tips/force-update-helper/Force Update with Remote ConfigIf you ever needed a force update prompt that is controlled remotely, you can use the force_update_helper package.https://codewithandrea.com/tips/force-update-helper/Tue, 15 Oct 2024 02:00:00 +0200Ever needed a force update prompt that is controlled remotely?

                        There's a package for that: forceupdatehelper.

                        This works by comparing the current app version with a required app version that is fetched from a remote source.

                        Force Update with Remote Config

                        The package requires a bit of setup, and this is all documented in the README:

                        Example apps are also included, showing how to use a GitHub Gist or a Dart Shelf app as the remote source.


                        To learn more about force update and how to get your app ready for production, check out my latest course:

                        ]]>
                        https://codewithandrea.com/tips/show-licenses-flutter-app/Show the Licenses in your Flutter appYour Flutter app should show the licenses for packages in use. This is often a legal requirement, as many open-source licenses require attribution.https://codewithandrea.com/tips/show-licenses-flutter-app/Mon, 14 Oct 2024 02:00:00 +0200Did you know?

                        Your Flutter app should show the licenses for packages in use. This is often a legal requirement, as many open-source licenses require attribution.

                        To do this, you have two options: - call the showLicensePage API directly - use the AboutListTile widget

                        Show the Licenses in your Flutter app

                        Note that the showLicensePage function displays licenses for all the packages your app depends on, including transitive dependencies. The large number of licenses is expected since even a few direct dependencies can pull in many others.

                        You can also use a more custom approach and get the raw licenses from the LicenseRegistry class.

                        For more details, read:

                        To learn more about how to get your app ready for production, check out my latest course:

                        ]]>
                        https://codewithandrea.com/articles/key-steps-before-releasing-flutter-app/6 Key Steps to Take Before Releasing your Next Flutter AppPrepare your Flutter app for launch with these 6 steps, including flavors and environments, error monitoring, force updates, and in-app reviews.https://codewithandrea.com/articles/key-steps-before-releasing-flutter-app/Fri, 11 Oct 2024 02:00:00 +0200Launching a Flutter app is more than just coding and hitting "publish". To increase your chances of success, there are crucial pre-release steps you need to take. These steps go beyond developmentโ€”they involve setting up systems to track your appโ€™s performance, gather user feedback, and ensure smooth updates.

                        In this article, weโ€™ll walk through six key challenges you need to consider before releasing your app:

                        • Flavors and Environments: keep development, testing, and production separate.
                        • Error Monitoring: catch bugs and crashes in production.
                        • Analytics: understand how users interact with your app.
                        • Force Update: ensure users are always on the latest version.
                        • In-app user feedback: gain valuable insights directly from your users.
                        • In-app reviews: improve your appโ€™s visibility and ranking in the stores.

                        Letโ€™s dive into each of these challenges. ๐Ÿ‘‡

                        Think of this article as a checklist covering all the pre-release steps. For advice about how to design and develop your app, read: 8 Steps to Follow When Building Your Next Flutter App.

                        1. Flavors and Environments

                        When building and releasing a Flutter app, one of the first challenges is managing different environments for development, testing, staging, and production.

                        An overview of the most common environments
                        An overview of the most common environments

                        Each environment should run in isolation to ensure that your production data, analytics, and error logs stay clean and unaffected during development. For example, you don't want test data mixing with production data, or error reports from development popping up in your production logs.

                        What Are Flavors?

                        Environments go hand-in-hand with flavors.

                        In Flutter, flavors allow you to build distinct versions of your app that connect to the right environment. These flavors can have different app icons, API keys, and even behavior. This is especially useful for QA testing because you can install multiple versions of your app on a single device without conflicts:

                        By supporting flavors, you can install and test multiple versions of your app simultaneously
                        By supporting flavors, you can install and test multiple versions of your app simultaneously

                        Flavors are also ideal in these scenarios:

                        • Free and paid: Publish separate versions of your app, deciding which features are available based on the flavor.
                        • Whitelabel apps: Rebrand and customize for multiple customers by applying cosmetic changes (assets, theming, etc.) for each version.

                        Note: If your app doesnโ€™t use user-generated content, third-party APIs, or analytics, and doesnโ€™t require different configurations for testing and production, you might not need multiple flavors or environments. In practice, I find this is only true for very simple apps.

                        How to Add Flavors to Your Flutter App

                        There are two main ways to set up flavors in Flutter:

                        1. Using the flutter_flavorizr package: This makes the process much quicker, but it works best on a new Flutter project since it works by modifying some specific project files with a specific folder structure.
                        2. Manual setup: This is more time-consuming and error-prone, but it may be your only choice if youโ€™re working on an existing project and need to retrofit flavors without breaking things.

                        Both approaches are covered in detail in my course about Flutter in Production.

                        Benefits of Using Flavors and Environments

                        • Safer development: Keep your testing and production environments separate to avoid data and error pollution.
                        • Multiple installs: Test multiple versions of your app on the same deviceโ€”great for QA and user acceptance testing.
                        • Whitelabel and free/paid versions: Easily manage different versions of your app with different features or branding.

                        2. Error Monitoring

                        Once your app is published, things get tricky. Unlike in development, where your IDE gives you instant feedback on crashes and errors, in production, you're flying blind unless you have proper error monitoring in place. When users encounter a crash, you need to know what went wrong, how often it happens, and how to fix itโ€”without relying on user reports.

                        And with tools like Sentry or Crashlytics, you can capture errors in real-time.

                        Sentry page showing the most frequent and recent issues in my Flutter Tips app
                        Sentry page showing the most frequent and recent issues in my Flutter Tips app

                        These tools give you detailed reports, including:

                        • Stack traces: Get the exact location in your code where the crash occurred.
                        • Device and OS information: Know which platform the error happened on.
                        • App version: See if the issue is confined to a specific release.
                        • Breadcrumbs: Discover what events led to the crash.

                        When to Add Error Monitoring

                        You should add error monitoring before your first release. Once your app is live, every crash that goes unnoticed is a potential user lost. Plus, if your app is being tested during the app store review process, a crash could lead to rejection. With error monitoring, youโ€™ll have immediate insights into what failed, so you can fix the issue quickly.

                        Using Sentry for Error Monitoring in Flutter

                        To monitor errors in your Flutter app, I recommend using Sentry.

                        When configured, Sentry will automatically capture and report unhandled exceptions. It also allows you to capture additional context, such as breadcrumbs (e.g., navigation events, HTTP requests) to help you trace what led up to the crash.

                        Benefits of Error Monitoring

                        • Proactive bug fixing: You can catch and fix issues without waiting for user reports.
                        • Improved app stability: As you address crashes earlier, your app becomes more reliable over time.
                        • Better user experience: Fewer crashes mean happier users, and happier users leave better reviews.

                        Donโ€™t wait until users start complainingโ€”add error monitoring now, and stay ahead of any issues.

                        3. Analytics

                        Youโ€™ve built an amazing app, but how do you know if people are using it the way you intended? Without analytics, youโ€™re left guessing about user behavior, engagement, and the effectiveness of your features.

                        Why You Need Analytics

                        Analytics allows you to make data-driven decisions instead of relying on assumptions. You can track how users interact with your app, which features they love, and where they might be getting stuck. Some key insights you can gain include:

                        • User Engagement: How often do users open your app? How long do they stay? Which features do they interact with the most?
                        • Retention: Are users coming back after the first session, or are they leaving after a single use?
                        • Feature Popularity: Which features are genuinely useful, and which ones are being ignored?

                        With this information, you can optimize your app to meet user needs, improve retention, and ultimately drive more revenue.

                        Choosing an Analytics Provider

                        When it comes to Flutter, you have several options for analytics providers. Two of the most popular choices are Firebase Analytics and Mixpanel. Both offer robust features but cater to different needs. Firebase is good for basic event tracking, while Mixpanel offers more advanced segmentation and funnel analysis.

                        Mixpanel user metrics for my Flutter Tips app
                        Mixpanel user metrics for my Flutter Tips app

                        Note: While App Store Connect and the Google Play Console provide basic analytics, their reports are limited to general app performance metrics like downloads, crashes, and retention. They donโ€™t capture custom in-app events or user behavior specific to your appโ€™s unique features. For deeper insights and more control, itโ€™s best to integrate a dedicated analytics SDK.

                        You'll also want to choose a suitable architecture for tracking custom events, page views, and more. Here's an example of what I use in one of my apps:

                        App Analytics Architecture with Navigator Observer
                        App Analytics Architecture with Navigator Observer

                        Benefits of Using Analytics

                        • Better Product Decisions: Understand which features work and which donโ€™t.
                        • Improved Retention: Identify why users might be dropping off and take action to keep them engaged.
                        • Monetization Insights: Optimize your revenue strategy by tracking purchases and subscriptions.

                        In short, analytics is essential if you want to grow your app and make informed decisions.

                        4. Force Update

                        Imagine this: youโ€™ve just discovered a critical bug in your app that needs to be fixed immediately. If youโ€™re developing a web app, you can quickly deploy the fix, and users will get it instantly. But on mobile, itโ€™s not that simple. Even though iOS and Android support automatic app updates, not all users have it enabled, and updates can take time.

                        Why You Need Force Update

                        Without a force update mechanism, you canโ€™t assume users will ever be on the latest version of your app. This can cause major headaches, such as:

                        • Missed security updates: Users on old versions might be vulnerable to issues youโ€™ve already fixed.
                        • Backend incompatibility: You may need to maintain old API versions because some users havenโ€™t updated.
                        • Limited feature availability: Some users wonโ€™t see your latest improvements or bug fixes unless they update.

                        By implementing a force update strategy, you can ensure that all users eventually upgrade to the latest version of your app, minimizing these risks.

                        Force update should be implemented in the very first version of your app. This diagram shows why:

                        The force update prompts will only appear for older versions that support the force update logic
                        The force update prompts will only appear for older versions that support the force update logic

                        How Force Update Works

                        The basic concept is simple: your app checks its current version against a minimum required version stored on your backend (or Firebase Remote Config, or even a GitHub Gist). If the app version is outdated, the user is blocked from continuing until they update.

                        Force update flow on iOS
                        Force update flow on iOS

                        To implement it in Flutter, I recommend using my force_update_helper package, which allows you to show a force update prompt that is controlled remotely.

                        Benefits of Force Update

                        • Ensure all users are on the latest version: No more worrying about outdated versions causing issues.
                        • Simplify backend maintenance: You can deprecate old API versions without disrupting users.
                        • Greater control over feature rollouts: Force users to update when major new features or critical bug fixes are released.

                        By adding a force update strategy early on, youโ€™ll save yourself from future headaches and ensure a better experience for your users.

                        As an alternative to force updates, you can use Shorebird to push app updates directly to your users, without going through the app submission process. You can use Shorebird for free for up to 5,000 patches per month.

                        5. In-App User Feedback

                        Crash reports are great for identifying technical bugs, but what if users want to provide direct feedback? Maybe theyโ€™ve encountered some confusing UI, or they want to suggest a new feature. Without an easy way to collect this feedback, youโ€™re missing out on valuable insights.

                        Why You Need In-App User Feedback

                        Making it easy for users to provide feedbackโ€”without leaving your appโ€”reduces friction and encourages them to share their thoughts. This helps you:

                        • Identify usability issues: Users might struggle with certain features, and their feedback can highlight these pain points.
                        • Prioritize new features: Understand what users want, so you can focus on building the features they care about most.
                        • Improve user satisfaction: By listening to your users and acting on their feedback, you build trust and loyalty.

                        Easy Feedback Collection with Flutter

                        Using the Feedback package, you can easily add an interactive feedback system to your Flutter app. When a user clicks a "Send Feedback" option within the app, they can describe the issue, take a screenshot, and submit it directly:

                        Example showing the feedback package in action
                        Example showing the feedback package in action

                        If integrated with a tool like Sentry (there's a package for that), this feedback can be automatically sent to your error tracking system, allowing you to address both bugs and user feedback in one place.

                        Benefits of In-App Feedback

                        • Lower friction: Users donโ€™t need to switch to email or social media to give feedbackโ€”they can do it right within the app.
                        • Actionable insights: Annotated screenshots help you understand exactly what users are experiencing.
                        • Improved user experience: By acting on feedback, you can continuously improve your app and show users that their input matters.

                        Adding in-app feedback is an easy win that can help you catch issues early and keep your users happy.

                        Note: In addition to in-app feedback, certain apps benefit from fostering an active community of users. Whether itโ€™s through a Discord server, Facebook group, or another platform, building a community can create a sense of belonging and accountability among users.

                        6. In-App Reviews

                        App reviews are critical to the success of any mobile app. They directly impact your app's visibility and ranking in the app stores. Positive reviews can boost your appโ€™s ranking and drive more downloads, while negative reviews can scare off potential users.

                        Why You Need In-App Reviews

                        To get good reviews, you need to make it easy for users to leave them. The best way to do this is by showing an in-app review prompt at the right momentโ€”when users are engaged and happy with your app. For instance, this could be after theyโ€™ve completed a task, leveled up in a game, or successfully used a feature. Here's an example from my Flutter Tips app:

                        When the user likes N tips, show the in-app rating prompt

                        By asking for reviews at the right time, you increase the chances of receiving positive feedback.

                        How to Ask for Reviews in Flutter

                        The easiest way to request reviews in Flutter is by using the in_app_review package, which allows you to show an in-app review dialog directly within your app:

                        In-app rating prompt for my Flutter Tips app
                        In-app rating prompt for my Flutter Tips app

                        Timing is Everything

                        If you show the review prompt right away, it may annoy users who havenโ€™t had enough time to form a positive opinion. On the other hand, if you wait too long, many users may never see the prompt. The sweet spot is showing the prompt when users are most satisfied with their experience.

                        Note: many popular apps show the rating prompt very early on, as soon as the user has completed the onboarding, or after making a purchase. While this may seem counter-intuitive, it appears to work well in practice.

                        Benefits of In-App Reviews

                        • More reviews: Making it easy for users to leave a review increases the number of reviews you receive.
                        • Higher ratings: Asking for reviews when users are engaged leads to more positive feedback.
                        • Boosted app visibility: More positive reviews improve your appโ€™s ranking in the store, driving more downloads.

                        Conclusion

                        Launching a Flutter app is an exciting milestone, but if you want to avoid headaches down the line, there are several key challenges you need to tackle before hitting "publish". From managing flavors and environments, to setting up error monitoring and analytics, each step ensures your app is ready for real-world use. Adding a force update mechanism, collecting in-app user feedback, and encouraging in-app reviews will help you maintain quality, keep users engaged, and grow your app's presence in the store.

                        These steps are essential if you're serious about launching your app successfully, and I cover them in detail in my latest course.

                        New course: Flutter in Production

                        When it comes to shipping and maintaining apps in production, there are many important aspects to consider:

                        • Preparing for release: splash screens, flavors, environments, error reporting, analytics, force update, privacy, T&Cs
                        • App Submissions: app store metadata & screenshots, compliance, testing vs distribution tracks, dealing with rejections
                        • Release automation: CI workflows, environment variables, custom build steps, code signing, uploading to the stores
                        • Post-release: error monitoring, bug fixes, addressing user feedback, adding new features, over-the-air updates

                        My latest course will help you get your app to the stores faster and with fewer headaches.

                        If youโ€™re interested, you can learn more and enroll here. ๐Ÿ‘‡

                        ]]>
                        https://codewithandrea.com/tips/dark-tinted-icons-ios-18/Dark and Tinted Icons on iOS 18How to enable dark and tinted icons on iOS 18 using the flutter_launcher_icons package.https://codewithandrea.com/tips/dark-tinted-icons-ios-18/Fri, 11 Oct 2024 02:00:00 +0200Did you know?

                        iOS 18 supports dark and tinted icons.

                        To enable this in your Flutter app:

                        • Add one icon variant with transparency
                        • Install and configure flutter_launcher_icons in your pubspec.yaml
                        • Run dart run flutter_launcher_icons

                        Then, run the app and join the dark side! ๐ŸŒš

                        Dark and Tinted Icons on iOS 18

                        Following all the app icon guidelines on iOS and Android can be tricky.

                        To make life easier, my new course includes a whole module about launcher icons and splash screens.

                        If you're interested, check it out here:

                        ]]>
                        https://codewithandrea.com/tips/flutterfire-config-multiple-flavors/FlutterFire Config with Multiple Flavors (Shell Script)If your Flutter app uses multiple flavors, you can use the FlutterFire CLI to generate the config files for each flavor.https://codewithandrea.com/tips/flutterfire-config-multiple-flavors/Thu, 10 Oct 2024 02:00:00 +0200Did you know?

                        If your Flutter app uses multiple flavors, you can use the FlutterFire CLI to generate the config files for each flavor.

                        FlutterFire Config with Multiple Flavors

                        Hereโ€™s what each argument does:

                        • --project: The Firebase project to use (note: pass the project ID, not the alias).
                        • --out: Output path for the Firebase config file.
                        • --ios-bundle-id: iOS appโ€™s bundle ID. Find it in Xcode underย Runnerย >ย Generalย >ย Identityย > Bundle Identifier.
                        • --ios-out: Output path for the iOSย GoogleService-Info.plist.
                        • --android-package-name: Android appโ€™s package name (found asย applicationIdย inย android/app/build.gradle).
                        • --android-out: Output path for the Androidย google-services.json.

                        Pro Tip: Create a Shell Script

                        To simplify the setup, here's a sample shell script that takes the flavor as an argument:

                        To use this script:

                        • Copy it to the root of your project
                        • Update the project, ios-bundle-id, and android-package-name for your app.
                        • Run it and follow the interactive prompts

                        This is only a small part of the Flutter app flavoring process.

                        For all the details, check my latest course. ๐Ÿ‘‡

                        ]]>
                        https://codewithandrea.com/tips/remote-config-github-gist/Remote Config via GitHub GistHere's how to remotely control the behaviour of your app by fetching some JSON from a GitHub gist.https://codewithandrea.com/tips/remote-config-github-gist/Wed, 9 Oct 2024 02:00:00 +0200Did you know?

                        You can remotely control the behaviour of your app by fetching some JSON from a GitHub gist.

                        This is super useful when implementing:

                        • Force update โœ…
                        • Feature flags ๐Ÿšฉ
                        • A/B testing ๐Ÿงช

                        No Firebase or custom backend needed! ๐Ÿ™Œ

                        Remote Config via GitHub Gist

                        I cover this in more detail in my latest course.

                        Check it out here and take advantage of my launch sale (currently 40% off!) ๐Ÿ‘‡

                        ]]>
                        https://codewithandrea.com/tips/copilot-generate-commit-messages/Generate Commit Messages with CopilotIf you're not too picky about how you write your commit messages, this can be a neat little time saver!https://codewithandrea.com/tips/copilot-generate-commit-messages/Tue, 8 Oct 2024 02:00:00 +0200Did you know that GitHub Copilot can generate commit messages for you?

                        If you're not too picky about how you write your commit messages, this can be a neat little time saver!

                        Generate Commit Messages with Copilot

                        Check this article for more Copilot tips and tricks:

                        Happy coding!

                        ]]>
                        https://codewithandrea.com/tips/control-codegen-order/Control the Code Generation OrderIf you're using multiple code generators that depend on each other, you can enforce the code generation order in your build.yaml file.https://codewithandrea.com/tips/control-codegen-order/Mon, 7 Oct 2024 02:00:00 +0200Did you know?

                        If you're using multiple code generators that depend on each other, build_runner may fail.

                        To fix this, you can enforce an explicit code generation order in your build.yaml file. ๐Ÿ‘‡

                        Control the Code Generation Order

                        For more techniques about effective codebase maintenance, read my ultimate guide about code generation:

                        Happy coding!

                        ]]>
                        https://codewithandrea.com/tips/async-stream-initialization/Async Stream Initialization with async*If you want to return a stream that depends on some asynchronous code, you can use async* and yield*https://codewithandrea.com/tips/async-stream-initialization/Tue, 1 Oct 2024 02:00:00 +0200Did you know?

                        If you want to return a stream that depends on some asynchronous code, you can use async* and yield*.

                        This can be handy when you have an object that needs to be initialized asynchronously before it can start emitting events.

                        Async Stream Initialization with async*

                        Happy coding!

                        ]]>
                        https://codewithandrea.com/articles/flutter-in-app-review-prompt/How to Ask for In-App Reviews in Your Flutter AppThe in_app_review package makes it easy to ask for reviews. And by using a data-driven approach, you can show the prompt at the right time.https://codewithandrea.com/articles/flutter-in-app-review-prompt/Fri, 27 Sep 2024 02:00:00 +0200App reviews play an important role in the success of your apps. They not only provide essential feedback but also significantly affect the app's store performance:

                        • Positive reviews boost the app's ranking and drive more downloads.
                        • Negative reviews can discourage new users from downloading.

                        For example, here are the reviews from my Flutter Tips app:

                        App Store Reviews for the Flutter Tips app

                        Glowing reviews don't just fall from the sky: you have to earn them by making a good app and make it as easy as possible for users to leave a review.

                        The best way to do this is by showing an in-app rating prompt at the right momentโ€”when users are most engaged and satisfied. This could be after completing a task, leveling up in a game, or using a feature successfully.

                        Timing the prompt correctly increases the chances of getting positive reviews, boosting your appโ€™s rating and visibility in the app store.

                        And by using the in_app_review package and a bit of extra code, you can do exactly that.

                        The in_app_review package

                        The in_app_review package allows you to ask for reviews in two different ways:

                        1. Call-to-action: by redirecting the user to the store with the openStoreListing API
                        2. Programmatically: by triggering the in-app review prompt with the requestReview API
                        The in-app review prompt on iOS

                        The second method is most effective, since it can be triggered when the user is most satisfied with the app. For example, I've programmed my Flutter Tips app to show the prompt after users like 5 tips in the app:

                        When the user likes N tips, show the in-app rating prompt

                        What we will cover

                        In this article, I'll show you how to apply the same technique to your apps.

                        Here's what we will cover:

                        • in_app_review installation and how to show the app-review prompt programmatically
                        • How to avoid showing the prompt too early (by following the quotas on the app stores)
                        • How to use analytics to ensure the prompt shows at the right time

                        When to show the prompt?

                        Showing the prompt is the easy part.

                        The real challenge is deciding when to do it:

                        • too early, and you will annoy your users (so many apps get this wrong!)
                        • too late, and hardly any users will even see it at all

                        As we will see, by using analytics and a data-driven approach, we can show the prompt at the right time, for the most engaged users, thus maximising our chances of getting positive reviews.

                        Ready? Let's go! ๐Ÿš€

                        This article is not a step-by-step tutorialโ€”it's more of a high-level guide showing how things fit together. If you want to go deeper, check my latest course about Flutter in Production.

                        Installation

                        Installing the in_app_review package is easy enough:

                        dart pub add in_app_review:2.0.9 flutter pub get

                        If you're using Riverpod, consider creating a separate provider for this:

                        // in_app_review_provider.dart import 'package:in_app_review/in_app_review.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'in_app_review_provider.g.dart'; @riverpod InAppReview inAppReview(InAppReviewRef ref) { return InAppReview.instance; }

                        Since the code above relies on code generation, you'll need to run dart run build_runner watch -d to generate your provider. To learn more, read: How to Auto-Generate your Providers with Flutter Riverpod Generator.

                        Showing the in-app review prompt

                        As discussed, we need to show the prompt at the right time.

                        This means that you need to choose the single, most important event that happens when the user is most satisfied with your app (e.g. completed a task, uses a feature successfully).

                        For my Flutter Tips app, this is the "tip liked" event:

                        Choosing an event that will trigger the in-app review prompt

                        The callback handler for this button looks something like this:

                        Future<void> updateTipLiked(int tipIndex, bool isLiked) async { if (isLiked) { // * Show app review prompt based on some conditional logic await inAppRatingService.requestReviewIfNeeded( userTotalLikesCount: userTotalLikesCount ); // * Log the event with analytics unawaited(analyticsFacade.trackTipLiked( tipIndex: tipIndex, userTotalLikesCount: userTotalLikesCount, )); } }

                        Two things to note:

                        • The updateTipLiked method has two purposes: show the app review prompt and track the event.
                        • Both the requestReviewIfNeeded and trackTipLiked methods take userTotalLikesCount as an argument.

                        We'll get back to some of these details later. But for now, let's focus on the InAppRatingService.

                        If you're not familiar with the unawaited function, read: Futures: await vs unawaited vs ignore and Use unawaited for your analytics calls.

                        The InAppRatingService class

                        To handle all the in-app review logic, we can use a dedicated class that looks like this:

                        /// Helper class used to show the in-app rating prompt when a certain number of /// tips has been liked class InAppRatingService { const InAppRatingService(this.ref); final Ref ref; // * Used to show the prompt InAppReview get _inAppReview => ref.read(inAppReviewProvider); /// Requests a review if certain conditions are met Future<void> requestReviewIfNeeded({required int userTotalLikesCount}) async { // * Don't show rating prompt on web (not supported) if (kIsWeb) { return; } // TODO: Only show prompt after a certain number of tips has been liked // * If we can show a review dialog if (await _inAppReview.isAvailable()) { // * Request the review await _inAppReview.requestReview(); } } } @riverpod InAppRatingService inAppRatingService(InAppRatingServiceRef ref) { return InAppRatingService(ref); }

                        Some notes:

                        • The class takes a ref argument, which can be used to read other providers (such as inAppReviewProvider).
                        • The requestReviewIfNeeded method doesn't do anything if kIsWeb is true (we can only show the prompt on iOS and Android).
                        • We check if the review dialog is available before showing it.

                        If your app doesn't use Riverpod, you can delete the provider and inject InAppReview as a constructor argument instead.

                        Does this code work as intended?

                        If we added the InAppRatingService class above to Flutter Tips app and ran it on iOS, the rating prompt would appear as soon as we like a tip for the first time, and then again for each subsequent like:

                        Showing the rating prompt too early will annoy the user

                        That's a bit too eager!

                        To avoid giving a bad first impression, we should add some logic that says "wait until the user liked N tips before showing the prompt".

                        Showing the Review Prompt After N Events

                        To accomplish what we want, we can use the userTotalLikesCount variable I mentioned before:

                        Future<void> updateTipLiked(int tipIndex, bool isLiked) async { if (isLiked) { // * Show app review prompt based on some conditional logic await inAppRatingService.requestReviewIfNeeded( userTotalLikesCount: userTotalLikesCount ); // * Log the event with analytics unawaited(analyticsFacade.trackTipLiked( tipIndex: tipIndex, userTotalLikesCount: userTotalLikesCount, )); } }

                        In your apps, this variable may have a different name. You could store it locally with Shared Preferences, and increment it every time the user completes a certain action.

                        Here's an updated version of the InAppReviewService:

                        /// Helper class used to show the in-app rating prompt when a certain number of /// tips has been liked class InAppRatingService { const InAppRatingService(this.ref); final Ref ref; // * Used to show the prompt InAppReview get _inAppReview => ref.read(inAppReviewProvider); // * Used to keep track of how many times we've requested // * a review from the user SharedPreferences get _sharedPreferences => ref.read(sharedPreferencesProvider).requireValue; static const key = 'in_app_rating_prompt_count'; int get _inAppReviewRequestCount => _sharedPreferences.getInt(key) ?? 0; /// Requests a review if certain conditions are met Future<void> requestReviewIfNeeded({required int userTotalLikesCount}) async { // * Don't show rating prompt on web (not supported) if (kIsWeb) { return; } // * If we can show a review dialog if (await _inAppReview.isAvailable()) { // * Use an exponential backoff function: // * - 1st request after 5 liked tips // * - 2nd request after another 10 liked tips // * - 3rd request after another 20 liked tips if (completedTasksCount >= 5 && _inAppReviewRequestCount == 0 || completedTasksCount >= 15 && _inAppReviewRequestCount == 1 || completedTasksCount >= 35 && _inAppReviewRequestCount == 2) { // * Request the review await _inAppReview.requestReview(); // * Increment the count await _sharedPreferences.setInt(key, _inAppReviewRequestCount + 1); } } } }

                        Note about storing the review request count with Shared Preferences

                        The code above uses Shared Preferences to keep track of how many times we've requested an in-app review.

                        This ensures that the request count is persisted locally and can be retrieved even if we quit the app and restart it. However, if we delete and reinstall the app, or clear its storage, the count will be reset, but the app stores will still remember the quota (more on this below).

                        If you want to persist this kind of information across app reinstalls and your app supports authentication, consider storing the event count on your remote database, for each user.


                        Reviewing the Conditional Logic

                        The most important code in the InAppReviewService class is this:

                        // * Use an exponential backoff function: // * - 1st request after 5 liked tips // * - 2nd request after another 10 liked tips // * - 3rd request after another 20 liked tips if (completedTasksCount >= 5 && _inAppReviewRequestCount == 0 || completedTasksCount >= 15 && _inAppReviewRequestCount == 1 || completedTasksCount >= 35 && _inAppReviewRequestCount == 2) { // * Request the review await _inAppReview.requestReview(); // * Increment the count await _sharedPreferences.setInt(key, _inAppReviewRequestCount + 1); }

                        By adding the conditional logic above, we can ensure the review is requested after 5, 15, and 35 liked tips, respectively.

                        This is in line with the iOS App Store quotas, which state that:

                        The system automatically limits the display of the prompt to three occurrences per app within a 365-day period (source).

                        But hold on! 5, 15, and 35 are arbitrary numbers! How have I decided they were optimal for my Flutter Tips app?

                        The answer lies in my analytics. ๐Ÿ‘‡

                        Making Data-Driven Decisions with Analytics

                        Recall that in addition to requesting the in-app review, I'm also calling this method when a user likes a tip:

                        // * Log the event with analytics unawaited(analyticsFacade.trackTipLiked( tipIndex: tipIndex, userTotalLikesCount: userTotalLikesCount, ));

                        Under the hood, this sends a custom event named "Tip Liked" to Mixpanel.

                        As a result, after publishing my app, I created a custom analytics report that looks like this:

                        75th and 90th percentiles for the user total likes in the Flutter Tips App
                        75th and 90th percentiles for the user total likes in the Flutter Tips App

                        This shows the maximum number of user total likes on the 75th and 90th percentile across all users.

                        Based on the given time period, I can see that, on average:

                        • users on the 75th percentile like 6.9 tips
                        • users on the 90th percentile like 17.1 tips

                        In plain English, this tells me how many tips are liked by the most engaged users. As a result, 5, 15, and 35 seemed to be reasonable thresholds for my app:

                        if (userTotalLikesCount >= 5 && inAppRatingPromptCount == 0 || userTotalLikesCount >= 15 && inAppRatingPromptCount == 1 || userTotalLikesCount >= 35 && inAppRatingPromptCount == 2) { // Request in-app review }

                        But how should you approach this in your apps?

                        Data-Driven In-App Review Prompt

                        Here are some guidelines for showing the app review prompt and maximising the number of positive reviews in your apps:

                        1. Decide which is the most important event that happens when the user is most satisfied with your app (e.g. completed a task, uses a feature successfully)
                        2. Add some analytics code to track that event, as well as how many times it has happened for that user (or device)
                        3. Launch the app on the stores
                        4. Create a custom report and measure the 75th and 90th percentile for that event (this is easy to do with Mixpanel)
                        5. Once you have enough data, add the in-app review logic and choose the appropriate thresholds, as shown above
                        6. Release a new version of your app

                        This approach has served me well, and I plan to use it in all my apps. Feel free to borrow the code in this article and tweak it for your own needs.

                        Caveat: if users don't enjoy using your app, you're more likely to get negative reviews. So make sure you build a good app first, and then you can optimise for getting more reviews. I recommend collecting user feedback separately with the feedback package, which also offers a Sentry plugin.

                        Conclusion

                        The in_app_review package makes it super easy to show an in-app review prompt in your app.

                        But the real challenge is deciding when to show the prompt:

                        • too early, and you will annoy your users
                        • too late, and hardly any users will even see it at all

                        As we've seen, by using analytics and a data-driven approach, we can ensure the prompt is shown at the right time for the most engaged users, thus maximising our chances of getting positive reviews.

                        And with this, I wish you the best in launching your apps! โญ๏ธ

                        New course: Flutter in Production

                        In-app reviews play an important role in the success of your apps in the stores.

                        But when it comes to shipping and monitoring apps in production, there are many more things to consider:

                        • Preparing for release: splash screens, flavors, environments, error reporting, analytics, force update, privacy, T&Cs
                        • App Submissions: app store metadata & screenshots, compliance, testing vs distribution tracks, dealing with rejections
                        • Release automation: CI workflows, environment variables, custom build steps, code signing, uploading to the stores
                        • Post-release: error monitoring, bug fixes, addressing user feedback, adding new features, over-the-air updates

                        My latest course will help you get your app to the stores faster and with fewer headaches.

                        If youโ€™re interested, you can learn more and enroll here. ๐Ÿ‘‡

                        ]]>
                        https://codewithandrea.com/tips/timing-in-app-review-prompt/Timing the In-App Review PromptTo avoid prompting users too early, track your desired event and only ask for a review after it is triggered N times.https://codewithandrea.com/tips/timing-in-app-review-prompt/Thu, 26 Sep 2024 02:00:00 +0200Did you know?

                        The in_app_review package makes it easy to ask for reviews in your app. โญ๏ธ

                        But timing is key and the App Store will only let you show up to 3 prompts per year.

                        To avoid prompting users too early, track your desired event and only ask for a review after it is triggered N times.

                        Timing the In-App Review Prompt

                        The code above only shows the main idea.

                        To learn more about in-app reviews, read:

                        Happy coding!

                        ]]>
                        https://codewithandrea.com/tips/multiple-xcode-versions/Working with Multiple Xcode VersionsHow to download multiple releases from xcodereleases.com, and switch between them with the xcode-select CLI.https://codewithandrea.com/tips/multiple-xcode-versions/Tue, 24 Sep 2024 02:00:00 +0200Did you know that you can install and use multiple Xcode versions?

                        Here's how:

                        • Head to xcodereleases.com and download your desired release
                        • Extract and rename it
                        • Drag it to the /Applications folder

                        To switch between them, use the xcode-select CLI. ๐Ÿ‘‡

                        Working with Multiple Xcode Versions

                        This can be useful if the latest release is cough-cough buggy, and you want to keep the old one around.

                        Or you just want to have multiple versions installed and easily switch between them.


                        Note that to use the xcode-select CLI, you will need to install the Xcode command line tools.

                        You can get them from here (sign-in required):

                        Bonus: Xcodes app

                        If you want to manage multiple Xcode versions with a mouse click or through a CLI, you can download the Xcodes app:

                        Happy coding!

                        ]]>
                        https://codewithandrea.com/tips/navigator-observer/Adding a Navigator ObserverBy implementing a NavigatorObserver, you can track page views or add navigation breadcrumbs to your error logs.https://codewithandrea.com/tips/navigator-observer/Mon, 23 Sep 2024 02:00:00 +0200Ever wanted to track page views or add navigation breadcrumbs to your error logs?

                        This can be done by implementing a NavigatorObserver. โœ…

                        Here's some sample code showing how to implement this. ๐Ÿ‘‡

                        Adding a Navigator Observer
                        import 'package:flutter/material.dart'; import 'dart:developer'; class LoggerNavigatorObserver extends NavigatorObserver { @override void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) { _logNavigation(route.settings.name, 'push'); } @override void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) { _logNavigation(route.settings.name, 'pop'); } @override void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) { if (newRoute != null) { _logNavigation(newRoute.settings.name, 'replace'); } } void _logNavigation(String? routeName, String action) { if (routeName != null) { log("Screen $action: $routeName", name: 'Navigation'); } } }

                        To use the navigator observer, simply add it to the MaterialApp widget.

                        As a result, navigation logs will show in the console.

                        Adding a Navigator Observer

                        A couple of extra tips:

                        • some packages already offer a navigator observer (e.g. SentryNavigatorObserver), so you may not need to implement your own
                        • whatever you do, DON'T track page views on the build method (this can be called many times when widgets rebuild, and is out of your control)

                        I will cover analytics and error monitoring in detail in my upcoming course.

                        If you want to ship your apps with confidence, check it out and join the waitlist. ๐Ÿ‘‡

                        ]]>