DevelopmentAll levels

Flutter

Flutter is Google’s UI framework that draws every pixel of an app with its own engine, from one Dart codebase for iOS, Android, web and desktop. That choice is why a Flutter app looks identical everywhere and does not change when the operating system does — even when the operating system changes on purpose, as iOS 26 did. The page opens with sixteen things an app needs and whether Flutter draws them or has to ask the platform, then covers the three trees and the frame budget, what a rebuild costs, Dart and isolates, the platform boundary and OS redesigns, releases up to Flutter 3.47, five documented cases, and where it degrades.

Current version
Flutter 3.47 · Aug 2026 · Dart 3.13
Model
Draws every pixel; asks the OS for the rest
Support
A release a quarter; only the latest gets hotfixes

What Flutter draws, and what it has to ask for — sixteen things an app needs

A native app asks the operating system for a button, and the system draws it. A Flutter app does not ask: it draws the button itself, on a canvas the system hands it, and the system never learns there is a button there. That single decision sorts everything an app needs into a few kinds. Some things Flutter draws. Some it reaches through a bridge it maintains. Some are native code in a plugin, written by somebody else. Some it cannot do at all. The kind decides what happens on the day the operating system changes — which is the question that matters for an app that lives for years. Here are sixteen things an app needs. Later sections cite the rows by number.

#What the app needsWho provides itWhen the OS changes itThe check
1Buttons, lists, dialogs, tab and navigation barsFlutter draws them — Material and Cupertino widgets, packages material_ui and cupertino_ui since August 2026Nothing changes: iOS 26’s Liquid Glass did not reach Flutter appsDecide which design you follow, and budget the next redesign yourself
2Scrolling physics and overscrollFlutter, imitating each platform — bounce on iOS, stretch on AndroidChanges only when Flutter copies the changeCompare both platforms side by side; physics are now your design decision
3Text layout and renderingFlutter’s own text engine, with the platform’s fontsIdentical on every device; system text tweaks do not apply by themselvesTest long Ukrainian words, right-to-left and Thai lines, and large text scales
4Page transitions and the back gestureFlutter draws the transition; Android’s predictive back has to be implemented by the frameworkAndroid 16 (API 36) turns predictive back on by defaultPopScope instead of the deprecated WillPopScope; test back on Android 16
5The rendering itselfFlutter’s engine, Impeller, on Metal or Vulkan, with OpenGL ES as Android’s fallbackA GPU driver bug on one Android model becomes your bugTest on low-end Android; below API 29 or without Vulkan the fallback runs
6Text input and the keyboardThe platform keyboard, connected to Flutter’s fields through a text-input channelNew keyboard features arrive when Flutter wires them upTest autocorrect, Ukrainian and CJK input on real devices
7Accessibility — VoiceOver, TalkBackA semantics tree Flutter builds and translates into the platform’s accessibility APIOnly as good as your Semantics labels; custom widgets have none by defaultWalk every flow with a screen reader on both platforms
8Autofill and password managersBridged to the platform’s autofill serviceWorks for fields that declare what they holdautofillHints on every login and address field
9Safe areas, the notch, system barsThe OS reports insets; Flutter lays out around themAndroid 16 makes edge-to-edge mandatorySafeArea or MediaQuery padding on every screen; test gesture and three-button navigation
10Camera, biometrics, Bluetooth, files, locationPlugins: a Dart API over Swift and Kotlin codeA new permission rule breaks a plugin until its maintainer ships a fixCheck each plugin’s publisher, last release and open issues before adopting it
11Push notifications and background workPlugins plus native project settings; the OS decides when background code may runThe most fragile category on every OS releaseTest with the app killed, on both platforms, after every OS update
12Maps, web views, video players, native adsPlatform views: a native view embedded in Flutter’s pictureBehaves natively, but composing it costs frames, and gestures can stick at the seamFew per screen; test scrolling over and around them
13In-app purchases and subscriptionsPlugins over StoreKit and Google Play BillingThe stores’ billing libraries have deadlines of their ownKeep the billing plugin current — revenue depends on it
14Home-screen widgets, watch apps, Live Activities, App IntentsNative code only — Flutter does not render thereEach OS release adds surfaces Flutter cannot reachBudget native skills for these from the start
15App icon, launch screen, permission texts, signing, app lifecycleThe native ios/ and android/ projects inside the Flutter projectStore rules land here — the UIScene lifecycle, target API levelsReview the native projects on every Flutter upgrade; 3.38 added the UIScene migration
16System UI — the keyboard, the share sheet, permission promptsThe OS, called up by FlutterChanges with the OS, as in any appNothing to do — the line between row 1 and row 16 is what this page is about

