1.0 What this article is about

This write-up is a primer on getting started with building React Native applications while presenting a startup template I have created, that you can use as a foundation to build your next React Native application, or borrow from the design and architecture patterns that you would like to implement on your React Native. Find the template here on my GitHub account.

In this article, I will guide you through the parts of a React Native application, and some best practices on how to architect your application.

Okay, let’s get to it.

2.0 Environment setup for React Native development

Before you can develop React Native applications, you need to have your environment ready. Check out React Natives official documentation which covers this in depth.

For my development environment, the following are the artifacts in my toolbox.

  • Java – check out digital ocean for installation directions.
  • Android studio and SDK
  • Node.js and npm – check out digital ocean for installation directions.
  • Make sure you have npx (from terminal run the command, $ sudo npm i -g npx).
  • Yarn package manager (my preferred node packages manager 🙂). To install yarn, run:
  • $ curl -s https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
  • $ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
  • If using Ubuntu 17.04 or higher, run $ sudo apt remove cmdtest . Because it may interfere with yarn installation.
  • $ sudo apt update && sudo apt install yarn
  • Optionally, an emulator. I recommend Genymotion. Or, you can just use your android device.

3.0 Starting React Native application development

Now that you have your environment all setup, you can now start developing your React Native application.

  • Open your development directory in terminal.
  • $ npx react-native init yourAppName --template react-native-template-typescript (I am not using the popular Expo here, or in any of my projects. Personally, I don’t use Expo because from the first time it was launched, it had some issues that did not attract me when I tried it out back then. Right now, I understand that it is quite stable but I have not yet adopted it.)
  • Start up your emulator or connect your android device.
  • Run $ yarn start
  • Run $ npx react-native run-android, and wait for application to install into emulator or device. After installation, the installed app will try to pull the JavaScript(js)/Typescript(ts) UI code, to display the UI.
  • To enable pushing UI code updates to the emulator, or your USB connected, physical device, with Android Debug Bridge (adb), run $ adb reverse tcp:8081 tcp:8081, and then:
  • If using an emulator, e.g Genymotion emulator, with the emulator in focus, double tap R from your PC keyboard to reload/push the UI code to the app in the emulator.
  • If using a physical device, give the device a shake, so that a development context-menu pops up. From context-menu, tap “reload” to reload/push the UI code to the app.
  • To push UI code updates to the emulator or physical device over WIFI/network:
  • If using an emulator, e.g Genymotion emulator, press Ctrl+M to access the development context-menu, then select debug port, and then enter 10.0.0.3.2:8081 and accept. That is the IP address that Genymotion recognizes your localhost. Then double tap R from the keyboard to reload/push the UI code to the app in the emulator.
  • If using a physical device, make sure your device and PC are both connected to the same network. Shake the device to the get the development context-menu, and then select debug port, enter your PC’s IP address:8081 and accept. Then shake the device again, and from development context-menu, select “reload” to reload/push the UI code to the app.

And happy development 🙂

4.0 Skipping process 3.0, and using the template I have created “React Native with Typescript App Starter Template” to develop your application

You can:

From github, create your project, directly based off of this template, by creating your RN app repository using this template  

  • After the creation, clone your repo to your local development directory.
  • Run $ yarn install to install the dependencies.
  • In package.json rename project name to your app project’s name. As well, remove any of my unnecessary details.
  • In app.json, rename “ReactNativeTsAppStarterTemplate” to the name of your application. This should be the name that will be displayed as the name of your app.
  • Open the android directory in android studio.
  • Under projects/android/app/java, refactor package “reactnativetsappstartertemplate” and rename to the package name you want for your application.
  • After the refactoring, do a global find and rename (Ctrl+Shift+R in android studio in Ubuntu) for any remnants of “ReactNativeTsAppStarterTemplate”, and rename to your application’s name that you had provided upper above in app.json.
  • Now run “clean project”. If it fails, exit android studio, delete “node_modules” directory, and then reinstall (run $ yarn install), and everything will work fine.

And all is good to go. Your application is ready and good to go, based on this template 👍👍👍

Clone this template to your already created app project repo directory

  • In your empty app directory (empty, except for the git files), clone this repo, i.e run $ git clone https://github.com/Kaybarax/react-native-with-typescript-app-starter-template.
  • Inside cloned “react-native-with-type-script-app-starter-template”, make sure you can see all files and directories (press Ctrl+h in Ubuntu), and the delete the “.git” folder.
  • Select everything, cut and paste in root directory. Now delete this empty “react-native-with-type-script-app-starter-template” directory.
  • Now repeat steps 2 through 8 from up above case.

