React Native SDK Integration
This guide provides a comprehensive implementation of FirstHive analytics tracking for React Native applications using the @logicwind/react-native-matomo-tracker library. The implementation follows FirstHive’s analytics standards with predefined event categories and custom dimensions.
Prerequisites
- React Native development environment
- @logicwind/react-native-matomo-tracker package installed. https://github.com/logicwind/react-native-matomo-tracker
- Access to FirstHive analytics dashboard
- Custom dimensions configured in FH dashboard (dimensions 1-10)
Installation
npm install git+https://gitlab.com/fh-sdk-toolkit/fhapp-toolkit-react-native.git # or yarn add git+https://gitlab.com/fh-sdk-toolkit/fhapp-toolkit-react-native.git
Configuration
Required Parameters
- FH Analytics URL: Provided by FirstHive (e.g., https://analytics.firsthive.com/piwik.php )
- Site ID: Provided by FirstHive (e.g., ‘417’)
- Auth Token: Optional, for enhanced tracking features
Custom Dimensions Schema
Reserved Dimensions (Visit-Level: 1-5) - Optional User Context
Dimension ID | Purpose | Example Value | Notes |
---|---|---|---|
1 | User Email / User ID | user@example.com | Any important user identifier |
2 | Mobile Number / Secondary ID | +1234567890 | Additional user context |
3 | User Name / Display Name | John Doe | User identification |
4 | User Role / Category | Premium User | User classification |
5 | Device ID / Session Info | device123456 | Device or session context |
Important Notes:
- These dimensions are optional and should only be set when user information is available
- If the current page/screen has no user context, it’s perfectly fine to skip these dimensions
- Use these dimensions for any important user-related information relevant to your app
- Not mandatory for every tracking call - only set when meaningful user data exists
Dynamic Dimensions (Action-Level: 6-10)
Dimension ID | Purpose | Example Value |
---|---|---|
6 | Product SKU / Order ID / Button Name | SKU12345 |
7 | Product Name / Total Amount / Video Title | iPhone 15 Pro |
8 | Category / Payment Method / Duration | Electronics |
9 | Price / Payment Gateway / Error Message | 999.99 |
10 | Quantity / Discount | 1 |
Implementation Guide
1. Basic Setup
import { createTracker, trackEvent, trackScreen, trackCustomDimension, setLogger, startSession, enableTracking, disableTracking, } from ‘@logicwind/react-native-matomo-tracker’;
// Initialize tracker const initializeTracker = async () => { try { await createTracker( ‘<https://analytics.firsthive.com/piwik.php>’ // Your url, 417, // Your site ID ‘optional_auth_token’ ); await setLogger(); // Enable debugging console.log(‘FirstHive Tracker initialized’); } catch (error) { console.error(‘Tracker initialization failed:’, error); } };
Event Categories & Implementation
Note: If event as a value , you can pass that value (here default is 1)
1. Login Operations Events
Login Event
const trackLogin = async () => { await trackEvent(‘LoginOps’, ‘Login’, ‘Mobile login’, 1,[ { key: “2”, value: “9876543210”}, { key: “6”, value: “9876543210” }, ]); };
Sign Up Event
const trackSignUp = async () => { await trackEvent(‘LoginOps’, ‘SignUp’, ‘Registration Complete’, 1,[ { key: “2”, value: “9876543210”}, { key: “6”, value: “9876543210” } ]); };
Logout Event
const trackLogout = async () => { await trackEvent(‘LoginOps’, ‘Logoff’, ‘User Logout’, 1,[ { key: “2”, value: “9876543210” }, { key: “6”, value: “9876543210”} ]); };
2. Product & Commerce Events
Product Viewed
const trackProductViewed = async () => { await trackEvent(‘Viewed’, ‘ProductViewed’, ‘Product Detail Page’, 1, [ { key: “6”, value: “SKU12345” }, // Product SKU { key: “7”, value: “iPhone 15 Pro” }, // Product Name { key: “8”, value: “Electronics” }, // Product Category { key: “9”, value: “999.99” }, // Price { key: “10”, value: “1” }, // Quantity ]); };
Category Viewed
const trackCategoryViewed = async () => { await trackEvent(‘Viewed’, ‘CategoryViewed’, ‘Category Page’, 1, [ { key: “6”, value: “ELEC001” }, // Category SKU { key: “7”, value: “Smartphones” }, // Category Name { key: “8”, value: “Electronics” }, // Main Category { key: “9”, value: “500-2000” }, // Price Range ]); };
Add to Cart
const trackAddToCart = async () => { await trackEvent(‘CartOpt’, ‘AddedtoCart’, ‘Product added to cart’, 1, [ { key: “6”, value: “ORDER123” }, // Order ID { key: “7”, value: “1050.99” }, // Grand Total { key: “8”, value: “50.00” }, // Tax { key: “9”, value: “15.00” }, // Shipping { key: “10”, value: “5.00” }, // Discount ]); };
Product Purchased
const trackProductPurchased = async () => { await trackEvent(‘Purchased’, ‘ProductPurchased’, ‘Checkout complete’, 1, [ { key: “6”, value: “SKU12345” }, // Product SKU { key: “7”, value: “iPhone 15 Pro” }, // Product Name { key: “8”, value: “Electronics” }, // Product Category { key: “9”, value: “999.99” }, // Price { key: “10”, value: “1” }, // Quantity ]); };
3. Payment Events
Payment Success
const trackPaymentSuccess = async () => { await trackEvent(‘Payment’, ‘PaymentSuccess’, ‘Order payment complete’, 1, [ { key: “6”, value: “999.99” }, // Payment Amount { key: “7”, value: “Stripe” }, // Payment Gateway { key: “8”, value: “Credit Card” }, // Payment Method { key: “9”, value: “Online” }, // Payment Mode ]); };
Payment Failure
const trackPaymentFailure = async () => { await trackEvent(‘Payment’, ‘PaymentFailure’, ‘Payment Declined’, 1, [ { key: “6”, value: “999.99” }, // Payment Amount { key: “7”, value: “Stripe” }, // Payment Gateway { key: “8”, value: “Credit Card” }, // Payment Method { key: “9”, value: “Insufficient Funds” }, // Payment Error ]); };
4. User Interaction Events
Button Click
const trackButtonClick = async () => { await trackEvent(‘OnClick’, ‘ButtonClick’, ‘cta button clicked’, 1, [ { key: “6”, value: “Buy Now Button” }, // Button Name { key: “7”, value: “John Doe” }, // Who Clicked ]); };
Link Click
const trackLinkClick = async () => { await trackEvent(‘OnClick’, ‘LinkClick’, ‘external link clicked’, 1, [ { key: “6”, value: “Privacy Policy” }, // Link Name { key: “7”, value: “John Doe” }, // Who Clicked ]); };
5. Video Events
Video Play
const trackVideoPlay = async () => { await trackEvent(‘Videos’, ‘Play’, ‘video started’, 1, [ { key: “6”, value: “iPhone 15 Demo” }, // Video Title { key: “7”, value: “<https://example.com/video1>” }, // Video Link ]); };
Video Duration
const trackVideoDuration = async () => {
await trackEvent(‘Videos’, ‘Duration’, ‘video watch time’, 1, [
{ key: “6”, value: “iPhone 15 Demo” }, // Video Title
{ key: “7”, value: “<https://example.com/video1>” }, // Video Link
{ key: “8”, value: “120” },
]);
};
6. Search Events
const trackSearch = async () => { await trackEvent(‘Search’, ‘SearchKeyword’, ‘product search’, 1, [ { key: “6”, value: “iPhone 15” }, // Search Keyword ]); };
7. Screen Tracking
const trackScreenView = async () => { await trackScreen(“ProductDetailScreen”, “Product detail page viewed”); };
Session Management
Start New Session
const startNewSession = async () => { await startSession(); // if you want to manually override session dimensions await trackCustomDimension({ dimensions: [ { key: “1”, value: userEmail }, // Email { key: “2”, value: userMobile }, // Mobile No { key: “3”, value: userName }, // User Name { key: “4”, value: userName }, // Name (duplicate for compatibility) { key: “5”, value: deviceId }, // DeviceID ] }); };
Enable/Disable Tracking
// Disable tracking (GDPR compliance) await disableTracking(); // Re-enable tracking await enableTracking();
Event Categories Reference
Category | Actions | Purpose |
---|---|---|
LoginOps | Login, SignUp, Logoff | User authentication flows |
Viewed | ProductViewed, CategoryViewed | Content consumption |
CartOpt | AddedtoCart | Shopping cart operations |
Purchased | ProductPurchased | Purchase completion |
Payment | PaymentSuccess, PaymentFailure | Payment processing |
OnClick | ButtonClick, LinkClick | User interactions |
Videos | Play, Duration | Video engagement |
Search | SearchKeyword | Search behavior |
Best Practices
1. Error Handling
Always wrap tracking calls in try-catch blocks:
const trackEventSafely = async (category, action, name, value, dimensions) => { try { await trackEvent(category, action, name, value, dimensions); } catch (error) { console.error(‘Tracking error:’, error); // Don’t let tracking errors break app functionality } };
2. Data Validation
Ensure all dimension values are strings:
const sanitizeDimensions = (dimensions) => { return dimensions.map(dim => ({ key: dim.key, value: String(dim.value || ”) })); };
3. Privacy Compliance
// Check user consent before initializing const initializeWithConsent = async (hasConsent) => { await initializeTracker();
if (!hasConsent) { await disableTracking(); } };
4. Session Management
Set user dimensions after every login and session start, but only when user data is available:
const handleUserLogin = async (userData) => { await trackLogin();
// Set user dimensions only after successful login when user data exists if (userData && userData.email) { await setUserDimensions(userData); }
await startNewSession(); };
// For anonymous sessions (no user logged in) const handleAnonymousSession = async () => { await startNewSession(); // Skip user dimensions - not mandatory for anonymous usage };
Debugging
Enable Logging
await setLogger(); // Enables console logging for debugging
Verify Dimension Data
- Visit dimensions (1-5): Appear in visitor profiles
- Action dimensions (6-10): Appear in action reports and custom dimension reports
- Check Visitors → Custom Dimensions in dashboard for action-level data
Common Issues
- Dimensions not showing: Verify they’re configured in FH dashboard
- Wrong scope: Ensure visit vs action scope matches usage
- Data delay: Allow 5-10 minutes for data processing
- Network issues: Check if events reach server
Testing Checklist
- [ ] Tracker initializes successfully
- [ ] User dimensions set after login
- [ ] All event categories tracked
- [ ] Screen views recorded
- [ ] Custom dimensions appear in reports
- [ ] Session management works
- [ ] Error handling prevents crashes
Sample Usage in Component
import React, { useEffect } from ‘react’; import { trackEvent, trackScreen } from ’./matomoTracker’;
const ProductScreen = ({ product, user }) => { useEffect(() => { // Track screen view trackScreen(‘ProductScreen’, ‘Product detail page’);
// Track product viewedtrackProductViewed(product);
}, [product]);
const handleAddToCart = async () => { // Your add to cart logic await addToCart(product);
// Track the eventawait trackAddToCart(orderData);
};
return ( // Your component JSX ); };
Sample Code
import React, { useState, useEffect } from ‘react’; import { View, Text, TouchableOpacity, StyleSheet, ScrollView, TextInput, Alert, SafeAreaView, StatusBar, } from ‘react-native’;
import { createTracker, setUserId, setVisitorId, trackDispatch, trackDownload, trackEvent, trackImpression, trackInteraction, trackScreen, trackSearch, disableTracking, enableTracking, startSession, trackMediaEvent, trackCampaign, trackCustomDimension, trackGoal, trackOutlink, setLogger, MediaType, } from ‘@logicwind/react-native-matomo-tracker’;
const FirstHiveMatomoTestApp = () => { const [isTrackerInitialized, setIsTrackerInitialized] = useState(false); const [isTrackingEnabled, setIsTrackingEnabled] = useState(true); const [matomoUrl, setMatomoUrl] = useState(‘<https://analytics.firsthive.com/piwik.php>’); const [siteId, setSiteId] = useState(‘417’); const [authToken, setAuthToken] = useState(”); const [userId, setUserIdState] = useState(‘test@example.com’); const [visitorId, setVisitorIdState] = useState(‘2c534f55fba6cf6e’); const [logMessages, setLogMessages] = useState<string[]>([]);
// FirstHive specific user data const [userEmail, setUserEmail] = useState(‘user@example.com’); const [userMobile, setUserMobile] = useState(‘+1234567890’); const [userName, setUserName] = useState(‘John Doe’); const [deviceId, setDeviceId] = useState(‘device123456’);
const addLog = (message: string) => { const timestamp = new Date().toLocaleTimeString(); setLogMessages(prev => [`[${timestamp}] ${message}`, …prev.slice(0, 19)]); };
const clearLogs = () => { setLogMessages([]); };
const getSiteIdAsNumber = (): number => { const numericSiteId = parseInt(siteId, 10); if (isNaN(numericSiteId)) { throw new Error(‘Site ID must be a valid number’); } return numericSiteId; };
// Set reserved dimensions (session-level) const setReservedDimensions = async () => { try { await trackCustomDimension({ dimensions: [ { key: “1”, value: userEmail }, // Email { key: “2”, value: userMobile }, // Mobile No { key: “3”, value: userName }, // User Name { key: “4”, value: userName }, // Name (duplicate for compatibility) { key: “5”, value: deviceId }, // DeviceID ] }); addLog(‘Reserved dimensions set (Email, Mobile, Name, DeviceID)’); } catch (error) { addLog(`Set reserved dimensions error: ${error}`); } };
// Initialize Tracker with FirstHive defaults const initializeTracker = async () => { try { if (!matomoUrl || !siteId) { Alert.alert(‘Error’, ‘Please provide Matomo URL and Site ID’); return; }
const numericSiteId = getSiteIdAsNumber();
if (authToken) { await createTracker(matomoUrl, numericSiteId, authToken); } else { await createTracker(matomoUrl, numericSiteId); }
setIsTrackerInitialized(true); addLog('FirstHive Tracker initialized successfully');
// Set reserved dimensions immediately after initialization await setReservedDimensions();
// Enable logging for debugging await setLogger(); addLog('Logger enabled');} catch (error) { addLog(\`Tracker initialization failed: ${error}\`); Alert.alert('Error', 'Failed to initialize tracker');}
};
// FirstHive Standard Events
// Login Operations Events const testLoginEvent = async () => { try { await trackEvent(‘LoginOps’, ‘Login’, ‘mobile_login’, 1000); await setReservedDimensions(); // Set user PII addLog(‘Login event tracked with user dimensions’); } catch (error) { addLog(`Login event error: ${error}`); } };
const testSignUpEvent = async () => { try { await trackEvent(‘LoginOps’, ‘SignUp’, ‘registration_complete’, 1000); await setReservedDimensions(); addLog(‘SignUp event tracked’); } catch (error) { addLog(`SignUp event error: ${error}`); } };
const testLogoffEvent = async () => { try { await trackEvent(‘LoginOps’, ‘Logoff’, ‘user_logout’, 1000); await setReservedDimensions(); addLog(‘Logoff event tracked’); } catch (error) { addLog(`Logoff event error: ${error}`); } };
// Product Events const testProductViewedEvent = async () => { try { await trackEvent(‘Viewed’, ‘ProductViewed’, ‘product_detail_page’, 1000, [ { key: “6”, value: “SKU12345” }, // Product SKU { key: “7”, value: “iPhone 15 Pro” }, // Product Name { key: “8”, value: “Electronics” }, // Product Category { key: “9”, value: “999.99” }, // Price { key: “10”, value: “1” }, // Quantity ]); addLog(‘Product Viewed event tracked with product dimensions’); } catch (error) { addLog(`Product Viewed error: ${error}`); } };
const testCategoryViewedEvent = async () => { try { await trackEvent(‘Viewed’, ‘CategoryViewed’, ‘electronics_category’, 1000, [ { key: “6”, value: “ELEC001” }, // Category SKU { key: “7”, value: “Smartphones” }, // Category Name { key: “8”, value: “Electronics” }, // Main Category { key: “9”, value: “500-2000” }, // Price Range ]); addLog(‘Category Viewed event tracked’); } catch (error) { addLog(`Category Viewed error: ${error}`); } };
const testAddToCartEvent = async () => { try { await trackEvent(‘CartOpt’, ‘AddedtoCart’, ‘product_added_to_cart’, 1000, [ { key: “6”, value: “ORDER123” }, // Order ID { key: “7”, value: “1050.99” }, // Grand Total { key: “8”, value: “50.00” }, // Tax { key: “9”, value: “15.00” }, // Shipping { key: “10”, value: “5.00” }, // Discount ]); addLog(‘Add to Cart event tracked’); } catch (error) { addLog(`Add to Cart error: ${error}`); } };
const testProductPurchasedEvent = async () => { try { await trackEvent(‘Purchased’, ‘ProductPurchased’, ‘checkout_complete’, 1000, [ { key: “6”, value: “SKU12345” }, // Product SKU { key: “7”, value: “iPhone 15 Pro” }, // Product Name { key: “8”, value: “Electronics” }, // Product Category { key: “9”, value: “999.99” }, // Price { key: “10”, value: “1” }, // Quantity ]); addLog(‘Product Purchased event tracked’); } catch (error) { addLog(`Product Purchased error: ${error}`); } };
// Payment Events const testPaymentSuccessEvent = async () => { try { await trackEvent(‘Payment’, ‘PaymentSuccess’, ‘order_payment_complete’, 1000, [ { key: “6”, value: “999.99” }, // Payment Amount { key: “7”, value: “Stripe” }, // Payment Gateway { key: “8”, value: “Credit Card” }, // Payment Method { key: “9”, value: “Online” }, // Payment Mode ]); addLog(‘Payment Success event tracked’); } catch (error) { addLog(`Payment Success error: ${error}`); } };
const testPaymentFailureEvent = async () => { try { await trackEvent(‘Payment’, ‘PaymentFailure’, ‘payment_declined’, 1000, [ { key: “6”, value: “999.99” }, // Payment Amount { key: “7”, value: “Stripe” }, // Payment Gateway { key: “8”, value: “Credit Card” }, // Payment Method { key: “9”, value: “Insufficient Funds” }, // Payment Error ]); addLog(‘Payment Failure event tracked’); } catch (error) { addLog(`Payment Failure error: ${error}`); } };
// Interaction Events const testButtonClickEvent = async () => { try { await trackEvent(‘OnClick’, ‘ButtonClick’, ‘cta_button_clicked’, 1000, [ { key: “6”, value: “Buy Now Button” }, // Button Name { key: “7”, value: userEmail }, // Who Clicked ]); addLog(‘Button Click event tracked’); } catch (error) { addLog(`Button Click error: ${error}`); } };
const testLinkClickEvent = async () => { try { await trackEvent(‘OnClick’, ‘LinkClick’, ‘external_link_clicked’, 1000, [ { key: “6”, value: “Privacy Policy” }, // Link Name { key: “7”, value: userEmail }, // Who Clicked ]); addLog(‘Link Click event tracked’); } catch (error) { addLog(`Link Click error: ${error}`); } };
// Video Events const testVideoPlayEvent = async () => { try { await trackEvent(‘Videos’, ‘Play’, ‘product_demo_video’, 1000, [ { key: “6”, value: “iPhone 15 Demo” }, // Video Title { key: “7”, value: “<https://example.com/video1>” }, // Video Link ]); addLog(‘Video Play event tracked’); } catch (error) { addLog(`Video Play error: ${error}`); } };
const testVideoDurationEvent = async () => { try { await trackEvent(‘Videos’, ‘Duration’, ‘video_watch_time’, 1000, [ { key: “6”, value: “iPhone 15 Demo” }, // Video Title { key: “7”, value: “<https://example.com/video1>” }, // Video Link { key: “8”, value: “120” }, // Duration in seconds ]); addLog(‘Video Duration event tracked’); } catch (error) { addLog(`Video Duration error: ${error}`); } };
// Search Events const testSearchEvent = async () => { try { await trackEvent(‘Search’, ‘SearchKeyword’, ‘product_search’, 1000, [ { key: “6”, value: “iPhone 15” }, // Search Keyword ]); addLog(‘Search Keyword event tracked’); } catch (error) { addLog(`Search error: ${error}`); } };
// Track Screen Views (Page Visited) const testScreenTracking = async () => { try { await trackScreen(“ProductDetailScreen”, “Product detail page viewed”); addLog(‘Screen tracked: ProductDetailScreen’); } catch (error) { addLog(`Screen tracking error: ${error}`); } };
// Manual dispatch const testTrackDispatch = async () => { try { await trackDispatch(); addLog(‘Manual dispatch triggered’); } catch (error) { addLog(`Track dispatch error: ${error}`); } };
// Start new session const testStartSession = async () => { try { await startSession(); await setReservedDimensions(); // Reset user dimensions for new session addLog(‘New session started with user dimensions’); } catch (error) { addLog(`Start session error: ${error}`); } };
// Toggle tracking const toggleTracking = async () => { try { if (isTrackingEnabled) { await disableTracking(); setIsTrackingEnabled(false); addLog(‘Tracking disabled’); } else { await enableTracking(); setIsTrackingEnabled(true); addLog(‘Tracking enabled’); } } catch (error) { addLog(`Toggle tracking error: ${error}`); } };
return ( <SafeAreaView style={styles.container}> <StatusBar barStyle=“dark-content” backgroundColor=“#f8f9fa” /> <ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}> <Text style={styles.title}>FirstHive Analytics Test App</Text>
{/\* Configuration Section \*/} \<View style={styles.section}> \<Text style={styles.sectionTitle}>FirstHive Configuration\</Text> \<TextInput style={styles.input} placeholder="FirstHive URL (default provided)" value={matomoUrl} onChangeText={setMatomoUrl} autoCapitalize="none" /> \<TextInput style={styles.input} placeholder="Site ID (provided by FirstHive)" value={siteId} onChangeText={setSiteId} keyboardType="numeric" /> \<TextInput style={styles.input} placeholder="Auth Token (optional)" value={authToken} onChangeText={setAuthToken} autoCapitalize="none" secureTextEntry /> \<TouchableOpacity style={\[styles.button, styles.primaryButton]} onPress={initializeTracker} \> \<Text style={styles.buttonText}>Initialize FirstHive Tracker\</Text> \</TouchableOpacity> {isTrackerInitialized && ( \<Text style={styles.successText}>✓ FirstHive Tracker Initialized\</Text> )} \</View>
{isTrackerInitialized && ( \<> {/\* User Information (Reserved Dimensions) \*/} \<View style={styles.section}> \<Text style={styles.sectionTitle}>User Information (Reserved Dimensions)\</Text> \<TextInput style={styles.input} placeholder="Email (Dimension 1)" value={userEmail} onChangeText={setUserEmail} /> \<TextInput style={styles.input} placeholder="Mobile Number (Dimension 2)" value={userMobile} onChangeText={setUserMobile} /> \<TextInput style={styles.input} placeholder="User Name (Dimension 3)" value={userName} onChangeText={setUserName} /> \<TextInput style={styles.input} placeholder="Device ID (Dimension 5)" value={deviceId} onChangeText={setDeviceId} /> \<TouchableOpacity style={styles.button} onPress={setReservedDimensions}> \<Text style={styles.buttonText}>Update User Dimensions\</Text> \</TouchableOpacity> \</View>
{/\* Session Management \*/} \<View style={styles.section}> \<Text style={styles.sectionTitle}>Session Management\</Text> \<View style={styles.buttonRow}> \<TouchableOpacity style={styles.button} onPress={testStartSession}> \<Text style={styles.buttonText}>Start Session\</Text> \</TouchableOpacity> \<TouchableOpacity style={\[styles.button, isTrackingEnabled ? styles.dangerButton : styles.successButton]} onPress={toggleTracking} \> \<Text style={styles.buttonText}> {isTrackingEnabled ? 'Disable' : 'Enable'} Tracking \</Text> \</TouchableOpacity> \</View> \</View>
{/\* Login Operations Events \*/} \<View style={styles.section}> \<Text style={styles.sectionTitle}>Login Operations Events\</Text> \<View style={styles.buttonRow}> \<TouchableOpacity style={styles.button} onPress={testLoginEvent}> \<Text style={styles.buttonText}>Track Login\</Text> \</TouchableOpacity> \<TouchableOpacity style={styles.button} onPress={testSignUpEvent}> \<Text style={styles.buttonText}>Track SignUp\</Text> \</TouchableOpacity> \</View> \<TouchableOpacity style={styles.button} onPress={testLogoffEvent}> \<Text style={styles.buttonText}>Track Logoff\</Text> \</TouchableOpacity> \</View>
{/\* Product Events \*/} \<View style={styles.section}> \<Text style={styles.sectionTitle}>Product & Commerce Events\</Text> \<View style={styles.buttonRow}> \<TouchableOpacity style={styles.button} onPress={testProductViewedEvent}> \<Text style={styles.buttonText}>Product Viewed\</Text> \</TouchableOpacity> \<TouchableOpacity style={styles.button} onPress={testCategoryViewedEvent}> \<Text style={styles.buttonText}>Category Viewed\</Text> \</TouchableOpacity> \</View> \<View style={styles.buttonRow}> \<TouchableOpacity style={styles.button} onPress={testAddToCartEvent}> \<Text style={styles.buttonText}>Add to Cart\</Text> \</TouchableOpacity> \<TouchableOpacity style={styles.button} onPress={testProductPurchasedEvent}> \<Text style={styles.buttonText}>Product Purchased\</Text> \</TouchableOpacity> \</View> \</View>
{/\* Payment Events \*/} \<View style={styles.section}> \<Text style={styles.sectionTitle}>Payment Events\</Text> \<View style={styles.buttonRow}> \<TouchableOpacity style={\[styles.button, styles.successButton]} onPress={testPaymentSuccessEvent}> \<Text style={styles.buttonText}>Payment Success\</Text> \</TouchableOpacity> \<TouchableOpacity style={\[styles.button, styles.dangerButton]} onPress={testPaymentFailureEvent}> \<Text style={styles.buttonText}>Payment Failure\</Text> \</TouchableOpacity> \</View> \</View>
{/\* Interaction Events \*/} \<View style={styles.section}> \<Text style={styles.sectionTitle}>User Interaction Events\</Text> \<View style={styles.buttonRow}> \<TouchableOpacity style={styles.button} onPress={testButtonClickEvent}> \<Text style={styles.buttonText}>Button Click\</Text> \</TouchableOpacity> \<TouchableOpacity style={styles.button} onPress={testLinkClickEvent}> \<Text style={styles.buttonText}>Link Click\</Text> \</TouchableOpacity> \</View> \</View>
{/\* Video Events \*/} \<View style={styles.section}> \<Text style={styles.sectionTitle}>Video Events\</Text> \<View style={styles.buttonRow}> \<TouchableOpacity style={styles.button} onPress={testVideoPlayEvent}> \<Text style={styles.buttonText}>Video Play\</Text> \</TouchableOpacity> \<TouchableOpacity style={styles.button} onPress={testVideoDurationEvent}> \<Text style={styles.buttonText}>Video Duration\</Text> \</TouchableOpacity> \</View> \</View>
{/\* Other Events \*/} \<View style={styles.section}> \<Text style={styles.sectionTitle}>Other Events\</Text> \<View style={styles.buttonRow}> \<TouchableOpacity style={styles.button} onPress={testSearchEvent}> \<Text style={styles.buttonText}>Search Event\</Text> \</TouchableOpacity> \<TouchableOpacity style={styles.button} onPress={testScreenTracking}> \<Text style={styles.buttonText}>Track Screen\</Text> \</TouchableOpacity> \</View> \</View>
{/\* Manual Actions \*/} \<View style={styles.section}> \<Text style={styles.sectionTitle}>Manual Actions\</Text> \<TouchableOpacity style={\[styles.button, styles.warningButton]} onPress={testTrackDispatch} \> \<Text style={styles.buttonText}>Manual Dispatch\</Text> \</TouchableOpacity> \</View>
{/\* Log Section \*/} \<View style={styles.section}> \<View style={styles.logHeader}> \<Text style={styles.sectionTitle}>Activity Log\</Text> \<TouchableOpacity style={styles.clearButton} onPress={clearLogs}> \<Text style={styles.clearButtonText}>Clear\</Text> \</TouchableOpacity> \</View> \<View style={styles.logContainer}> {logMessages.length === 0 ? ( \<Text style={styles.logEmpty}>No activity yet...\</Text> ) : ( logMessages.map((message, index) => ( \<Text key={index} style={styles.logMessage}> {message} \</Text> )) )} \</View> \</View> \</> )} \</ScrollView>\</SafeAreaView>
); };
const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: ‘#f8f9fa’, }, scrollView: { flex: 1, padding: 16, }, title: { fontSize: 24, fontWeight: ‘bold’, textAlign: ‘center’, marginBottom: 24, color: ‘#333’, }, section: { backgroundColor: ‘#fff’, borderRadius: 12, padding: 16, marginBottom: 16, shadowColor: ‘#000’, shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3, }, sectionTitle: { fontSize: 18, fontWeight: ‘600’, marginBottom: 12, color: ‘#333’, }, input: { borderWidth: 1, borderColor: ‘#e1e5e9’, borderRadius: 8, padding: 12, marginBottom: 12, fontSize: 16, backgroundColor: ‘#f8f9fa’, }, button: { backgroundColor: ‘#007bff’, borderRadius: 8, padding: 12, alignItems: ‘center’, marginBottom: 8, flex: 1, marginHorizontal: 4, }, primaryButton: { backgroundColor: ‘#28a745’, marginHorizontal: 0, }, dangerButton: { backgroundColor: ‘#dc3545’, }, successButton: { backgroundColor: ‘#28a745’, }, warningButton: { backgroundColor: ‘#ffc107’, marginHorizontal: 0, }, buttonRow: { flexDirection: ‘row’, justifyContent: ‘space-between’, marginHorizontal: -4, }, buttonText: { color: ‘#fff’, fontSize: 14, fontWeight: ‘600’, textAlign: ‘center’, }, successText: { color: ‘#28a745’, fontSize: 14, fontWeight: ‘600’, textAlign: ‘center’, marginTop: 8, }, logHeader: { flexDirection: ‘row’, justifyContent: ‘space-between’, alignItems: ‘center’, marginBottom: 12, }, clearButton: { backgroundColor: ‘#6c757d’, borderRadius: 6, paddingHorizontal: 12, paddingVertical: 6, }, clearButtonText: { color: ‘#fff’, fontSize: 12, fontWeight: ‘600’, }, logContainer: { backgroundColor: ‘#f8f9fa’, borderRadius: 8, padding: 12, maxHeight: 200, }, logEmpty: { color: ‘#6c757d’, fontSize: 14, textAlign: ‘center’, fontStyle: ‘italic’, }, logMessage: { fontSize: 12, color: ‘#495057’, marginBottom: 4, }, });
export default FirstHiveMatomoTestApp;
Mobile Push Notifications Integration
Prerequisites for Push Notifications
- Set up a Firebase Cloud Messaging client app in your Android/iOS app
- Follow the Firebase documentation: https://firebase.google.com/docs/cloud-messaging/android/client
- Obtain FCM registration token from your app
FCM Token Management & Push Notifications
New Functions Added
The SDK now includes built-in FCM token management and push notification subscription functions:
jsx
import { updateFCMToken, doAppUserSubscription, } from ‘@logicwind/react-native-matomo-tracker’;
1. Update FCM Token
Use this function to store the FCM token in the SDK for later use:
// Store FCM token for push notifications const storeFCMToken = (token) => { updateFCMToken(token); console.log(‘FCM token stored in SDK’); };
// Example: Get token from Firebase and store it import messaging from ‘@react-native-firebase/messaging’; const initializeFCM = async () => { try { const token = await messaging().getToken(); updateFCMToken(token); // Store in SDK } catch (error) { console.error(‘FCM token retrieval failed:’, error); } };
2. Subscribe User for Push Notifications
Register user for FirstHive push campaigns using the stored or provided FCM token:
// Subscribe user for push notifications const subscribeUserForPush = async (userMobile, visitorId) => { try { const result = await doAppUserSubscription({ siteId: ‘417’, // Your site ID from FirstHive mobileNo: userMobile, visitorId: visitorId, // Current visitor ID from Matomo// fcmToken: ‘optional_token’ // Optional: override stored token }); console.log(‘Push subscription successful:’, result);
*// Track the subscription event* await trackEvent(‘PushOps’, ‘PushSubscribed’, ‘user_subscribed_to_push’, 1, [ { key: “6”, value: userMobile }, { key: “7”, value: visitorId }, ]); } catch (error) { console.error(‘Push subscription failed:’, error);
*// Track the subscription failure* await trackEvent(‘PushOps’, ‘PushSubscriptionFailed’, ‘subscription_error’, 1, [ { key: “6”, value: error.message }, ]);
*// Track the subscription failure* await trackEvent(‘PushOps’, ‘PushSubscriptionFailed’, ‘subscription_error’, 1, [ { key: “6”, value: error.message }, ]); } };
Complete Integration Example
import React, { useEffect } from ‘react’; import messaging from ‘@react-native-firebase/messaging’; import { createTracker, trackEvent, updateFCMToken, doAppUserSubscription, startSession, } from ‘@logicwind/react-native-matomo-tracker’;
const PushIntegrationExample = () => { useEffect(() => { initializeSDKWithPush(); setupMessageHandlers(); }, []);
const initializeSDKWithPush = async () => { try { // 1. Initialize FirstHive tracker await createTracker(‘https://analytics.firsthive.com/piwik.php’, 417); // 2. Get and store FCM token* const fcmToken = await messaging().getToken(); updateFCMToken(fcmToken);
// 3. Subscribe user for push (after user login)\*const userMobile = '+1234567890';const visitorId = 'current\_visitor\_id'; \*// Get from your visitor tracking\*
await doAppUserSubscription({siteId: '417',mobileNo: userMobile,visitorId: visitorId
}); console.log(‘SDK and Push integration complete’); } catch (error) { console.error(‘Integration failed:’, error); } };
const setupMessageHandlers = () => { // Handle foreground messages messaging().onMessage(async remoteMessage => { await trackEvent(‘PushOps’, ‘PushReceived’, ‘foreground_push’, 1, [ { key: “6”, value: remoteMessage.messageId }, { key: “7”, value: remoteMessage.notification?.title || ‘No Title’ }, ]); });
*// Handle notification tap* messaging().onNotificationOpenedApp(remoteMessage => { trackEvent(‘PushOps’, ‘PushOpened’, ‘notification_tapped’, 1, [ { key: “6”, value: remoteMessage.messageId }, { key: “8”, value: ‘AppOpened’ }, ]); }); }; return null; };
export default PushIntegrationExample;
API Reference
updateFCMToken(token: string)
- Purpose: Store FCM token in the SDK for later use
- Parameters:
- token: FCM registration token from Firebase
- Returns: void
- Usage: Call this when you receive FCM token from Firebase
doAppUserSubscription(params)
- Purpose: Subscribe user to FirstHive push campaigns
- Parameters:
- siteId: Your FirstHive site ID (string)
- mobileNo: User’s mobile number (string)
- visitorId: Current Matomo visitor ID (string)
- fcmToken: Optional FCM token (uses stored token if not provided - from updateFCMToken method)
- Returns: Promise with API response
- Endpoint: https://ind14.firsthive.com/engage/mobile/doAppUserSubscription
Push Event Categories
Add these event tracking patterns for push notifications:
// Push subscription events await trackEvent(‘PushOps’, ‘PushSubscribed’, ‘user_opted_in’, 1); await trackEvent(‘PushOps’, ‘PushUnsubscribed’, ‘user_opted_out’, 1);
// Push message events await trackEvent(‘PushOps’, ‘PushReceived’, ‘message_delivered’, 1); await trackEvent(‘PushOps’, ‘PushOpened’, ‘message_clicked’, 1); await trackEvent(‘PushOps’, ‘PushDismissed’, ‘message_dismissed’, 1);
// Push errors await trackEvent(‘PushOps’, ‘PushError’, ‘subscription_failed’, 1, [ { key: “6”, value: error.message } ]);`
Error Handling
Always wrap push operations in try-catch blocks:
const handlePushSubscription = async () => { try { // Get fresh FCM token const newToken = await messaging().getToken(); updateFCMToken(newToken); // Subscribe user await doAppUserSubscription({ siteId: ‘417’, mobileNo: userMobile, visitorId: currentVisitorId }); } catch (error) { console.error(‘Push subscription error:’, error); // Track error for debugging await trackEvent(‘PushOps’, ‘PushError’, ‘subscription_error’, 1, [ { key: “6”, value: error.message } ]); } };
Token Refresh Handling
Handle FCM token refresh:
// Listen for token refresh messaging().onTokenRefresh(token => { console.log(‘FCM token refreshed:’, token); updateFCMToken(token); // Update stored token
// Re-subscribe with new token if user is logged in if (userIsLoggedIn) { doAppUserSubscription({ siteId: ‘417’, mobileNo: currentUserMobile, visitorId: currentVisitorId }); } });`