Three trees, two threads and the frame budget

When you write a button in Flutter, no UIButton and no Android Button is created. Flutter builds its own tree, lays it out itself and hands the result to its engine, which turns it into drawing commands for the . The platform provides a surface to draw on, input events, and very little else. This is the single fact that explains almost everything else about the framework — including row 1.

Flutter does not ask the operating system for a button. It builds its own tree, lays it out itself, and hands the engine a canvas to paint on the GPU — every pixel on screen is drawn by Flutter. That is why the same code looks identical on both platforms and why the widget set does not change when the OS updates its own. The price is that anything only the OS can provide — camera, biometrics, notifications, permissions — is reached through a platform channel, and accessibility and text input have to be bridged rather than inherited.

The pipeline runs through three trees, and knowing which is which makes the framework’s behaviour legible. Widgets are your description of the screen: immutable, cheap, thrown away and rebuilt constantly. Elements are what is actually mounted; they persist across rebuilds and hold the state, which is why a widget can be recreated sixty times a second without losing it. Render objects do layout and painting. They are the expensive ones, so Flutter reuses a render object whenever the new widget has the same type and key as the old one.

The engine is Impeller, which replaced Skia because of one specific problem. Skia compiled GPU shaders — the small programs that draw each effect — the first time an effect appeared, on the user’s phone. A first animation could stall for tens of milliseconds while it compiled, every time the app was installed; the cases section tells that story. Impeller compiles all its shaders when the engine is built, so nothing is compiled at run time. It became the default on iOS in Flutter 3.10 (May 2023) and the only renderer there in 3.29; the default on Android 29 and later in 3.27 (December 2024), with OpenGL ES as the fallback on older devices or without Vulkan; and the default on desktop in 3.47. Flutter on the web still draws with Skia, compiled to WebAssembly.

Two threads do the work of every frame. The thread runs all Dart code — yours and the framework’s — and produces a description of the frame called a layer tree. The raster thread turns that into GPU commands. At 60 Hz both together have 16.7 ms per frame; at 120 Hz, 8.3 ms. Since Flutter 3.29 the UI thread on iOS and Android is not a separate thread at all: Dart runs on the platform’s main thread (desktop followed in 3.35). That lets Dart call platform APIs synchronously through FFI. It also means a long Dart task now delays plugin code and system callbacks too, not only your frames.

At 60 Hz Flutter has 16.7 ms to build and paint each frame, and 8.3 ms at 120 Hz. All Dart code — yours and the framework's — runs on the UI thread, which since Flutter 3.29 is the platform's main thread on iOS and Android. Decode a large JSON response there and the thread cannot build the next frames: in this illustrative run a 40 ms decode costs two frames, and the screen shows the old picture for 33 ms. Isolate.run moves the same work to a worker isolate with its own memory; the UI thread keeps building frames, and the result arrives when it is ready.

The performance overlay and Flutter DevTools show both threads as bars against the 16 ms line, and the colour tells you which problem you have. A red bar on the UI thread means Dart is doing too much: a large build, parsing, sorting, work that belongs in an isolate. A red bar on the raster thread means the frame is too expensive to draw: layers saved and restored by effects like opacity, clipping and blur, oversized images, shadows on every list row. They have different fixes, so look before you optimise.

Rebuilds: what one setState actually redraws

