React Native Project Setup with MVVM
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.
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.
.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.
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 ahome
folder in thescreens
directory with MVVM boilerplate templates.npm run gc card
will generate acard
component in thecomponents
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.