Avoid Partially Cross-Platform Apps
You can build client apps separately for each platform, such as once for Android and once again for iOS, without any code sharing between the two, instead deploying shared code in the server.
Or you can build a cross-platform app completely in a hybrid framework like Flutter.
But many companies try to have some parts of their app cross-platform while keeping the rest platform-specific.
One way to do this is to build the UI separately for each platform, while sharing the rest, by writing it once in a common language. This could be Java, with J2Objc, which I’ve worked with, and found to have an enormous amount of complexity, to the point where I wondered (right or wrong) whether it’s more trouble than it’s worth. Or C++, as Slack and Dropbox both tried to use. And failed. Whichever the language, this approach calls for writing the non-UI code in a common language.
Another approach is to use React Native, but only for some screens and some features, as Airbnb tried and failed.
The conclusion is not to mix languages and frameworks this way. Pick one and stick with it. If you pick Flutter, build your app completely in Flutter, not just some screens or features. And share your UI code, business logic, storage, model layer, offline — all of it. Or none. Don’t try to share non-UI code.
Because these kinds of halfway solutions require convolutions like trying to pass messages between Javascript and native across a bridge. You should pass messages back and forth between a client and a server, not between two parts of a mobile app! And over-engineered solutions break down in ways you won’t be able to foresee, after you’ve committed to them, at which point it’s too late to go back. Airbnb can install absorb this loss of millions of dollars, but it’s enough to kill most early-stage startups.
Even if you were to build your app completely in React Native, RN internally depends on Android and iOS to render text, for example. This could be different between Android and iOS, forcing you to deal with the very platform differences it’s supposed to abstract away.
If you’re going to use a cross-platform framework, you should bypass the native stack entirely, by rendering to a framebuffer, as Flutter does. Don’t try to be partially cross-platform.
Platforms are a layer cake. On iOS, the highest rendering layer1 is called UIKit, with views like buttons and toggles. If you want to do your own layout and event handling, and want the system to only render absolutely-positioned views, go one level down, to Core Animation. But you should bypass Core Animation, too, and talk directly to the GPU to render your UI, using Vulkan 2. As games do. Why do games do this? Because they want the highest responsiveness and frame rates. So does a framework like Flutter. If you build a cross-platform framework like RN on top of the first-party framework like UIKit or Core Animation, the overhead of each layer adds up, resulting in poor performance. Either use all the native layers by building an iOS app in Swift, or none, by using Flutter.
If you’re going cross-platform, go all the way. Or don’t even begin the journey.
Stick to one of the following standard architectures:
Don’t mix them up, building a partially cross-platform app. If Airbnb, Dropbox and Slack couldn’t get it to work, you’d be smart not to try.
Before SwiftUI’s introduction.
Or Metal on Apple platforms.
This is the right choice if it’s a business priority to cater to users who don’t want to install an app. Or you want to iterate fast. Or you don’t want Apple or Google to hold your business ransom. Or you need to share code with your desktop UI, because you don’t have the resources to build separate mobile and web apps.
This is the right choice if you’re doing something technically sophisticated, or if you need deep hardware or OS integration. And you’re not worried about spending millions of dollars running parallel Android and iOS teams.