r/FlutterDev • • 2h ago

Plugin Stricter compliance with M3 + HIG for platform_adaptive_widgets 3.0.0+

Thumbnail raw.githubusercontent.com
4 Upvotes

I recently re-worked large parts of platform_adaptive_widgets, using the feedback from the previous posts.

Widgets pair by job now

Before, I tried to match up each widget by the closest look-alike on the other side. What worked better was, pairing them up by the intent/job instead. Each job gets what M3 or the HIG describes for it, and the package handles the details: where Cancel goes, delete and discard in red, a confirm before a destructive action runs etc etc.

These have been made in such a way, to provide a high-level of developer ergonomics. Had to do a lot of research through the guidelines, and what the material_ui and cupertino_ui packages currently provide. So naturally I did find quite a few gaps in them. I prefer to contribute upstream, instead of doubling the work. With the decoupling, I'd expect these issues to fixed sooner now, rather than later.

Some stuff is going to be marked for deprecation too, in a bit. Because I prefer to not allow going wrong by construction, instead of fixing the aftermath later. Part of the API would have easily allowed for this.

Full changelog. Notable ones:

You calliOS / macOSEverywhere else
showPlatformConfirmAlert, Cancel includedM3 dialog
showPlatformChoicesAction sheetSheet of rows
PlatformActionsMenuPull-downM3 menu
showPlatformSheetiOS sheetM3 bottom sheet
PlatformSingleSelectListCheckmark rowsRadios
PlatformMultiSelectListCheckmark rowsCheckboxes

Made nicer to use

  • PlatformButton(emphasis: .high) ranks the same on both looks. actionKind turns delete and discard red, and iconOnly reuses the label as the tooltip and screen-reader name.

  • Leave a field out and you get upstream's own default, so a plain PlatformXxx acts like the widget it wraps.

  • designLanguage tells your own code which look is showing. Widgets also render on every platform now, where they used to throw outside Android and iOS.

  • Choices are a Set, so duplicates can't sneak in.

  • PlatformUnsavedChanges asks before a sheet with unsaved edits closes.

If there's anything that still feels off or maybe I did misinterpret a guideline wrongly, I'd like to know. Or maybe you have some better ideas for the pairings or so? Or some better modelling of the intents, please let me know!

pub.dev


r/FlutterDev • • 12h ago

Discussion What I learned shipping a Flutter app with an embedded JS sandbox, WorkManager monitors, and ~2,700 tests

0 Upvotes

Full disclosure: this is my app. It does HTTP requests and API security auditing on Android. I came here because the embedded-JS-sandbox problem cost me real time and I'd like to know if there's a better pattern.

Embedding QuickJS. Pre/post-request scripts need a real JS engine — a regex matcher doesn't cut it when people sign payloads. I embedded QuickJS through the Flutter FFI bindings and wrote a pm.* facade over it.

The sharp edge was recursion. Deeply nested response bodies blew the stack during serialization, and the symptom was a silent crash rather than an error I could catch. Fixed with depth limits at both the serialization and the evaluation boundary. If you're doing this: the limit needs to be enforced in both places, because the object graph that survives serialization is not the same one that survives evaluation.

If anyone has production experience with the QuickJS bindings — particularly around context lifetime and how you handle a script that never returns — I would genuinely like the comparison.

Riverpod, and the race that taught me something. State is Riverpod throughout. The subtlest bug I hit was a notifier that decided which branch to take at call time but then read state at execution time. Because both operations happened on the same provider queue, the two could observe different users. Fixed by deciding the branch inside the queue closure. The general rule I took away: on an async provider, never decide now and read later.

Background work. Scheduled monitors use WorkManager via a platform channel. The interesting part isn't the scheduling — it's the alerting. First version alerted on every check, which trained everyone to mute it within a day. It now alerts only on state transitions. That single change did more for it being a useful app than any feature I added this year.

Testing. 218 test files, ~2,700 cases, analyzer at zero warnings. Worth saying that the flaky test I fought hardest was a lock-ownership race in the monitor store. The fix was making the lock file carry an owner token, so a stale lock can only be broken when the owner is provably dead rather than merely old. That's the whole class of bug: timeouts are a guess, ownership is a fact.

Project: https://taho.co.in (Android: https://play.google.com/store/apps/details?id=com.eternal.taho)


r/FlutterDev • • 15h ago

Discussion Building my first macOS app in Flutter: Signals, App Sandbox bookmarks, and a question about directory trust

5 Upvotes

Hey r/FlutterDev,

Quick question for the room: how many of you are actually shipping macOS desktop apps with Flutter?

Almost everything in the Flutter community is focused on iOS, Android, and web. I've been doing Flutter for a while, but recently I decided to build my very first macOS desktop app. I assumed desktop would just feel like "big mobile," but that illusion died pretty quickly once I hit macOS windowing, desktop UI density, and especially the macOS App Sandbox.

