跪拜 Guibai
← Back to the summary

A Frontend Developer's Map to Flutter and Cross-Platform Reality

Suppose you are a frontend developer who usually writes React, uses TypeScript, and relies on npm for packages. One day, you discover that your team is also working on an app in parallel, using Flutter with the Dart language. Three thoughts will naturally come to mind:

These three questions actually pull out a whole map of "cross-platform" development: a comparison of languages and ecosystems, how to unify design tokens across both ends, how Flutter actually renders, how a native app is built and packaged, the differences between iOS and Android build environments, and the current state of solutions like React Native, Flutter, and Kotlin Multiplatform.

This article does not hype a specific technology, nor does it persuade you to switch careers to app development. It is more like a "review" written for frontend developers: using concepts you are familiar with as anchors, it strings these scattered knowledge points together into a map you can understand at a glance. All facts involving versions and current status are based on official sources as of July 2026, listed at the end for your own verification.

First, three coordinates; you will understand them better after reading the whole article:

One, language and ecosystem can be learned by analogy, but the rendering model cannot be analogized—this is the biggest cognitive trap for frontend migration. Two, what is truly mature and worth sharing across platforms is design tokens (values), not components (controls); the former has industry standards, while almost no one does the latter. Three, it is realistic for one person to handle "tokens + Web + one App," but native building, signing, store compliance, and release cadence for both platforms is an independent heavy workload that won't be waived just because you know frontend.

1. Using the Known to Analogize the Unknown: Flutter vs. React, Dart vs. JS/TS, pub vs. npm

A frontend developer's first step into Flutter is to hang unfamiliar terms on familiar hooks. The good news is: a large number of concepts indeed map one-to-one.

Language and Framework: Similar, but Not the Same Thing

Flutter and React share the same mental model—declarative UI (you describe "what the interface looks like," and the framework is responsible for turning it into a real picture), plus componentization and state-driven re-rendering. Look at two almost symmetrical code snippets:

// React: a counter button
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>Clicked {count} times</button>;
}
// Flutter: the same counter button
class _CounterState extends State<Counter> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return TextButton(
      onPressed: () => setState(() => count++),
      child: Text('Clicked $count times'),
    );
  }
}

useState corresponds to setState, return JSX corresponds to the Widget tree returned by build(), and onClick corresponds to onPressed. Even the hot reload that React developers love most, Flutter has it too, and the experience is just as smooth.

Looking at the language layer, the comparison between Dart and JS/TS is also very neat:

Frontend World (JS/TS + React) Flutter World (Dart + Flutter) Explanation
Declarative UI framework React Declarative UI framework Flutter Both are "data changes, interface automatically changes"
Component Widget In Flutter, "everything is a Widget," even layout and padding are
JavaScript / TypeScript Dart Both are C-family syntax, class, async/await writing is almost the same
TS type system (optional, gradual) Dart type system (default, built-in) Dart is strongly typed from the language level, unlike TS which is "pasted" onto JS
TS strict null checks (must be manually enabled) Dart sound null safety (enabled by default) From Dart 3, String? and String are two types, null is enforced at compile time
Single-threaded event loop + Web Worker Event loop + Isolate Dart's Isolates do not share memory, communicate via message passing, naturally avoiding data races
JIT execution in the engine JIT during development, AOT for release This difference is critical, expanded in the next section

In one sentence: At the syntax and framework level, a frontend developer's muscle memory can transfer 70-80%. You won't find Dart unfamiliar; it has async/await, arrow functions, destructuring, and classes.

Package Management: pub Indeed Corresponds to npm

You ask "Can Dart's packages correspond to npm?" The answer is yes, and the correspondence is quite direct:

