Mobile App Architecture -React Native VS Native
When I first started working with React Native, coming from both an iOS and Android background, I struggled. The environment was nothing like anything I knew from my experience with mobile development. The paradigm shift I had to make was developing the mindset and tech skills of a web developer, while still adhering to the mobile ecosystem rules, such as low memory usage, saving the user’s battery, slick UI and others.
The Native World
For people coming from native, either iOS or Android, the decision of how to architect your app is not as difficult as it was in the past. The community have settled on a few very good options, where the main ones are MVP, MVVM and VIPER (mostly iOS).
I won’t get into details about each one, but they all have the same goals in mind:
- Separating rendering from business logic
- Making your app testable
- Structuring your app in a modular way with clear responsibilities
After many years without an official statement from either Google or Apple about the right way to go, 2 years ago Google launched their take on this debate at Google I/O. They published a very detailed blog post about how they think an app should be structured (tl;dr, MVVM), and added a very extensive framework to the Android SDK to support it.
The idea behind most of these paradigms is to separate each screen into its own native components, with UI, business logic, networking and storage all separated into their own entities. It looks something like the following diagram:
React Native is based on React so most of the architectural decisions are coming from the FED world. For native developers, this can be quite intimidating at first, since the web ecosystem is very different and fragmented between different solutions.
When we look at it in terms of architecture though, the web is mostly aligned these days with Flux. The idea behind it is to create a very predictable and unidirectional data flow for your app, which is both easy to understand and to debug.
There are different implementations of Flux but the best known is Redux.
The state of your app is kept in a store (or several stores) and every change to the store re-renders the necessary parts of the app.
Again, a picture is worth a thousand words:
Components dispatch actions, actions do something and then update the store, which in turn re-render the component appropriately.
As seen in the diagram above, the flow in React has both similarities and differences to how stuff is done on the native side.
Let’s see the differences and try to understand why they exist and what the parallels are in the native realm. I mostly use the Android jargon, but iOS devs can just switch Fragment for View Controller and feel right at home.
Smart Components = Fragments
While in native we are speaking in terms of Screens, in React we speak in terms of Components. Components are the building blocks of every screen. A screen can be comprised of a dozen or more components. While most components are “Dumb” and just render UI, some of them are “Smart” and are connected to the store, and query the needed data.
A screen can have a few smart components, each one accessing its own store, with its own business logic.
An example of this could be:
This is an example of a task addition screen in a todo list app. In the screen above each one of the inner components (e.g Pick Location) is a smart component with its own state or store, UI states, business logic storage and server API. This Allows us to develop each part separately, maybe even by different teams, and have the screen itself as just a dumb container.
In native mobile development, the idea of composing your screen to different independent components exists as well. In native, each of these smart components can be its own Fragment. Your screen would be built out of several smaller Fragments, each one managing its own logic and interactions. With that said, most of the time the logic and state would be extracted to a different component, which brings us to the next point.
Actions + Store = ViewModel
The part of the component that’s in charge of the component’s state and presentational/business logic. This is probably the most important part and where there’s a big difference in terms of implementation, even though the concepts are very similar.
This is the heart of our application.
With Redux, the idea is that we dispatch an action, and from that point on we just wait till our store changes and we re-render if appropriate.
Inside the action we can choose to do a network request, storage query, and update the store.
The store is just a global object that’s not tied to the lifecycle of the component.
Actions would be analogical to just calling a method on the appropriate View Model.
Store would be the state saved in the View Model. Making that state change trigger a UI re-render will give us the exact reactivity benefits.
There are frameworks making reactivity very simple in native like RXJava or RXSwift.
While in native the ViewModel’s state is private within the View Model, in Redux that store is global. This has the advantage where one component’s action can trigger an effect on another component, enabling UI consistency, using one source of truth.
However, with this comes the disadvantage of having that state leaked to other components that can misuse it and rely on internal details that should be encapsulated. Additionally, when one component needs to be shown on the screen a few times simultaneously, updating that shared store can cause lots of issues where one screen suddenly affects another, causing lots of hard to debug bugs.
To mitigate such issues, we need to think carefully what to keep in the store and what not, but such a topic deserves its own blog post.
Async Storage ≠ Relational DB
This is where the biggest difference lies.
While in React we’re mostly tied to key/value storages (though we can use third party solutions like Realm), in native we’re mostly used to relational DB solutions. Those are quite different paradigms, each with its own pros and cons, but from people coming from relational db background, trying to work with key/value storages can be quite hard to get used to, especially the schema-less part.
So, as we can see, even though in the beginning it looked like React Native was very different from native development, when we look carefully, lots of the ideas are quite similar, just with different naming and small changes.
While I compared the common architectural patterns, it’s worth mentioning that in the Android community there’s a new paradigm that’s getting traction called MVI, that aims to bring the Flux pattern to native development.
Also while I used redux for comparison, since it’s the most used state management library in use today, there are others like Mobx that are bringing the MVC way back to the Web.
So for native mobile developers starting their React Native path, I hope you see that the goals of all the architectures mentioned above are quite the same, mainly about adhering to SOLID principles of software engineering.
Implementation can vary; some are more functional, some more reactive, some have more boilerplate, but they are not so different in their core goal.