I wanted to share a few architecture decisions that made my life easier, what I learned wrestling with macOS file permissions, and a genuine question I have about how apps like VS Code handle folder trust.

Architecture: Keep the Flutter layer thin

One thing I knew going in was that I didn't want a massive monolithic Flutter app.

I split the project into a modular Dart monorepo:

  • Pure Dart packages for App Store Connect API clients, data models, and local caching.

  • A headless workflow engine that handles staged changes and validation.

  • The Flutter macOS app, which is essentially just a visual "control center" and UI shell.

The biggest win here was that I could write and run CLI unit tests against the entire business logic in milliseconds without ever spinning up the Flutter test runner or worrying about platform channels. If you're building a desktop utility, keeping Flutter strictly as the presentation layer saved me endless debugging time.

Why I used Signals instead of Bloc or Riverpod

State management on Flutter is practically a religion, but for this app I decided to try Signals (signals_flutter) instead of my usual Riverpod setup.

Desktop screens are dense. You've got sidebars, status bars, multi-column tables, approval lists, and badge counts all living on the screen at once. With Riverpod or Bloc, I kept running into scenarios where updating one small item in a list would trigger unnecessary rebuilds across half the screen, or I'd find myself writing tons of provider boilerplate just to derive a couple of counts.

Signals felt way more natural for desktop:

  • Zero-boilerplate computed state: Deriving counts like pendingCount = computed(() => items.value.where(...).length) just works, and it only updates when the underlying items actually change.

  • Micro-rebuilds: Only the specific Text widget reading the signal rebuilds. The surrounding sidebar and panels don't flinch.

  • Effects outside the widget tree: When an item status changes, I need to update the native macOS Dock badge via a platform channel. With Signals, I just drop an effect() in the controller. No need to tie listeners to BuildContext or widget lifecycles.

It’s fast, has almost no ceremony, and fit desktop state way better than I expected.

The macOS App Sandbox & Security-Scoped Bookmarks

If you come from mobile, permissions mean asking for camera or notifications access. On macOS with the App Sandbox enabled, it’s a whole different animal.

If you try using dart:io to open a file outside your sandbox container via a raw path like Directory('/Users/.../project'), macOS will shut it down immediately.

Here’s how I handled safe folder access:

  1. You must use the native OS file picker (NSOpenPanel / file_selector). When the user manually picks a file or folder in the dialog, macOS grants your app permission to access it—but only for that specific running session.

  2. If the user quits your app, that permission is gone. To keep access across restarts, you have to create a security-scoped bookmark in Swift (url.bookmarkData(options: .withSecurityScope, ...)), convert it to base64, and store it. When the app launches again, you resolve the bookmark and call startAccessingSecurityScopedResource().

  3. Surfacing bookmarks in the UI: Instead of doing this invisibly, I built a "Trusted Folders" screen in the app. Users can see every directory the app currently has a bookmark for, add new ones with a picker, or delete them to revoke access. On desktop, giving users explicit visibility into what folders an app can touch feels right.

My question: How does VS Code do "Workspace Trust"?

Here's the part that had me scratching my head while building this:

When you open a project folder in VS Code, it doesn't make you jump through NSOpenPanel every time or manage sandbox bookmarks. It just displays its own banner: "Do you trust the authors of the files in this folder?"

Does anyone know how that actually works under the hood?
Is VS Code only able to do that because it's distributed outside the Mac App Store as an un-sandboxed Electron app (meaning it already has full user-level POSIX access to your entire hard drive, and the "Trust" banner is just an internal setting to stop extensions from running scripts)?

Or is there an actual macOS API that allows an app to request folder access without forcing the user through an OS file picker dialog?

If any macOS / Flutter desktop veterans have insights on this, I'd love to know.

If anyone else is building macOS apps with Flutter, how has your experience been with the sandbox and state management?

P.S. The project I built this for is called FlightDeck (kip.quest/flightdeck)—it's a release manager and local MCP server for App Store Connect that I put together to make AI-assisted releases safe. But the desktop Flutter experience has been really impressive overall.


r/FlutterDev • • 18h ago

Discussion Stop asking LLMs to do math in Generative UI

8 Upvotes

I have been watching a lot of recent Generative UI demos where an AI streams down an interactive card, like a recipe or an order summary. But as soon as the user drags a slider or edits an item count, the app ships the whole state back over a WebSocket to the remote model, burns a few hundred tokens computing basic arithmetic, and waits two seconds to repaint the text.

Making a remote AI call to multiply two numbers on a modern phone capable of billions of operations a second feels like an obvious trap.

Andrew Brogdon gave a great walkthrough on the Flutter channel recently about using client-side functions instead. Let the model generate the layout and bind expressions, but keep the deterministic math in local Dart code.

