Tutorial · 15 min · beginner

Porting a Flutter screen

DartNative is almost Flutter-compatible — most widgets port unchanged, a few differ on purpose. Here's the honest map, and how to let your LLM do the mechanical work.

DartNative implements the Flutter widget API — almost. Most widgets are identical or near-identical (an extra parameter here, a small naming difference there), and a typical screen ports quickly. But “change one import and you’re done” would be overselling it, so here’s the honest map.

What ports unchanged

The core vocabulary: Row, Column, Stack, Padding, SizedBox, Expanded, Container, Text, Image, ListView, GridView, GestureDetector, setState, Navigator.push/pop, controllers, MediaQuery… For screens built from these, the import swap really is the whole port:

// before
import 'package:flutter/material.dart';
// after
import 'package:dartnative/dartnative.dart';

Layout compatibility is engineered, not accidental — the reconciler enforces Flutter’s sizing rules (cross-axis fill, Stack sizing, double.infinity idioms) on top of native flexbox, so your layouts land where Flutter put them.

What differs on purpose

Where widgets meet real native structure, DartNative deliberately extends or diverges — these are features, but they’re also the diffs you’ll touch in a port:

  • Scaffold — extra slots and controls Flutter doesn’t have: bottomInputBar (the keyboard-attached bar), bottomAccessory, brightness (declare the screen’s trait — dark-by-colors screens should set it), platform-tuned extendBody semantics.
  • AppBar — the native-bar superset: largeTitle, subtitle, searchBar, glass-surface controls. Plain title + actions port as-is.
  • TextField — a real platform field, so decoration is intentionally simpler than Flutter’s canvas-drawn InputDecoration: borders and fills come from containers around it (see the chat tutorial’s input capsule).
  • Lists at scale — ListView ports directly; for very long feeds you’ll want FastList/FastGrid (native cell recycling — an addition, not a blocker).
  • Not portable — packages that reach into Flutter’s renderer (RenderObject machinery, flutter_svg, go_router) need replacements: first-party plugins cover the common cases, Navigator covers routing.

The per-widget truth lives in the widgets reference — every widget, its native lowering, and its differences.

The porting loop

  1. Swap the import; dn run.
  2. Fix what the analyzer flags — usually a removed package, a Flutter-renderer dependency, or a widget parameter that moved.
  3. Compare the screen against Flutter: layout should match; where a widget differs on purpose (bars, fields), adopt the DartNative form.
  4. Keep what you get for free: real scroll physics, real text, the keyboard that just works.

Let your LLM do the mechanical part

Porting is exactly the kind of rule-driven work coding assistants excel at — if they know the rules. DartNative ships two skills (instruction files for AI assistants) in the public repo’s skills/ folder:

  • dart-native — the framework guide: widget catalog and differences, layout rules, state, navigation, the classic mistakes.
  • dart-native-porting — the porting playbook: pubspec changes, package replacements, main() setup, native project configuration, and the known failure modes (white screen, missing symbols) with their fixes.

Hand both to your assistant — Claude Code, GPT, Cursor, Copilot, anything that accepts instruction files — and ask it to port a screen: it swaps imports, applies the API diffs, replaces incompatible packages, and flags the pieces that need your judgment. The skills encode everything this page says, plus the long tail.

Why this is native

A ported screen doesn’t just still work — it upgrades. The same widget tree now drives real platform views: system scroll physics, CoreText, native fields, the OS keyboard choreography. The port is the cheap part; the feel is the payoff.