Skip to content

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

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

Custom Dimensions Schema

Reserved Dimensions (Visit-Level: 1-5) - Optional User Context

Dimension IDPurposeExample ValueNotes
1User Email / User IDuser@example.comAny important user identifier
2Mobile Number / Secondary ID+1234567890Additional user context
3User Name / Display NameJohn DoeUser identification
4User Role / CategoryPremium UserUser classification
5Device ID / Session Infodevice123456Device 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 IDPurposeExample Value
6Product SKU / Order ID / Button NameSKU12345
7Product Name / Total Amount / Video TitleiPhone 15 Pro
8Category / Payment Method / DurationElectronics
9Price / Payment Gateway / Error Message999.99
10Quantity / Discount1

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 ]); };

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

CategoryActionsPurpose
LoginOpsLogin, SignUp, LogoffUser authentication flows
ViewedProductViewed, CategoryViewedContent consumption
CartOptAddedtoCartShopping cart operations
PurchasedProductPurchasedPurchase completion
PaymentPaymentSuccess, PaymentFailurePayment processing
OnClickButtonClick, LinkClickUser interactions
VideosPlay, DurationVideo engagement
SearchSearchKeywordSearch 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

  1. Dimensions not showing: Verify they’re configured in FH dashboard
  2. Wrong scope: Ensure visit vs action scope matches usage
  3. Data delay: Allow 5-10 minutes for data processing
  4. 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 viewed
trackProductViewed(product);

}, [product]);

const handleAddToCart = async () => { // Your add to cart logic await addToCart(product);

// Track the event
await 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

  1. Set up a Firebase Cloud Messaging client app in your Android/iOS app
  2. Follow the Firebase documentation: https://firebase.google.com/docs/cloud-messaging/android/client
  3. 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 }); } });`