In the perfect world of no time, cost, or development limitations, native applications are a gold standard approach to creating a mobile app. In the real world, these limitations exist for companies both small and large. Creating a cross-platform or hybrid app offers the ability to address these issues, providing a shortened release cycle and the opportunity to reach a wider audience, than if you had a single application released only on iOS or on Android.
From start to finish creating an app is a process that expands much further than simply writing the code. Attention also needs to be given to other aspects such as deployment and CI/CD pipelines, the quality assurance process, Apple App Store and Google PlayStore accounts, and then the review and release process for each platform on these stores. Whilst cross-platform frameworks sell themselves as the solution to write code once and target multiple platforms there are factors when creating a cross-platform or hybrid app that are often underestimated or not considered at all. In this article, I am to highlight some of these points for you.
Platform design guidelines
Apple publishes the Human Interface Guidelines and Android publishes the Material Design Guidelines. Concretely, these are a set of standards or principles that can be used for reference when designing and developing mobile applications for the two platforms. The guidelines should be considered not just for the initial release but for future feature implementations, to craft an aesthetically pleasing user experience and to ensure the application aligns seamlessly with the requirements of the relevant platform.
Both React Native and Flutter provide different mechanisms for how the platform customisation is implemented. Flexibility is greater with React Native due to the ability to use the platform module provided by the framework. Doing so makes it easy to conditionally write code relevant to a specific platform, meaning that complex customisation is relatively easy to achieve. On the other hand, Flutter uses a unified design language, Cupertino for iOS and Material Design for Android, enhancing the prospect of consistent user experience between platforms. In general, widgets are non-adaptive and additional customisation will require extra work.
Whilst the goal of a cross-platform framework is to generally achieve solid feature parity and both React Native and Flutter perform strongly here, certain features can perform or function differently between the two platforms. From a UI/UX angle, the design, app icons, splash screens, navigation, orientation, animations, and gesture interactions are all examples of where platform-to-platform considerations may need extra attention. As the complexity of an application grows you may wish to tap into built-in device capabilities such as the network or Bluetooth. Whilst many community libraries support additional feature implementations for example push notifications, the behaviour of a feature such as this will differ per platform. Additionally for a feature such as push notifications, there will be permission differences between iOS and Android. Subsequently, implementation, testing, and maintenance time need to be adequately considered across both platforms if this coverage is what you are looking to achieve. Writing the code once and then expecting the outcome to work instantly and similarly across the two platforms is not always a realistic expectation. As an application grows in its complexity you may wish to access native functionality. Whilst this demands more from your development team React Native enables this through the use of native modules and Flutter through Platform channels so it is worth having the awareness that this is a possibility.
Whilst you are gaining the benefit of one app which targets multiple platforms there is an impact that should be considered from the overhead cost of the abstraction. Both React Native and Flutter introduce a layer that interacts between the app code and the native code of the target platform. Factoring in time to spend on optimisation, profiling, and memory management is critical to avoid performance impacts that could arise. It is also wise to consider the learning curve around these ecosystems and strike the balance between development speed and enhancements.
For best practices and reliability you should be working towards creating a test strategy for the application that involves a combination of unit testing, integration testing, and end-to-end testing. Many frameworks can assist you here such as Jest for unit tests and Enzyme for integration testing with React Native, and Dart Testing Framework and Flutter Driver for Flutter. Performance and accessibility testing should also be assessed. Whilst both iOS and Android provide various simulators and emulators, you should be factoring in testing across a number of different physical devices. Apple provides TestFlight and Google has testing tracks. These enable pre-release testing across teams before you submit the app to review in one of the stores.
It is too simple to underestimate the maintenance aspects of hybrid or cross-platform applications. If we start by looking at the frameworks themselves. Both React Native and Flutter are relatively immature. You need to expect to consider updating these frameworks and if you want to stay on the cutting edge you should be expecting to undertake this work regularly. In 2023 Flutter released updates four times and React Native had major releases three times, with many more minor releases in between. Performing these updates takes development time but also increases demands on testing and release time. The impact of not staying up to date with supported versions can be costly, upgrading multiple versions at once can be a brittle process to undertake.
Given the nature of these applications, it's common to use multiple dependencies to support development. The trade-off here is that it is then not only the libraries that need to be considered for upgrades but also the dependencies. You need to consider staying up to date with the dependency updates for improvements, security patches and also for the main library releases. This is another factor that promotes the necessity of staying up-to-date with the frameworks stable versions. Unfortunately, not all dependencies end up continuing to be maintained so from time to time these may need to be replaced.
Lastly, there are also native updates to consider depending on the type of application you create. For example, if you are creating a React Native application that does not use Expo then you will also need to address iOS platform updates and Android SDK updates. Not doing so can block you from releasing an app update, particularly with Android on Google Play where the target API level requirements can change annually.
The steps to creating the releases on either Apple or Google Play can take some time. Before you can release the app you will need to create an account on both Apple and Google Play. At the time of writing the cost for this on Apple is $99 per year and for Google Play $25 as a one-time fee. Creating the initial release requires several declarations, application descriptions, and promotional materials such as device screenshots, usually inclusive of both the phone and tablets. You should expect to allocate at least a few hours per platform to create the initial release. Once the initial release has been created you should then be aware that it is important to stay on top of declaration updates, failing to do so can result in an urgent release being blocked. Be mindful that both platform stores require the app to be submitted for review before it can (hopefully) be released. Coordinating the releases across the platforms can require some planning and patience.
Continuous integration and delivery/deployment
Code signing is a crucial step in the cycle of releasing an app on Google Play or Apple. On Android this can be a little more straightforward, essentially requiring keystores to generate a digital signature. Apple can be a bit more convoluted with profiles and certificates needing to be created. Platforms such as Fastlane can be of assistance with managing this procedure.
As an entity, the processes around building the app, deploying, code signing, and then releasing can vary depending on the platform. There are a variety of platforms that can automate and significantly speed up the process. Examples of these include Bitrise and Code Magic. These require time to set up and also to maintain and it is worth being mindful that many of these platforms come at a cost. This is often an investment worth making due to the time that the build process alone for these applications can take.
Asset management and localisations
The application will likely use multiple images, icons, and other resources. It is important to be aware of performance and other issues such as resolution. Compression and bundling can assist with reducing the overall app size, especially if you do not need all the images across both platforms. Image caching and dynamic loading can also provide optimisations that benefit the overall user experience, especially if you are targeting users who may have poorer network connections. You should expect to provide images in multiple sizes and resolutions to cover the range of devices with various screen sizes and pixel densities. Vector graphics scale without affecting the image quality so these can be used but with both React Native and Flutter you should expect to use additional dependencies such as React Native SVG and Flutter SVG if this is the path you take.
Depending on your application and target demographic it is potentially naive to expect all your users to be based in one country or culture. Managing localisations in the application is another factor to consider. Getting this setup at the start can save a lot of time as the application and audience grows. Some libraries and platforms can help assist you with the process. For React Native an example is React Native Localize and for Flutter intl. For managing the translations themselves many cloud-based platforms can speed up this cycle, Localazy and POEditor are a couple of examples.
Attention should be given to accessibility within the application so that can be used by a diverse and inclusive audience. Aside from consulting the Web Content Accessibility Guidelines both iOS and Android have their recommendations. Aside from design considerations such as colour schemes, keyboard interaction, and focusable elements, alternative text descriptions and semantic HTML and Native components can assist the experience for users with screen readers and other assistive technology.
Once the application has been released you should expect to allocate time for managing errors and resolving issues users may experience. This can become more complicated if the application also contains native code. Implementing an effective, structured, and consistent approach to managing errors should be initiated. Packages that provide error tracking and reporting include Firebase Crashlytics and Sentry, both of which have packages for React Native and Flutter. Logging services include Bugsnag and LogRocket. Using an error-tracking platform such as Sentry or Bugsnag can help with ongoing monitoring and error reporting.
Invest in the time to weigh up the pros and cons of building an app of this type at the start. Choose the right framework wisely for your requirements and place a focus on the tooling and infrastructure around the app to speed up and ease the process moving forward. Be realistic with time for issues such as maintenance and manage design expectations with extra focus on UX. With all these topics acknowledged the overall experience of creating a hybrid or cross-platform app can and should be an exciting endeavour. You have the opportunity to rapidly prototype a product, target wider audiences, and experiment with cutting-edge technologies.