Use this template to guide you in "architecting" and designing your RN app, that you are already in the process of developing

If you have already been developing your application, you can clone this app template, and just go through the code and look at the architecture and design, to help you as you continue to develop your application.

5.0 Walk through “React Native with Typescript App Starter Template” architecture and design for RN development

Architecture

At the top/root directory, you have:

  • < android directory> – auto-generated when the app was created.
  • < ios directory > – auto-generated when the app was created.
  • < __tests__ directory > – auto-generated when the app was created. You will write your tests here, be it jest, enzyme, mocha and chai tests, etc.
  • < node_modules directory > – auto-generated on node modules directory.
  • <tsconfig.json file file >
  • <.buckconfig file file >
  • <.eslintrc.js file file >
  • <.gitattributes file file >
  • <.gitignore file >
  • <.prettierrc.js file >
  • <.watchmanconfig file >
  • < app.json file >
  • < babel.config.js file >
  • < index.js file >
  • < metro.config.js file >
  • < package.json file >
  • < ReactNativeIntro.tsx file >
  • < app directory > – created by me. I always create an “app” directory where I contain 99% of the bulk of my development (UI business logic) code. This way, should the android, or ios native parts crash, I can always start a new RN app, and copy over this “app” directory which, essentially, has everything. So, this app directory being the, essentially, central point, is where the discussion on architecture and design, be based.

<app directory> contents

The following are the contents of this directory.

  • <App.tsx> file

This is the point that connects the whole js/ts application UI code/logic in this directory to the outside “index.js” where the app is bootstrapped. See UML figure below.

This is also where the application’s global state (stores) with MobX state manager (further discussion on MobX later) is loaded into the application.

  • <app-entry.tsx> file

This is where the app with all “composed routes” (more explanation on this, later under routing and navigation) is loaded and presented to “App.tsx”. Because it deals with loading the app with “composed routes”, which in this app, is being handled by React Navigation (RNvg), in this file, is where “react-native-gesture-handler” is loaded (as the very first “import” - that is mandatory), which is required by RNvg.

  • <safe-component-wrapper.tsx file>

I created this file to use it to handle React components, run-time errors. I use it also in my web-apps. RN has a “SafeAreaView” which you can also use, that should serve the same function as this component.

  • <fallback-page.tsx file>

When <safe-component-wrapper.tsx file> catches a React component run-time error, this is the component that is rendered, and reports that there has been a problem. This and the former component, work well together for graceful app failing and error reporting in production.

  • <app-config.tsx file>

This is just a file I created where I declare global constants that I am working with.

  • Views/Activities < views directory >

This is essentially the base of a RN app. It’s the views that are rendered to a user of the app, for interaction. A view has its bits and pieces that will comprise a screen/view component that is to be rendered, its MobX global state/store, and other MobX global states/stores it may be subscribed to, a controller that has functions that operate on it, children sub-views, and associated routing and navigations.

In this application template there are page-1-example-view...4 views, and views of a sub-application “recipe-box”, that demonstrates them, in depth, on the architecture and design. The <…/ app-dev-scratchpad.tsx > view component is just an extra one, that I added for mocking and testing out other view functionalities.

  • Controllers < controllers directory >

As mentioned up above, the files here contain the functions that are used by the views to perform their business logic; from API calls to state/store updates.

  • Media < media directory >

Store here static images, videos, gifs, etc.

  • Theme < theme directory >

Store here the application theme, and styling files that will be used by the view components and their sub-views. You can as well store here theming and styling for any modules you have.

  • Shared components and modules < shared-components-and-modules directory >

Here I have items that are shared across the app, by the application’s views, for example:

  • Notifications module < notification-center directory>
  • Managed form controls < form-controls directory> like:
  • Text inputs.
  • Star ratings component.
  • Select (spinners/pickers).
  • Radio buttons and check boxes.
  • Etc.
  • Camera photo/video capture module < camera-photo-capture-module directory>
  • Progress loaders.
  • Etc.
  • Stores < stores directory >

Herein are contained the files that manage the app’s global state (stores). In this template and all RN apps (and even React and Vue apps) I have built, I always use MobX for my global stores' management. I prefer it because of its reactive programming philosophy. Check out MoBX official documentation, https://mobx-state-tree.js.org/intro, to learn more about MoBX.