Frontend Dart / Flutter Role
npm / pnpm / yarn pub (dart pub / flutter pub) Package manager CLI
npmjs.com pub.dev Official public package repository
package.json pubspec.yaml Dependency manifest (one is JSON, one is YAML)
package-lock.json / pnpm-lock.yaml pubspec.lock Locks exact versions
npm install dart pub get / flutter pub get Fetch dependencies
semver (^1.2.0) Same semver syntax Version constraint syntax is consistent
npm workspaces / pnpm workspace pub workspaces (Dart 3.6+) / third-party melos Monorepo multi-package management

Comparing the manifest files, it's almost "the same person in different clothes":

// package.json (Web)
{
  "name": "my-web-app",
  "dependencies": { "react": "^19.0.0" },
  "devDependencies": { "typescript": "^5.6.0" }
}
# pubspec.yaml (Flutter)
name: my_flutter_app
environment:
  sdk: ^3.6.0
dependencies:
  flutter:
    sdk: flutter
  dio: ^5.7.0        # Dart's axios
dev_dependencies:
  flutter_lints: ^4.0.0

There is one scale difference worth knowing: npm is the world's largest package repository, with packages numbering in the millions; the pub.dev ecosystem is mature but much smaller, with public packages in the tens of thousands. This doesn't mean the Dart ecosystem is bad, but it reminds you—some small utility tools you "casually install" in frontend might need to be written yourself or approached differently in Flutter.

How Far Can the Analogy Go? It Stops at "Rendering"

At this point, you might think: so it's just swapping a set of APIs. This is precisely the most dangerous illusion. The analogy holds at the levels of "language, framework mentality, and package management," but once you delve into "how the interface is actually drawn on the screen," Flutter and the browser you are familiar with are two different species. We will put this dividing line in section three specifically, because it directly determines "what can be shared cross-platform and what cannot."

Before that, let's answer the practical question you care about most: can design specifications be unified.

2. Unifying Dual Platforms with One Set of Design Tokens: Is This Mainstream, and How to Do It

Your idea is very specific: define a set of design variables in Figma, export them, and automatically generate an npm package for Web and a dart package for App, so the style is always consistent across both ends. Let's first clarify the key concepts here, then answer "is it mainstream."

What are Design Tokens

Design tokens are turning design decisions into named, platform-agnostic data. Not "use #165dff here," but "use color.brand.primary here, whose value is #165dff." The name is stable, the value is replaceable.

Tokens are usually divided into three layers; understanding these three layers is the key to understanding the whole system:

flowchart TD
  p[Primitive Tokens record specific values, e.g., blue600 equals a certain color value] --> s[Semantic Tokens record usage, e.g., brand primary color references a primitive token]
  s --> c[Component Tokens record local parts, e.g., button background color references a semantic token]

The benefit is: when a designer changes the base color pointed to by color.brand.primary once, all places referencing it (Web, App, even email templates) change together, without needing to find #165dff everywhere.

Is That "One Set of Tokens Feeding Two Ends" Idea Mainstream?

The answer is split in two; this is the most crucial point to clarify in the whole article:

Sharing "design tokens" (that is, values: colors, spacing, border radii, font sizes, shadows) is an undoubted industry mainstream practice in 2026. Sharing "components" (that is, controls: buttons, dialogs, things with interaction and rendering) is something almost no team does, and it is not recommended.

First, why "sharing tokens" is mainstream—because it has just been standardized in the last two years.

On October 28, 2025, the W3C Design Tokens Community Group (DTCG) released the first stable version of the token format specification (Design Tokens Format Module 2025.10). It defines a vendor-neutral JSON format, describing tokens with fields like $value, $type, $description:

{
  "color": {
    "brand": {
      "primary": { "$type": "color", "$value": "#165dff" }
    }
  }
}

It needs to be clarified: this specification is not a formal W3C standard (not on the W3C Standards Track); it is a "Final Report" from the Community Group, but is explicitly marked as "stable and ready for production implementation." Mainstream tools like Figma, Sketch, Penpot, Tokens Studio, and Style Dictionary have already supported or are implementing it. Incidentally, the term "design token" itself was coined by Jina Anne during her time at Salesforce.

