rive-react-native-sdk
v1.1.3
Published
Behavioral event tracking SDK for React Native with offline support and intelligent batching
Downloads
1,066
Maintainers
Readme
Rive SDK - React Native/Expo
Behavioral event tracking SDK for React Native and Expo applications
Table of Contents
What is Rive SDK?
Rive SDK is a behavioral event tracking library for React Native and Expo applications.
Features
- ✅ Offline Support: Events are queued when offline and automatically sent when connection is restored
- ✅ Intelligent Batching: Events are automatically batched for optimal performance
- ✅ Automatic Retry: Failed requests are automatically retried with exponential backoff
- ✅ Background Flush: Events are automatically flushed when app goes to background
- ✅ User Identification: Manages both anonymous and external user IDs
- ✅ Push Notification Support: FCM token management
- ✅ Zero Configuration: Backend URL is hardcoded, only API key is required
- ✅ Pending Events Queue: Events tracked before SDK initialization are automatically processed
Use Cases
- Track user behavior and interactions in your app
- Monitor which screens are viewed most
- Track button clicks, form submissions, and user actions
- Segment users based on their behavior
- Send targeted push notifications
Installation
1. Install the Package
npm install rive-react-native-sdkor
yarn add rive-react-native-sdk2. Install Required Dependencies
AsyncStorage is required for the SDK to function:
npm install @react-native-async-storage/async-storage3. (Optional) Expo Modules
If you're using Expo, install these for device info and network monitoring:
npx expo install expo-device expo-application expo-localization expo-network4. (Optional) Native Modules
For better performance with native modules (Development Build or Bare React Native):
npm install react-native-device-info @react-native-community/netinfoQuick Start
Step 1: Initialize the SDK
Initialize the SDK at the start of your app (typically in App.tsx or _layout.tsx):
import { useEffect } from 'react';
import Rive from 'rive-react-native-sdk';
export default function App() {
useEffect(() => {
initializeSDK();
}, []);
const initializeSDK = async () => {
try {
// Get your API key from Nock dashboard
await Rive.initialize('nock_live_xxxxxxxxxxxxxxxxxxxxx', {
// Basic configuration
batchSize: 10, // Number of events per batch (default: 10)
batchTimeout: 30000, // Auto-send after 30 seconds (default: 30000ms)
});
console.log('✅ SDK initialized successfully');
} catch (error) {
console.error('❌ SDK initialization error:', error);
}
};
return (
// Your app content
);
}Important Notes:
- The
baseUrlis hardcoded in the SDK (https://api.nockspace.com), no need to pass it - Get your API key from the Nock Dashboard
Step 2: Identify Users
When a user logs in or user information is available:
import Rive from 'rive-react-native-sdk';
// When user logs in
const handleLogin = async (userId: string, userEmail: string) => {
try {
await Rive.identify(userId, {
email: userEmail,
name: 'User Name',
user_type: 'premium',
// Add any custom properties you want
});
console.log('✅ User identified');
} catch (error) {
console.error('❌ Identify error:', error);
}
};When to Identify:
- When user logs in
- When user information is updated
- On app start (if user is already logged in)
Step 3: Track Events
Track important actions in your app:
import Rive from 'rive-react-native-sdk';
import { useFocusEffect } from '@react-navigation/native';
import { useCallback } from 'react';
// Screen view tracking (use useFocusEffect for tabs)
useFocusEffect(
useCallback(() => {
Rive.track('screen_viewed', {
screen_name: 'Home',
screen_category: 'main',
}).catch(console.error);
}, [])
);
// Button click
const handleButtonClick = async () => {
try {
await Rive.track('button_clicked', {
button_id: 'subscribe_button',
button_text: 'Subscribe',
screen: 'Home',
});
// Button action
// ...
} catch (error) {
console.error('Track error:', error);
}
};
// Form submission
const handleFormSubmit = async (formData: any) => {
try {
await Rive.track('form_submitted', {
form_name: 'contact_form',
form_fields: Object.keys(formData).length,
success: true,
});
} catch (error) {
console.error('Track error:', error);
}
};Event Naming Best Practices:
- Use
snake_case:screen_viewed,button_clicked - Use descriptive names:
user_subscribed✅,event1❌ - Be consistent:
screen_viewedfor all screens,button_clickedfor all buttons
Advanced Features
1. Custom Event Properties
Add detailed information to events:
await Rive.track('purchase_completed', {
product_id: 'prod_123',
product_name: 'Premium Subscription',
price: 99.99,
currency: 'USD',
payment_method: 'credit_card',
discount_code: 'SUMMER2024',
});2. Manual Flush
Send events immediately when needed:
await Rive.flush();When to Use:
- Before user exits the app for important events
- After critical actions (e.g., payment completed)
3. Reset User
When user logs out:
await Rive.reset();This will:
- Clear external user ID
- Clear user properties
- Generate a new anonymous ID
- Clear event queue
4. FCM Token Management
Register FCM token for push notifications:
import * as Notifications from 'expo-notifications';
// Get FCM token
const token = await Notifications.getExpoPushTokenAsync();
// Register with SDK
await Rive.setFcmToken(token.data);Best Practices
1. Initialize Early
Initialize the SDK as early as possible:
// ✅ GOOD: At app start
export default function App() {
useEffect(() => {
Rive.initialize('api_key');
}, []);
}
// ❌ BAD: After user action
const handleButtonClick = () => {
Rive.initialize('api_key'); // Too late!
};2. Error Handling
Always use try-catch:
// ✅ GOOD
try {
await Rive.track('event_name', {});
} catch (error) {
console.error('Track failed:', error);
// Don't break app flow, just log it
}
// ❌ BAD
await Rive.track('event_name', {}); // App may crash on error3. Async/Await vs Promise
// ✅ GOOD: Async/await (more readable)
const handleAction = async () => {
try {
await Rive.track('action_completed');
} catch (error) {
console.error(error);
}
};
// ✅ GOOD: Promise catch (for quick actions)
Rive.track('quick_action').catch(console.error);
// ❌ BAD: Unhandled promise
Rive.track('action'); // Fails silently on error4. Event Naming Convention
Use a consistent naming convention:
// ✅ GOOD: Consistent and descriptive
Rive.track('screen_viewed', { screen_name: 'Home' });
Rive.track('screen_viewed', { screen_name: 'Profile' });
Rive.track('button_clicked', { button_id: 'subscribe' });
Rive.track('button_clicked', { button_id: 'cancel' });
// ❌ BAD: Inconsistent
Rive.track('home_viewed');
Rive.track('profile_screen');
Rive.track('click_subscribe');
Rive.track('cancelButton');5. Tab Navigation Events
Use useFocusEffect for tab screens to avoid tracking events for unmounted tabs:
import { useFocusEffect } from '@react-navigation/native';
// ✅ GOOD: Only tracks when tab is actually focused
useFocusEffect(
useCallback(() => {
Rive.track('screen_viewed', {
screen_name: 'Home',
}).catch(console.error);
}, [])
);
// ❌ BAD: Tracks even if tab is not visible
useEffect(() => {
Rive.track('screen_viewed', {
screen_name: 'Home',
}).catch(console.error);
}, []);6. Critical Events
Use flush for critical events:
// Payment completed - send immediately
await Rive.track('purchase_completed', {
order_id: 'order_123',
amount: 99.99,
});
await Rive.flush(); // Send immediately
// Screen view - send in batch (normal)
Rive.track('screen_viewed', { screen_name: 'Home' });Configuration Options
All Configuration Parameters
await Rive.initialize('api_key', {
// Debugging
debugMode: false, // Enable detailed logging (default: false)
// Batch Settings
batchSize: 10, // Number of events per batch (default: 10)
batchTimeout: 30000, // Auto-send timeout in ms (default: 30000)
// Network Settings
requestTimeout: 10000, // API request timeout in ms (default: 10000)
// Retry Settings
maxRetries: 3, // Maximum retry attempts (default: 3)
retryDelay: 1000, // Retry delay in ms (default: 1000)
// Storage Settings
persistEvents: true, // Save events to storage (default: true)
maxQueueSize: 1000, // Maximum queue size (default: 1000)
eventRetentionDays: 7, // Days to keep failed events (default: 7)
// Automatic Operations
autoFlushOnBackground: true, // Auto-flush on background (default: true)
});Recommended Configurations
Development:
{
debugMode: true, // Enable detailed logging
batchSize: 5, // Smaller batches (for testing)
batchTimeout: 10000, // Faster sending (for testing)
}Production:
{
debugMode: false, // Only WARN and ERROR logs (default)
batchSize: 10, // Standard batch size
batchTimeout: 30000, // Wait 30 seconds
autoFlushOnBackground: true, // Send when backgrounded
}High Volume (Many events):
{
batchSize: 20, // Larger batches
batchTimeout: 60000, // Wait 1 minute
maxQueueSize: 5000, // Larger queue
}Troubleshooting
Problem 1: SDK Not Initializing
Symptoms:
SDK not initializederror- Events are not being tracked
Solution:
// Wait for initialization to complete
useEffect(() => {
const init = async () => {
await Rive.initialize('api_key');
console.log('SDK initialized');
};
init();
}, []);
// Check before tracking
if (Rive.isReady()) {
await Rive.track('event');
}Note: Events tracked before initialization are automatically queued and processed after initialization.
Problem 2: Events Not Sending
Checklist:
- ✅ Is SDK initialized?
- ✅ Is API key correct?
- ✅ Is there internet connection?
Debug:
// Enable debug mode to see detailed logs
await Rive.initialize('api_key', {
debugMode: true, // Shows DEBUG, INFO, WARN, and ERROR logs
});
// With debugMode: false (default), only WARN and ERROR are visible:
// [Rive SDK] [WARN] Network offline, events will be stored
// [Rive SDK] [ERROR] Failed to send batch
// With debugMode: true, you'll see everything:
// [Rive SDK] [DEBUG] Config: {...}
// [Rive SDK] [INFO] Making HTTP request: POST /api/sdk/track
// [Rive SDK] [INFO] Event tracked: button_clicked
// [Rive SDK] [WARN] Retry attempt 1...
// [Rive SDK] [ERROR] Failed after 3 retriesProblem 3: Offline Events Not Sending
Normal Behavior:
- Offline events are saved to AsyncStorage
- Automatically sent when internet connection is restored
- Sent when app is reopened
Manual Check:
// Manually flush
await Rive.flush();
// Or on app start
useEffect(() => {
Rive.initialize('api_key').then(() => {
Rive.flush(); // Send pending events
});
}, []);Problem 4: Too Many Events Accumulating
Solution:
// Increase batch size
await Rive.initialize('api_key', {
batchSize: 20, // Larger batches
batchTimeout: 15000, // Send more frequently
});Problem 5: TypeScript Errors
Solution:
// Use type assertion
await Rive.track('event_name', {
custom_prop: value,
} as any); // Temporary solution
// Or proper typing
interface MyEventProperties {
custom_prop: string;
}
await Rive.track('event_name', {
custom_prop: 'value',
} as MyEventProperties);API Reference
Rive.initialize()
Initializes the SDK.
await Rive.initialize(apiKey: string, config?: RiveConfig): Promise<void>Parameters:
apiKey(string, required): Your API key from Nock dashboardconfig(object, optional): Configuration object
Example:
await Rive.initialize('nock_live_xxx', {
batchSize: 10,
});Rive.identify()
Identifies a user.
await Rive.identify(externalUserId: string, properties?: object): Promise<void>Parameters:
externalUserId(string, required): User's unique IDproperties(object, optional): User properties
Example:
await Rive.identify('user_123', {
email: '[email protected]',
name: 'John Doe',
});Note: If user properties are updated, call identify again with the new properties.
Rive.track()
Tracks an event.
await Rive.track(eventName: string, properties?: object): Promise<void>Parameters:
eventName(string, required): Event nameproperties(object, optional): Event properties
Example:
await Rive.track('button_clicked', {
button_id: 'subscribe',
screen: 'Home',
});Note: Can be called before SDK initialization. Events will be queued and processed after initialization.
Rive.flush()
Immediately sends all pending events.
await Rive.flush(): Promise<void>Example:
await Rive.track('purchase_completed');
await Rive.flush(); // Send immediatelyRive.reset()
Clears user information and queue.
await Rive.reset(): Promise<void>Example:
// When user logs out
await Rive.reset();Rive.setFcmToken()
Registers FCM token (for push notifications).
await Rive.setFcmToken(token: string): Promise<void>Example:
const token = await Notifications.getExpoPushTokenAsync();
await Rive.setFcmToken(token.data);Rive.isReady()
Checks if SDK is initialized.
const isReady: boolean = Rive.isReady();Example:
if (Rive.isReady()) {
await Rive.track('event');
}Rive.getQueueSize()
Gets current queue size.
const queueSize: number = Rive.getQueueSize();Rive.getFailedBatchCount()
Gets number of failed batches waiting for retry.
const failedCount: number = await Rive.getFailedBatchCount();Example Scenarios
Scenario 1: E-Commerce App
// 1. On app start
useEffect(() => {
Rive.initialize('api_key');
}, []);
// 2. When user logs in
const handleLogin = async (user: User) => {
await Rive.identify(user.id, {
email: user.email,
name: user.name,
membership_type: user.membershipType,
});
};
// 3. Product view
const handleProductView = (product: Product) => {
Rive.track('product_viewed', {
product_id: product.id,
product_name: product.name,
product_category: product.category,
price: product.price,
});
};
// 4. Add to cart
const handleAddToCart = (product: Product) => {
Rive.track('add_to_cart', {
product_id: product.id,
product_name: product.name,
price: product.price,
quantity: 1,
});
};
// 5. Purchase completed
const handlePurchaseComplete = async (order: Order) => {
await Rive.track('purchase_completed', {
order_id: order.id,
total_amount: order.total,
items_count: order.items.length,
payment_method: order.paymentMethod,
});
await Rive.flush(); // Critical event, send immediately
};Scenario 2: Social Media App
// 1. Post view
const handlePostView = (post: Post) => {
Rive.track('post_viewed', {
post_id: post.id,
author_id: post.authorId,
post_type: post.type,
});
};
// 2. Like/Unlike
const handleLike = (post: Post) => {
Rive.track('post_liked', {
post_id: post.id,
author_id: post.authorId,
});
};
// 3. Comment
const handleComment = (post: Post, comment: string) => {
Rive.track('comment_added', {
post_id: post.id,
comment_length: comment.length,
});
};
// 4. Profile view
const handleProfileView = (userId: string) => {
Rive.track('profile_viewed', {
profile_user_id: userId,
});
};Scenario 3: Game App
// 1. Level start
const handleLevelStart = (level: number) => {
Rive.track('level_started', {
level_number: level,
difficulty: 'normal',
});
};
// 2. Level completion
const handleLevelComplete = async (level: number, score: number) => {
await Rive.track('level_completed', {
level_number: level,
score: score,
time_spent: getTimeSpent(),
});
await Rive.flush(); // Important event
};
// 3. In-app purchase
const handleInAppPurchase = async (item: Item) => {
await Rive.track('in_app_purchase', {
item_id: item.id,
item_name: item.name,
price: item.price,
currency: 'USD',
});
await Rive.flush();
};Security
API Key Security
- ✅ Never commit API keys to public repositories
- ✅ Use environment variables or secure storage
- ✅ Use different API keys for different projects
Example:
// ✅ GOOD: Environment variable
const API_KEY = process.env.EXPO_PUBLIC_RIVE_API_KEY || '';
// ❌ BAD: Hardcoded
const API_KEY = 'nock_live_xxxxxxxxxxxxx';Support
For questions and support:
- 📧 Email: [email protected]
- 📚 Documentation: https://docs.nockspace.com
- 💬 Discord: Nock Community
License
MIT
Last Updated: 2025-01-29 SDK Version: 1.1.1