The main views (activities) that will render the main screens/views, each have a store assigned to them. MobX marks those stores (a store is just JavaScript object) as “observable” so that any changes that happen to them will result to a view that has been paired (subscribed to it) with it, to be re-rendered and get updated data. Here is a sample code from </stores/app-stores.js> showing this marking of store objects as “observable”.

...

loadAppStores = async () => {

    try {

      this.stores = {};
      this.appStoresLoaded = false;

      for (let key in StoreProviders) {
        let storeKey = StoreProviders[key].storeKey(AppStores.namespace);
        let storeProvider = StoreProviders[key];
        let store = await persistedStoreFromAsyncStorage(storeKey, storeProvider, AppStores.namespace);
        isNullUndefined(store) && (store = storeProvider.storeProvider(AppStores.namespace));
        this.stores[key] = observable(store);
        console.log('CREATED STORE -> ', key, ' -> ', toJS(this.stores[key]));
      }

      this.appStoresLoaded = true;

    } catch (err) {

      console.log('loadAppStores err', err);

      //create brand new stores

      this.stores = {};
      this.appStoresLoaded = false;

      for (let key in StoreProviders) {
        let storeProvider = StoreProviders[key];
        let store = storeProvider.storeProvider(AppStores.namespace);
        this.stores[key] = observable(store);
        console.log('CREATED STORE -> ', key, ' -> ', toJS(this.stores[key]));
      }

      this.appStoresLoaded = true;

    }

  };
  
  ...

The function above, when called, gets and creates store objects from object StoresProviders in </stores/stores-providers.js> and wraps the created store in an “observerble” high order component (HOC) from MoBX. Notice that I attempt to find a store from AsyncStorage before creating a new store from </stores/store-schemas.js>, that is provided by StoreProviders. This is because I have provided functionality for persisting a store to AsyncStorage. This can be useful, for things such as offline storage and use, and also persisting data during app development to save you from re-entering huge form data, for example, across reloads. To persist store data, just call a persist function from </stores/store-utils.js> in a view component that is subscribed to a store (call the function, before rendering the view from that view component) and pass the store(s) to that persist function, that you want that view component to persist, as it works with that/those store(s). See < app/views/recipe-box-sub-app-views/login.tsx > for example, where “loginStore” is persisted, so that on subsequent app restarts, a user doesn’t have to re-input their username.

...

//persist login store 
persistStoreToAsyncStorage(loginStore).then(null);

...

*** You will notice that I don’t hold the user password in the “loginStore”. This is to avoid any potential security incidence. Don’t persist sensitive user credentials, if it might cause a security incidence. ***

One more thing about the approach to creating observable stores. I am using them as HOCs here, but you can also use them as annotations to variables that are to hold stores, if you decide to declare your stores as variable in a class.

In the file </stores/with-stores-hoc.tsx> I have a HOC which I use to pair an observable store with an “observer” view (activity) component. Once, more, when a view component has been marked “observer” and paired with the “observable” stores it observes, through my helper HOC </stores/with-stores-hoc.tsx>, then MoBX is in control, such any changes to nature or data of the store(s) that view is observing, the view component will get re-rendered and updated. </stores/with-stores-hoc.tsx> HOC is not only for subscribing the main views to stores. Sometimes you might need, that a sub-view, when called by a parent view, has store(s) to work with some stores data, and you don’t want to “prop-down” that/those store(s) to the sub-view child component. In that case you can use the HOC to inject store(s) to that child view component, when using it in a parent, main view component. See < app/views/recipe-box-sub-app-views/home.tsx > for demonstration:

...

//inject needed appStore and recipeBoxStore
let RecipeListItemCardWithStores = WithStoresHoc(RecipeListItemCard,['recipeBoxStore', 'appStore']);

...

The stores are declared through a schema in </stores/ store-schemas.js >, then made ready for provision in object StoreProviders, and instantiated in the function loadAppStores() in </stores/ app-stores.js >. I like this approach of creating the stores from schemas, because a) the schemas are useful in defining a store data structure, and b) it gives me a reference point to reset a store’s data, if need be.

Besides </stores/ app-stores.js >, you can have any other “...-store.js/ts”, or classes/objects/functions, if you want to use them to separate instantiation of different stores or groupings of stores, e.g for sub-applications in your application.

