This guide provides step-by-step instructions to integrate the Wellspent React Native SDK into your React Native application. The SDK enables features like device activity monitoring, app shielding, and habit tracking using iOS Family Controls and custom configurations.
- Prerequisites
- Creating a New React Native Project (Optional)
- Installing the RNWellspentSDK Wrapper
- Configuring iOS Extension Targets
- Setting Up the Podfile
- Opening the Xcode Workspace
- Updating Podfile for Extensions
- Adding Strip Duplicate XCFramework Signatures Script
- Adding Wellspent iOS SDK via Swift Package Manager
- Adding Family Controls Usage Description
- Building, Running, and Archiving
- JavaScript Usage Example
Before you begin, ensure you have the following installed:
- macOS: 15.0 or higher with Xcode 16.0 or higher
- Node: Version 20 or higher
- Yarn: Version 3 or higher (or npm 8 or higher)
- React Native CLI: Installed globally
npm install -g react-native-cli
- CocoaPods: Version 1.11 or higher
sudo gem install cocoapods
- A React Native app (new or existing)
If you have an existing React Native project, skip to Installing the RNWellspentSDK Wrapper. Otherwise, create a new project:
npm install -g react-native-cli
react-native init WellspentApp
cd WellspentApp
This creates a project structure like:
WellspentApp/
├── android/
├── ios/
├── App.js
├── index.js
├── package.json
└── …
Install the Wellspent React Native SDK wrapper from GitHub using one of the following commands:
-
Using Yarn:
yarn add git+https://github.com/nlbb/wellspent-reactnative-sdk.git#main
-
Using npm:
npm install --save git+https://github.com/nlbb/wellspent-reactnative-sdk.git#main
Verify the installation by checking the node_modules
directory:
ls node_modules/react-native-wellspentsdk/ios
You should see at least:
RNWellspentSDK.podspec
RNWellspentSDK.swift
You need to create three iOS extension targets in Xcode before editing the Podfile:
-
Open the project in Xcode (not the workspace, as it doesn’t exist yet):
open ios/WellspentApp.xcodeproj
-
In Xcode’s Project Navigator, select the
WellspentApp
project. -
Go to File → New → Target….
-
Create the following extensions using the specified templates and product names:
- Device Activity Monitor Extension: Name it
DeviceActivityMonitorExtension
- Shield Action Extension: Name it
ShieldActionExtension
- Shield Configuration Extension: Name it
ShieldConfigurationExtension
- Device Activity Monitor Extension: Name it
After adding, your Xcode TARGETS should include:
- WellspentApp
- DeviceActivityMonitorExtension
- ShieldActionExtension
- ShieldConfigurationExtension
All four targets (WellspentApp
, DeviceActivityMonitorExtension
, ShieldActionExtension
, ShieldConfigurationExtension
) must share an App Group and include the Family Controls entitlement.
- In Xcode, select
DeviceActivityMonitorExtension
→ Signing & Capabilities. - Click + Capability → Choose App Groups.
- Add the App Group:
group.com.mycompany.wellspent
- Repeat for
ShieldActionExtension
,ShieldConfigurationExtension
, andWellspentApp
.
Verify that each target’s Signing & Capabilities lists:
- App Groups:
group.com.mycompany.wellspent
Update each extension’s Info.plist
to include:
<key>AppGroup</key>
<string>group.com.mycompany.wellspent</string>
- Select
WellspentApp
→ Signing & Capabilities. - Click + Capability → Add Family Controls.
- Repeat for
DeviceActivityMonitorExtension
,ShieldActionExtension
, andShieldConfigurationExtension
.
Each target’s Signing & Capabilities should now list:
- App Groups:
group.com.mycompany.wellspent
- Family Controls
Note: For App Store distribution, request the “Family Controls (Distribution)” entitlement from Apple. For local development, adding the capability is sufficient.
Replace the boilerplate code in each extension’s Swift file to subclass the Wellspent extension classes:
-
DeviceActivityMonitorExtension.swift:
import Extension_DeviceActivityMonitor class DeviceActivityMonitorExtension: Extension_DeviceActivityMonitor.ActivityMonitorExtension {}
-
ShieldActionExtension.swift:
import Extension_ShieldAction class ShieldActionExtension: Extension_ShieldAction.ShieldActionExtension {}
-
ShieldConfigurationExtension.swift:
import Extension_ShieldConfiguration class ShieldConfigurationExtension: Extension_ShieldConfiguration.ShieldConfigurationExtension {}
No additional code is needed in these files.
In the ios/
folder, create or edit Podfile
with the following content:
require_relative '../node_modules/react-native/scripts/react_native_pods'
platform :ios, '16.0'
prepare_react_native_project!
target 'WellspentApp' do
use_frameworks!
# Install RNWellspentSDK (React Native wrapper)
pod 'RNWellspentSDK',
:path => '../node_modules/react-native-wellspentsdk/ios',
:modular_headers => true
# Install React Native’s own pods
use_react_native!(
:path => '../node_modules/react-native',
:hermes_enabled => false,
:fabric_enabled => false
)
end
# Extension targets will be added later
Troubleshooting:
-
If you encounter:
[!] CocoaPods could not find compatible versions for pod "RNWellspentSDK":
Ensure the
platform :ios, '16.0'
is set in the Podfile. -
If you see:
ArgumentError - [Xcodeproj] Unable to find compatibility version string for object version `70`.
Edit
ios/WellspentApp.xcodeproj/project.pbxproj
and change:- objectVersion = 70; + objectVersion = 60;
Run the following commands:
cd ios
rm -rf Pods Podfile.lock
pod install
This generates WellspentApp.xcworkspace
in the ios/
folder.
Always use the workspace from now on:
open ios/WellspentApp.xcworkspace
The Project Navigator should show:
WellspentApp.xcworkspace
├── Pods/
├── WellspentApp/
├── DeviceActivityMonitorExtension/
├── ShieldActionExtension/
├── ShieldConfigurationExtension/
└── …
Update the Podfile incrementally to include extension targets and header search paths. After each change, run:
cd ios
pod install
To avoid duplicate signature errors during archiving, add a Run Script in Xcode:
- Open
WellspentApp.xcworkspace
. - Select the
WellspentApp
target → Build Phases. - Click + → New Run Script Phase.
- Drag the script below
[CP] Embed Pods Frameworks
and above[CP] Copy Pods Resources
. - Rename the phase to
Strip Duplicate XCFramework Signatures
. - Paste the following script:
# Remove _every_ xcframework .signature file before Archive: if [ "${XCODE_VERSION_MAJOR}" -ge 1500 ]; then echo "🧹 Removing all duplicate xcframework signature files…" find "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${FRAMEWORKS_FOLDER_PATH}" \ -type f -name "*.xcframework-*.signature" \ -print -delete fi
- Ensure “For install builds only” is unchecked.
- In Xcode, go to File → Add Packages….
- Enter the Wellspent iOS SDK URL (requires access):
https://github.com/nlbb/Wellspent-iOS-SDK-Binaries.git
- In the package popup, assign products to targets:
Package Product | Kind | Add to Target |
---|---|---|
Extension_DeviceActivityMonitor | Library | DeviceActivityMonitor |
Extension_ShieldAction | Library | ShieldAction |
Extension_ShieldConfiguration | Library | ShieldConfiguration |
Wellspent | Library | WellspentApp |
WellspentSDK | Library | None |
-
For the Pods project, assign:
Package Product Kind Add to Target Wellspent Library RNWellspentSDK -
Click Add Package.
This resolves any “No such module” errors in extension classes.
Add the following to ios/WellspentApp/Info.plist
:
<key>NSFamilyControlsUsageDescription</key>
<string>Wellspent needs permission to monitor your device usage so it can lock distracting apps.</string>
-
(Optional) Clean DerivedData:
rm -rf ~/Library/Developer/Xcode/DerivedData
-
In Xcode, select the
WellspentApp
scheme and a Generic iOS Device or physical iPhone. -
Build the project (⌘+B). Ensure no errors like “No such module ‘WellspentSDK’.”
-
Run on a device or simulator (⌘+R). On a real iPhone (iOS 16+), you’ll see the Family Controls UI.
-
Archive the project (Product → Archive). The log should show:
🧹 Removing all duplicate xcframework signature files…
The archive should succeed without signature errors.
Replace your App.js
or App.tsx
with the following TypeScript example to interact with the Wellspent SDK:
// File: example/App.tsx
import React, { useEffect, useState } from 'react';
import {
View,
Text,
Button,
StyleSheet,
NativeModules,
NativeEventEmitter,
Alert,
ActivityIndicator,
} from 'react-native';
/** 1) Type definitions for our native module */
export interface WellspentThemeValues {
backgroundPrimary: string; // e.g. "#FFFFFF"
backgroundSecondary: string;
backgroundTertiary: string;
backgroundShield: string;
labelPrimary: string;
labelSecondary: string;
labelTertiary: string;
backgroundButton: string;
backgroundButtonDisabled: string;
buttonLabel: string;
buttonLabelDisabled: string;
fills: string;
}
interface RNWellspentSDKType {
configure(
apiKey: string,
name: string,
bundleId: string,
appGroupIdentifier: string,
activity?: string | null,
successCriteria?: string | null,
theme?: WellspentThemeValues
): Promise<boolean>;
startOnboarding(): Promise<boolean>;
showNudgeSettings(): Promise<boolean>;
completeDailyHabit(): void;
isHabitCompleted(): Promise<boolean>;
emitEvent(name: string, body: any): void;
}
/** 2) Grab the native module from NativeModules */
const { RNWellspentSDK } = NativeModules as {
RNWellspentSDK: RNWellspentSDKType;
};
/** 3) Set up an event emitter for native events */
const eventEmitter = new NativeEventEmitter(RNWellspentSDK);
const App: React.FC = () => {
/** 4) Local state to track configuration status */
const [isConfiguring, setIsConfiguring] = useState(true);
const [configured, setConfigured] = useState(false);
useEffect(() => {
/** 5) Subscribe to native events */
// a) Generic SDK events:
const sdkSub = eventEmitter.addListener(
'WellspentEvent',
(event) => {
console.log('[WellspentSDKEvent]', event.name, event.body);
Alert.alert(`SDK Event: ${event.name}`, JSON.stringify(event.body));
}
);
// b) Screen‐view tracking events:
const screenSub = eventEmitter.addListener(
'WellspentTrackScreen',
(event) => {
console.log('[Track Screen]', event.screenName, event.properties);
// Forward this to your analytics provider if needed
Alert.alert(
'Tracking ‒ Screen View',
`Screen: ${event.screenName}\nProps: ${JSON.stringify(event.properties)}`
);
}
);
// c) Generic event tracking events:
const eventSub = eventEmitter.addListener(
'WellspentTrackEvent',
(event) => {
console.log('[Track Event]', event.eventName, event.properties);
// Forward this to your analytics provider if needed
Alert.alert(
'Tracking ‒ Event',
`Event: ${event.eventName}\nProps: ${JSON.stringify(event.properties)}`
);
}
);
return () => {
sdkSub.remove();
screenSub.remove();
eventSub.remove();
};
}, []);
useEffect(() => {
/** 6) On mount, call configure(...) exactly once */
const runConfigure = async () => {
try {
setIsConfiguring(true);
const ok = await RNWellspentSDK.configure(
// ← your actual API key & params go here
'ios-ws-sdk_sandbox_aa7d600559446aa936b063d81e8a9de48a33c65b3e2705f066d7c87d65b6a7dc',
'Kilimanjaro',
'co.mindamins.WellspentB2BRN',
'group.co.wellspent',
'learning',
'math lesson',
{
backgroundPrimary: '#FFFFFF',
backgroundSecondary: '#F2F2F2',
backgroundTertiary: '#E5E5E5',
backgroundShield: '#000000',
labelPrimary: '#111111',
labelSecondary: '#333333',
labelTertiary: '#666666',
backgroundButton: '#007AFF',
backgroundButtonDisabled: '#CCCCCC',
buttonLabel: '#FFFFFF',
buttonLabelDisabled: '#888888',
fills: '#00C853',
}
);
if (!ok) {
Alert.alert(
'Configuration Failed',
'Wellspent SDK could not initialize. Please verify API key, bundle ID, and app group.'
);
setConfigured(false);
} else {
setConfigured(true);
}
} catch (error) {
console.error('WellspentSDK.configure error', error);
Alert.alert('Error', 'Failed to initialize Wellspent SDK.');
setConfigured(false);
} finally {
setIsConfiguring(false);
}
};
runConfigure();
// Note: no dependencies → runs once per app launch
}, []);
/** 7) Handler for “Start Onboarding” (only calls if configured) */
const handleStartWellspent = async () => {
if (!configured) {
Alert.alert('Not Ready', 'Still configuring, please wait a moment.');
return;
}
try {
const completed = await RNWellspentSDK.startOnboarding();
if (completed) {
Alert.alert('Onboarding Complete', 'User has finished the onboarding flow.');
} else {
Alert.alert('Onboarding Canceled', 'User dismissed the onboarding flow.');
}
} catch (error) {
console.error('WellspentSDK.startOnboarding error', error);
Alert.alert('Error', 'Failed to launch onboarding.');
}
};
/** 8) Other button handlers remain unchanged */
const handleShowNudgeSettings = async () => {
if (!configured) {
Alert.alert('Not Ready', 'Still configuring, please wait a moment.');
return;
}
try {
await RNWellspentSDK.showNudgeSettings();
Alert.alert('Nudge Settings', 'User has closed the nudge settings.');
} catch (error) {
console.error('WellspentSDK.showNudgeSettings error', error);
Alert.alert('Error', 'Failed to show nudge settings.');
}
};
const handleCompleteHabit = () => {
if (!configured) {
Alert.alert('Not Ready', 'Still configuring, please wait a moment.');
return;
}
RNWellspentSDK.completeDailyHabit();
Alert.alert('Habit Completed', 'Marked today’s habit as completed.');
};
const handleCheckHabit = async () => {
if (!configured) {
Alert.alert('Not Ready', 'Still configuring, please wait a moment.');
return;
}
try {
const done = await RNWellspentSDK.isHabitCompleted();
Alert.alert('Habit Status', done ? 'Already completed today.' : 'Not completed yet.');
} catch (error) {
console.error('WellspentSDK.isHabitCompleted error', error);
Alert.alert('Error', 'Failed to check habit status.');
}
};
/** 9) While configuring is in progress, show an ActivityIndicator */
if (isConfiguring) {
return (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#007AFF" />
<Text style={styles.loadingText}>Configuring Wellspent SDK…</Text>
</View>
);
}
/** 10) Once configuration has finished, show the normal UI */
return (
<View style={styles.container}>
<Text style={styles.title}>Wellspent React Native Demo</Text>
<View style={styles.buttonContainer}>
<Button
title="Start Onboarding"
onPress={handleStartWellspent}
disabled={!configured}
/>
</View>
<View style={styles.buttonContainer}>
<Button
title="Show Nudge Settings"
onPress={handleShowNudgeSettings}
disabled={!configured}
/>
</View>
<View style={styles.buttonContainer}>
<Button
title="Complete Daily Habit"
onPress={handleCompleteHabit}
disabled={!configured}
/>
</View>
<View style={styles.buttonContainer}>
<Button
title="Check Habit Status"
onPress={handleCheckHabit}
disabled={!configured}
/>
</View>
</View>
);
};
export default App;
/** 11) Styles */
const styles = StyleSheet.create({
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 24,
backgroundColor: '#F5F5F5',
},
loadingText: {
marginTop: 12,
fontSize: 16,
color: '#333',
},
container: {
flex: 1,
justifyContent: 'center',
paddingHorizontal: 24,
backgroundColor: '#F5F5F5',
},
title: {
fontSize: 22,
fontWeight: '600',
textAlign: 'center',
marginBottom: 24,
},
buttonContainer: {
marginVertical: 8,
},
});
This example demonstrates how to configure the SDK, start onboarding, manage nudge settings, track habits, and emit custom events.