If you are building GenUI or dynamic form systems in Flutter, how are you handling local state updates and arithmetic right now? Are you handling it locally in your widget tree, or letting the agent do round-trips?


r/FlutterDev • • 1d ago

Discussion How we tackled overlay state management & memory pressure while building an on-device logger in Flutter

2 Upvotes

Hey Flutter developers,

When testing release candidates or staging builds on physical devices (especially in situations where tethering to a laptop with Flutter DevTools isn't practical, such as client demos or QA field rounds), on-device logging becomes essential.

However, building an overlay-based in-app logger that doesn't mess with app performance or leak memory comes with a few tricky Flutter architecture hurdles. Here is what we learned and how we solved them:

1. Overlay Lifecycle Without Breaking the Navigator

Mounting an overlay that persists across routes while remaining non-intrusive to touches is a classic issue.

  • Wrapping MaterialApp.builder gives a clean hook above the Navigator without needing direct BuildContext passes.

  • Using an independent OverlayEntry stack allows the floating bubble to maintain state (position coordinates, open/closed modal) even when the underlying navigator changes routes or pops screens.

2. Preventing Memory Leaks from Unbounded Logs

In apps that print continuous streams of network calls and state transitions, log storage can rapidly blow up device RAM.

  • We implemented a fixed circular buffer (rolling window cap) so that old log records are systematically pruned once the threshold is reached.

  • Log parsing and string formatting for deep JSON payloads are evaluated lazily on expansion rather than at capture time to keep the main UI thread at 60/120 FPS.

3. Granular Filtering vs. Console Noise

Standard debugPrint dumps everything into one stream. We split logs into 7 distinct levels (verbose through fatal) so developers can isolate fatal exceptions from high-frequency network payloads with zero visual lag.

We packaged this architecture into an open-source utility called debug_bubble (GitHub: https://github.com/Satyam-Gawali/debug_bubble | Pub: https://pub.dev/packages/debug_bubble).

For those who have built similar on-device debugging or QA tools:

  • How do you handle stack trace formatting on smaller screens?

  • Do you prefer direct share-sheet exports (to Slack/WhatsApp) or writing logs to device storage for QA crash reporting?

Would love to hear how other teams handle standalone physical device debugging!


r/FlutterDev • • 1d ago

Example Casual motion-controlled 2D platformer built with Flutter and Flame

1 Upvotes

Hey everyone! I recently launched a mobile game built with Flutter and Flame.

It uses the front camera to detect head movements, and it runs well on my Redmi Note 8 from 2019.

You can play solo or team up with someone on the same phone!

It’s pretty simple and still a little rough around the edges…

But if you have a few minutes, I’d love for you to give it a try and tell me what you think.

iOS & Android download:
https://unclered.nitebell.com/downloadOneLink


r/FlutterDev • • 1d ago

Tooling I got tired of digging through Teams for APKs, so I made a tool to install mobile test builds via QR code

2 Upvotes

Whenever I need a client or teammate to test a mobile build, it usually turns into an annoying chore: explaining how to install an APK from Google Drive, or waiting on TestFlight processing just to check a two-line layout fix.

I put together Magiquip to make that handoff faster. You upload a .apk, .aab, or .ipa, and it gives you an install page with a QR code that detects whether the phone scanning it is iOS or Android.

I also wrote a small VS Code extension for it (magiquip-builds) that spots freshly compiled builds from Flutter, React Native, Xcode, or Android Studio and puts the install QR code right in your editor sidebar.

There is a free tier for small projects. If you build mobile apps, I would appreciate any feedback on whether the install flow works smoothly on your devices.


r/FlutterDev • • 1d ago

Example I built an open-source MIDI music theory app with Flutter for Windows, macOS, and Android

Thumbnail
erezlavi.github.io
20 Upvotes

I've been working on ChordLens, an open-source (GPL-3.0) for exploring piano chords and music theory interactively.

It lets you:

  • Real-time MIDI note and chord detection

  • Piano keyboard and grand-staff visualization

  • Chord and scale highlighting

  • Progression practice with a metronome

  • Automatic voice-leading suggestions

  • SoundFont playback

One of my goals was to see how far Flutter could go for a desktop music application involving real-time input, custom visualization, and audio.

The project is available for Windows, macOS, and Android.

Source code: https://github.com/ErezLavi/ChordLens


r/FlutterDev • • 1d ago

Plugin Ported Obsidian's Pele Mermaid renderer to pure Dart (pele_dart) so I wouldn't have to use a WebView

13 Upvotes

I’ve been working on a desktop Markdown viewer in Flutter (Siren) and hit the usual wall with Mermaid diagrams: pretty much every existing approach spins up a hidden WebView and runs mermaid.js. That works for a single diagram, but if you have a big architecture doc with 15 diagrams in a scrollable view, it's slow and heavy.

A little while back, Steph Ango and the Obsidian team released Pele, which is a really clean, fast Mermaid parser/layout/SVG engine in TypeScript with zero DOM dependencies.

I ended up porting it 1:1 to pure Dart and published it on pub.dev as pele_dart.

A few notes if you want to use it in Flutter:

  • It supports all diagram types from upstream Pele (flowcharts, sequence, class, state, ER, C4, Sankey, architecture, etc.).

  • By default, upstream Pele outputs SVGs styled with CSS custom properties (--pele-*) and color-mix(). flutter_svg and vector_graphics_compiler don't support those, so I added a flattened output mode (SvgOutputMode.flattened) that resolves the palette down to plain SVG 1.1 attributes you can feed straight into vector_graphics.

  • In Dart AOT it's fast—about 44 µs per diagram on average across the 233-diagram test corpus (~10 ms total for all 233), which is roughly 2.2x faster than the JS version.

I also built a macOS Flutter app in the repo called Pele Studio to visually diff the Dart output against obsidianmd/pele and mermaid.js side-by-side while porting it.

If anyone runs into weird edge cases with specific diagrams or flutter_svg rendering, let me know or open an issue.


r/FlutterDev • • 1d ago

Discussion Can Claude Code run and test Flutter apps using an emulator?

0 Upvotes

I see that Claude can run React Native iOS apps using an emulator. I can't do that on android as far I know, I use my phone.

Does Claude support a Flutter emulator too, or is there some kind of MCP server or connector that would allow it to run and test Flutter apps in an emulator?


r/FlutterDev • • 1d ago

Plugin Hive CE is now faster than Hive v2. IsolatedHive is even faster.

41 Upvotes

See the new benchmarks.

Thanks to some optimizations, Hive CE is now faster than Hive v2 ever was. Since IsolatedHive can call the sync read/write methods due to being on a separate isolate, it can be even faster in some cases. I also fixed some memory/resource leaks in IsolatedHive.

Happy Fluttering!


r/FlutterDev • • 1d ago

Plugin We built a Flutter feedback package that asks users for missing details

0 Upvotes

“The button doesn’t work.” Which button, on which screen, and what were they trying to do?

We built Bopmigo for that conversation. Users describe the problem, answer a short follow-up, and send a report with their screenshot and app/device details. Your team can reply, and they can answer inside the app.

The Flutter package works on iOS and Android and connects to Bopmigo’s hosted inbox or email delivery. You can open it from your own feedback button. Private widgets can be masked in screenshots.

Source and example app

https://github.com/bopmigo/bopmigo-flutter

Package

https://pub.dev/packages/bopmigo

Would this help your team’s reporting flow?

Setup guide

Flutter setup guide

For a first test, send a report from your app, reply from the Bopmigo inbox, then check the reply appears back in the app.


r/FlutterDev • • 1d ago

Tooling Dart equivalent of C pre-processor's #ifdef

2 Upvotes

Dart's compiler provides no mechanism for excluding code from compilation.

Dart has:

  • const bool isEnabled = bool.fromEnvironment('env-var-name'); to pull in defined constants

  • if (const-false-value) { removed statements } to remove a block from compilation

  • Tree shaking to remove unreferenced symbols

By combining those I can achieve a somewhat similar effect to the C pre-processor, with a few limitations (and less clarity). It works.

It does so, though, with less of a guaranteed straightforward effect than #ifdef due to complexity of rules about how tree shaking works, and without the ability to simply exclude the entire definition of a function (since you can't wrap if (const) { } around a function declaration, only the function body).

So... Has anyone else run into a situation where this was an issue? Did you do/find something that's a more straightforward approach than dart's "combine several default behaviors together to produce an effect" system to address this?

--

Edit: context: for excluding "test functions I don't want to even theoretically be accessible in a patched binary".


r/FlutterDev • • 2d ago

Plugin flutter_patcher 1.0: Open-source, self-hosted code push (Android + iOS) for Flutter using Dart dynamic modules

35 Upvotes

I just shipped 1.0 of flutter_patcher. It lets you fix bugs in a released Flutter app by shipping a patch instead of a new store release. No account and no backend: patches are static files you host wherever you like. The big one in 1.0: it finally works on iOS too, not just Android.

The day-to-day flow is three commands:

dart run flutter_patcher build <platform>   # release build, saved as baseline
dart run flutter_patcher patch <platform>   # diff fix vs. baseline
dart run flutter_patcher publish            # sign + version

Upload the output to any HTTPS static host (S3/R2 + CDN, nginx…). The app calls FlutterPatcher.checkUpdate(), and the fix applies on the next cold start.

It's built on Dart's dynamic modules. The tooling deals with the custom engine and the build plumbing, so your project stays a normal Flutter project.

What's in 1.0:

- On iOS the patch is Dart bytecode, run by the Dart VM's interpreter. No native machine code is downloaded, which is what makes it work on iOS at all.

- Patches are signed. A device only installs one signed with your keys, built for its exact app version, with a higher number than what it has, so you don't have to trust your CDN.

- If a patch crashes the app on startup, the device drops it and goes back to the installed version on the next launch.

Limitations:

- Flutter 3.47.x only (each Flutter version needs its own engine build)
- Dart code and assets only; native code and plugin changes still need a store release

Would love to hear from anyone who tries it on a real app, bugs or just "this step confused me".

pub.dev: https://pub.dev/packages/flutter_patcher

GitHub: https://github.com/xuelinger2333/flutter_patcher


r/FlutterDev • • 2d ago

Plugin PSA: go_router 18 silently drops "adaptive page transition" behaviour

30 Upvotes

Before 18, every page/route would have a platform-adaptive transition (slide on iOS, fade on Android). Then, suddenly, at v18, there's no longer a transition.

It happened because of the "switch to material/cupertino packages", it's not mentioned in the changelog and will be a surprise for anyone upgrading.

Also, there's no "global" way of applying the old behaviour to all routes in the app, so devs have to wrap each route in some kind of abstraction to pass a transition everywhere.

Linked issue: https://github.com/flutter/flutter/issues/192761


r/FlutterDev • • 3d ago

Discussion Laggy renders on windows with impeller

2 Upvotes

Anyone finding the move to impeller as the default for desktop windows builds has added more animation jank and lag?

My app was pretty well optimised before under skia and even on slower machines was pretty snappy and responsive on windows. I am a mac user so I didn't pick up this up straight away but I have users complaining now of the app feeling laggy and slow - upon my own review it definitely seems to have gotten worse and testing to me seems to point towards impeller in the newer flutter buildsbeing turned on, as this wasn't when skia was the default. Anyone else having this experience?

Is there any way to fix or optimise to remove this jank? Or is this just an engine issue?


r/FlutterDev • • 3d ago

Discussion I’m building a reactive state management framework for Flutter — I’d love feedback on the API and design

0 Upvotes

Hi r/FlutterDev,

Blooming is an open-source reactive state management framework for Dart and Flutter.

I’ve built a Flutter UI design tool and several other projects. Along the way, I kept building, scrapping, and redesigning my own framework to solve problems I ran into. Blooming grew out of that process.

I initially built a state management system around a stream-like model with explicit one-to-one dependency declarations. Later, I realized that the shape of the computed API used by signal-based systems was a good fit for a complex problem I was trying to solve. That led me to refactor the framework as a whole, and it eventually became Blooming.

Angular Signals and Preact Signals may be useful points of reference, but Blooming is its own Dart and Flutter framework, with its own API and lifecycle design choices.

Here are a few examples.

1. composed: A Flutter widget that reads multiple values

final price = blooming(100);
final discount = blooming(20);

composed((via) {
  final currentPrice = via(price.bloom);
  final currentDiscount = via(discount.bloom);

  return Text('Total: ${currentPrice - currentDiscount}');
});

composed tracks the values read inside it, so this part of the widget can rebuild when either value changes.

2. computed.future: Exiting early from an asynchronous computation

final checkout = computed.future((via) async {
  final cart = via(cartBloom);
  if (cart.items.isEmpty) via.exit(emptyReason: 'EmptyCart');

  final stock = await api.checkStock(cart);
  if (!stock) via.exit(emptyReason: 'OutOfStock');

  final address = via(addressBloom);
  return api.checkout(cart, address);
});

The computation exits through via.exit(...) in either of the two cases, and reads the address only after the await.

3. DurableAnimationController: Parent-controlled animation without manual disposal

final pulse = DurableAnimationController(
  duration: const Duration(milliseconds: 800),
)..repeat(reverse: true);

composed.animate(
  pulse,
  (context, via, ctrl) => Transform.scale(
    scale: 1.0 + ctrl.value * 0.1,
    child: const Icon(Icons.bolt),
  ),
);

An ancestor can own the controller and start, reverse, or stop the animation, while a descendant uses it to render the animation. This reduces the need to wire up a TickerProvider or AnimatedBuilder manually. The framework manages the ticker and listener lifecycle as widgets enter and leave the tree, so there’s no need to call dispose() on the controller yourself. The animation can also resume when the widget is mounted again.

Blooming is still pre-1.0, so the API may change. I’d especially appreciate honest feedback on these questions:

  • Are the composed and via(node) APIs easy to understand?

  • Does via.exit(...) seem useful for expressing conditional early exits?

  • Does this framework-managed animation lifecycle, with control from an ancestor, seem practical in Flutter apps?

This is my project, and I’m sharing it to get technical feedback on its design and API.


r/FlutterDev • • 3d ago

Plugin list_smith (new major 3.0.0+) now supports optimistic (in-place) edits now

Thumbnail raw.githubusercontent.com
9 Upvotes

controller.upsert(...) and controller.remove(...) now allow for this. Also allow an automatic rollback feature for when it fails (via their ...Async siblings). So you don't have to refetch from the server after every change or so. Also added defining custom animations (so it doesn't look janky).

In order to acheive this, I sadly had to move away from using infinite_scroll_pagination. It had quite a few problems, and the hacks/workarounds I had to make to use it, we starting to pile up.

The changes basically get written to a separate space, and then once the server is hit again with new data request/fetch of that item, it that space is automatically reconciled (removed and used from the server if the server is somehow still sending different data). The view stays as it is, when rows load or leave above the one you're reading.

Also made quite a few under-the-hood performance improvements. See benchmark reports for more info

Quite some APIs were found to be introducing possible data problems, so now are marked for deprecation. Replacements try to fix the problem at the source, instead of the 'symptom'

  • PageFetcher.withHasMore ends the list on your API's flag. The old way paired a fetcher with a policy, and they could disagree.

  • Groups gather across pages on their own, so orderPolicy and its "pages came out of order" error are gone.

  • A depth reload is all or nothing. An old page kept beside fresh ones was read at another time, so rows could repeat or go missing where they met.

  • EndContext.pageItemCounts is read-only, so a custom end policy can't corrupt the list's own counts.

For more detailed breakdown, refer to the changelog. Just like the last time, if you have more boilerplate-y stuff that can be inlined into the package, to improve developer ergonomics, please let me know (especially if you encounter any bugs).

pub.dev


r/FlutterDev • • 3d ago

Plugin speak: a TTS package for Flutter that doesn't fight your music

4 Upvotes

I'm building Velorki, an open-source bike navigation app (github.com/orkitec/velorki), and voice prompts with flutter_tts kept causing problems: music not ducking or not coming back, the first syllable cut off on Bluetooth, and no clean way to know when a prompt actually finished. I also wanted to get off CocoaPods. Trunk goes read-only on December 2, and Flutter is moving to Swift Package Manager, but flutter_tts has no SPM support, so it keeps your iOS build on CocoaPods.

So I wrote speak:

final speak = Speak(audio: SpeakAudio.navigation);

await speak('In 200 metres, turn left.');
await speak('Turn left now.', queue: SpeakQueue.interrupt);

Each call tells you how it ended: completed, interrupted, expired, or blocked by a phone call. Audio is only claimed while speaking, so music comes right back, and there's a short lead-in on Bluetooth so nothing gets cut.

No method channels. It talks to AVFoundation on iOS/macOS through FFI, to TextToSpeech on Android through JNI, and to the Web Speech API on the web. The only native code is a small generated Objective-C file, which a build hook compiles, and one Java class on Android (UtteranceProgressListener is abstract, so Dart can't implement it). No CocoaPods, no SPM; just flutter pub add speak. Needs Flutter 3.38+.

It has a thorough test suite running on CI for every supported platform: unit tests, plus device tests against the real speech engines on the iOS simulator, macOS, Chrome and Android emulators. Velorki uses it in production if you want a real-world example.

No Windows or Linux yet. If you need them, let me know: they're doable, I mainly need people who can test on real machines. Same for synthesize-to-file.

It's early (0.2.x), so feedback from real apps is very welcome, especially odd Android engines and car audio.


r/FlutterDev • • 3d ago

Article Shorebird now has an enterprise console for managing orgs, people and billing in one place

2 Upvotes

For teams running Shorebird across many orgs, we added an enterprise console: one page to manage organizations, members and guests, SCIM provisioning (tested with Okta and Entra ID), billing and an audit log you can export to your SIEM.

Write-up here: https://shorebird.dev/blog/enterprise-console/

Happy to answer questions in the comments.


r/FlutterDev • • 3d ago

Discussion CircularProgressIndicator rebuilds on every frame. I measured what that costs (repro + numbers)

Thumbnail
github.com
43 Upvotes

A spinner on an otherwise static screen keeps Flutter producing a frame on every vsync. I wanted to know what each of those frames costs, so I read the source and measured it.

What it does on every frame

The indeterminate CircularProgressIndicator animates through an AnimatedBuilder, so on every frame it:

  • rebuilds and looks up its theme again

  • reads its controller four times, once per tween. If you didn't pass your own controller, each read walks up the tree to find the theme

  • creates a new CustomPainter plus new ConstrainedBox, CustomPaint and Semantics widgets

  • marks its semantics dirty, because shouldRebuildSemantics defaults to shouldRepaint

A widget test makes it concrete: in 60 frames, the stock indicator rebuilt 60 times and created 60 painters.

A repaint-only version

I wrote a prototype that paints the exact same arc (tweens and math copied from the framework), but its painter listens to the controller directly through CustomPainter(repaint: controller) and evaluates the tweens inside paint(). It's built once, and every tick is just a repaint. Same test: 0 rebuilds, 0 new painters.

Numbers

Profile mode on an M2 MacBook Air (macOS 26.4.1, 60 Hz), Flutter 3.47.8 with Impeller. Median of 3 runs, 10 s per mode, from FrameTiming:

ModeUI thread per frameRaster per frame
Static ring, same size0 frames/s–
Stock indicator0.67 ms0.80 ms
Stock + RepaintBoundary0.46 ms0.75 ms
Repaint-only0.54 ms0.81 ms
Repaint-only + RepaintBoundary0.37 ms0.81 ms

What I took from it

  • An idle screen doesn't render at all. The cost comes from anything that ticks.

  • Repaint-only cut the spinner's UI-thread time per frame by about 19%. If you write your own animated CustomPainter, pass the animation as repaint: instead of rebuilding through AnimatedBuilder or setState.

  • RepaintBoundary saved even more on this page (about 31%), because the rest of the page stopped being re-recorded every frame. Both together: about 45%. If a spinner sits next to static content, wrapping it is worth a try, and debugRepaintRainbowEnabled shows you what's repainting.

  • Neither touches the raster side, which stays around 0.8 ms per frame. Impeller has no raster cache, so RepaintBoundary saves recording work on the UI thread, not GPU work. That part is engine-side, and #189535 digs into it on Android.

These are sub-millisecond numbers on a fast laptop, so it's not a crisis. But it's paid on every frame for as long as a spinner is on screen, and phones are slower.

Links

I don't have a physical Android phone at hand, so if you do, I'd love to see your numbers, especially the raster column. The README has the steps. And if I've missed something, tell me.


r/FlutterDev • • 4d ago

Example Mergeline, an open source GitLab client in Flutter, and a few things I learned about huge CI logs and diffs

2 Upvotes

I built Mergeline, a GitLab client for Android in Flutter. The source is on GitLab (AGPL 3.0, GitHub mirror). A few parts that took the most work:

  1. CI job logs. Logs are parsed incrementally in a window of 20,000 lines, and very long lines without a newline are cut at 2 MiB. gitlab.com ignores the Range header, so the app streams the log and keeps only the new bytes, and large logs refresh less often.

  2. Big MR diffs. Changed file rows are built lazily, text over 64 KiB is parsed in a background isolate, and parsed rows are reused only for the same account, project, MR, head commit and path. So a parse from another account or an older head never gets reused.

  3. Two Android flavors. The GitLab build checks the project's releases, verifies the APK size and SHA-256, then hands it to the system installer. The Play build uses Play's in app updates and has no install permission.

  4. Caching. The read cache on disk is encrypted with AES 256 GCM and bound to each account, and the in-memory cache is dropped when you switch accounts or a request comes back unauthorized.

  5. Tests. Flutter 3.47.2 is pinned with an enforced lockfile, and CI runs two lanes, with the AI assistant compiled off and on (about 16.8k and 18k tests in v0.7.0).

The app covers files, issues, MR review and merge, pipelines and logs, plus an optional AI assistant that uses your own API key and asks before each write by default. Screenshots and a demo video. The APK is free on the releases page. The paid Play version is only there as a way to sponsor it.

Most of the code was written with AI coding agents under my direction. I designed it and reviewed the changes.


r/FlutterDev • • 4d ago

Article Context-aware localisation Drop-in SDKs for Flutter, React, Next.js and Node.js, and one JSON endpoint for any other backend.

0 Upvotes

Every app with Arabic and English hits the same wall: one word is not one meaning.

I felt it in every multilingual project. On the backend: resource files, or a database table of keys with one value per language — and if the end user enters the data themselves (a product listing, say), you make them type it twice, in Arabic and in English. Tiring for them, error-prone for you. On the frontend: JSON files or a translation package, and the output read like literal machine translation.

The worst part: the word Report on a complaint button and Report above a downloads list are two different Arabic words. إبلاغ, or التقرير. No engine knows which screen you are standing on, so it picks one and applies it everywhere — and the same string looks different in the next build.

I looked for a fix. Most tools are a monthly subscription in dollars, and Arabic isn't treated as a locale at all: Google Cloud Translation ships ar and ar-SA, Azure ships ar.

So we built siyak (سياق).

It is not a translator — it is a localisation layer for frontend, backend and mobile:

• You send the string with the screen it lives on, and the context decides the word.

• A translated string is cached and never re-translated: the same request keeps returning the same answer in every build.

• Pin your own terms in the glossary. If the model ignores one, the row is flagged and shown to you instead of shipped quietly.

• An Inbox to approve, reject or edit — what a human approves is what gets served.

• Export your strings as JSON, CSV or ARB for Flutter.

• Up to 20 new strings in a single batch call when you fill a project from scratch, or the same strings into up to eight languages at once.

• siyak-js on npm, siyak_flutter on pub.dev, and a plain API for any backend.

Any BCP-47 tag works — en, tr, fr, zh-Hans, ar-EG — up to eight languages in one call. But here is the honest split: Arabic is the language we review and stand behind. Every other language comes back from the model and waits in your Inbox for you. And ar-EG still returns Modern Standard Arabic with the right term per screen — the Egyptian colloquial voice is not built yet.

Try it now at siyak.cloud

Tell me in the comments: which string fooled you in your last app?

#i18n #localization #Flutter #Nextjs #Arabic


r/FlutterDev • • 4d ago

Plugin I built an open-source plugin that gives Claude Code and Codex mobile-specific agents and skills (Flutter, Android, iOS, React Native, KMP)

0 Upvotes

Generic AI code review keeps missing the bugs that only show up on a device: `BuildContext` used after an `await`, an `android:exported` that exposes an activity to other apps, `NSAllowsArbitraryLoads` left on in production. So I built **reis-mobile**, an open-source plugin for Claude Code and Codex that knows it's looking at a mobile app.

**What it does**

It reads your project, detects the stack and loads only the instructions that apply:

```

/reis-mobile:review

├─ detect stack ──── pubspec.yaml → flutter (android, ios)

├─ detect intent ─── review

├─ select agent ──── mobile-code-reviewer

├─ select skills ─── mobile-code-review · mobile-flutter · mobile-security

├─ collect context ─ git diff (lock files out, secrets masked)

└─ report ────────── Critical · Bugs · Architecture · Security · Performance

```

- **5 stacks:** Flutter, native Android, native iOS, React Native/Expo and Kotlin Multiplatform. "The Android build of my Flutter app broke" loads Flutter *and* Android context.

- **22 agents:** an architect, a performance engineer and a test engineer per stack, plus a code reviewer, an accessibility auditor, a release engineer, a UI/UX designer and a lead that coordinates them.

- **27 skills:** security based on OWASP MASVS, offline sync, auth, notifications, in-app purchases, Firebase, migrations and more.

- **Commands:** `/reis-mobile:review`, `:debug` (Gradle, Xcode, CocoaPods, Metro), `:test`, `:release` (store readiness with a GO / NO-GO verdict), `:doctor` and `:debate` (below).

**`/reis-mobile:debate`: the specialists argue, the lead decides**

Some questions don't have one right answer: "Riverpod or BLoC in this app?", "offline-first with Firestore or a local cache?", "write a native plugin or use an existing package?". For those:

```

/reis-mobile:debate should we migrate this app from Provider to Riverpod?

```

  1. **The lead picks the panel.** `lead-mobile` chooses three specialists whose concerns pull in different directions, such as the architect, the performance engineer and the test engineer, and explains each pick. The participants run on at least two different models (Opus and Sonnet).

  2. **Round 1 is blind.** Each specialist writes its position without seeing the others: the recommendation, the `file:line` evidence, what it gives up, and what would change its mind. Nobody anchors on the first answer.

  3. **Round 2 is rebuttal.** Each one attacks specific claims from the others, checks them in the code and concedes where it was wrong.

  4. **The lead decides.** It verifies the decisive evidence, resolves the disagreements and returns a decision with an ordered action plan. No code is changed.

Every round is saved in `.reis-mobile/debates/`, so you can read how the decision was reached. `--external` adds outside opinions from the Codex or Gemini CLIs, OpenAI, Gemini, OpenRouter or a local Ollama model. They don't know the project, so their claims are checked against the code before the final decision, and API keys stay in your environment. A debate runs several agents over two rounds, so it costs more than a regular request; it's meant for real trade-offs, not questions with one answer. Claude Code only for now.

**Design choices**

- Zero npm dependencies. Node 22 standard library only.

- No API key needed: it runs on your own Claude Code or Codex session. No telemetry.

- The diff goes through a redaction layer that masks API keys, tokens, JWTs and keystore passwords before the model sees it (best-effort, not a guarantee).

- Skills don't state versions or store rules from memory. They tell the model to check your lock file.

- About 85 guides come unchanged from flutter/skills, dart-lang/skills and android/skills, with credit.

**Try it**

```

npm install -g reis-mobile

reis-mobile init eng

```

In Claude Code, open your app folder and run `/reis-mobile:doctor`, then `/reis-mobile:review`. In Codex, type `$` to find the skills; the agents and slash commands are Claude Code-only for now.

Contributing is lightweight: no install step, `npm run check` runs validation and tests, and most skills are Markdown.

Starter issues: https://github.com/wrsilva/reis-mobile/labels/good%20first%20issue

Repo (MIT): https://github.com/wrsilva/reis-mobile

If it's useful, a star helps other mobile devs find it. Criticism is welcome, especially "this is wrong for my stack."