setState does one thing: it marks the widget that owns the state as dirty, and on the next frame Flutter calls that widget’s build method again and walks down everything it returns. Widgets are cheap, so a rebuild is usually not the problem. What costs is how much sits below the widget whose state changed, and how much work each build does on the way. A counter whose state lives at the top of a screen rebuilds the screen; the same counter in its own small widget rebuilds two widgets.

When setState is called, Flutter rebuilds the widget that owns the state and everything below it. Keep the cart counter's state at the top of one large build method and a single tap rebuilds the whole screen — nine widgets for one changed number. Move the state into a small CartButton widget and the same tap rebuilds two: the button and its counter. The Icon inside the button was created with const, so it is the identical object as last time and Flutter skips it. That is why the advice is to extract widgets into classes rather than helper methods — a method has no element of its own and cannot be skipped.
  • Push state down to the smallest widget that needs it, and lift it only when a second place really does.
  • Extract parts of a screen into widget classes, not into methods that return widgets: a class gets its own element and can be skipped; a method is re-run with its parent.
  • Use const constructors wherever the arguments are constant: a const widget is the identical object on every build, so Flutter stops there.
  • Keep build free of work: no parsing, no sorting, no creating controllers or streams — build can run sixty times a second.
  • Give list items a Key when they can be reordered, inserted or removed, or state will stay with the position instead of the item.
  • Wrap a part that repaints often — an animation, a progress bar — in a RepaintBoundary, so painting it does not repaint its neighbours.

State management, and the honest answer

Flutter has no official state-management library, which is why every discussion of it turns into a comparison of packages. The useful way to see the field is by how much structure each option imposes, and the right answer depends more on the size of the team than on the merits of the library.

  • setState

    Built in, local to one widget. Right for anything a single screen owns, and wrong the moment two screens need the same value.

  • Provider / Riverpod

    plus observable state. Riverpod is the newer of the two: checked at compile time, testable, and independent of the widget tree.

  • BLoC

    Events in, states out, as streams. Too much ceremony for a small app; predictable and traceable for a large team on a long project.

The advice that holds regardless of the choice is the rebuild rule above, applied to state: keep it as close to where it is used as it can live, and move it outward only when a second place genuinely needs it. Most of the complexity people attribute to Flutter state management is self-inflicted — a global store introduced on day one for an app with four screens and no shared state to speak of, which then rebuilds more than it needs to on every change.

Dart: two compilers, isolates and code generation

Learning a language for a framework sounds like a cost, and Dart is about the smallest version of that cost: anyone comfortable with , Java or C# reads it on the first afternoon. It is statically typed and sound about null — a value that may be null cannot be used as one that cannot, and the compiler enforces it; since Dart 3 there is no other mode. What makes Dart unusual is that it compiles two ways for two purposes. In development it runs on a virtual machine with a just-in-time compiler, which is what makes stateful hot reload possible: change a widget and the running app updates in under a second without losing its place. For release it compiles ahead of time to machine code, so the shipped app has no interpreter. Development speed and release performance usually trade against each other; here they do not.

Hot reload has edges worth knowing before they cost an afternoon. It injects changed code and rebuilds the widget tree, but it does not re-run main() or a state object’s initState, and it keeps the current values of global and static variables. A change to any of those needs a hot restart, which resets the app’s state. A change to native code — a plugin, the ios/ or android/ projects — needs a full rebuild, because hot reload only reaches Dart.

Concurrency works like JavaScript’s: each isolate has one thread and an , so await never blocks the screen, but a long computation does. For heavy work you start another isolate, which has its own memory and talks to yours only by message — no shared state, so none of the that threads bring. Isolate.run makes that one line: it runs a function in a new isolate and returns its result. Decoding a large response, resizing an image or parsing a file belongs there, as the frame-budget diagram above shows.

DartReleasedWhat changed in how code looks
3.0May 2023Sound null safety only; records, patterns, sealed classes and class modifiers
3.10Nov 2025Dot shorthands: .center where the type is already known
3.12May 2026Private named parameters
3.13Aug 2026Primary constructors — fields declared in the class header

