RMC React Native iOS - Developer Onboarding Guide

Table of Contents

  1. Overview

  2. Critical Warnings

  3. Project Setup

  4. Folder Structure

  5. Native Modules Implementation

  6. Styling Approach

  7. State Management

  8. Data Fetching & Validation

  9. Navigation

  10. Key Architectural Patterns

  11. Technology Stack

  12. Agora Implementation

  13. Deployment


Overview

This is a Remote Medical Care (RMC) telemedicine application built with bare React Native CLI (not Expo). The app integrates with multiple medical devices via Bluetooth and provides features like video consultations, health data tracking, and Apple HealthKit integration.

Key Characteristics:


Critical Warnings

DO NOT Delete the ios Folder

Unlike standard React Native projects where the ios folder can be regenerated using npx react-native init, this project contains extensive custom native code that cannot be regenerated. Deleting the ios folder will result in:

The native code took significant effort to develop and is critical to the app’s functionality.

DO NOT Modify Native Module Bridges Without Understanding

The native bridges (VisionController.m, SmarthoController.m, HealthKitBridge.swift) communicate with React Native via specific event names and data structures. Changing these without updating the corresponding TypeScript hooks will break device functionality.

DO NOT Update iOS SDKs Without Testing

The following native SDKs are tightly integrated and should not be updated without thorough testing:

DO NOT Run pod install After Modifying Podfile Without Backup

Always backup the ios/Pods directory before making Podfile changes since some SDK configurations are sensitive.


Project Setup

Prerequisites

  1. macOS (required for iOS development)

  2. Xcode 15+ (latest stable version recommended)

  3. Node.js 18+

  4. CocoaPods (sudo gem install cocoapods)

  5. Watchman (brew install watchman)

  6. Ruby (for CocoaPods, comes with macOS)

Installation Steps


# 1. Clone the repository

git  clone <repository-url>

cd  RMC_RN_IOS

  

# 2. Install JavaScript dependencies

npm  install

# or

yarn  install

  

# 3. Install iOS dependencies

cd  ios

pod  install

cd  ..

  

# 4. Start Metro bundler

npm  start

# or

yarn  start

  

# 5. Run the app (in a separate terminal)

npm  run  ios

# or

yarn  ios

Environment Configuration

Check /src/utils/config.ts for API endpoints and environment-specific configurations.

Common Setup Issues

| Issue | Solution |

|-------|----------|

| Pod install fails | Run pod repo update then pod install |

| Build fails with signing error | Open Xcode, select your team in Signing & Capabilities |

| Metro bundler port conflict | Kill process on port 8081: lsof -i :8081 then kill -9 <PID> |

| Native SDK not found | Ensure Frameworks are properly linked in Xcode |


Folder Structure


RMC_RN_IOS/

├── ios/ # iOS native code (DO NOT DELETE)

│ ├── *.swift # Swift native modules

│ ├── *.m # Objective-C native modules

│ ├── CustomCamera/ # Custom camera module

│ ├── Frameworks/ # Native SDKs (Mintti, Camera libs)

│ └── Podfile # iOS dependencies

│

├── src/ # Main application source

│ ├── api/ # API layer

│ │ ├── action/ # Mutation hooks (POST, PUT, DELETE)

│ │ ├── query/ # Query hooks (GET requests)

│ │ └── schema/ # Zod validation schemas

│ │

│ ├── assets/ # Static assets

│ │ ├── fonts/ # Inter font family

│ │ ├── icons/ # SVG and image icons

│ │ └── images/ # App images

│ │

│ ├── components/ # React components

│ │ ├── ui/ # Reusable UI components

│ │ └── [Feature]/ # Feature-specific components

│ │

│ ├── constant/ # App constants

│ │ ├── Colors.ts # Color definitions

│ │ ├── TestMapping.ts # Medical test mappings

│ │ └── AgoraConfig.ts # Video call configuration

│ │

