Skip to content
phattv.dev
EmailLinkedIn

Single Code Base to Develop Web & Mobile Applications

coding & tutorials, projects12 min read

This is my first post so I'm a bit excited, the content is quite long too, grab a cup of tea 🍵 or coffee ☕️, sit back and enjoy! (skip to TLDR if you find this too long)

Cross platform software development has never been easy. There have been many tools developed by big companies to make cross-platform development feasible and performant. However, the end goal is usually on mobile cross-platform level. Facebook introduced ReactJS in 2013, which is a library to build reusable and customizable user interface for the web. With the ease of use and sematic syntax, ReactJS has become the most loved web framework. Two years later in 2015, Facebook introduced React Native using the almost identical syntax with React to develop applications for Android and iOS. Two years later in 2017, React Native Web was created to reuse React Native code and make it run on the browser.

With the use of React Native and React Native Web, using the same code base and release to 3 different platforms of web, ios and android is now possible. To make the most out of the single code base concept, this code is setup under a monorepo structure to maximize code reuse. The project structure contains code for specific platforms: /packages/mobile & /packages/web for configurations, and reusable code /packages/components, which is main part of the application. The vendor code, /node_modules are almost all shared between the platforms and this helps to reduce the storage size too.

The results, 3 standalone web, ios and android production applications, prove this setup is realistic and achievable, without being prompted, the users cannot recognize any UI or performance difference compare to applications written in native code.

Demo

Deploy production iOS app to App Store and Android app to Play Store can be costly. So, please have a look at the videos for the 2 platforms instead :)

  • Web: https://ecommerce.phattv.dev/ (taken down due to cost saving)
  • iOS:
  • Android:

Background knowledge

Skip this part if you already have the knowledge about the differences between Web, Android & iOS development.

Let's have a look on the differences between the 3 platforms to understand more about the approach as well as monorepo structure.

Web & ReactJS

Web applications are powered by browser engines and their primary job is to transform HTML and CSS documents into an interactive visual representation on a browser. There are multiple browser engines implementations, most commons are Blink (Google Chrome, Opera), Trident (Internet Explorer), Gecko (Firefox) and Webkit (Safari). A Javascript engine is a program or interpreter which executes Javascript code. Different browser engines come with different javascript engines, too. The most powerful engine is V8 which is built by Google, written in C++ and used in Google Chrome browser. It is not only used in the browser but also for Node.js runtime. V8 translates javascript into a more efficient machine code at execution time by implement Just-In-Time compiler. Optimization techniques used include inlining, elision of expensive runtime properties, inline caching and garbage collector.

React's effectiveness lays in the way it detects data changes and applies them. React uses Virtual DOM, which performs significantly better than DOM. Virtual DOM is only a virtual representation of the DOM. With state and prop a component can be represented as a structured tree data. When there is a state change, virtual DOM calculates the best possible method to make these changes to the real DOM.

virtual DOM and diffing process

iOS

The iOS architecture consists of four layers which illustrate the level of abstraction from high to low Cocoa Touch, Media, Core Services and Core OS. The Coca ­Touch layer contains key frameworks that native iOS applications rely on, such as UIKit, which all developers have to touch on. This layer defines and provides developers with high-level features, such as layout, gesture and document support, hence it is the layer that developers spend the most time on. The media layer handles graphics, audio, and video and it provides frameworks to work with them. Core Services layer takes care of system services, such as networking and location. Core OS layer contains the lowest level features, such as acceleration, security, and environment, which powers the higher-level layers.

iOS architecture

Almost all iOS applications follow Model-View-Controller (MVC) architecture, which is suggested by Apple documentation. In the MVC pattern, the controller will handle communication between Model and View. When there is a Model change, it notifies a Controller, which updates the appropriate View object.

Android

Android applications are built using four main types of components: Activities, Services, Content Providers and Broadcast Receivers. Each application needs to declare its components, permissions and OS in the app manifest AndroidManifest.xml file. An Activity is the entry point for interacting with the user, which is a screen or UI. A Service is a general-purpose entry point for keeping an app running in the background. It is a component that runs in the background that performs long-running operations or for remote processes, such as playing music while browsing the web. A broadcast receiver enables system to deliver events that are outside of a regular user flow, such as push notification while the app is not running. A content provider manages a shared set of app data stored in the system. Each component can have its own lifecycle that handles different events and the system will keep track of activity state in memory.

