[{"data":1,"prerenderedAt":737},["ShallowReactive",2],{"blog-\u002Fblog\u002Fflutter-for-non-flutter-shops":3},{"id":4,"title":5,"body":6,"date":724,"description":725,"extension":726,"icon":727,"meta":728,"navigation":146,"path":729,"readingTime":156,"seo":730,"stem":731,"tags":732,"__hash__":736},"content\u002Fblog\u002Fflutter-for-non-flutter-shops.md","Flutter for shops that don't do Flutter — when to reach for it",{"type":7,"value":8,"toc":717},"minimark",[9,19,22,27,30,37,48,630,637,643,647,650,656,662,668,674,678,681,684,688,691,694,697,700,704,707,713],[10,11,12,13,18],"p",{},"A client walked into a project a few months back with a deceptively simple ask: a polished consumer mobile app that needed to ship to both iOS and Android, with animation-heavy screens, in a tight timeline. The team had two strong React engineers, one PHP backend developer, and zero native mobile experience. The default reflex was to reach for React Native because the existing JavaScript fluency would carry over. I pushed back and ended up shipping the app in Flutter. The result is on the ",[14,15,17],"a",{"href":16},"\u002Fprojects\u002Fvantage","Vantage"," project page.",[10,20,21],{},"Most engineering teams that don't already use Flutter dismiss it on instinct. \"We don't know Dart.\" \"The community is smaller.\" \"We'd rather use what we already have.\" These are reasonable concerns, but they are not always correct conclusions. There are specific scenarios where Flutter beats React Native, beats going native, and beats hiring two separate platform teams.",[23,24,26],"h2",{"id":25},"where-flutter-wins-for-non-flutter-shops","Where Flutter wins for non-Flutter shops",[10,28,29],{},"Flutter excels in three specific scenarios, and they map cleanly to the kind of work agencies and product teams actually ship.",[10,31,32,36],{},[33,34,35],"strong",{},"Single codebase across iOS and Android with consistent visual fidelity."," Flutter renders its UI on its own canvas (via Skia, now Impeller) rather than wrapping platform-native widgets. This means a button looks the same on a budget Android phone as it does on the latest iPhone — pixel for pixel. For brand-driven consumer apps where the design has to feel unified across both stores, this saves you from the React Native trap where iOS subtly differs from Android because each platform's native UIKit\u002FMaterial components diverge in dozens of small ways.",[10,38,39,42,43,47],{},[33,40,41],{},"Animation-heavy interfaces and custom-painted widgets."," This is where Flutter genuinely outclasses React Native. Vantage's hero element is an animated arc gauge that sweeps from 0 to the user's credit score on load, with colour interpolating across the 300–850 range. In React Native this would mean reaching for Reanimated 3 plus a custom SVG library plus a wrapper for gesture handling. In Flutter it is one custom-painted widget with a ",[44,45,46],"code",{},"CustomPainter"," extending the standard canvas.",[49,50,55],"pre",{"className":51,"code":52,"language":53,"meta":54,"style":54},"language-dart shiki shiki-themes tokyo-night","\u002F\u002F A scrap of the animated arc gauge — runs at 60fps on five-year-old phones\nclass ScoreGauge extends StatefulWidget {\n  final int score;\n  const ScoreGauge({super.key, required this.score});\n\n  @override\n  State\u003CScoreGauge> createState() => _ScoreGaugeState();\n}\n\nclass _ScoreGaugeState extends State\u003CScoreGauge>\n    with SingleTickerProviderStateMixin {\n  late final AnimationController _controller;\n  late final Animation\u003Cdouble> _sweep;\n\n  @override\n  void initState() {\n    super.initState();\n    _controller = AnimationController(\n      vsync: this,\n      duration: const Duration(milliseconds: 1400),\n    );\n    _sweep = Tween\u003Cdouble>(begin: 0, end: widget.score \u002F 850).animate(\n      CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic),\n    );\n    _controller.forward();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return AnimatedBuilder(\n      animation: _sweep,\n      builder: (_, __) => CustomPaint(\n        size: const Size(280, 280),\n        painter: _GaugePainter(progress: _sweep.value),\n      ),\n    );\n  }\n}\n","dart","",[44,56,57,66,87,104,141,148,154,187,193,198,217,228,245,265,270,275,287,302,316,330,358,366,419,449,456,471,477,482,487,505,517,530,553,580,605,613,620,625],{"__ignoreMap":54},[58,59,62],"span",{"class":60,"line":61},"line",1,[58,63,65],{"class":64},"sbD-w","\u002F\u002F A scrap of the animated arc gauge — runs at 60fps on five-year-old phones\n",[58,67,69,73,77,80,83],{"class":60,"line":68},2,[58,70,72],{"class":71},"sd1Qi","class",[58,74,76],{"class":75},"sySf4"," ScoreGauge",[58,78,79],{"class":71}," extends",[58,81,82],{"class":75}," StatefulWidget",[58,84,86],{"class":85},"sGX4V"," {\n",[58,88,90,94,97,100],{"class":60,"line":89},3,[58,91,93],{"class":92},"sN7LL","  final",[58,95,96],{"class":75}," int",[58,98,99],{"class":85}," score",[58,101,103],{"class":102},"sAklC",";\n",[58,105,107,110,112,115,119,122,125,128,131,134,136,139],{"class":60,"line":106},4,[58,108,109],{"class":92},"  const",[58,111,76],{"class":75},[58,113,114],{"class":85},"({",[58,116,118],{"class":117},"s0U2E","super",[58,120,121],{"class":102},".",[58,123,124],{"class":85},"key",[58,126,127],{"class":102},",",[58,129,130],{"class":92}," required",[58,132,133],{"class":117}," this",[58,135,121],{"class":102},[58,137,138],{"class":85},"score})",[58,140,103],{"class":102},[58,142,144],{"class":60,"line":143},5,[58,145,147],{"emptyLinePlaceholder":146},true,"\n",[58,149,151],{"class":60,"line":150},6,[58,152,153],{"class":71},"  @override\n",[58,155,157,160,163,166,169,173,176,179,182,185],{"class":60,"line":156},7,[58,158,159],{"class":75},"  State",[58,161,162],{"class":85},"\u003C",[58,164,165],{"class":75},"ScoreGauge",[58,167,168],{"class":85},"> ",[58,170,172],{"class":171},"s3R4Z","createState",[58,174,175],{"class":85},"() ",[58,177,178],{"class":102},"=>",[58,180,181],{"class":75}," _ScoreGaugeState",[58,183,184],{"class":85},"()",[58,186,103],{"class":102},[58,188,190],{"class":60,"line":189},8,[58,191,192],{"class":85},"}\n",[58,194,196],{"class":60,"line":195},9,[58,197,147],{"emptyLinePlaceholder":146},[58,199,201,203,205,207,210,212,214],{"class":60,"line":200},10,[58,202,72],{"class":71},[58,204,181],{"class":75},[58,206,79],{"class":71},[58,208,209],{"class":75}," State",[58,211,162],{"class":85},[58,213,165],{"class":75},[58,215,216],{"class":85},">\n",[58,218,220,223,226],{"class":60,"line":219},11,[58,221,222],{"class":71},"    with",[58,224,225],{"class":75}," SingleTickerProviderStateMixin",[58,227,86],{"class":85},[58,229,231,234,237,240,243],{"class":60,"line":230},12,[58,232,233],{"class":92},"  late",[58,235,236],{"class":92}," final",[58,238,239],{"class":75}," AnimationController",[58,241,242],{"class":85}," _controller",[58,244,103],{"class":102},[58,246,248,250,252,255,257,260,263],{"class":60,"line":247},13,[58,249,233],{"class":92},[58,251,236],{"class":92},[58,253,254],{"class":75}," Animation",[58,256,162],{"class":85},[58,258,259],{"class":75},"double",[58,261,262],{"class":85},"> _sweep",[58,264,103],{"class":102},[58,266,268],{"class":60,"line":267},14,[58,269,147],{"emptyLinePlaceholder":146},[58,271,273],{"class":60,"line":272},15,[58,274,153],{"class":71},[58,276,278,281,284],{"class":60,"line":277},16,[58,279,280],{"class":71},"  void",[58,282,283],{"class":171}," initState",[58,285,286],{"class":85},"() {\n",[58,288,290,293,295,298,300],{"class":60,"line":289},17,[58,291,292],{"class":117},"    super",[58,294,121],{"class":102},[58,296,297],{"class":171},"initState",[58,299,184],{"class":85},[58,301,103],{"class":102},[58,303,305,308,311,313],{"class":60,"line":304},18,[58,306,307],{"class":85},"    _controller ",[58,309,310],{"class":102},"=",[58,312,239],{"class":75},[58,314,315],{"class":85},"(\n",[58,317,319,322,325,327],{"class":60,"line":318},19,[58,320,321],{"class":85},"      vsync",[58,323,324],{"class":71},":",[58,326,133],{"class":117},[58,328,329],{"class":102},",\n",[58,331,333,336,338,341,344,347,349,353,356],{"class":60,"line":332},20,[58,334,335],{"class":85},"      duration",[58,337,324],{"class":71},[58,339,340],{"class":92}," const",[58,342,343],{"class":75}," Duration",[58,345,346],{"class":85},"(milliseconds",[58,348,324],{"class":71},[58,350,352],{"class":351},"sOJ5S"," 1400",[58,354,355],{"class":85},")",[58,357,329],{"class":102},[58,359,361,364],{"class":60,"line":360},21,[58,362,363],{"class":85},"    )",[58,365,103],{"class":102},[58,367,369,372,374,377,379,381,384,386,389,391,394,396,399,401,404,407,410,412,414,417],{"class":60,"line":368},22,[58,370,371],{"class":85},"    _sweep ",[58,373,310],{"class":102},[58,375,376],{"class":75}," Tween",[58,378,162],{"class":85},[58,380,259],{"class":75},[58,382,383],{"class":85},">(begin",[58,385,324],{"class":71},[58,387,388],{"class":351}," 0",[58,390,127],{"class":102},[58,392,393],{"class":85}," end",[58,395,324],{"class":71},[58,397,398],{"class":85}," widget",[58,400,121],{"class":102},[58,402,403],{"class":85},"score ",[58,405,406],{"class":102},"\u002F",[58,408,409],{"class":351}," 850",[58,411,355],{"class":85},[58,413,121],{"class":102},[58,415,416],{"class":171},"animate",[58,418,315],{"class":85},[58,420,422,425,428,430,432,434,437,439,442,444,447],{"class":60,"line":421},23,[58,423,424],{"class":75},"      CurvedAnimation",[58,426,427],{"class":85},"(parent",[58,429,324],{"class":71},[58,431,242],{"class":85},[58,433,127],{"class":102},[58,435,436],{"class":85}," curve",[58,438,324],{"class":71},[58,440,441],{"class":75}," Curves",[58,443,121],{"class":102},[58,445,446],{"class":85},"easeOutCubic)",[58,448,329],{"class":102},[58,450,452,454],{"class":60,"line":451},24,[58,453,363],{"class":85},[58,455,103],{"class":102},[58,457,459,462,464,467,469],{"class":60,"line":458},25,[58,460,461],{"class":85},"    _controller",[58,463,121],{"class":102},[58,465,466],{"class":171},"forward",[58,468,184],{"class":85},[58,470,103],{"class":102},[58,472,474],{"class":60,"line":473},26,[58,475,476],{"class":85},"  }\n",[58,478,480],{"class":60,"line":479},27,[58,481,147],{"emptyLinePlaceholder":146},[58,483,485],{"class":60,"line":484},28,[58,486,153],{"class":71},[58,488,490,493,496,499,502],{"class":60,"line":489},29,[58,491,492],{"class":75},"  Widget",[58,494,495],{"class":171}," build",[58,497,498],{"class":85},"(",[58,500,501],{"class":75},"BuildContext",[58,503,504],{"class":85}," context) {\n",[58,506,508,512,515],{"class":60,"line":507},30,[58,509,511],{"class":510},"sEsAJ","    return",[58,513,514],{"class":75}," AnimatedBuilder",[58,516,315],{"class":85},[58,518,520,523,525,528],{"class":60,"line":519},31,[58,521,522],{"class":85},"      animation",[58,524,324],{"class":71},[58,526,527],{"class":85}," _sweep",[58,529,329],{"class":102},[58,531,533,536,538,541,543,546,548,551],{"class":60,"line":532},32,[58,534,535],{"class":85},"      builder",[58,537,324],{"class":71},[58,539,540],{"class":85}," (_",[58,542,127],{"class":102},[58,544,545],{"class":85}," __) ",[58,547,178],{"class":102},[58,549,550],{"class":75}," CustomPaint",[58,552,315],{"class":85},[58,554,556,559,561,563,566,568,571,573,576,578],{"class":60,"line":555},33,[58,557,558],{"class":85},"        size",[58,560,324],{"class":71},[58,562,340],{"class":92},[58,564,565],{"class":75}," Size",[58,567,498],{"class":85},[58,569,570],{"class":351},"280",[58,572,127],{"class":102},[58,574,575],{"class":351}," 280",[58,577,355],{"class":85},[58,579,329],{"class":102},[58,581,583,586,588,591,594,596,598,600,603],{"class":60,"line":582},34,[58,584,585],{"class":85},"        painter",[58,587,324],{"class":71},[58,589,590],{"class":75}," _GaugePainter",[58,592,593],{"class":85},"(progress",[58,595,324],{"class":71},[58,597,527],{"class":85},[58,599,121],{"class":102},[58,601,602],{"class":85},"value)",[58,604,329],{"class":102},[58,606,608,611],{"class":60,"line":607},35,[58,609,610],{"class":85},"      )",[58,612,329],{"class":102},[58,614,616,618],{"class":60,"line":615},36,[58,617,363],{"class":85},[58,619,103],{"class":102},[58,621,623],{"class":60,"line":622},37,[58,624,476],{"class":85},[58,626,628],{"class":60,"line":627},38,[58,629,192],{"class":85},[10,631,632,633,636],{},"The 6-month history chart in Vantage uses ",[44,634,635],{},"fl_chart"," — a single dependency that gives you touch-to-inspect tooltips, smooth line interpolation, and gesture-driven zooming out of the box. The equivalent React Native chart libraries always involve a comparison spreadsheet, a tradeoff between performance and features, and at least one library that hasn't been updated since 2023.",[10,638,639,642],{},[33,640,641],{},"Prototype velocity."," Hot reload in Flutter is the closest thing in mobile development to a web dev workflow. Edit a widget, save, see the change in the simulator in under a second with state preserved. React Native has this too but it is more fragile — Metro bundler quirks, native module rebuilds, simulator restarts. Flutter's hot reload just works, and for a team prototyping a consumer app, that compounding cycle saves real days.",[23,644,646],{"id":645},"where-flutter-loses","Where Flutter loses",[10,648,649],{},"I would not reach for Flutter for every mobile project, and being honest about its limits is what makes the rest of the pitch credible.",[10,651,652,655],{},[33,653,654],{},"Deep platform integration."," If your app needs to drop into native iOS frameworks for HealthKit, CarPlay, ARKit, or anything that lives close to the OS, you end up writing platform channels — bridges between Dart and Swift\u002FKotlin — and the productivity gain of a single codebase evaporates. You are now maintaining three codebases (Dart + Swift + Kotlin) instead of two, which defeats the entire pitch.",[10,657,658,661],{},[33,659,660],{},"Teams that already have strong native expertise."," If you have an iOS engineer who lives in Xcode and an Android engineer who can reason about Compose internals, Flutter is a downgrade for them, not a force multiplier. Their tools become Android Studio, their debugger becomes less rich than what they had, and they spend the first month frustrated about losing their workflow.",[10,663,664,667],{},[33,665,666],{},"Apps that need to feel completely native per platform."," A banking app that wants to look exactly like a native iOS app to iOS users and exactly like Material You to Android users is fighting Flutter's design philosophy. Flutter wants the UI to look like Flutter — your brand — across both stores. If your product strategy is \"blend in completely with the platform,\" React Native or going native is the correct choice.",[10,669,670,673],{},[33,671,672],{},"Backend-heavy apps with thin UI."," If the mobile app is mostly forms and lists wrapped around an API, the UI performance edge of Flutter doesn't matter and the Dart learning curve isn't worth it. React Native or even a well-crafted PWA does the job for less context-switching.",[23,675,677],{"id":676},"the-dart-problem-is-overstated","The Dart problem is overstated",[10,679,680],{},"The single biggest objection I hear is \"we don't want to learn Dart.\" This concern is real but exaggerated. Dart is a small, predictable language that any developer fluent in TypeScript can write competently within two weeks. It has strong typing, null safety, async\u002Fawait that works like you expect, and an ecosystem that is small but high-quality.",[10,682,683],{},"What it lacks is the sheer breadth of the JavaScript ecosystem. If you need a niche library — say, a SAML 2.0 client or a specific industrial protocol parser — you might not find it on pub.dev, where you would have found three options on npm. For consumer apps with standard requirements (HTTP, state management, local storage, charting, animations, gestures, camera, notifications), the Dart ecosystem covers the ground completely.",[23,685,687],{"id":686},"trade-offs-and-what-doesnt-work","Trade-offs and what doesn't work",[10,689,690],{},"The trade-offs are real and worth flagging upfront.",[10,692,693],{},"App binaries are larger than React Native equivalents because Flutter ships its own rendering engine. A Vantage-sized app comes in around 12-15MB on iOS, 18-22MB on Android. Not a blocker for most use cases but worth mentioning if you are targeting users on slow data connections.",[10,695,696],{},"Debug tooling is not as deep as what you get inside Xcode for native iOS work. The Flutter DevTools suite is good for widget inspection, network monitoring, and performance profiling, but if you need to trace a memory issue down to specific platform allocations, you will be reaching for Xcode anyway.",[10,698,699],{},"And if you ever need to hand the codebase off to a different team, your hiring pool for \"experienced Flutter engineers\" is smaller than the pool for \"experienced React Native engineers\" or \"experienced iOS engineers.\" Smaller doesn't mean tiny — Flutter has been mainstream for years — but it is a consideration if your business model assumes easy team scaling.",[23,701,703],{"id":702},"closing-take","Closing take",[10,705,706],{},"Flutter is the right tool when you need a single codebase that delivers a brand-consistent, animation-heavy consumer experience across iOS and Android, with a small team that does not have existing native expertise. It is the wrong tool when you need deep platform integration, native-platform visual mimicry, or have specialists who already ship iOS or Android natively. The Dart learning curve is real but small. The bigger commitment is to Flutter's design philosophy — your app will look like your app on both stores, not like iOS on iOS and Android on Android.",[10,708,709,710,121],{},"If you want to see the patterns playing out — the animated gauge, the swipeable card carousel, the gesture-driven insight cards — the full breakdown lives on the ",[14,711,712],{"href":16},"Vantage project page",[714,715,716],"style",{},"html pre.shiki code .sbD-w, html code.shiki .sbD-w{--shiki-default:#51597D;--shiki-default-font-style:italic}html pre.shiki code .sd1Qi, html code.shiki .sd1Qi{--shiki-default:#BB9AF7}html pre.shiki code .sySf4, html code.shiki .sySf4{--shiki-default:#0DB9D7}html pre.shiki code .sGX4V, html code.shiki .sGX4V{--shiki-default:#A9B1D6}html pre.shiki code .sN7LL, html code.shiki .sN7LL{--shiki-default:#9D7CD8;--shiki-default-font-style:italic}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s0U2E, html code.shiki .s0U2E{--shiki-default:#F7768E}html pre.shiki code .s3R4Z, html code.shiki .s3R4Z{--shiki-default:#7AA2F7}html pre.shiki code .sOJ5S, html code.shiki .sOJ5S{--shiki-default:#FF9E64}html pre.shiki code .sEsAJ, html code.shiki .sEsAJ{--shiki-default:#BB9AF7;--shiki-default-font-style:italic}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":54,"searchDepth":68,"depth":68,"links":718},[719,720,721,722,723],{"id":25,"depth":68,"text":26},{"id":645,"depth":68,"text":646},{"id":676,"depth":68,"text":677},{"id":686,"depth":68,"text":687},{"id":702,"depth":68,"text":703},"2026-03-20","When Flutter beats React Native or going native, when it loses, and how I would pitch it to a team that has never shipped Dart.","md","📱",{},"\u002Fblog\u002Fflutter-for-non-flutter-shops",{"title":5,"description":725},"blog\u002Fflutter-for-non-flutter-shops",[733,734,735],"Flutter","Mobile","Dart","0Xwyycfgw1pcChILlRugmzvMC_mshjocQeLCcilmnbs",1779864409800]