│ ├── i18n/ # Internationalization

│ │ ├── i18n.config.ts # i18next configuration

│ │ └── translations/ # Language files (en, de, es, ar)

│ │

│ ├── nativemodules/ # TypeScript bridges for native code

│ │ ├── SmarthoStethoScope/ # Stethoscope integration

│ │ ├── HealthKit/ # HealthKit bridge

│ │ ├── useMinttiVision.ts # Vision device hook

│ │ ├── EcgChart.tsx # ECG visualization component

│ │ └── BoGraph.tsx # Blood oxygen graph component

│ │

│ ├── screens/ # Screen components (~60 screens)

│ │

│ ├── styles/ # Global styles

│ │ └── style.ts # StyleSheet definitions

│ │

│ └── utils/ # Utilities and helpers

│ ├── hook/ # Custom React hooks

│ ├── store/ # Zustand stores (21 stores)

│ ├── AppNavigation.tsx # Navigation configuration

│ ├── mmkv.ts # MMKV storage setup

│ └── config.ts # App configuration

│

├── App.tsx # Root component

├── index.js # App entry point

├── package.json # Dependencies

├── tsconfig.json # TypeScript config

├── babel.config.js # Babel config (NativeWind, Reanimated)

├── tailwind.config.js # Tailwind CSS config

└── metro.config.js # Metro bundler config

Folder Purpose Breakdown

| Folder | Purpose |

|--------|---------|

| src/api/action/ | Contains 47 mutation hooks for form submissions, updates, and deletions |

| src/api/query/ | Contains 35 query hooks for fetching data (appointments, tests, health data) |

| src/api/schema/ | Zod schemas for request/response validation |

| src/components/ui/ | 18 reusable components: Button, FormInput, DropDown, Loader, etc. |

| src/nativemodules/ | TypeScript wrappers for native iOS modules |

| src/utils/store/ | 21 Zustand stores for state management |

| src/utils/hook/ | 11 custom hooks for permissions, Bluetooth, camera, etc. |

| ios/ | Native iOS code, frameworks, and Xcode project |


Native Modules Implementation

This section details the custom native modules that bridge iOS functionality to React Native.

Architecture Overview


React Native (TypeScript)

│

▼

NativeModules API

│

▼

Native Bridge (Objective-C/Swift)

│

▼

Native SDK / Framework

1. HealthKit Integration

Native File: ios/HealthKitBridge.swift

Purpose: Reads health data from Apple Health (50+ metrics)

Supported Metrics:

TypeScript Usage:


// src/nativemodules/HealthKit/

import { NativeModules } from  'react-native';

const { HealthKitBridge } = NativeModules;

  

// Request authorization

await  HealthKitBridge.requestAuthorization();

  

// Fetch health data

const  steps = await  HealthKitBridge.getSteps(startDate, endDate);

2. Mintti Vision Device

Native File: ios/VisionController.m

Purpose: Multi-parameter medical device integration

Capabilities:

Event Types Emitted (16 events):

TypeScript Hook: src/nativemodules/useMinttiVision.ts


import { useMinttiVision } from  '@/nativemodules/useMinttiVision';

  

const {

startScan,

stopScan,

connect,

disconnect,

startEcgMeasurement,

// ... other methods

} = useMinttiVision();

3. Smartho Stethoscope

Native File: ios/SmarthoController.m

Purpose: Digital stethoscope for heart and lung sounds

Capabilities:

TypeScript Hook: src/nativemodules/SmarthoStethoScope/useStethoScope.tsx


import { useStethoScope } from  '@/nativemodules/SmarthoStethoScope/useStethoScope';

  

const {

scanDevices,

connectDevice,

startRecording,

stopRecording,

} = useStethoScope();

4. Custom Camera Module

Native Files:

Purpose: Integration with dental/dermatoscope cameras (FYB800 device)

5. Real-time Visualization Components

Native Views (Objective-C):

