Xamarin vs Ionic vs React Native: differences under the hood
Traditionally, Android applications are developed in Java, and iOS ones are written in Swift and Objective-C. Nevertheless, there exist plenty of other alternate tools that can be used instead. Xamarin, React Native and Ionic are popular examples of such tools. What is their purpose? What makes them different? Which of them is the best? We’ll try to answer these questions in the article below.
You often need to develop the same application both for iOS and Android. Sometimes you also need to support other operating systems like Windows Phone, but here we’ll make focus on iOS and Android only.
One of the solutions would be to develop two completely separate applications: one in Java for Android, and the other in Swift or Objective-C for iOS. This approach has its advantages such as:
- Using official tools designed for this platform. They will definitely be supported as long as the operating system itself stays in development. Moreover, they get new features first.
- Native UX and UI. By using official tools, it’s easier to conform to platform human interface guidelines (HIG). End user gets familiar experience interacting with your application, and its look-and-feel matches other applications designed for this platform.
However, this approach has a serious disadvantage: you have to write the same pieces of code twice! Considering different programming languages (Java and Swift), different approaches to do the same things on iOS and Android, different ways to build the GUI, it’s difficult to do anything to avoid writing the same code twice. The situation is even worse if two different developers create iOS and Android applications because two people are likely to implement the same feature slightly differently.
Xamarin, React Native and Ionic address this issue and provide powerful tools to reach one goal: to use a single stack of technologies to develop both iOS and Android applications. The idea seems to be simple, but it unleashes a bunch of nice things:
- The developer doesn’t need to learn two absolutely different technologies. Of course, they still need to know each platform’s features and restrictions, but there is no need to write in two different languages and to design GUI using different tools any more!
- Shared code base allows huge code deduplication. The code that is common for both applications like business logic, networking, etc. can be separated into its own modules, and you still don’t lose the opportunity to create a custom UI for each platform, or even to build the UI once with some platform-specific traits added.
- Still there may be two or more developers working on the application, and the one who knows iOS better develops iOS-specific parts, the one who knows Android better does Android stuff, and any of them can do the common platform-independent stuff. As the common stuff is written once, there is no chance to make some inconsistency between platforms in this code.
However, what is the price of having all of these features? Let’s take a closer look at the frameworks mentioned above.
Xamarin can be used in different ways. One option is to preserve as much as possible and only switch language to C#. That is, you design XIBs, storyboards and Android layout files using Xamarin Studio tools, and write code in C# instead of Java or Swift. iOS XIBs and storyboards can also be designed in Xcode. All the native frameworks can be accessed easily. The code that doesn’t rely on UI can be shared between iOS and Android projects. Though, if you want to experience all the power of Xamarin, there is another option: you can use cross-platform Xamarin.Forms to design even the UI for two platforms at once.
Well, the core difference is that React Native works more like Xamarin: you use a special templating language to make the GUI, and it ends up in creating native widgets. On the contrary, Ionic doesn’t use native widgets at all. Instead, it just displays a web page written in HTML that mimics the native widgets design. If you look deeper, React Native uses an original approach: you declare the UI as a function of current state. It’s React Native’s job now to find changed parts of UI on each state update and to redraw only them. In case of complex layouts, it can simplify things a lot, because the logic that updates the UI when state changes is not spread all over the code any more.
As we can see, every framework uses slightly different approach. A brief sum-up is in the table below. We’ll discuss pros and cons of each approach in the following sections. But now let’s talk about possible pitfalls with cross platform programming.
As soon as you start developing a cross platform mobile application, some well-known problems will start to appear.
First, you might have noticed that you can’t use the libraries that are familiar to you. You could use some libraries e.g. for networking, but now you can notice that the networking layer became common for both iOS and Android, and you can’t just bind iOS-only or Android-only library to do networking. The solution is to use cross platform libraries in C# specifically designed to address this problem. And a possible problem is that there may be no alternative for some feature that was available in the iOS- or Android-only library.
The second potential issue is having inconsistencies in behavior of platform-specific code. We have mentioned above that deduplicating logic prevents inconsistencies between two platforms from happening. But as operating systems differ, something needs to be done twice, e.g. asking for permissions. iOS and Android have different sets of permissions and different UX when asking for them, and although some libraries exist at least for C# that try to create a single interface for two platforms, it’s often better to write a separate code that takes into account platform features and differences. In this case, it’s difficult to talk about some inconsistencies because the flow was deliberately made differently on iOS and Android, but the problem exists anyway. Some common parts like asking the camera permission need to be implemented twice, and potential mistakes can occur. Anyway, the majority of potential inconsistencies are eliminated by deduplicating the biggest part of the code, and all you need is to be extra careful when you need to write the code twice.
JIT and AOT
There are two common ways to improve performance of such languages: JIT and AOT. Just-in-time compilation (JIT) is the process of compiling a bytecode or a source code into the native code at runtime. Hence, a piece of code (bytecode or source code) is not interpreted at runtime every time it gets executed, but it’s only interpreted once at runtime, and every next time it is executed, a fast native code runs. Ahead-of-time compilation (AOT) is the same process performed before starting the application, at compile time.
Let’s have a look at a simple example:
Even a simple addition operator can actually mean different things, e.g. number addition or string concatenation. The interpreter does all the necessary type checks when it faces the plus sign in the code, and so would do the AOT-compiled native code. If this piece of code is called multiple times, and every time arr is an array of numbers, JIT-compiler can provide an optimized version of code that works only with numbers. Most of the checks can be performed before entering the loop, and in case they fail, there is a fallback to interpreting the code, otherwise a native code is run, and it’s much more efficient than AOT-compiler could produce.
On the other hand, Android has no such limitations, and all JIT-compilers should be run without any trouble.
Many handset devices with 64-bit CPUs are appearing now. Generally, 64-bit code runs faster and has a bigger memory footprint. As our primary concern is speed, we’ll assume that it’s better to have support for 64-bit targets, considering that Apple requires it for iOS applications.
There is a common point of view that HTML DOM is slower than Android or iOS native widgets. There are a lot of things that cause repainting of the layout in HTML, and that takes pretty much time. Here, we give preference to the frameworks that use native widgets to render the UI.
Taking into consideration everything said above, let’s compare the performance features available in Xamarin, React Native and Ionic.
C# can be either JIT-compiled or AOT-compiled. As JIT-compilation is impossible with iOS, Xamarin AOT-compiles the application by default when targeting iOS. On Android it uses JIT by default, but can be configured to use AOT. AOT-compiled code should run faster, but there are some limitations (e.g. no dynamic code generation via System.Reflection.Emit).
Links for reference:
Lists of important limitations:
Good news is that 64-bit support is not a problem for Xamarin at all!
If necessary, code can be forced to be compiled to a 32-bit target.
Another huge plus of Xamarin is that it uses native widgets to render the UI. You can either design the user interface in a platform-specific way (that is Android XML layout files and iOS XIBs and storyboards) or use Xamarin.Forms. Using the native way to layout the UI is as fast as it is when you are writing a standard Java Android or Swift/Objective-C iOS application. On the other hand, Xamarin.Forms allows you to use some nice things such as designing the common layout only once, data binding, etc., and it uses native widgets under the hood anyway.
The situation with Ionic could be better because it uses a web view for rendering, and WKWebView on iOS supports JIT. However, by default it uses UIWebView on iOS, but a plugin exists to make it use WKWebView. Of course, JIT is supported on Android.
Starting with Cordova 3.4.1, 64-bit mode on iOS is supported. Unfortunately, we couldn’t find any mentions of 64-bit support on Android.
The UI is created in HTML in CSS, and Ionic provides a set of Angular components that mimic the platform’s widgets to make your application look native.
Xamarin shows the best result allowing to run the fastest code on both iOS and Android. If you are developing only for Android, there is no reason to worry too. The worst result is shown by React Native on iOS. Ionic requires some additional setup to speed up the application.
64-bit mode seems to be supported by all frameworks on iOS (as Apple requires it), but React Native doesn’t support it on Android for now. Xamarin fully supports 64-bit mode.
The fastest UI is provided by Xamarin as it allows to use native tools directly. Also, Xamarin and React Native provide you with custom UI designing frameworks that use fast native widgets under the hood. Ionic, however, uses HTML with obvious consequences (a performance hit and possible look-and-feel discrepancies).
The convenience for the developer is no less important than the technical advantages of the framework. All the framework features would have no sense if it were too difficult to write code with that framework.
During development, you always write some new code. Of course, you want to observe the results, because it’s a kind of feedback you can use to fix the mistakes or experiment. The sooner you can look at your results is, the more comfortable it is to write the code.
With Xamarin you are tied to the classical approach: you write the code, you build it, you deploy it to the device or the simulator, you see the results. It works almost in the same way it worked in Xcode or Android Studio. Some differences exist though.
With Android Studio, there is a feature called Instant Run. It allows to save some time deploying the application by hot, warm or cold swapping depending on the code that has changed:
- Hot swapping is replacing parts of code without restarting the application.
- Warm swapping is restarting only the current activity.
- Cold swapping is restarting the whole application, but without redeploying the whole APK.
There is also a tool named JRebel that has even more features than Instant Run.
Xamarin offers a fast deployment feature available on Android in debug mode that works similarly to cold swapping in Android Studio. It can save some deploying time by putting assemblies (DLLs) into the data directory of the application. Thus, there is no need to waste time on creating the APK every time and reinstalling it. But there is one caveat: clearing the application data makes the application unlaunchable, that’s why this mode is not used in production builds.
React Native supports a wide variety of ways to launch the application. The most common way to debug and develop a React Native application is to use Expo mobile application that connects to your computer, downloads the application and reloads it when necessary. Of course, a standalone application can be built too. These ways allow you to use either the classical approach or live reloading, but there is one more interesting feature: hot reloading. The difference between live and hot reloading is that hot reloading doesn’t involve a full application reload. Instead, it uses hot module replacement to replace only changed parts of the application. Moreover, you can even preserve the state of the components! That makes experimenting with layout a fun thing to do. Of course, some deep changes will require a manual full reload (e.g. if the state object changed a lot), but it’s a very convenient tool for making lots of small changes requiring immediate feedback.
Development in browser
Using web technologies like HTML and CSS allows you to run your mobile application directly in the browser on your computer. You can forget about devices and simulators, especially if you can mock the hardware. Isn’t that cool? As Ionic uses HTML for making the GUI, it supports running the application in browser.
But don’t get excited too early. It’s known that all browsers render certain web pages slightly differently. If you develop and test only in your desktop browser, the result might look differently on the real device. Moreover, even Ionic’s components that mimic native widgets look properly only on that platform. Here is an example of how a standard Ionic alert look in different browsers:
As you can see, Firefox’s rendering is not even close to the expected result (different size, and that gray rectangle on the right is a scrollbar). Safari looks closer to the desired result as it uses the same engine as iOS web view, but you can see the scrollbar too, although it’s hard to notice due to its color and height. Firefox displays the bigger scrollbar because it cannot shrink infinitely, and it’s the bottom limit of its height.
The outcome is that you can develop in browser with Ionic, but it won’t reflect the real look of the application.
- AppHub. Supports React Native. Either own server (open source) or hosted.
- CodePush. Supports React Native and Ionic (Cordova). Seems to be hosted only.
Integration into OS and existing code
An important thing is the integration with platform frameworks, libraries, OS features, etc. Your mobile application would unlikely be very useful if it lacked any possibility to interact with the phone hardware and OS features. The application should also have a native look-and-feel, and it should behave in a familiar way to the user. And another important point is the ability to use existing third-party libraries, as some widgets and other stuff may be available in those libraries.
Traditional code binding
Good news is that all of these three frameworks support binding Java code on Android and Objective-C code on iOS. Swift code is supported as well, but it has to meet some restrictions such as classes must inherit from NSObject and have @objc annotation. Even though we still can compare these frameworks as the level of support is different.
Xamarin is very good at bindings. It can bind iOS static libraries (with headers available), iOS frameworks, Java JARs and Android AARs. Good point is that it doesn’t require source code to be available (but if it’s available, you can build it into a static library or JAR/AAR), and it doesn’t require it to be modified. Here are some useful links:
- Binding a Java Library
- Binding iOS Libraries
- Binding Native Frameworks
- Binding a Swift Library in Xamarin.iOS
React Native and Ionic, however, can’t just bind any random code you want. You need to create a plugin, that means, some native adaptor code must be written, which complicates the process a bit. Here is some information on creating plugins for these frameworks:
- React Native — Native Modules — Android
- Cordova — Android Plugin Development Guide
- React Native — Native Modules — iOS
- Cordova — iOS Plugin Development Guide
Standard library binding
The ability to access standard Android and iOS libraries and frameworks is not a nice-to-have feature, it’s absolutely necessary. But what can Xamarin, React Native and Ionic offer us?
Well, Xamarin seems to have a pretty full binding of the standard libraries. You can use everything you could use when writing in Java or Objective-C, the only difference is the language. Some iOS methods were renamed because C# doesn’t support Objective-C-style selectors, but in general, every iOS framework is available.
There is an advantage in having this abstraction layer as it tries to hide OS differences and make them implementation details, but in practice there are some problems with this approach:
- Mobile operating systems are anyway very different, and something that is allowed in one OS is disallowed in another. That leads to having some iOS-only or Android-only methods in the common abstraction layer or to limiting the functionality available to the application.
- The developer has to learn how to do the familiar things with this new library.
Relation to Android Java and iOS Swift/Objective-C code
React Native has an interesting feature of writing only a part of the application using React Native and writing the main part using traditional technologies. This way you can easily leverage the advantages of React for a limited number of screens where React fits best, and write the main part of the app with some traditional architecture.
This is a feature of React Native, and the rest of frameworks don’t seem to provide such functionality. You can read more about this feature in the documentation.
Xamarin and Ionic support binding Java/Objective-C code to them, but not vice-versa. Actually, there is a way to call C# methods from C code if you like with Mono library, but it’s not straightforward, and Xamarin doesn’t officially advertise this feature. If it is absolutely necessary, you can use it from your Objective-C code in iOS or from C code connected by JNI in Android.
Xamarin’s GUI binding is the fullest. If you use native widgets directly, you can use any of them, and if you use Xamarin.Forms and feel some widget is missing, you can insert a native one.
React Native has its own hierarchy of widgets, and although they end up in native widgets, not everything is available. Even though the native widgets are used under the hood, the UI elements don’t look natively. Of course, you can make an effort and create bindings for all native widgets, but it’s as time-consuming as creating a new cross platform GUI framework. So, here we lack the native look-and-feel and have nothing but some performance improvement over solutions like Ionic.
Ionic took a different approach. It doesn’t use native widgets at all (except for web view, obviously), but renders everything in HTML. It, however, tries to mimic the native widgets and indeed looks close to the native. Of course, it has some drawbacks, but it’s still good.
Background code execution
It’s very important for mobile applications to execute some code in background. iOS and Android have different limitations: on Android you can execute arbitrary code in background while iOS has a lot of restrictionson what can be run in background.
Each of the discussed frameworks allows running some code in background; however, the level of support and the implementations differ.
Xamarin allows you to use the familiar set of platform native features for running code in background. No need to learn something new, and all platform restrictions must be taken into account for both iOS and Android. Hence, you need to write different code, but you are not limited by using more restrictive platform features only.
React Native only supports running background code on Android. It’s called ‘Headless JS’ here, and the interface differs from using background services, but it’s much simpler:
Ionic has a background mode Cordova plugin:
This plugin doesn’t provide means to actually run some background job, but it has methods to prevent interrupting the application if it’s backgrounded. It seems to be more Android-oriented, because most of the methods are Android-only, and we have doubts whether it works on iOS (iOS doesn’t allow doing arbitrary work in background for unlimited time).
Xamarin’s integration with OS and existing stuff is the best. However, the other frameworks may still be useful if you don’t need some features and want to leverage their advantages. Another drawback is that React Native doesn’t provide native look-and-feel, so your application needs to be manually styled if you want it to look native.
As we can see, Xamarin is the fastest and fullest framework of these three. It stays close to classical approach to the mobile applications development. That means that a lot of things are familiar, platform’s features can be used without restrictions, and the development process is similar to that with Xcode and Android Studio. Xamarin is my personal choice. However, if you’d like to try some new approach to development with fancy features like hot reloading and instant updates, you should try React Native. Designed with some interesting ideas in mind, it is a really interesting framework that makes you think in a different way. But there is a drawback: React Native has worse performance and doesn’t offer native look-and-feel to your application. A compromise between performance and modern development process could be Ionic. With Ionic, your application will look almost like native, but creating the UI in HTML and CSS may be painful.
Originally published at cruxlab.com.