What Dart still does not have is a built-in way to generate toJson, copyWith and equality for a data class. Teams write them with code generation — build_runner running packages such as freezed and json_serializable, which write .g.dart files next to the source. Macros were meant to replace that and were cancelled in January 2025; the cases section explains why. Primary constructors in 3.13 are the first of the smaller features promised instead, and they remove some of the boilerplate without removing the generator.

The platform boundary: channels, FFI, plugins and OS redesigns

What Flutter cannot draw, it has to ask for, and there are four ways across. A plugin is just a package that uses one of them and ships the native half for you.

Way acrossHow it callsUse it for
Platform channel (MethodChannel)An asynchronous message to Swift or Kotlin codeMost plugin calls; anything that can wait a frame
PigeonGenerated, typed channel code on both sidesYour own native code, without hand-written message strings
FFI (dart:ffi, with ffigen and jnigen)A direct, synchronous callC libraries, and since 3.29 platform APIs that must run on the main thread
Platform viewA whole native view composited into Flutter’s pictureMaps, web views, video players, native ads (row 12)

Rows 10–15 make plugins the part of a Flutter app most exposed to time. Each one is native code for two platforms that change every year, maintained by whoever published it. When Apple announced that apps built with the iOS 27 must use the scene-based app lifecycle or they will not launch, Flutter itself added the migration in 3.38 — and issues asking for UIScene support then opened on one popular plugin after another. Before adding a plugin, check who publishes it, when it last shipped, which platforms it supports, and how long its open issues wait. After adding it, it is your code as far as your users are concerned.

When the operating system redesigns itself

iOS 26, released in September 2025, redesigned the system’s controls in a translucent style Apple calls Liquid Glass. Any app rebuilt with the iOS 26 SDK — required for App Store uploads since 28 April 2026 — takes on the new look wherever the operating system draws the control. That draws the line between approaches exactly. A SwiftUI or UIKit app changed almost everywhere. A React Native app changed wherever its components are real platform views. A Flutter app changed only in the system UI it calls up, because its Cupertino widgets are drawings of iOS 18’s controls, not the controls themselves.

iOS 26 redesigned the system's controls in Liquid Glass, and any app rebuilt with the iOS 26 SDK — required for App Store uploads since 28 April 2026 — gets the new look wherever the operating system draws the control. That draws the line between the three approaches exactly: a SwiftUI or UIKit app changes almost everywhere; a React Native app changes where its components are real platform views and stays put where they are drawn from JavaScript views; a Flutter app, which draws its own Cupertino widgets, keeps the old look except in system UI it calls up. Apple's opt-out key, UIDesignRequiresCompatibility, is ignored once an app is built with the iOS 27 SDK.

Flutter’s team said on its tracker in June 2025 that it was “not developing the new Apple’26 UI design features in the Cupertino library right now”. Its answer came in August 2026: Material and Cupertino were published as the standalone material_ui and cupertino_ui packages — the copies inside the framework are to be deprecated and removed — so design updates can ship weekly instead of with each quarterly release, and the announcement says that “work is already underway on official implementations of Liquid Glass”. The clock runs the other way for native apps. Apple let them keep the old look with an Info.plist key, UIDesignRequiresCompatibility, and its documentation now says the key is ignored for apps built with the iOS 27 SDK. So within a year native apps cannot avoid the new design, and Flutter apps keep the old one until somebody rebuilds it.

Releases, support and which Flutter you are looking at

Flutter ships a stable release every quarter — February, May, August and November — and the version number rises by three each time: 3.41, 3.44, 3.47. Each pairs with a Dart release. Between them only the current stable gets hotfixes: 3.47.6 shipped on 1 October 2026, and 3.44 has had none since 3.47 came out. There is no long-term-support line, so “supported” means “the latest”, and a team that skips a year arrives four releases behind.

FlutterReleasedWhat changed
3.10May 2023Dart 3; Impeller the default on iOS
3.16Nov 2023Material 3 by default
3.22May 2024WebAssembly for Flutter web, stable; MaterialState renamed WidgetState
3.27Dec 2024Impeller the default on Android API 29+; wide-gamut colour, withOpacity deprecated
3.29Feb 2025UI and platform threads merged on iOS and Android; Skia removed on iOS; the HTML web renderer removed
3.35Aug 2025Threads merged on macOS and Windows
3.38Nov 2025Dart 3.10; iOS 26 and Xcode 26 support; the UIScene lifecycle migration
3.47Aug 2026Dart 3.13; Material and Cupertino as separate packages; Impeller the default on desktop; Widget Previewer stable