| File | Purpose | React Component |

|------|---------|-----------------|

| EcgChartViewManager.m | ECG waveform rendering | <EcgChart /> |

| BoGraphViewManager.m | Blood oxygen graph | <BoGraph /> |

| HeartLive.m | Real-time heart rate | Used internally |

| OxyWaveView.m | SpO2 waveform | Used internally |

| WaveView.m | General waveform | Used internally |

Usage in React Native:


import  EcgChart  from  '@/nativemodules/EcgChart';

import  BoGraph  from  '@/nativemodules/BoGraph';

  

// In your component

<EcgChart  data={ecgData} />

<BoGraph  data={spO2Data} />

6. Audio Playback

Native File: ios/PCMDataPlayer.m

Purpose: Plays PCM audio data from stethoscope recordings

Native Module Communication Pattern


// Pattern used throughout the app

import { NativeModules, NativeEventEmitter } from  'react-native';

  

const { VisionController } = NativeModules;

const  eventEmitter = new  NativeEventEmitter(VisionController);

  

// Subscribe to events

useEffect(() => {

const  subscription = eventEmitter.addListener('onEcgData', (data) => {

// Handle ECG data

});

  

return () =>  subscription.remove();

}, []);

  

// Call native methods

VisionController.startEcgMeasurement();


Styling Approach

The app uses a multi-layered styling system:

1. Tailwind CSS + NativeWind (Primary)

Configuration: tailwind.config.js


module.exports = {

content: ['./App.{js,jsx,ts,tsx}', './src/**/*.{js,jsx,ts,tsx}'],

theme: {

extend: {

colors: {

primary:  '#46b98d', // Main brand color (green)

accent:  '#fc5e53', // Accent color (red)

secondary:  '#60A5FA', // Secondary color (blue)

},

},

},

};

Usage:


<View  className="flex-1 bg-white p-4">

<Text  className="text-primary text-lg font-bold">Hello</Text>

<TouchableOpacity  className="bg-primary rounded-lg p-3">

<Text  className="text-white text-center">Button</Text>

</TouchableOpacity>

</View>

2. React Native StyleSheet (Secondary)

Location: src/styles/style.ts


import { StyleSheet } from  'react-native';

  

export  const  globalStyles = StyleSheet.create({

container: {

flex:  1,

backgroundColor:  '#fff',

},

shadow: {

shadowColor:  '#000',

shadowOffset: { width:  0, height:  2 },

shadowOpacity:  0.1,

shadowRadius:  4,

elevation:  3,

},

});

3. Custom Fonts

Font Family: Inter (Regular, Medium, SemiBold, Bold, Thin)

Linked via: react-native.config.js


// Usage

<Text  style={{ fontFamily: 'Inter-Bold' }}>Bold  Text</Text>

// or with Tailwind

<Text  className="font-bold">Bold  Text</Text>

Styling Best Practices

  1. Prefer Tailwind classes for common styles

  2. Use StyleSheet for complex or reusable styles

  3. Avoid inline style objects when possible (performance)

  4. Use the defined color palette from tailwind.config.js


State Management

Framework: Zustand + MMKV

Why Zustand:

Store Structure

Located in src/utils/store/ (21 stores):

| Store | Purpose |

|-------|---------|

| useSignInStore | User authentication and profile |

| useMinttiVisionStore | BLE device state, scanning, battery |

| useSmarthoInitalization | Stethoscope initialization |

| useMeetingStore | Video call state |

| useLanguageStore | App language preference |

| useBookAppointmentStore | Appointment booking form |

| useUnitStore | Measurement unit preferences |

| useChatStatus | Chat connection status |

| useConsentStore | User consent tracking |

Store Pattern


// src/utils/store/useExampleStore.ts

import { create } from  'zustand';

import { persist, createJSONStorage } from  'zustand/middleware';

import { mmkvStorage } from  '../mmkv';

  

