React Native Project Setup with MVVM

Jerrin Thomas
6 min readMay 17, 2024

--

This document outlines the folder structure used in our React Native project. It is designed to help maintain a clean, organized, and scalable codebase, adhering to the MVVM (Model-View-ViewModel) pattern and best practices.

Project Folder Structure

app/
├── assets/
│ ├── fonts/
│ │ └── custom-font.ttf
│ ├── icons/
│ │ └── icon.png
│ └── images/
│ └── logo.png
├── components/
│ ├── Button/
│ │ ├── ButtonModel.js
│ │ └── ButtonStyle.js
| | └── ButtonView.js
| | └── ButtonViewModel.js
│ ├── Header/
| │ ├── HeaderModel.js
│ │ └── HeaderStyle.js
| | └── HeaderView.js
| | └── HeaderViewModel.js
│ └── Card/
| │ ├── CardModel.js
│ │ └── CardStyle.js
| | └── CardView.js
| | └── CardViewModel.js
├── screens/
│ ├── auth/
│ │ ├── AuthModel.js
│ │ └── AuthStyle.js
| | └── AuthView.js
| | └── AuthViewModel.js
│ └── home/
│ │ ├── HomeModel.js
│ │ └── HomeStyle.js
| | └── HomeView.js
| | └── HomeViewModel.js
├── services/
│ ├── NotificationService.js
│ ├── RequestService.js
│ └── StorageService.js
├── store/
│ ├── slices/
│ │ └── appSlice.js
│ │ └── authSlice.js
│ │ └── utilitiesSlice.js
│ │ └── index.js
│ └── sagas/
│ | └── appSaga.js
| |__ store.js
├── utilities/
├── Constants.js
├── EndPoints.js
├── Helpers.js
├── Images.js
├── Localization.js
├── Navigation.js
└── Style.js

assets ->
The assets directory holds static resources such as fonts, icons, and images. These are used throughout the application to provide a consistent look and feel.

components->

The components directory includes reusable UI components. These components can be shared across different screens and features, promoting reusability and reducing redundancy. The components folder is organized following the MVVM (Model-View-ViewModel) pattern. Each component (e.g., Button, Header, Card) has its own directory containing four main files: Model for the data logic, ViewModel for handling the interaction logic, View for the UI representation, and Style for the component's styling. This structure promotes a clear separation of concerns, making the code more maintainable and scalable.

screens->

The screens directory contains the components for each screen or page of the application and is organized into subdirectories based on feature areas or functional modules. It follows the MVVM (Model-View-ViewModel) pattern. For example, the auth subdirectory includes four main files: AuthModel.js for data logic, AuthViewModel.js for managing interaction logic, AuthView.js for the UI representation, and AuthStyle.js for styling. This structure ensures a clear separation of concerns, making the screens modular and easier to maintain.

services->

The services directory is a vital part of our React Native project structure, providing a centralized and organized way to manage external interactions and business logic. By keeping these operations separate from UI components, we maintain a cleaner and more maintainable codebase, which is easier to test and scale as our application grows.

store->

The store directory is the main folder where all the Redux-related logic and configurations are organized. It typically contains subdirectories for slices and sagas, among other potential configurations.

The slices directory holds the individual slice files. Each slice corresponds to a specific part of the application's state and contains the logic for that part, including reducers, actions, and selectors.

utilities->

The utilities directory contains utility functions, helpers, constants, and other modules that are used throughout the application.

Folders and Files Needed in the Root

envs/

The envs directory typically contains configuration files for different environments (e.g., development, testing, production). These files might define environment-specific variables, settings, and credentials needed to run the application in various stages of the development lifecycle.

env

scripts/

The scripts directory includes various utility scripts that automate tasks such as building, testing, deploying, and other development workflows. These scripts help streamline repetitive processes and ensure consistency across different development environments.

scripts

.env

The .env file stores environment variables that are loaded into the application at runtime using the react-native-config package. This file can include sensitive information like API keys, database credentials, and other configuration settings that need to be kept secure and separate from the source code. The react-native-config package reads these variables and makes them available within the React Native application.

APP_NAME = appName
BUNDLE_ID = com.org.appName
SERVER_URL = https://sample/api/
GOOGLE_MAP_API_URL = https://maps.googleapis.com/maps/api/
GOOGLE_API_TOKEN_ANDROID =
GOOGLE_API_TOKEN_IOS =
AES_IV_STRING = 0123456789abcdef0123456789abcdef
AES_PWD = sample@#22aesprotcted
AES_SALT = FINGENTSAMPLESALT
LOGIN_USER_NAME =
LOGIN_USER_PASSWORD =
VERSION_INFO = P_1.0 build(1)
VERSION_TYPE = P