In this directory, you’ll also find </store-utils.js > for any utility functions pertaining to stores, and other files that help with the stores’ functionality and logic. It’s just my way of spreading out code logic and functionalities, in a way that I find clean, and easy to trace, what belongs where.

  • Routing and navigation (R&N) architecture <routing-and-navigation directory>

Herein is contained the applications routing and navigation logic. Like I had mentioned earlier, I have used React Navigation (RNvg), which is currently the most popular, stable and well-maintained library for routing and navigation for RN. In this template, I have tried to use as much of the functionality availed by the RNvg library, to show you, as much of the use cases and scenarios, as possible. For your in-depth knowledge and information on RNvg, see RNvg official documentation https://reactnavigation.org/.

You can see all of these, application of RNvg’s multiple functionalities, here < app/routing-and-navigation/routing-composition.tsx>.

  • Drawer navigation – I have used this, as the main/base R&N that the app’s UI sits on. It creates the usual, left-side or right-side drawer menu.
  • Stack navigation - I have used this for the first child of the above drawer navigation. The child has several views that are put together with “stack navigation”. The other place I have used this, is as the main R&N for the “recipe-box” sub-application example.
  • Top tabs navigation - Used in main screen/view of the main app landing view to show how this “tabs navigation” works. I went further and used custom “tabBars”, < app/routing-and-navigation/main-app-top-navigation-tabs-custom-tab-bars.tsx > to demonstrate how to use your own “tabBar” components on top of RNvg’s “top tabs navigation”.
  • Bottom tabs navigation - Used in main screen/view of “recipe-box” sub-application. And just like with the “top tabs navigation”, I also gave the tabs, their own custom “tabBars” < app/routing-and-navigation/recipe-box-bottom-navigation-tabs-custom-tab-bars.tsx >

In this directory the other important/main files are:

  • View routes declarations <views-routes-declarations.tsx> - Here, I import all the views that will need to be registered as a route (viewRoute) that can be navigated to. The registration happens in < routing-composition.tsx >, where a route is made known to RNvg routing functionalities. A route that has not been registered, cannot be navigated to. The view routes defined here are also mainly exported to < app-navigation.ts >. Cue <app-navigation.ts>
  • App navigation class <app-navigation.ts> - this class defines the functionality for controlled navigation in the app using ANY (R&N agnostic) library you might want to use. Once again, ANY, not just RNvg. So, whatever R&N library is your favorite or you fancy, you can pair it with the functionality in this class to handle all your navigation functionality. On top of that defined, controlled navigation functionality, this class defines and provides named navigations (functions) that are called to navigate to the registered routes. Instead of just reusing the defined navigation functionality, and passing to it different parameters, for different navigation routes, I found named navigations to be more preferable, clean and easier to manage. This class also has a “navStore” object references a “navStore” object from the app’s main store, “appStore”. I use it maintain a track of navigations. This can be useful for analytics on how a use traverses your application. In this class you can also create any other ad hoc navigation functionalities you might require.
  • R&N util <routing-and-navigation-utils.tsx> - utility functions, specifically, for R&N.
  • The afore discussed, are the main and important files in R&N directory. The other files come in to serve their R&N functionalities as needed, and some of those extra files that are worth mentioning, are:
  • Popup menu navigation (I created this, not based off of RNvg) < popup-menu.js > - I created this for pop-up menu navigation functionality. It has been used in < home.tsx > of recipe-box sub application. Note, this is how to create that “popular pop-up-menu” in RN.
  • < app-drawer-navigation-content.tsx > - Custom menu content for the RNvg drawer navigation.
  • Android custom native modules < android-custom-native-modules directory>

In RN, when you want to handle some stuff from the native side with native code (java/kotlin/objective-c), RN facilitates this through “custom native modules”. You write native functionality in native code (custom native modules), and then you import your native functionality over to the ts/js side, for your use.  To demonstrate this, for this template, I created a java module for hashing a user password on sign-up for “recipe-box” sub-application. And then validating that password on login.

An android custom native module on the native, android side of RN consists of 2 parts:

  • A module class, <android/app/src/main/java/app_custom_native_modules/ AppSecurityModule.java >.
  • A module packager class, <android/app/src/main/java/app_custom_native_modules/ AppSecurityPackage.java >.