interface  ExampleState {

value: string;

setValue: (value: string) =>  void;

reset: () =>  void;

}

  

export  const  useExampleStore = create<ExampleState>()(

persist(

(set) => ({

value:  '',

setValue: (value) =>  set({ value }),

reset: () =>  set({ value:  '' }),

}),

{

name:  'example-storage',

storage:  createJSONStorage(() =>  mmkvStorage),

}

)

);

MMKV Storage

Location: src/utils/mmkv.ts

MMKV is used instead of AsyncStorage for:


Data Fetching & Validation

API Layer Structure


src/api/

├── action/ # 47 mutation hooks (useMutation)

├── query/ # 35 query hooks (useQuery)

└── schema/ # Zod validation schemas

React Query (TanStack Query v5)

Query Hook Pattern:


// src/api/query/useGetAllAppointments.tsx

import { useQuery } from  '@tanstack/react-query';

import  axios  from  'axios';

  

export  const  useGetAllAppointments = (userId: string) => {

return  useQuery({

queryKey: ['appointments', userId],

queryFn:  async () => {

const  response = await  axios.get(`/appointments/${userId}`);

return  response.data;

},

enabled: !!userId,

});

};

Mutation Hook Pattern:


// src/api/action/useCreateAppointment.tsx

import { useMutation, useQueryClient } from  '@tanstack/react-query';

import  axios  from  'axios';

  

export  const  useCreateAppointment = () => {

const  queryClient = useQueryClient();

  

return  useMutation({

mutationFn:  async (data: AppointmentData) => {

const  response = await  axios.post('/appointments', data);

return  response.data;

},

onSuccess: () => {

queryClient.invalidateQueries({ queryKey: ['appointments'] });

},

});

};

Zod Validation

Location: src/api/schema/


// src/api/schema/loginSchema.ts

import { z } from  'zod';

  

export  const  loginSchema = z.object({

username:  z.string().min(3, 'Username must be at least 3 characters'),

password:  z.string().min(1, 'Password is required'),

});

  

export  type  LoginFormData = z.infer<typeof  loginSchema>;

With React Hook Form:


import { useForm } from  'react-hook-form';

import { zodResolver } from  '@hookform/resolvers/zod';

import { loginSchema, LoginFormData } from  '@/api/schema/loginSchema';

  

const { control, handleSubmit } = useForm<LoginFormData>({

resolver:  zodResolver(loginSchema),

});


Framework: React Navigation v6

Configuration: src/utils/AppNavigation.tsx

  1. Native Stack - Main screen navigation

  2. Drawer - Side menu navigation

  3. Material Top Tabs - Tab-based screens

Route Structure


// Simplified type definition

type  HomeStackNavigatorParamList = {

// Auth

Login: undefined;

Register: undefined;

  

// Main

Home: undefined;

Dashboard: undefined;

  

// Appointments

BookAppointment: { doctorId: string };

AppointmentDetails: { appointmentId: string };

  

// Health Tests

ECGTest: { appointmentId: string };

BloodPressureTest: { appointmentId: string };

BloodGlucoseTest: { appointmentId: string };

  

// Devices

InitMinttiVision: undefined;

InitSmartho: undefined;

  

// ... 50+ more routes

};


import { useNavigation } from  '@react-navigation/native';

import { NativeStackNavigationProp } from  '@react-navigation/native-stack';

  

type  NavigationProp = NativeStackNavigationProp<HomeStackNavigatorParamList>;

  

const  navigation = useNavigation<NavigationProp>();

  

// Navigate

navigation.navigate('AppointmentDetails', { appointmentId:  '123' });

  

// Go back

navigation.goBack();

  

// Reset stack

navigation.reset({

index:  0,

routes: [{ name:  'Home' }],

});


Key Architectural Patterns

1. React Query + Zustand Hybrid

2. Custom Hooks Architecture


hooks/

├── useBluetoothPermission.ts # Permission management