sonar-project.properties

The sonar-project.properties file is used to configure SonarQube, a tool for continuous inspection of code quality. This file defines settings and properties for running static code analysis, such as the project key, source directories, and exclusions, helping to ensure code quality and maintainability throughout the development process.

templates/

The templates directory contains boilerplate code for generating components or screens following the MVVM (Model-View-ViewModel) architecture pattern. These templates serve as a starting point for creating new components or screens with predefined structure and organization.

templates

MVVM Boilerplate Generation

MVVM boilerplate generation involves fetching templates from the templates folder and using a generate.js script in the scripts directory. Triggered by npm run gs NAME_OF_SCREEN or npm run gc NAME_OF_COMPONENT, the script dynamically creates a new folder within screens or components, respectively. Within this folder, it generates Model.js, ViewModel.js, View.js, and Style.js files, establishing the MVVM structure. Custom npm scripts "gs" and "gc" in package.json simplify the generation process, ensuring consistency and efficiency when creating new components or screens.

Example:

  • npm run gs home will generate a home folder in the screens directory with MVVM boilerplate templates.
  • npm run gc card will generate a card component in the components directory with boilerplate templates.

scripts/generateComponent.js

// generateComponent.js
const fs = require('fs');
const path = require('path');

const generateComponent = (name, type) => {
const nameFirstUpper = name.charAt(0).toUpperCase() + name.slice(1);
const rootFolderPath = path.join(__dirname, '..');

const controllerTemplate = path.join(
rootFolderPath,
'templates',
'ViewModelTemplate.txt',
);
const viewTemplate = path.join(rootFolderPath, 'templates', 'ViewTemplate.txt');
const styleTemplate = path.join(rootFolderPath, 'templates', 'StyleTemplate.txt');
const modelTemplate = path.join(rootFolderPath, 'templates', 'ModelTemplate.txt');

const targetDirectory =
type === 'screens'
? path.join(rootFolderPath, 'app', 'screens', name.toLowerCase())
: path.join(rootFolderPath, 'app', 'components', name.toLowerCase());

// Create the target directory if it doesn't exist
if (!fs.existsSync(targetDirectory)) {
fs.mkdirSync(targetDirectory, {recursive: true});
}

const controllerContent = fs
.readFileSync(controllerTemplate, 'utf8')
.replace(/\${name}/g, nameFirstUpper);
fs.writeFileSync(
path.join(targetDirectory, `${nameFirstUpper}ViewModel.js`),
controllerContent,
);

const viewContent = fs
.readFileSync(viewTemplate, 'utf8')
.replace(/\${name}/g, nameFirstUpper);
fs.writeFileSync(
path.join(targetDirectory, `${nameFirstUpper}View.js`),
viewContent,
);

const styleContent = fs
.readFileSync(styleTemplate, 'utf8')
.replace(/\${name}/g, nameFirstUpper);
fs.writeFileSync(
path.join(targetDirectory, `${nameFirstUpper}Style.js`),
styleContent,
);

const modelContent = fs
.readFileSync(modelTemplate, 'utf8')
.replace(/\${name}/g, nameFirstUpper);
fs.writeFileSync(
path.join(targetDirectory, `${nameFirstUpper}Model.js`),
modelContent,
);

console.log(
`Component '${nameFirstUpper}' generated successfully in ${targetDirectory} directory!`,
);
};

const name = process.argv[2];
const type = process.argv[3] || 'component';

if (!name) {
console.error('Please provide a component name.');
process.exit(1);
}

generateComponent(type, name);

templates/ModelTemplate.txt

// generated from template

const ${name}Model = {
loading: true,
arrayData: [],
objectData: {}
};

export default ${name}Model;

templates/StyleTemplate.txt

// generated from template

import {StyleSheet} from 'react-native';

export const ${name}Styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});

templates/ViewModelTemplate.txt

// generated from template

import {useState, useEffect} from 'react';
import ${name}Model from './${name}Model';

export default function use${name}ViewModel({ navigation }) {
const [loading, setLoading] = useState(${name}Model.loading);

useEffect(() => {
// component Initialization
}, []);


return {loading, setLoading};
}

templates/ViewTemplate.txt

// generated from template

import React from 'react';
import {View, Text} from 'react-native';
import use${name}ViewModel from './${name}ViewModel';
import {${name}Styles} from './${name}Style';

export default function ${name}View(navigation) {
const {loading, setLoading} = use${name}ViewModel({navigation});

return (
<View style={${name}Styles.container}>
<Text>Welcome to ${name}View </Text>
</View>
);
}

Note: Utilizing ESLint and SonarLint enhances code quality and maintains consistency throughout the project’s development lifecycle.

--

--

Responses (1)