Which Flutter are you looking at

What you see in the codeWhat it tells you
WillPopScopeWritten before predictive back; deprecated in favour of PopScope — row 4
useMaterial3: false, or no useMaterial3 on code from 2023Material 2 styling; the default flipped in 3.16
MaterialStatePropertyOlder than 3.22, where it became WidgetStateProperty
color.withOpacity(0.5)Older than 3.27; withValues(alpha: 0.5) keeps wide-gamut precision
--web-renderer html in a build scriptThe HTML renderer and the flag were removed in 3.29; the build script needs updating
SkSL warm-up files (--bundle-sksl-path)A Skia-era workaround for shader jank; irrelevant under Impeller
import 'package:flutter/material.dart' after August 2026Works for now — the framework copies are to be deprecated and removed; dart fix moves imports to material_ui, where Liquid Glass and Material 3 Expressive are being built
An iOS AppDelegate with no scene delegateA template older than 3.38; required to change before building with the iOS 27 SDK

The stores’ build deadlines apply to Flutter exactly as to any other app — the React Native page tabulates them: the iOS 26 SDK since April 2026, the UIScene lifecycle with the iOS 27 SDK, Android 16 as the target API from 31 August 2026, and 16 KB memory pages for every native library. For Flutter the engine handles its part when you upgrade; the native projects (row 15) and every plugin (rows 10–14) must handle theirs.

Five cases, and what each one was missing

The cases come from Flutter’s own issue tracker and blogs, Google’s showcase, and the community’s public record. Between them they cover the renderer, a large rewrite, the people behind the framework, a language feature that did not ship, and the platform redesigning itself.

CaseWhat happenedWhat was missing
Shader jank, 2019–2025First animations stuttered while Skia compiled shaders on the phone; the warm-up workaround was fragileShaders compiled before shipping — Impeller
Google Pay, 2020Two native apps, 1.7 million lines, rewritten as one Flutter app 35% smallerNothing — a whole-app rewrite with a team trained for it
Flock, Oct 2024A former Flutter engineer forked the framework, citing a small, frozen teamA view of the framework’s staffing as a dependency risk
Dart macros, Jan 2025A previewed language feature was cancelled after it slowed the editor and hot reloadA rule against building on a feature that has not shipped
Liquid Glass, Jun 2025 – 2026iOS redesigned its controls; native apps changed on a rebuild, Flutter apps did notA plan that names the redesign as the team’s own work

The first case is the renderer itself. Skia, the graphics library Flutter used from the start, compiled each GPU shader the first time the app needed it — on the user’s phone, in the middle of a frame. The first time a page transition or a blur ran, it could stall for tens of milliseconds; the second time it was smooth. Users saw it as an app that stuttered when new, especially on iOS, which had no persistent shader cache. Issue #32170, opened on 6 May 2019, asked for an automatic fix. The interim answer, from Flutter 1.20 in 2020, was to run the app, capture the shaders it used into a file and bundle that file — which covered only the paths someone remembered to exercise and could differ by device. The real fix was a different engine: Impeller compiles every shader when the engine is built. It became the default on iOS in 3.10 (May 2023), on Android in 3.27 (December 2024), and Skia was removed from iOS in 3.29. Six years from report to removal is the cost of a problem that lived in the architecture rather than in any one app.

The third case is about people. In October 2024 Matt Carroll, who had worked on Flutter at Google, announced Flock — a fork meant to stay in step with Flutter while merging fixes and features faster. His reasons were public and specific: by his count about 50 people at Google built a framework used by around a million developers, head count had been frozen since about 2023, there had been layoffs in 2024, and three of Flutter’s six platforms were effectively in maintenance mode. Flutter kept shipping every quarter since, so the fork did not predict a collapse. What it exposed is a dependency most teams never list: a framework is also the team that maintains it, and that team’s size and priorities are set by one company’s budget. Write down what you would do if the framework slowed — which parts of your app you could keep, and which plugins you could maintain yourself.