Android stack

Cross-platform development

There are several approaches to develop cross-platform applications:

  • Mobile Web apps: web application with mobile layout
  • Hybrid apps: native WebView showing web applications. e.g. PhoneGap & Iconic
  • Native Scripting apps: apps that use an interpreter that is bundled with the device, the interpreter runs the code during runtime to interact with native APIs. Because of the interpreter capability to call native APIs, those applications can fully behave like native apps. e.g. NativeScript & React Native

Now you may ask: Why React Native? Here are some of the best reasons:

  • React Native outweighs NativeScript in both popularity and usability.
  • React Native combines the best parts of native development with ReactJS, a library for building User Interfaces.
  • Since it is a scripting framework, it can be for new or existing applications without an issue.
  • The framework is written in Javascript and render to native platform UI, which means the developers only need to write the code once and compile to multiple platforms.
  • One of the biggest advantages of using React Native for mobile development is the ability to fast refresh the changes on emulator or simulator.
React Native interaction scheme with native components.png

Monorepo

Monorepo (Monolithic Repository) is a single repository (usually with Git) containing more than one project. The model is used by many big companies, such as Google and Facebook. There are many benefits working with a monorepo, such as code base centralization, standardized toolset, better code view and searchability, simplified dependencies (many projects can reference the same dependencies) and easier code sharing and refactoring. On the contrary, monorepo also introduces some drawbacks, such as cross-project dependency conflicts, codebase complexity, and sophisticated environment, tooling and deployment setup.

Design

Interface

product - cross platforms - home screen product - cross platforms - detail screen

Client-side

The client-side application is put under monorepo structure, meaning multiple packages co-exists in the same repository. This project uses Yarn workspaces to setup the monorepo by declaring a root package.json file containing workspaces array. Each workspace will be a mini repo and they can refer each other by using name in its own package.json. There are 3 packages in this project: components, mobile and web. Components package contains the application UI and logic. Mobile package contains the React Native setup and it points to the components index.js. Web package contains the React setup and it also points to the component’s index.js. This structure requires some further configuration to share the vendor code (node_modules) but it allows developers to write only one code base and make it works on multiple platforms. package.json holds various metadata of a project including project name, description, version, license and dependency list. The mobile package will build to binary Android apk or iOS ipa files while web package will be deployed with Docker and deployed to Google Cloud Platform.

client side project structure

Server-side

Server side project is written in ExpressJS. Its purpose is to sever APIs through HTTP calls to connect with the database. The application will also be deployed with Docker and pushed to Google Cloud Platform. "index.js” is the entry point of the project which creates the API server, configure CORS, setup connection to a database and register routes. Each set of routes is defined and implemented inside routes folder.

server side project structure

Database

phattv_ecommerve database is designed with simplicity and ease to extend. The chosen database type is Postgres. There are four entities: category, user, photo and listing. With only these four tables, this database can handle most of the frequent actions of an ecommerce site: browsing, sorting, searching and filtering. Each entity has its own primary key, foreign key(s) and index(es). All the entities have auto-generated created_at and updated_at fields upon creating and update respectively. The timestamps will act as a mini audit feature to determined if the listing id updated. For example, a listing is sold, status should change to sold and updated_at should update with the timestamp of the update action. All the entities also have “id” field as primary key with postgres’ uuid_generate_v4() function. UUID ensures that the ID is globally unique and this helps backing up and restoring data easier with no conflicts.

category

Table "public.category"
Column | Type | Modifiers
------------+-----------------------------+-------------------------------------
id | uuid | not null default uuid_generate_v4()
name | text | not null
parent_id | text |
image_url | text |
created_at | timestamp without time zone | not null default now()
updated_at | timestamp without time zone | not null default now()
Indexes:
"category_pk" PRIMARY KEY, btree (id)
"category_name_index" btree (name)
Referenced by:
TABLE "listing" CONSTRAINT "listing_category_id_fkey" FOREIGN KEY (category_id) REFERENCES category(id)

listing

