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.
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’.
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.
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).