The module java class has methods that are called from the js/ts side across the RN bridge to, hash a password on sign up, and another method that, using the generated hash salt, verifies a password on login. For a Java class method to be able to be called from the js/ts side of RN, the method should be annotated with @ReactMethod. So, I have my two methods, therein, for those purposes.

In <android/app/src/main/java/app_custom_native_modules/ AppSecurityModule.java >.

...

@ReactMethod
public void createPasswordHash(String passwordText, Callback callback) {

    this.passwordText = passwordText;
    this.callback = callback;
    this.currentActivity = getCurrentActivity();

    if (this.currentActivity == null) {
        this.callback.invoke(NULL_CURRENT_ACTIVITY);
        return;
    }

    if (this.passwordText == null) {
        Toast.makeText(this.currentActivity,
                "Please provide password",
                Toast.LENGTH_LONG).show();
        this.callback.invoke(FAILURE_CALLBACK);
        return;
    }

    try {

        String hash = createHash(this.passwordText);

        this.responseMap = Arguments.createMap();

        //feedback data
        this.responseMap.putString("message", SUCCESS_CALLBACK);
        this.responseMap.putString("passwordHash", passwordHash);
        this.responseMap.putString("passwordSalt", Arrays.toString(passwordSalt));

        //respond
        this.callback.invoke(this.responseMap);

    } catch (NoSuchAlgorithmException e) {

        Toast.makeText(this.currentActivity,
                "Hash password failure #1",
                Toast.LENGTH_LONG).show();
        this.callback.invoke(FAILURE_CALLBACK);

        e.printStackTrace();

    } catch (InvalidKeySpecException e) {

        Toast.makeText(this.currentActivity,
                "Hash password failure #2",
                Toast.LENGTH_LONG).show();
        this.callback.invoke(FAILURE_CALLBACK);

        e.printStackTrace();

    }

}

...

And

...

@ReactMethod
public void validatePasswordWithHashAndSalt(String passwordToValidate, String hash, String salt, Callback callback) {

    this.passwordToValidate = passwordToValidate;
    this.userHash = hash;
    this.userSalt = salt;
    this.callback = callback;
    this.currentActivity = getCurrentActivity();

    if (this.currentActivity == null) {
        this.callback.invoke(NULL_CURRENT_ACTIVITY);
        return;
    }

    if (this.passwordToValidate == null ||
            this.userSalt == null ||
            this.userHash == null) {
        Toast.makeText(this.currentActivity,
                "All required data to validate password, not provided",
                Toast.LENGTH_LONG).show();
        this.callback.invoke(FAILURE_CALLBACK);
        return;
    }

    boolean passwordValidationPassed = false;

    try {

        passwordValidationPassed = validatePassword(this.passwordToValidate.toCharArray(),
                this.userHash, this.userSalt.getBytes());

        //and then prepare result to emit feedback
        WritableMap params = Arguments.createMap();
        //data to emit back
        params.putString("passwordValidationPassed", String.valueOf(passwordValidationPassed));

        this.callback.invoke(SUCCESS_CALLBACK);
        Util.emitPasswordValidationResult(this.reactContext, params);


    } catch (NoSuchAlgorithmException e) {

        Toast.makeText(this.currentActivity,
                "Password Validation failure #1",
                Toast.LENGTH_LONG).show();
        this.callback.invoke(FAILURE_CALLBACK);

        e.printStackTrace();

    } catch (InvalidKeySpecException e) {

        Toast.makeText(this.currentActivity,
                "Password Validation failure #2",
                Toast.LENGTH_LONG).show();
        this.callback.invoke(FAILURE_CALLBACK);

        e.printStackTrace();

    }

}

...

The module packager, then gets an instance of the module and exports to < android/app/src/main/java/com/reactnativetsappstartertemplate/MainActivity.java > so that that it can be availed for import on the js/ts side.

In <android/app/src/main/java/app_custom_native_modules/ AppSecurityPackage.java >.

...

@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
    List<NativeModule> modules = new ArrayList<>();
    modules.add(new AppSecurityModule(reactContext));
    return modules;
}

...

And in < android/app/src/main/java/com/reactnativetsappstartertemplate/MainActivity.java>

...

@Override protected
List<ReactPackage> getPackages() {
    @SuppressWarnings("UnnecessaryLocalVariable")
    List<ReactPackage> packages = new PackageList(this).getPackages();
    // Packages that cannot be autolinked yet can be added manually here, for example:
    // packages.add(new MyReactNativePackage());
    packages.add(new AppSecurityPackage());
    packages.add(new AppIntentsPackage());
    return packages; 
}