With a unified format, there is a pipeline for "define once, generate everywhere." The typical form looks like this:

flowchart LR
  fig[Figma Variables maintained by designer] --> ts[Tokens Studio exports as DTCG JSON]
  ts --> sd[Style Dictionary conversion engine]
  sd --> web[Web output CSS Variables and TS Constants]
  sd --> dart[Dart output Theme Classes]
  sd --> nat[Native output Android XML and iOS Swift]
  web --> cicd[CI/CD auto-distributes to each platform's repository]
  dart --> cicd
  nat --> cicd

The core tool in this chain is called Style Dictionary (originally open-sourced by Amazon). It takes in a token JSON file and spits out the formats needed by each platform—CSS variables for Web, Swift for iOS, XML for Android, and of course, the Dart constant classes you want. Its v4 already provides first-class support for the DTCG format, and v5 is completing full compatibility with the 2025.10 specification. That is to say, your envisioned "Figma → npm package + dart package" is not only technically feasible but also backed by existing standards and toolchains; it's not a wild fantasy.

Adoption is indeed rising. A design systems survey (by zeroheight) of about 300 practitioners showed token usage rose from 56% to 84% in one year.

But Why Not Share Components

Since tokens can be shared, why not make buttons and input fields into a "dual-platform universal component package"? Because Web components and Flutter components are fundamentally not the same thing underneath:

Their APIs, lifecycles, event models, and styling mechanisms are all different. Forcing an abstraction into one package only yields a "lowest common denominator" that no one finds easy to use. So the industry consensus is very clear:

Dimension Suitable for sharing across Web / Flutter? Reason
Colors / Spacing / Radii / Font sizes / Shadows (token values) Suitable, and there is a standard Pure data, unrelated to rendering
Icons (SVG source files) Partially suitable Source files can be shared, but each end must convert them into components separately
Copy / Internationalization text Suitable Pure data
Validation rules / Pure business logic Depends Different languages, need to write separately or use cross-platform solutions (see Section 5 KMP)
UI components (buttons, dialogs, etc.) Not suitable Rendering models, events, styling mechanisms are completely different
Animations / Gesture interactions Not suitable Highly platform-dependent

So a more accurate description of that idea is: Share the "design tokens" layer, and each end uses the tokens to build its own components. This is the mainstream and sustainable approach.

How a Dart Token Package is Used by Flutter

The generated dart token package is just a regular dependency in Flutter. The simplest form is exporting a set of constants:

// In the token package app_tokens, auto-generated by the pipeline
class AppColors {
  static const brandPrimary = Color(0xFF165DFF);
  static const bgDefault = Color(0xFFFFFFFF);
}

// In App code, import and reference directly like any dependency
Container(color: AppColors.brandPrimary);

A more "Flutter-idiomatic" approach is to connect tokens into Flutter's theme system (ThemeData + ThemeExtension), so components get values via Theme.of(context), naturally supporting light/dark mode and multi-brand switching—this exactly corresponds to using CSS variables for theming on the Web side. The mechanisms are different on both ends, but the idea of "token-driven theming" is consistent.

It's worth honestly stating: the realistic starting point for many teams is actually manually writing a color constant table on each end (one TS file for Web, one colors.dart for App), with values aligned manually. This is not shameful; it's the first stage for the vast majority of projects. Whether to upgrade to the automated pipeline above depends on scale—

Your Situation More Pragmatic Choice
One or two pages, dozens of colors, infrequent changes Manually write constant tables, align manually, don't over-engineer
Multiple platforms, multiple brands/tenants, frequent design adjustments Worth setting up the Figma + Tokens Studio + Style Dictionary pipeline
In-between, want to test the waters first First implement "one DTCG JSON + manually run generation once," not connecting CI is also fine

3. How Flutter Actually Renders and Builds Cross-Platform

Now back to the foreshadowing from section one: why the rendering model cannot be analogized. Understanding this section allows you to truly grasp what Flutter is, and what actually happens when "a dart package is compiled into an App."

Three Cross-Platform Paradigms, Three Ways to "Draw an Interface"

To display a button on the screen, the industry has three fundamentally different paths:

flowchart TB
  ui[Want to display a button on screen]
  ui --> w[WebView Approach e.g., Ionic Capacitor]
  ui --> r[Bridge Approach e.g., React Native]
  ui --> f[Self-Drawing Approach e.g., Flutter]
  w --> wd[Embed a system browser in the App, use it to render an HTML button]
  r --> rd[Translate the button you wrote into a system native button control]
  f --> fd[Comes with its own engine, uses GPU to hand-draw every pixel of this button]

This is the meaning of "the rendering model cannot be analogized": when you write React, the interface is ultimately rendered by the browser's layout engine; when you write Flutter, the interface is rendered by the Flutter engine itself, with no browser and no DOM in between.

The trade-offs among the three:

Paradigm Representative Interface Source Performance/Experience Cross-Platform Consistency Typical Suitability
WebView Ionic / Capacitor System browser renders webpage Average, limited by WebView High (it is a webpage) Content-type, limited budget, pure frontend team
Bridge React Native Mapped to real native controls Good, close to native Medium (follows each platform's native style) Want to reuse React skills, want native look and feel
Self-Drawing Flutter Engine draws pixel by pixel with GPU Good, smooth animations Extremely high (drawn by itself) Want strongly consistent brand visuals, complex animations

Supplement a 2026 status update: Flutter's rendering engine has fully switched from the old Skia to the new Impeller. In Flutter 3.44 (released at Google I/O 2026), Impeller is already the only renderer on iOS, and is enabled by default on Android 10 and above, with the old Skia backend removed. Impeller's goal is to eliminate jank caused by runtime shader compilation. You don't need to memorize these details; just know: Flutter has been continuously polishing the matter of "drawing itself faster and more stably."

What Happens During Build When "a dart package is used by Flutter"

This is one of the core questions you asked. The frontend mentality is "install a package → import it at runtime," but Flutter (release version) compiles all Dart code into native machine code at build time. The process is like this:

flowchart LR
  ps[pubspec manifest declares dependencies] --> pg[flutter pub get resolves and locks versions]
  pg --> aot[Your Dart code plus all dependency packages are AOT compiled together into native machine code]
  aot --> eng[The output is packaged together with the Flutter engine]
  eng --> av[Android side Gradle produces APK or AAB]
  eng --> iv[iOS side Xcode produces IPA]

Key points:

4. Really Producing a Package: iOS and Android Build Environment Checklist

This section is aimed at people who have "never touched native," clearly explaining what is needed to build an App, install it on a phone, and upload it to a store. Whether you use Flutter, React Native, or native, this final stretch of building/signing/listing is a path everyone must walk, because it's the rule set by the operating system vendors, not something a framework can bypass.

iOS: A Mac is Unavoidable

iOS builds have several hard thresholds:

Android: The Threshold is Lower, but Has Its Own Set of Rules

Android is not picky about the operating system; Windows, macOS, and Linux can all build:

Putting both ends side by side, the differences are clear at a glance:

Dimension iOS Android
Build OS macOS only Windows / macOS / Linux all work
Main IDE / Tool Xcode Android Studio
Native Language Swift / Objective-C Kotlin / Java
Dependency Management SPM (CocoaPods phasing out) Gradle
Signing Mechanism Certificate + Provisioning Profile Keystore + Play App Signing
App Store App Store Google Play (and various Android stores)
Developer Account Fee $99/year One-time ~$25
Review Relatively strict, time-consuming Relatively lenient, faster

A Detail Crucial for "Individual Developers": CI/CD

If you want to automate builds (produce a package on every commit), there is one hard constraint: automated iOS builds must run on a macOS machine. So you either need a Mac that is always on, or use a cloud CI that provides Mac build machines—like GitHub Actions' macOS runner, Codemagic (Flutter-friendly), Bitrise, or Apple's Xcode Cloud. Android builds can run on ordinary Linux servers. Fastlane is commonly used for automating signing and uploading.

This constraint means: "One person maintaining both platforms" naturally incurs an unavoidable Mac cost (hardware or cloud) on the iOS side. This is one of the bills to calculate in section six.

5. Cross-Platform Panorama and Evolution: Where Native, RN, Flutter, and KMP Stand Now

The previous sections focused on Flutter, but you asked about the "full picture." This section zooms out, first looking at how native itself has evolved, then comparing the major cross-platform solutions side by side. Understanding evolution is more important than memorizing a version number—because it tells you what pain point each solution was born to solve.

Native Over the Years: All Moving Towards "Declarative"

Interestingly, the direction of native development in recent years has precisely been moving closer to the "declarative UI" you are familiar with in frontend.

flowchart LR
  subgraph Android
    a[Java origin] --> b[Kotlin 2017 support 2019 official priority] --> c[Jetpack Compose 2021 declarative UI] --> d[Kotlin Multiplatform and Compose Multiplatform]
  end
  subgraph iOS
    e[Objective C] --> f[Swift 2014] --> g[SwiftUI 2019 declarative UI]
  end

So you will find: When you learn React's declarative thinking, you have actually already mastered the universal key to understanding Jetpack Compose, SwiftUI, and Flutter. They look different, but the core is the same set.

Current State of the Three Major Cross-Platform Solutions (2026)

Let's lay out the three most mainstream cross-platform routes. Their positioning is actually different; understanding this is more meaningful than arguing "which is better":

React Native (by Meta)—A bridge solution that writes with React and renders into native controls. In 2026, it has just completed a major architectural upgrade: the so-called "New Architecture" (Fabric renderer + TurboModules + JSI, removing the old asynchronous "bridge") has been enabled by default since 0.76, and since 0.82 (October 2025) it cannot be turned off, with the old architecture frozen. The latest 0.86 (June 2026) works with React 19.2; the higher-performance Hermes V1 engine has been the default since 0.84 (February 2026). The official recommendation is to use Expo as the upper-level framework. Most friendly to frontend developers—because it is React.

Flutter (by Google)—A self-drawing solution with its own engine, drawing pixel by pixel, much discussed earlier. Its current state requires an objective account of a "storm": In April 2024, Google's layoffs across multiple teams affected some Flutter/Dart positions (according to Google's statement at the time, most of those laid off were infrastructure/operations roles, and the roadmap was unchanged). Subsequently, a former team member in the community initiated a fork named Flock, questioning Google's investment. But two years later, the mainstream community has reacted coldly to the fork, and Flutter itself has continued to update at a pace of four stable releases per year (the latest in 2026 is 3.44 / Dart 3.12), and has even introduced a more open governance model (e.g., Canonical leading desktop maintenance). The conclusion is neutral: it has not "died," but its sustainability has indeed been seriously discussed, which is background worth knowing when making a technology choice.

Kotlin Multiplatform (by JetBrains, endorsed by Google)—Its philosophy differs from the previous two. KMP advocates sharing business logic, with UI written natively for each platform (Compose for Android, SwiftUI for iOS), maximizing native experience. KMP itself became stable in November 2023; Compose Multiplatform for sharing UI also announced iOS stability in May 2025 (interestingly, on iOS it also self-draws like Flutter). Netflix, McDonald's, Cash App, and others use it in production. Most friendly to existing native teams.

Comparing all four (including pure native):

Dimension Pure Native React Native Flutter Kotlin Multiplatform
Creator Apple / Google Meta Google JetBrains (Google endorsed)
Language Swift / Kotlin JS / TS Dart Kotlin
How UI is produced System native Mapped native controls Engine self-drawn Logic shared, UI mostly native per platform
Code reuse scope No reuse UI + Logic UI + Logic Primarily logic (UI sharing optional)
Ease of adoption for frontend Low High (it's React) Medium (Dart easy to learn) Low (need to know Kotlin/native)
Cross-platform visual consistency Each native Medium Extremely high Depends on whether UI is shared
2026 Maturity Highest High High High (logic mature, shared UI newer)
Best suited for Ultimate experience/heavy native capability Teams with React, want speed Want strongly consistent branding/animations Existing native teams, want to avoid logic duplication

A neutral quick-reference for technology selection (no standard answer, only degree of fit):

Your Situation Solution Worth Prioritizing
Team is pure frontend, wants to produce an App fastest React Native (or try WebView approach first)
Want pixel-perfect consistent brand visuals, complex animations across platforms Flutter
Already have mature iOS/Android native teams, just don't want to write logic twice Kotlin Multiplatform
Have extreme requirements for performance/platform capabilities, and sufficient manpower Pure Native
Just content display, frequent updates, tight budget WebView approach (Capacitor, etc.)

6. Can One Person Handle It: The Realistic Bill from an Individual Developer's Perspective

Returning to your initial third question. Summarizing all the previous content into "the skill areas one person needs to master" looks like this:

flowchart TD
  center[A frontend developer wants to independently unify and maintain Web plus App]
  center --> s1[Dart language Quick to pick up]
  center --> s2[Flutter rendering and Widget mentality Medium]
  center --> s3[Design token pipeline Style Dictionary Medium]
  center --> s4[iOS build Requires Mac plus Xcode plus Signing plus Developer Program]
  center --> s5[Android build JDK plus Gradle plus keystore plus version matrix]
  center --> s6[Both stores review compliance and continuous release cadence]

Breaking it down into "investment" and "risk" is much more useful than a vague judgment of "feasible or not":

Link Learning/Investment Cost Long-term Burden and Risk Individual Friendly?
Design token layer (unifying style) Low to Medium Low, toolchain mature Friendly, highest leverage link
Dart + Flutter writing UI Medium Low, good docs, large community Friendly
A small to medium App itself Medium Medium Basically friendly
iOS build/signing/listing Medium to High Requires always-available Mac, annual fee, certificate maintenance One-time threshold + ongoing small burden
Android build/signing/listing Medium Version matrix, store policy changes One-time threshold + ongoing small burden
Both stores review + long-term iteration cadence Medium High: review rejections, policy updates, annual system adaptation This is the most easily underestimated continuous heavy work

From this table, a relatively sound judgment can be read:

"Using one set of design tokens to unify the style of Web and App"—this matter is very friendly to an individual, because it has standards, tools, and the highest leverage; it is the most worthwhile first step. "One person writing both Web and a not-too-complex App"—realistic, the learning curve of Dart/Flutter is not steep for a frontend developer. "One person long-term shouldering native builds for both platforms, dual store compliance, annual system update adaptations, and a stable release cadence"—feasible, but this is an independent, continuous heavy workload. Its cost is not in "learning" but in "long-term maintenance." Many indie developers can do it, but they usually leverage managed CI, narrow their scope to one or two platforms, and accept that "updates won't be as fast as a team."

In other words: What determines "whether one person can do it" is often not whether the technology can be learned, but the energy for long-term maintenance and that unavoidable Mac. Token unification is low-hanging fruit, pick it first; full-stack native is a long slope with thick snow, act according to your capacity.

Conclusion: A Map for Frontend Developers

Stringing this whole journey together, it's actually one sentence: When a frontend developer crosses into cross-platform, the closer to "language and design," the easier; the closer to "rendering and building," the harder.

If you only take away one action suggestion: Start with "unifying design tokens." It has the lowest risk, the most direct benefit, and will naturally let you get a feel for the theming mechanisms of both ends, accumulating experience for whether to go deeper into cross-platform. For the rest of the road, follow this map and walk slowly according to your own situation.

References and Data Sources