The fourth case is a feature that was promised, previewed and withdrawn. Dart macros — code that writes code inside the compiler, so a data class could get toJson and equality without a generator — were shown with the JsonCodable preview at Google I/O in May 2024. On 29 January 2025 Vijay Menon announced that the Dart team had stopped: the implementation made editing slower (analysis and code completion) and made incremental compilation, the first step of hot reload, slower too, and the team was not confident it could fix both in reasonable time. It promised three smaller things instead: augmentations, a faster build_runner, and dedicated language features for data. By Dart 3.13 in August 2026 primary constructors had shipped; augmentations had not appeared in a stable release. Teams that had postponed code generation while waiting for macros went back to it. Plan on what has shipped.

Where it degrades

Flutter is generous with everything it draws and silent about everything it does not. A screen is fast to build and identical everywhere, so a team can go a long way before it meets the native side — and then meets it all at once. Almost every failure is one of these:

  • A build method hundreds of lines long with its state at the top, rebuilding a whole screen for one value.
  • Widgets extracted into helper methods rather than classes, so const can never skip them.
  • Parsing, decoding and sorting on the UI thread — which since 3.29 is also the platform’s main thread.
  • Performance judged in debug mode or on an emulator rather than in profile mode on a mid-range phone.
  • Plugins adopted without checking who maintains them, which then block an OS deadline (rows 10–15).
  • Custom widgets with no Semantics, invisible to a screen reader (row 7).
  • A Cupertino look chosen to “feel native” and then left behind by the platform’s next redesign (row 1).
  • A Flutter version a year old, with deprecated APIs from the “which Flutter” table still in place.
  • A global state package adopted on day one for an app with no shared state.
  • Flutter web chosen for a public, text-heavy site, where a canvas is the wrong output for selection, search engines and accessibility.

The honest limit is the line between row 1 and row 16. Flutter owns every pixel it draws, so it cannot inherit anything the platform adds to its own controls — a redesign, a new interaction, an accessibility behaviour — until someone reimplements it. An app that should feel exactly like the platform pays that cost on every major OS release, and an app whose value lives in widgets, watch faces and system integrations writes most of that value natively anyway. The engine travels inside the app, so a Flutter binary starts a few megabytes larger than a native one. For brand-led products with one design across platforms, animation-rich interfaces and teams without existing React skills, the trade runs the other way, and Google Pay is the scale it has been proven at.

The practical check takes ten minutes. Run flutter --version and find the release in the table. Search the code for WillPopScope, withOpacity and MaterialStateProperty. List the plugins in pubspec.yaml with each one’s publisher and last release. Open the slowest screen in profile mode on a mid-range Android phone with the performance overlay on, and note which bar turns red. Turn on VoiceOver and TalkBack and walk the main flow. And check that the iOS project has a scene delegate before anyone builds with the iOS 27 SDK.

When to use it

Use it when

  • Products that must look and behave identically on every platform, from one design specification.
  • Brand-led interfaces with their own design system, where you were going to override the platform widgets anyway.
  • Animation-rich UI — owning the rendering pipeline is exactly the advantage here.
  • Teams without existing React skills, for whom Dart is no bigger a hurdle than any other new stack.

Avoid it when

  • Apps that should feel exactly like the platform and follow its redesigns — inherited native controls are the point there.
  • Products whose value lives in widgets, watch apps and system integrations, which are native code anyway.
  • Public, text-heavy websites — the canvas renderer is the wrong tool for text, search and accessibility.
  • Anything built around a brand-new platform API, which reaches Flutter after it reaches native code.

Sources and method

What this page stands on

  • 13 sources
  • Checked October 13, 2026

Researched with Claude. Finding and cross-checking the primary sources was done with Claude (Anthropic). Selection, writing and fact-checking: the Anatomy of IT Product editors.

Found this useful?

Share it with someone who is working on the same problem.