Using Flutter to build a native-looking desktop app for macOS and Windows
blog.whidev.com
blog.whidev.com
I agree that wrapping every single UI widget in conditionals and writing it twice is not a good practice, but I'd still want one codebase.
Any reasonable application architecture will separate UI presentation code from application logic code. You'd still benefit from a single, shared backend to the codebase.
I managed a dual-platform software project a few years ago. I made the call to build native apps for each because our use case didn't work well with the cross-platform tools.
Writing the UI code on each platform was not difficult, but it was surprisingly challenging to make sure the application logic behaved the same on both platforms for every possible edge case. It's easy in a small app, but as complexity grows so do the edge cases. Having a shared codebase, even if the UI code had to be special-cased, wouldn't be too bad.
That said: The app in the article appears to have equivalent layouts for most screens on Mac and Windows. It seems that a single UI library could be constructed to handle both without special-casing every single thing. I'd much rather have a library that made a best-effort guess at the right thing for either platform and then add platform-specific exceptions.
And yes, the main advantage of my approach is that I use the exact same business logic in both versions: database calls, models, settings, application behavior are shared in the codebase.
I know JS is a lot better than it was 20 years ago and even half a dozen years ago, but it gave me enough PTSD over the years that compared to using JS for hybrid apps, Dart was such a breath of fresh air when I built my first Flutter app! I felt it made sense and just got out of the way so I could focus on what I wanted my app to do, and I didn’t even have to read any docs to start using it in Android Studio. And this is coming from having a lot of experience with compiled languages and a lot of Ruby in addition to JS.
And I don't particularly hate Dart. It's fine. I've written a lot of Java so it's not much worse. But if I had free reign to choose, I'd much rather take Rust.
Also, as a TypeScript/Swift/Kotlin person, Dart is fine; sure I’d like algebraic data types but overall it is good. The tooling (auto completion etc) is great. And it has a few cool unique(ish?) features like cascade and every class is an ‘interface’.
The situation described above, abstracted, happens all the time in software. Littering the code with these system specific conditionals is a pain so encapsulate those system specific concerns in objects/components/whatever-you-call-your-lego-blocks and then use a factory to encapsulate which system specific thing you get.
The trade off is your code is a little harder to understand, but the benefit is now you can design a common ‘core’ and contain the platform specific details in bundles.
That’s ((just)) my two cents!
But the entire point of having a "cross-platform" GUI toolkit is that you don't have to do that. This guy wrote two bits of code for a text input box which is one of the most basic GUI components there is after buttons.
That's still true for flutter but it all depends on the requirements of the project. In the OP article, the point was to make something that is "native-looking" to Mac OS and Windows OS. This requirement does imply a requirement for different looking and behaving UI. Lose that requirement and then you can get the non-fractured code base back.
Perhaps you may reply "the framework should do that translation for you," to which I would respond "maybe you're right." It would be nice as a framework user to offload this type of work to the framework authors. Conversely, from the perspective of the framework authors they get to work on other features if they offload this type of work to the framework users.
I suspect the deeper questions for both framework users and authors is "who owns this work?" "what tradeoffs are users willing to make to gain this framework features?" and "what does joint ownership of this requirement look like?" Different framework communities will arrive at different answers.
At the end of the day as a pragmatic programmer you just kinda have to hack around the edges to smooth everything out for the end user.
Usually this is not the case, since interfacing with Cocoa is so different from Win32, for example. However, one shouldn't conflate that difficulty with the difficulty of testing an app across platforms.
Note that whatever solution you choose, you still need separate tests for every platform. Ignoring the differences between them and assuming everything is going to work the same way is a recipe for disaster.
It wasn't that hard to handle the conditional logic for the UI components. It can also be encapsulated in a separate package, something that is already available for Flutter on mobile (iOS and Android UI): https://github.com/stryder-dev/flutter_platform_widgets
I could also not strive to make it look native, but go with the default UI (Google's Material UI). As I explained in the post, I decided to take some extra steps and use the two UI packages (macos_ui and fluent_ui), to make it adapt to the platform.
Please bear in mind that I am a single developer, with ~1 year of working with Flutter and Dart, and my main background is web development. I think that teams with more members and experience can certainly do this for even larger in scope apps.
- for an application that modifies files (e.g. a text editor), are changes applied to the file immediately or is there a separate "save" action? Platforms differ on this. Basically your internal data handling becomes platform-dependent.
- define a set of keyboard shortcuts that is consistent and intuitive, but plays well with the platform's shortcuts. You'll basically define a separate set of shortcuts per platform.
- whenever functionality overlaps with existing native applications, cooperate with them instead of duplicating their functionality (especially in an incompatible way)
Looking at the examples given, perhaps it would be relatively trivial to wrap both these libraries into a single one where the library does the if statements for you.
It indeed can be extended to do a similar job for macOS and Windows (or Linux).
Author of the post here! Didn't expect this to be posted here and making the rounds, so I will try to give some more context.
First, you can check a short presentation of the app's features in a previous post: https://blog.whidev.com/shortcut-keeper-app .
Then, I wrote on why I chose Flutter for this instead of native or Electron, and how it helped me ship an app on both stores in one month, here: https://blog.whidev.com/building-a-flutter-desktop-app-in-on...
My main background is web development (JS, jQuery, Vue, WordPress, etc.) and I have been using Flutter for the past year or so for another mobile app (iOS and Android).
If you want to keep one thing from this, is that Flutter enabled me, a developer with no prior desktop dev experience, to:
- Build on my own a desktop app for macOS and Windows (Linux is also possible, and I will try it).
- Solve my own problem (that's where the idea of the app came from), and the problem of a few hundred users in the past two months. Also bear in mind that it's a paid app.
- Get it accepted and published on both platform stores in a short timeframe.
- Make it look native on both platforms by using two community packages and some conditional logic. Of course, I could use the default UI design (Material) or adapt my own design system, but, as I explain in the post, I consider it a huge advantage to feature an adaptive app design.
- Do this from a single codebase, with the same business logic: database calls, models, controllers, settings, etc. are all shared between the two versions.
Happy to answer any questions!
Minas
*edited formatting
The biggest downside of Electron (or Cordova/Capacitor for mobile apps) is not necessarily performance (as proven by VSCode), but the non-standard implementation of UI controls.
For example, if I change some accessibility settings on the OS level, all UI elements in all apps implementing truly native UI components would inherit the OS-level settings. The toggle button on iOS could be set to have an I and an O for the ON and OFF state. A web-based app would not respect that setting.
So, if we talk about native, the most important part is OS-level integration of the UI. I'm not sure if Flutter really is an alternative, if the only thing you get is a bit less memory consumption and a smaller download size.
Please correct me if I'm wrong.
This is not always so cut and dry awful as you might think though. The platform native widgets also have limitations and expectations that might not fit your vision of the app so even fully "native" apps might re-invent the wheel in a couple of places to make things work as they want. Also I've hardly ever come accross an application that works by just combining the basic platform widgets and not look and feel pretty basic.
Also cross platform apps not respecting e.g. Zoom, Resolution, Orientation, A11y, etc. is more often than not, just the developer's not implementing the required things. The framework might support all of these use-cases but not fully automatic in some cases.
There's no reason (in theory) that electron shouldn't support something like that, and it could (and should) be fixed at the electron layer, which would be ideal!
The author is using 3rd-party UI libraries that have each been designed to be platform-specific.
Traditionally a Flutter app would use the built-in UI components that are not platform specific, but then you end up with a Material-style look and feel.
There's not actually anything stopping the app from running with the macOS style UI on a Windows machine or using the Windows-style app on the web platform because they're all inherently cross-platform. In fact, the Windows-style UI package actually has compiled to Flutter web and you can use it straight from your browser: https://bdlukaa.github.io/fluent_ui/
The decision to build separate UIs for each platform was the author's choice, but it's not a traditional Flutter practice.
Mac users will fine some tiny difference that happens in some incredibly rare combination of elements, and call for you to die in a fire.
If you want it to look native on each platform (and this is key) you need to write different code anyway. MacOS and Windows design rules behave sufficiently differently that some of your UI logic will need to be different on each platform - it's not just a different skin that can be abstracted away.
Flutter itself doesn't magically do anything for you - it doesn't care what platform you're running on because it does all its own rendering. It'll perfectly happily let you build a Mac looking app on Windows. It's then up to the libraries that you use (or create) to do whatever you want and abstract away whatever cross platform details.
The point I am trying to make is that Flutter is doing the right thing here, but the ecosystem is still maybe a bit limited.
IIRC, Photoshop is implemented this way - there is little code reuse at the UI side, but the bulk of the program is platform neutral. I had Photoshop (2.5) running on IRIX on my desktop for some (very fun) time.
Man those were the days...
You still use the same development tools, reuse your business logic, and reuse generic components/widgets.
You still need to account for the fact that users from different platforms expect different interfaces, but the common bits can be reused.
https://api.flutter.dev/flutter/material/AlertDialog-class.h...
https://api.flutter.dev/flutter/cupertino/CupertinoAlertDial...
My guess is there were enough widgets with enough differences in behavior or features that they abandoned a theming approach.
Plenty of other opportunities to reuse code, this is not one of them.
If you don't care, or if your app has its own L&F (e.g like Spotify app), you only need 1 widget sets (which you can adjust the theme).
As others have pointed out in the comments, Flutter is drawn like a game engine, as in everything is drawn on a canvas, so at the end of the day it's just drawings, not native widgets.
How well does that work with accessibility features from the different operating systems? I never used Flutter or an application written with it, I just remember the horrible experience of using Gimp and Inkscape on macOS, and the lack of support for native accessibility features can be a pain.
There's ongoing work on the Windows implementation here, which is tracked in https://github.com/flutter/flutter/projects/209, but this is supported on macOS, Linux, phone and web platforms.
Flutter Apps should last longer using fewer system libs.
On the windows side the maximize widget is messed up and there is a stray 1pm border top left.
If "native looking" is your goal then please take screenshots of your faked window borders and widgets and overlay them over the real thing and see where you went wrong.
Microsoft maintains desktop versions for Windows and macOS which are pretty functional. And you don't need to create 2 entirely different UI layers (which kind of defeats the purpose IMHO) - you'll be able to share 90% + (closer to 99% if you don't care about re-arranging content but still getting native controls for each platform).
If you’re gonna try to emulate native on multiple platforms, go with React Native and not Flutter. Especially on Apple’s platforms flutter is always going to feel a little bit off compared to native applications.
I would like to see more content like this, promoting cross-platform tools while not forgetting native platform capabilities and guidelines.
Question for the author: do you have anything to say about building/signing/publishing for both app stores? Is there any automation involved?
Building can be a hassle, as you can only produce the release version on the host machine. This means there is a bit of back and forth between my Windows laptop and my iMac, but it's expected.
I haven't looked for an automation process for this, but I believe Codemagic are building a solution for Flutter desktop as well: https://blog.codemagic.io/codemagic-ci-cd-releases-support-f...
The code is pure crap since it was written while figuring out the framework. But easy enough to refactor into something more reasonable.
Now to add OTA updating over bluetooth for an ESP32 device to the app.
https://github.com/flutter/flutter/issues/59327
https://github.com/flutter/flutter/issues/59327#issuecomment...
> ...
> I've retitled the bug to specifically target the blinking cursor issue. I don't expect us to attack this soon, but it's on our radar. The best way to indicate your support for this or any other issue is to use the +1 button.
I don't get prioritizing based on 'popularity' contests. To me, 'a blinking cursor' taking up 10% CPU and 50MB RAM doesn't sound like a bug that needs one.
Of course, unless you are a team at Google having issues, then the flutter team's going all hands on deck: https://news.ycombinator.com/item?id=26333973
Flutter on the other hand is architected as a solve-it-all solution. They even went as far as designing Dart rather than hacking a decent solution on top of JS and the current mess of web standards. IMO that's a much more interesting and powerful approach, that isn't fully ready yet, but if it ever becomes actually good I'll personally probably jump ship and use it for everything.
Like man just calm down and stabilize for a moment, it’s not an arms race.
In my mind Flutter isn’t native to any platform rather than all of them, so I like React Native much more these days.
I haven’t seen any type of movement on react native web becoming something official, but it works amazingly well at the moment and is in use in production at Twitter.
We just need a Linux port and the react native ecosystem will be pretty complete.
RNW actually has about three teeny tiny customers that no one has heard of except for the Microsoft Store on Xbox. So much for Microsoft's "Universal" Windows Platform! Lmao
But note that someone from Microsoft linked that tweet in response to a question of who uses RNW: https://github.com/microsoft/microsoft-ui-xaml/issues/6050#i...
by 'react native' i'm guessing you're referring to RNW, although a port of their iOS app is possible Facebook had previously just used a port of their iOS for their Windows app too.
(ETA: Sorry, that sounds way harsher than I intended. Waiting for coffee to kick in.)
The equivalent Electron app would probably be 100MB+.
Re: Sibling comment it really is an order of magnitude better than many other languages. It's worth giving a try. Null safety alone is worth it (although other languages like C# now support this too).
My only complaints are as follows:
- the dart formatter which is a cancer that wants to do things in only one way. The forced 2px indentation for example is unbearable for me. The maintainers are close-minded and refuse to understand that some people cannot operate with 2px indents. They will even delete your issues/comments at this point.
- Flutter is backed by Google which likes to anihilate, sometimes without warning, projects that don't go as planned. It is open source but still, without Google backing it, it would have never left the ground maybe.
Also even I was sceptical about google cemetery. But at this point flutter is widely used and is fully open source and licenses. It will not drop dead like Google services. May be no more bugfixes after its final release.