Mobile Development6 min read

Flutter

Google's Dart-based UI toolkit that compiles to native ARM code and renders every pixel itself using Skia - pixel-perfect parity across iOS, Android, web, and desktop from one codebase

DartGoogleCross-platformiOSAndroid
---
name: flutter
description: >
  You are a Flutter developer. You build cross-platform apps with Dart, Riverpod for state management, GoRouter for navigation, and platform channels for native integrations.
---

Flutter renders its own UI using the Skia graphics engine - it does not use native platform widgets, which means pixel-perfect parity across iOS, Android, web, and desktop from one codebase. Choose Flutter over React Native when design fidelity and custom animation performance are the top requirements; choose React Native when your team has a React background and code sharing with a web app matters.

Workflow

  1. Use Riverpod (not Provider or BLoC) for all state management in new projectsProvider is deprecated in favor of Riverpod; Riverpod is compile-time safe, testable without context, and does not suffer from the context dependency problem that makes Provider hard to test
  2. Lift state to the lowest ancestor that needs it - use StatelessWidget everywhere possibleStatefulWidget rebuilds its entire subtree on setState; keeping state high and narrow reduces the rebuild surface and makes the widget tree easier to reason about
  3. Use GoRouter for all navigation - define named routes with path parametersFlutter's built-in Navigator.push is imperative and breaks deep linking; GoRouter declaratively maps URL paths to screens and handles Android back button, deep links, and web routing correctly
  4. Run flutter analyze and flutter test before every commitDart's static analyzer catches unused imports, missing required parameters, and type mismatches; skipping analysis allows errors that will cause runtime exceptions to accumulate in the codebase
  5. Profile with Flutter DevTools Performance tab on a physical device, not the simulatorThe iOS Simulator runs on x86 and skips GPU rendering - frame drops caused by expensive build() methods or unoptimized shaders only appear on real ARM hardware

Output Format

A production Flutter screen is a ConsumerWidget (Riverpod), references one or more providers via ref.watch(), and delegates business logic to a Notifier - never to the widget itself.

class ItemsScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(itemsProvider);
    return switch (state) {
      AsyncData(:final value) => ItemsList(items: value),
      AsyncError(:final error) => ErrorView(error: error),
      _ => const LoadingSpinner(),
    };
  }
}

Examples

Riverpod async provider

Input

Fetch a list of products from an API

Output

final productsProvider = FutureProvider.autoDispose<List<Product>>((ref) => ref.watch(apiClientProvider).getProducts()); - auto-disposes when no longer watched, cancels the request

Wrong: business logic inside build()

Input

Making an API call or parsing data inside the build() method

Output

build() is called on every frame and every rebuild - API calls inside build() fire repeatedly, causing multiple simultaneous requests and UI flicker

Right: logic in a Notifier

Input

Same API call

Output

Move it to an AsyncNotifier.build() - Riverpod calls this once, caches the result, and rebuilds widgets only when the state changes

Edge Cases & Gotchas

  • Jank from expensive widget builds - use const constructors on widgets with no runtime-varying data; const widgets are never rebuilt even when a parent rebuilds
  • Platform channel type mismatch - Flutter sends Dart types over the platform channel; only Map, List, String, int, double, bool, and Uint8List are supported; custom classes must be manually serialized
  • Hot reload not reflecting changes to initState - hot reload preserves widget state; changes to initState only take effect after a hot restart (r vs R in the terminal)

When to Break These Rules

  • Use React Native when your team has React expertise and you need to share business logic (hooks, stores) with a web app - Flutter's Dart ecosystem does not integrate with JS tooling
  • Use native iOS/Android for apps that require deep platform integration (CallKit, background location, custom camera pipelines) where platform channels add complexity exceeding the cross-platform benefit

Reference Files

  • src/data/skills/index.js - add skill entries here
  • src/components/Skills/DocRenderer.js - renders this doc format

More skills in this library

Browse all technical skills - Mobile, AI/ML, Web, Backend, Design, and Product.