├── useDentalCamera.ts # Camera device logic

├── useNetworkStatus.ts # Connectivity status

└── ...

3. Form Management

4. Internationalization (i18n)

Languages Supported: English, German, Spanish, Arabic


import { useTranslation } from  'react-i18next';

  

const { t } = useTranslation();

<Text>{t('common.submit')}</Text>

5. Feature-Based Component Structure


components/

├── ui/ # Shared UI components

├── BookAppointment/ # Appointment booking components

├── HealthData/ # Health data display components

├── Devices/ # Device management components

└── ...


Technology Stack

| Category | Technology | Version |

|----------|------------|---------|

| Framework | React Native | 0.73.4 |

| Language | TypeScript | 5.0.4 |

| UI Library | React | 18.2.0 |

| State Management | Zustand | 4.5.0 |

| Data Fetching | TanStack React Query | 5.20.5 |

| HTTP Client | Axios | 1.6.7 |

| Forms | React Hook Form | 7.50.1 |

| Validation | Zod | 3.22.4 |

| Styling | NativeWind + Tailwind | 2.0.11 / 3.2.2 |

| Navigation | React Navigation | 6.x |

| Animations | React Native Reanimated | 3.7.0 |

| Storage | MMKV | Latest |

| i18n | i18next | 23.14.0 |

| Video Calls | Agora RTC SDK | Latest |

| Messaging | Agora RTM SDK | Latest |

| Testing | Jest | 29.6.3 |


Agora Implementation (Video Calls & Chat)

The app uses Agora RTC SDK for real-time video consultations and Agora RTM SDK for in-app messaging between patients and doctors.

Configuration Setup

The Agora configuration file is gitignored to protect credentials. You must create it manually.

Step 1: Rename or create the config file:


# If an example file exists:

cp  src/constant/agoraConfig.example.ts  src/constant/agoraConfig.ts

  

# Or create it manually

touch  src/constant/agoraConfig.ts

Step 2: Add your Agora credentials to src/constant/agoraConfig.ts:


import {VideoContentHint} from  'react-native-agora';

  

function  generateRandomLargeNumber(

type: 'screen-sharing' | 'dental-cam-android' | 'dental-cam-ios',

): number {

let  min: number;

let  max: number;

switch (type) {

case  'screen-sharing':

min = 10000000;

max = 10000099;

break;

case  'dental-cam-android':

min = 10000100;

max = 10000199;

break;

case  'dental-cam-ios':

min = 10000200;

max = 10000299;

break;

default:

throw  new  Error('Invalid type passed');

}

return  Math.floor(Math.random() * (max - min + 1)) + min;

}

  

// Replace with your Agora credentials

const  agoraConfig = {

appId:  'YOUR_AGORA_APP_ID',

screenSharingUID:  generateRandomLargeNumber('screen-sharing'),

dentalCamUID:  generateRandomLargeNumber('dental-cam-ios'),

product:  null  as  any,

};

  

const  agoraChatConfig = {

APP_KEY:  'YOUR_AGORA_CHAT_APP_KEY',

WS_ADDRESS:  'YOUR_WEBSOCKET_ADDRESS',

REST_API:  'YOUR_REST_API_ADDRESS',

};

  

const  screenSharingConfig = {

sampleRate:  16000,

channels:  2,

captureSignalVolume:  0,

width:  640,

height:  360,

frameRate:  10,

bitrate:  500,

contentHint:  VideoContentHint.ContentHintNone,

captureAudio:  true,

captureVideo:  true,

};

  

export {agoraChatConfig, agoraConfig, screenSharingConfig};

Getting Agora Credentials

  1. Go to Agora Console

  2. Create a new project or select existing one

  3. Copy the App ID from project settings

  4. Enable Agora Chat if using messaging features

  5. Get Chat credentials (APP_KEY, WS_ADDRESS, REST_API) from Chat settings

Key Files