...

On the js/ts side < app/android-custom-native-modules/custom-native-modules.js > the java custom native modules are imported. And then consumed with js/ts functions in < app/android-custom-native-modules/app-security-custom-native-module.js >

When a js/ts function makes a call to custom native module, the function call is asynchronous, so it needs a promise, or a callback function to receive the results of the function call. I am using a callback in this case. The module on android, java side, can return a result through the callback function and/or emitting the result through an event listener for the result. I demonstrate how to make a call and receive the result both through a callback function and an event listener.

Result feedback through a callback < android/app/src/main/java/app_custom_native_modules/AppSecurityModule.java>

...

String hash = createHash(this.passwordText);
this.responseMap = Arguments.createMap();
//feedback data
this.responseMap.putString("message", SUCCESS_CALLBACK); this.responseMap.putString("passwordHash", passwordHash); this.responseMap.putString("passwordSalt", Arrays.toString(passwordSalt));  //respond
this.callback.invoke(this.responseMap);

...

Result feedback through an eventEmitter < android/app/src/main/java/app_custom_native_modules/Util.java>

...

public static void emitPasswordValidationResult(ReactContext reactContext, WritableMap params) { reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("password_validation_result", params); 
}

...

When the js/ts function call’s result is to be received through a callback, you need to just make the function call, and pass a callback function to receive the result. If the result will be received from listening to a DeviceEventEmitter, then in the js/ts function call, you first add an event listener for that event name, and then pass to it an event listener function, that will listen for the result feedback from that “event name”. Both, through a callback and an event listener, the result can be received as primitive data types or even json or array data type.

  • Extras:
  • App dev scratchpad < app-dev-scratch-pad directory> – Like I had mentioned upper above, I just added this, and its component to mock and test out stuff.
  • App-management <app-management directory> - This is not really needed. I just added it to put in there, stuff that I have been using during the template development. This is stuff such as data that I should have been receiving from a server.
  • Auth-and-security <auth-and-security directory> - Not really needed, but you can place in here anything that you would like to do with your app’s security or authentication.
  • Util <util directory>

Utility functionalities for the application. Here I have:

  • < app/util/util.js> - Just JavaScript utility functions.
  • < app/util/network-calls-util.ts > - for all network calls utility.
  • < app/util/react-native-based-utils.js > - JavaScript utility functions that solely fit with the RN environment. The utilities in < app/util/util.js >, I can share globally with my other projects outside of RN, but the ones in here, I can’t. I can maybe only share their logic.
  • < app/util/react-native-data-collection-utils.js > - Used by “form-controls” on data collection.

Important to note, as I head towards concluding this article. For the “recipe-box” sub-application, I have used “react-native-sqlite-storage”, but I would not recommend it. It is rather buggy, on retrieving data you have saved, and also it does not come with good handling of such as batch SQL transactions, and you have to write a lot of your own extra code to handle that. If you want use SQL-lite, I recommend using the native one that comes with mobile devices.

6.0 Conclusion: Building RN app for release (android release in this case)

After developing a RN app, the final part is to build for release to target users. This involves building a signed apk. The RN official documentation covers this in detail, and you can see it there. The gist of it involves creating a signing key, and then signing your release apk with that key. For this template, I have already provided it with a signing key, and filled out the apk signing details in < android/gradle.properties > and < android/app/build.gradle >. When using this template as a base for your own app, make sure to replace the values in < android/gradle.properties > with your own, and as well replace the signing keystore < android/app/kaybarax-apk-apps.keystore >, with your own, when you want to build for release.

For now, since you have everything there for release ready testing and building out the signed apk, you can go ahead and build and test a release, signed apk. From the root directory of the app:

  • To test a release apk, run $ cd android/ && ./gradlew installRelease, to install the release, test apk to a physical device.
  • To build a release apk, run $ cd android/ && ./gradlew assembleRelease

Happy hacking, and bringing your React Native products to market!

A little preface about me, and my experience with React Native; I have 3 (three) years of professional experience in building React Native applications. I started building React Native apps in 2017, from the first company I worked for.

Connect with me:

Github:   LinkedIn:   Twitter:  

You've successfully subscribed to Decoded For Devs
Welcome back! You've successfully signed in.
Great! You've successfully signed up.
Your link has expired
Success! Your account is fully activated, you now have access to all content.