Service — Service — Flutter development

Flutter apps from one codebase.

Cross-platform applications that feel native on iOS and Android. One codebase, shorter timelines, and an architecture your own team can keep alive.

02What Flutter is genuinely good at.

What Flutter is genuinely good at.

Flutter does not wrap the platform's UI components. It draws every pixel itself, through its own renderer, and Dart compiles ahead of time to native machine code. That single architectural decision explains most of Flutter's strengths and all of its weaknesses, so it is worth understanding before choosing it.

  • Design consistency: a screen looks the same on both platforms and on an old Android device, because the framework drew it rather than asking the operating system to.
  • Custom interfaces: animation, motion and brand-heavy design are ordinary work in Flutter instead of a fight against platform controls.
  • One team, one language, one set of tests for iOS and Android, with hot reload keeping the design loop short enough to iterate with a designer in the room.
  • Predictable performance for the kind of app most companies build: lists, forms, media, charts, API calls.
  • It reaches past phones — web, desktop and embedded targets exist — which matters for a product that may later need a kiosk or an internal desktop tool.

It is a mature choice rather than a bet. Google builds its own products with it, large consumer apps ship on it, and the release cadence is steady enough to plan around.

03Where Flutter is the wrong tool.

Where Flutter is the wrong tool.

  • When the product depends on a vendor SDK that exists only for Swift and Kotlin and the community plugin is unmaintained. You can bridge it, but you are now maintaining a bridge.
  • When the app is mostly operating-system surface: home-screen widgets, live activities, watch apps, share extensions. Those are written natively whatever the main app is built in.
  • When the requirement is to look exactly like the platform, on every control, forever. Flutter's Cupertino widgets imitate iOS; they do not change the day Apple does.
  • When your team is React-shaped and will maintain the app themselves. The best framework is the one your people can keep alive.
  • When install size is a hard constraint in a market of low-end devices and expensive data. Flutter ships an engine before it ships your first screen.

We say this openly because the alternative is worse. An agency that recommends the same framework for every project is telling you something about itself, not about your product.

04One codebase, honestly.

One codebase, honestly.

Sharing a codebase removes real duplicated work. The interface, the state handling, the models, the validation, the API layer and most of the tests are written once. That is the bulk of an application, and it is the whole reason cross-platform exists.

It does not make the second platform free, and anyone who tells you it does has not shipped one. What stays platform-specific:

  • Permissions, background execution and notification behaviour differ, and each needs its own setup and its own testing.
  • Store presence is duplicated: two accounts, two reviews, two sets of assets, two release schedules.
  • Native configuration lives in the iOS and Android projects — signing, entitlements, manifests, deep-link association files.
  • Some plugins behave differently per platform, and a few bugs appear on only one of them.
  • Testing happens on both. A shared codebase is not a shared device.

The honest summary: one codebase makes two platforms materially cheaper than two native builds, and materially more expensive than one. We estimate on that basis, and the estimate changes when the scope changes — in writing, before the work starts, never quietly.

05Moving an existing app to Flutter.

Moving an existing app to Flutter.

A rewrite is the most expensive way to fix an app, so the first question is whether the existing one is actually the problem. If two native codebases have drifted apart, every feature is built twice and half the budget goes on keeping them in step, then a single Flutter codebase is a real answer. If the app feels slow because the backend is slow, changing the frontend framework changes nothing at all.

Two ways to do it

Progressive: Flutter is added to the existing app and takes over one area at a time, so users keep receiving releases and the risk stays contained. It costs more in plumbing — two build systems, navigation across the boundary, a shared design system — and it is usually the right choice for an app with real users and real revenue.

Full rebuild: cheaper per feature and simpler to reason about, and the right call when the existing app is small, undocumented, or built on something nobody maintains any more. The trap is scope. A rebuild quietly becomes a redesign, and then it takes twice as long as the estimate everyone agreed to.

What we look at before recommending either

  • How much of the app is genuinely platform-specific, rather than screens, forms and API calls.
  • Whether the backend and the API are stable enough to build against, or are part of the problem.
  • Which native dependencies exist today, and whether each has a maintained Flutter equivalent.
  • Whether the current app has any tests. Without them, feature parity is a matter of opinion.
  • Who will maintain the result, and in which language.

The answer comes back written down, with the reasoning and with the case for doing nothing included. A rewrite nobody can justify on paper is a rewrite that gets regretted.

06Platform channels and native work.

Platform channels and native work.

Flutter can reach anything the platform can do. The only question is who writes the bridge. Method channels pass typed messages between Dart and Swift or Kotlin. Pigeon generates that boilerplate from a schema, which removes a whole class of runtime mismatch between the two sides. And dart:ffi calls C and C++ libraries directly, with no channel at all, which is usually the right route to an existing native SDK written in C.