Table "public.listing"
Column | Type | Modifiers
-------------+-----------------------------+-------------------------------------
id | uuid | not null default uuid_generate_v4()
title | text | not null
price | double precision | not null
image_url | text |
description | text | not null
status | text | not null
created_at | timestamp without time zone | not null default now()
updated_at | timestamp without time zone | not null default now()
seller_id | uuid | not null
category_id | uuid | not null
Indexes:
"listing_pk" PRIMARY KEY, btree (id)
"listing_title_index" btree (title)
Foreign-key constraints:
"listing_category_id_fkey" FOREIGN KEY (category_id) REFERENCES category(id)
"listing_user_id_fkey" FOREIGN KEY (seller_id) REFERENCES "user"(id)
Referenced by:
TABLE "photo" CONSTRAINT "photos_listing_id_fk" FOREIGN KEY (listing_id) REFERENCES listing(id)

photo

Table "public.photo"
Column | Type | Modifiers
------------+-----------------------------+-------------------------------------
id | uuid | not null default uuid_generate_v4()
listing_id | uuid |
image_url | text | not null
created_at | timestamp without time zone | default now()
Indexes:
"photos_pk" PRIMARY KEY, btree (id)
"photos_id_uindex" UNIQUE, btree (id)
Foreign-key constraints:
"photos_listing_id_fk" FOREIGN KEY (listing_id) REFERENCES listing(id)

user

Table "public.user"
Column | Type | Modifiers
------------+-----------------------------+-------------------------------------
id | uuid | not null default uuid_generate_v4()
username | text | not null
image_url | text |
created_at | timestamp without time zone | not null default now()
updated_at | timestamp without time zone | not null default now()
bio | text |
Indexes:
"user_pk" PRIMARY KEY, btree (id)
"user_username_uindex" UNIQUE, btree (username)
Referenced by:
TABLE "listing" CONSTRAINT "listing_user_id_fkey" FOREIGN KEY (seller_id) REFERENCES "user"(id)

Development

Prerequisites

Server-side

  • Setup:
git clone git@github.com:phattv/phattv_ecommerce_backend.git
cd phattv_ecommerce_backend
yarn
psql
CREATE DATABASE phattv_ecommerce;
Control + D
psql phattv_ecommerce < schema.sql
  • Run:
yarn start

Client-side

  • Setup:
git clone git@github.com:phattv/phattv_ecommerce.git
cd phattv_ecommerce
yarn
cd packages/mobile/ios && pod install

Run Client-side

Web

yarn start:web

Check your browser at http://localhost:3000/

Android

yarn start:mobile

Open Android Studio, create a new emulator or connect a physical phone to the laptop and allow USB debugging. Then open another tab:

adb devices

Make sure there is a device listed in the result, if you don't, try these ways

iOS

yarn start:mobile

Open Xcode, create a new simulator. Then in new terminal:

yarn build:ios

Live Reload

Changes made on the code should automatically reflect on web browser, ios simulator and android emulator at the same time. Here are some of the screenshots of local development:

  • local development - web: local development - web
  • local development - ios: local development - ios
  • local development - android: local development - android
  • local development - cross platforms: local development - cross-platforms
  • local development - backend: local development - backend

Conclusions (TLDR)

  • Monorepo code structure is a great way to share custom and vendor code, eliminate code repeat, update dependencies and it is convenient to refer code of other engineers. Monorepo enhances software development process but it also introduces some difficulties in deploying because of the linking issues.
  • React Native encourages web front-end developer to try out mobile development without learning new languages, which is great. React Native Web brings React Native one step further by bring the React Native code to the browser. The combination of React Native and React Native Web has shown that a web developer with zero experience in mobile development can build good-looking and performant mobile applications.
  • Using React and Redux provides a new way of software development by using Functional Programming: managing state through pure functions, immutability and its architecture: actions, action creators, reducers and selectors.
  • Using Javascript to develop mobile applications with live and hot reload enhances development experience tremendously. There is no need to wait for IDEs to build everytime there is a change in the code. This helps to eliminate wait time and maximize develops’ idea process.
  • Even though most of the code is reused throughout the applications, custom code per specific platform is feasible. Example of this is the process of setting up the project and linking the native modules. This allows existing native applications to have parts of them developed in react native.

I hope you have learned something new, and thank you for your valuable time! Cheers! 🎉