| File | Purpose |

|------|---------|

| src/constant/agoraConfig.ts | Agora credentials and config (gitignored) |

| src/screens/AppointmentMeeting.tsx | Video call screen implementation |

| src/utils/hook/useRtm.tsx | RTM (Real-time Messaging) hook |

| src/components/RemoteUserContainer.tsx | Remote participant video view |

| src/utils/store/useMeetingStore.ts | Video call state management |

Features

Important Notes


Deployment

Deployment Process

The app is deployed manually using Xcode’s archive and upload workflow:

  1. Create Archive in Xcode:

1. Open ios/RMC.xcworkspace in Xcode

2. Select "Any iOS Device (arm64)" as the build target

3. Go to Product > Archive

4. Wait for the archive to complete

  1. Upload to App Store Connect:

1. Once archived, the Organizer window opens automatically

2. Select the archive and click "Distribute App"

3. Choose "App Store Connect" > "Upload"

4. Follow the prompts to upload the build

5. Go to App Store Connect to submit for review

App Store Apps

There are three apps in the App Store Connect account:

| App Name | Purpose | Server |

|----------|---------|--------|

| Telecare Connect | Testing app with latest code | Development/Test server |

| Remote Medical Care | Production app on App Store | Clinic server (production) |

| Third App | Legacy/Other | - |

Important:

Switching Between Apps

Telecare Connect and Remote Medical Care are two separate apps with different:

The assets (icons) for both apps are provided in the docs/ folder.

To deploy to a different app (e.g., from Telecare Connect to Remote Medical Care), follow these steps:

1. Update Info.plist

Open ios/RMC/Info.plist in Xcode or a text editor:


<key>CFBundleDisplayName</key>

<string>Remote Medical Care</string>  <!-- or "Telecare Connect" -->


<key>CFBundleShortVersionString</key>

<string>1.0.0</string>  <!-- App version shown to users -->

  

<key>CFBundleVersion</key>

<string>1</string>  <!-- Build number, increment for each upload -->

2. Replace App Icons

  1. Navigate to ios/RMC/Images.xcassets/AppIcon.appiconset/

  2. Replace the icon images with the appropriate set from docs/ folder:

  1. Ensure all required icon sizes are included

3. Replace Splash Screen

  1. Navigate to ios/RMC/Images.xcassets/ (or the LaunchScreen assets)

  2. Replace the splash screen images with the appropriate set from docs/ folder:

  1. Verify the splash screen displays correctly in ios/RMC/LaunchScreen.storyboard

4. Verify Server Configuration

Deployment Checklist

Before deploying to production (Remote Medical Care):


Quick Reference

Common Commands


# Start Metro bundler

npm  start

  

# Run iOS app

npm  run  ios

  

# Run iOS on specific simulator

npm  run  ios  --  --simulator="iPhone 15 Pro"

  

# Install new dependency

npm  install <package-name>

  

# Install iOS pods after adding native dependency

cd  ios && pod  install && cd  ..

  

# Clear Metro cache

npm  start  --  --reset-cache

  

# Clean iOS build

cd  ios && xcodebuild  clean && cd  ..

  

# Open Xcode project

open  ios/RMC.xcworkspace

Useful Debugging


# View Metro logs

npm  start

  

# View native logs

# Open Xcode > Window > Devices and Simulators > View Device Logs

  

# React Native Debugger

# Shake device or Cmd+D > Debug with Chrome

Adding a New Screen

  1. Create screen in src/screens/NewScreen.tsx

  2. Add route to src/utils/AppNavigation.tsx

  3. Add type to HomeStackNavigatorParamList

  4. Navigate using navigation.navigate('NewScreen')

Adding a New API Hook

  1. Query: Create file in src/api/query/useGetSomething.tsx

  2. Mutation: Create file in src/api/action/useDoSomething.tsx

  3. Schema: If needed, add to src/api/schema/


Last updated: January 2026