How we treat plugins

Most needs are already covered by a package, and choosing one is a maintenance decision rather than a search result. Before a dependency goes in we look at when it was last released, whether it supports the current Flutter version and both platforms properly, how open issues are handled, and how hard it would be to replace. A popular abandoned package is worse than a small maintained one.

Where nothing suitable exists we write the plugin — federated where behaviour genuinely differs per platform — and it lives in your repository, tested on both, so nobody has to reverse-engineer it in a year.

07Binary size, and the things that actually stutter.

Binary size, and the things that actually stutter.

A Flutter app carries its engine, so the floor for an installed app is higher than the native equivalent. In Europe this is usually a non-issue. In markets where storage and mobile data cost real money it is a genuine constraint — and it is mostly manageable.

  • Ship per-architecture builds. An Android App Bundle serves each device only the code it needs.
  • Icon fonts and unused assets are tree-shaken in release builds; what actually inflates a package is large images and bundled media.
  • Deferred loading can push rarely used parts of an Android app out of the initial download.
  • Audit dependencies for weight, not only for features.

Performance in practice

Stutter in a Flutter app is rarely the framework. It is almost always work on the UI thread that belongs somewhere else: decoding images at full resolution, rebuilding an entire tree because state sits too high, parsing a large JSON payload synchronously instead of in an isolate, or building every item in a list rather than only the visible ones. We profile in release mode on a mid-range Android phone rather than the newest iPhone, because that is where the problem lives.

08Web and desktop are targets, not freebies.

Web and desktop are targets, not freebies.

Flutter compiles to web and to desktop, and both work. Neither is free, and choosing them for the wrong reason is how a project gets stuck.

Flutter web

The page is a canvas the framework paints. That makes it a reasonable way to ship an internal dashboard or an admin tool behind a login while reusing the app's own code. It is a poor choice for anything that has to be found by search or read by a crawler: the initial download is heavy, there is little in the HTML, and text does not behave the way it does in a document. If a page has to rank, build it in Next.js and let Flutter own the app.

Desktop

Windows, macOS and Linux builds are real and useful for internal tools. Each brings its own packaging, code signing, notarisation and update mechanism — which is a separate project with its own budget, not a checkbox in the build configuration.

09Testing, architecture and staying current.

Testing, architecture and staying current.

  • Unit tests for business logic, which is where bugs cost the most and tests cost the least.
  • Widget tests for screens and their states — loading, empty, error, populated — because those are the states that ship broken.
  • Integration tests for the flows that must never break: sign-in, payment, the core action of the product.
  • Golden tests where a visual regression would genuinely matter.
  • All of it in CI on every pull request, with builds distributed to testers automatically.

Architecture serves the same goal. Business logic sits away from widgets, state management is one choice applied consistently — Riverpod or BLoC, decided once and written down rather than mixed per screen — and platform access sits behind interfaces so it can be faked in a test. Flutter itself moves quickly, so upgrading the SDK and its dependencies is scheduled work done in small steps, instead of a jump across several versions when something finally forces it.

10Questions

The questions that come before a contract.

Why Flutter rather than two native apps?
One Dart codebase compiles to iOS and Android, so a screen is designed, built and tested once. The interface is drawn by Flutter itself, so the app looks the same on both platforms and on older devices. For product apps — screens, data, forms, media, notifications — the reduction in work is real.
When would you tell us not to use Flutter?
When the app is a thin shell over platform-specific capability: deep system integration, heavy background processing, home-screen widgets and OS extensions, or a device SDK that only ships native bindings. Also when one platform is all you need. We say so during scoping, not after the contract.
Can Flutter use the backend we already have?
Yes. Flutter is a client and talks to REST or GraphQL like any other. We read your existing API contract first and tell you what is missing for mobile — usually pagination, push tokens, offline-friendly endpoints and versioning.
Will it feel like a real app, and how large is it?
Flutter compiles to native ARM code, and lists, animation and navigation run at full frame rate when the app is built properly. Binaries start larger than a minimal native app because the rendering engine ships inside them — a difference measured in a few megabytes, which we cut down with per-ABI builds and asset trimming.
Can the same codebase give us a web or desktop version?
Technically yes, but we scope it separately and never sell it as free. Web and desktop need their own navigation, input handling, layout and test pass. Where the web target is content that has to be found by search engines, we normally recommend a proper web app instead.
How is the project priced, and who owns the result?
A fixed price against a written scope, or a monthly rate for ongoing work — decided after scoping, which costs nothing. The code, the design and the store assets are yours, in a repository you control from the first commit.
Contact

Start a Flutter project

Start a Flutter project