Installing Activity Flow in React Native apps
Learn how to install the Activity Flow SDK in React Native apps for Android and iOS to track navigation, order events, deep links, ads, and customer interactions.
In this guide, you'll learn how to install and use the Activity Flow SDK in React Native apps for Android and iOS. By following these steps, you'll be able to track user navigation, order events, deep links, ad events, and customer interaction events (clicks, views, and impressions) in your app.
Before you begin
Use React Navigation or Expo Router in your project
To manage navigation between different screens of your app, you must use either React Navigation or Expo Router. This is important because Activity Flow relies on React Navigation's useFocusEffect hook to detect page changes. For more information, refer to the React Navigation guide.
Install the Activity Flow package
Activity Flow depends on @react-native-async-storage/async-storage to store navigation data locally. This ensures events are captured even when the device is offline.
To install the library, follow these steps:
-
In your project directory, run:
_10yarn add @vtex/activity-flow @react-native-async-storage/async-storageor
_10npm install @vtex/activity-flow @react-native-async-storage/async-storage -
For iOS, sync native dependencies:
_10cd ios && pod install && cd ../
This process installs the Activity Flow script in your app by adding a new dependency in the package.json file.
Instructions
Step 1 - Importing the Activity Flow plugin
In your app's main file (for example, App.js or App.tsx), import the plugin as follows:
_10import { initActivityFlow } from '@vtex/activity-flow';
Step 2 - Creating an Activity Flow instance
Set the account name to create an instance of the main package class:
_10function App() {_10 // Initialize the Activity Flow plugin with your VTEX account name_10 initActivityFlow({_10 accountName: 'your-account-name', // Replace with your VTEX account name_10 });_10 // ...rest of your app_10}
Usage
Tracking page views automatically
To track navigation state changes, integrate the usePageViewObserver hook with the NavigationContainer, the root component of React Navigation, in your app's main file. To do so, follow these steps:
-
Import the observer hook:
_10import { usePageViewObserver } from '@vtex/activity-flow'; -
Create a reference for the
NavigationContainer:_10const navigationRef = useRef(null); -
Pass the reference to
NavigationContainer:_10<NavigationContainer ref={navigationRef}>_10{/* Stack configurations */}_10</NavigationContainer> -
Use the observer hook to track page views:
_10usePageViewObserver({ navigationRef });
Activity Flow now automatically tracks page transitions. When you navigate between screens, it records each page view and sends event data.
Tracking order group
The Activity Flow SDK can automatically capture order details from navigation parameters, such as orderGroup or orderPlaced, as well as any query parameters from the navigator stack. These parameters help validate checkout events and confirm successful purchases.
To enable this feature, include the required parameter in your page redirect configuration. See the example below:
When a user completes a purchase, Activity Flow automatically captures orderGroup and other query parameters during the screen transition.
Tracking deep links
The Activity Flow SDK automatically captures deep-link query parameters from the usePageViewObserver hook and includes them in page view events. Use this hook to route users to the appropriate screen based on the incoming URL and query parameters. The callback receives an object with the URL and the parsed query parameters.
_10usePageViewObserver({_10 navigationRef,_10 onDeepLink: ({ url, queryParams }) => {_10 if (url.includes('/product') && queryParams?.naid) {_10 navigationRef.current?.navigate('/product', { id: queryParams.naid });_10 }_10 },_10});
Also, to enable this feature, configure deep linking in your app according to its platform: Android or iOS.
Android
To enable deep-link handling in your Android app, add intent filters to the AndroidManifest.xml file for each route that can be accessed via a deep link. See the example below:
This AndroidManifest adds two intent filters for deep linking:
- One for HTTPS URLs starting with
"https://example.com/{APP_ROUTE}". - One for a custom scheme
"{YOUR_CUSTOM_SCHEME}".
Both use action.VIEW, category_DEFAULT, and category_BROWSABLE, allowing the Activity Flow to launch from browsers or other apps.
The data tag for a deep link via HTTP specifies the scheme, host, and an optional pathPrefix, matching any path that begins with that prefix (for example, "/products/42" if {APP_ROUTE} is "/products"). The main difference between intent filters for different routes is the android:pathPrefix attribute, which specifies the app route.
For a deep link via a custom scheme, the data tag sets the scheme, matching any URL that starts with that scheme (for example, myapp://... if {YOUR_CUSTOM_SCHEME} is myapp).
iOS
To enable deep-link handling in your iOS app, add your URL scheme to the Info.plist file and handle incoming URLs in the AppDelegate file:
-
Register your custom URL scheme by adding the following configuration to your
Info.plistfile:In the
Info.plistconfiguration,{YOUR_BUNDLE_URL_SCHEME}is the custom URL scheme your app will handle, that is, the prefix before://in deep links. For example, setting it tomyappmakes links likemyapp://pathopen your app. Enter only the scheme name, without://. The{YOUR_BUNDLE_URL_NAME}label is a unique identifier for this URL type entry, typically in reverse-DNS format, used to distinguish the configuration and doesn't affect routing. For example,com.example.appname. -
Handle incoming deep links by modifying your
AppDelegate.swift(orAppDelegate.mm) file. The following example handles deep links for cold starts (when the app isn't running) and warm starts (when the app is already running).
Your Android app can now be launched from both web links (https://mystore.com/products/42) and custom scheme links: (myapp://products/42).
Tracking ad events
Ad tracking is available only for accounts that use VTEX Ads. If you're interested in this feature, open a ticket with VTEX Support.
To configure ad tracking, follow these steps:
-
Create the ad object
To track events, provide an object containing all information relevant to the ad event. The accountName field is required.
_10const adParams = {_10accountName: 'your-account-name',_10adId: 'main-banner-01',_10// ...other parameters_10}; -
Integrate the ad tracker into your components
There are two methods to integrate the ad tracker into your components: using the AFAdsTracker Wrapper component or using the
useAdsTrackerhook.Using the AFAdsTracker Wrapper component
To track ad events with the AFAdsTracker, wrap your ad component with it and provide the necessary tracking parameters. The wrapper will automatically manage all required event handlers.
Use this method when your entire child component represents a clickable ad. See the example below:
_10<AFAdsTracker params={adParams}>_10<TouchableOpacity_10style={styles.button}_10onPress={() => {_10navigation.navigate('/checkout');_10}}_10>_10<Text style={styles.buttonText}>Buy</Text>_10</TouchableOpacity>_10</AFAdsTracker>The example below shows a React Native app that uses
@vtex/activity-flowwith React Navigation to track navigation across multiple screens and ad events from a single location:_94// App.tsx_94import React, { useRef, useEffect } from 'react';_94import { View, Text, Button, TouchableOpacity, StyleSheet } from 'react-native';_94import { NavigationContainer } from '@react-navigation/native';_94import { createNativeStackNavigator } from '@react-navigation/native-stack';_94import {_94initActivityFlow,_94usePageViewObserver,_94AFAdsTracker,_94} from '@vtex/activity-flow';_94_94type NavProps = { navigation: any };_94_94const HomeScreen = ({ navigation }: NavProps) => {_94return (_94<View>_94<Text>Home</Text>_94<Button title="Go to Profile" onPress={() => navigation.navigate('Profile')} />_94<View />_94<Button title="Go to Ads" onPress={() => navigation.navigate('Ads')} />_94</View>_94);_94};_94_94const ProfileScreen = ({ navigation }: NavProps) => (_94<View>_94<Text>Profile</Text>_94<Button title="Go to Details" onPress={() => navigation.navigate('Details')} />_94</View>_94);_94_94const DetailsScreen = ({ navigation }: NavProps) => {_94return (_94<View>_94<Text>Details</Text>_94<Button title="Go Back" onPress={() => navigation.goBack()} />_94</View>_94);_94};_94_94const AdsScreen = () => {_94// Include your VTEX accountName here (required for sending ad events)_94const adParams = {_94accountName: 'your-account-name',_94adId: '12345',_94campaign: 'summer_campaign',_94};_94_94return (_94<View>_94<Text>Ads</Text>_94{/* Wrap your ad with AFAdsTracker. The wrapper wires impression/view/click. */}_94<AFAdsTracker params={adParams}>_94<TouchableOpacity_94onPress={() => {_94// Your CTA action goes here (e.g., navigate, open link)_94console.log('Ad clicked — navigate or handle CTA');_94}}_94>_94<Text>Sponsored Ad</Text>_94<Text>Check out our new campaign!</Text>_94<Text>Buy now</Text>_94</TouchableOpacity>_94</AFAdsTracker>_94</View>_94);_94};_94_94const Stack = createNativeStackNavigator();_94_94export default function App() {_94const navigationRef = useRef(null);_94_94// Initialize Activity Flow once with your VTEX account name_94useEffect(() => {_94initActivityFlow({_94accountName: 'your-account-name',_94});_94}, []);_94_94// Track page views automatically (attach to the navigation ref)_94usePageViewObserver({ navigationRef });_94_94return (_94<NavigationContainer ref={navigationRef as any}>_94<Stack.Navigator initialRouteName="Home">_94<Stack.Screen name="Home" component={HomeScreen} />_94<Stack.Screen name="Profile" component={ProfileScreen} />_94<Stack.Screen name="Details" component={DetailsScreen} />_94<Stack.Screen name="Ads" component={AdsScreen} />_94</Stack.Navigator>_94</NavigationContainer>_94);_94}The app initializes tracking via
initActivityFlowwith your VTEX account name and enables automatic page view tracking by callingusePageViewObserverwith aNavigationContainerref.On the Ads screen, ad parameters (including the required
accountName) are passed to the AFAdsTracker wrapper, which wraps a fully clickable child to automatically fire impression, view, and click events without manual handler wiring.The wrapper detects clicks via
onTouchEndCapture. If your ad interaction isn't based on it or can't be wrapped, use theuseAdsTrackerhook instead.To use this example in your project, remember to replace
accountNamebased on your scenario.Using the
useAdsTrackerhookThe
useAdsTrackerhook provides more control over how events are attached. It returns handlers and a ref that you must apply manually to your components.Use this method when:
- You can't use an extra
<View>to wrap your component. - The component that receives events (for example, a
TouchableOpacity) is different from the component that defines the visibility area. - You integrate with third-party component libraries that don’t allow a generic wrapper.
- The click property isn't
onTouchEnd, since the AFAdsTracker wrapper usesonTouchEndCapture.
Follow the steps below to use this hook:
-
Import and call the hook:
_10import { useAdsTracker } from '@vtex/activity-flow';_10_10const { handleLayout, handlePress, viewRef } = useAdsTracker(adParams); -
Attach the handlers and ref to your component. See the example below:
_10<TouchableOpacity_10ref={viewRef} // Attach the ref to track visibility (view)_10onLayout={handleLayout} // Attach the layout handler (impression)_10onPress={handlePress} // Attach the click handler (click)_10>_10<Image source={{}} />_10</TouchableOpacity>
The hook returns an object with three properties:
handleLayout: A function to be passed to your component'sonLayoutprop. It fires the impression event.handlePress: A function to be passed to a touch prop, likeonPressoronTouchEndCapture. It fires the click event.viewRef: Arefthat must be attached to therefprop of the ad's main component. It monitors visibility and fires the view event.
Below is an example implementation of a React Native component that defines a
CustomAd. This component uses theuseAdsTrackerhook from the@vtex/activity-flowlibrary to integrate with an ad tracking system for visibility and interaction._24import { useAdsTracker } from "@vtex/activity-flow";_24import { Image, TouchableOpacity } from "react-native";_24_24export const CustomAd = () => {_24// 1. Define the parameters_24const adParams = {_24accountName: 'my-company-x',_24adId: 'sidebar-banner-02',_24};_24_24// 2. Call the hook to get the handlers and the ref_24const { handleLayout, handlePress, viewRef } = useAdsTracker(adParams);_24_24return (_24// 3. Attach the handlers and ref to your component_24<TouchableOpacity_24ref={viewRef} // Attach the ref to track visibility (view)_24onLayout={handleLayout} // Attach the layout handler (impression)_24onPress={handlePress} // Attach the click handler (click)_24>_24<Image source={{}} />_24</TouchableOpacity>_24);_24};The component's purpose is to render an ad inside a touchable container and report three key events to the tracking system: impression, view, and click.
- You can't use an extra
Tracking customer interaction events
Beyond page views and ad events, Activity Flow provides individual hooks for tracking click, view, and impression events on any component. These hooks can be used independently or combined on the same component.
All hooks require elementSource, a string that identifies the tracked element. This field is sent as a top-level property in the event payload, separate from the other attributes.
The
elementSourcekey is required for all events and must be provided to every hook.
Click event
Use useClickObserver to track click or press interactions. The hook returns an afHandlePress callback to be called in the press handler.
_10import { useClickObserver } from '@vtex/activity-flow';_10_10const { afHandlePress } = useClickObserver({_10 elementSource: 'buy-button',_10 productId: '123',_10});
The example below shows how to attach the click tracking handler to a TouchableOpacity button, firing afHandlePress() alongside an existing navigation logic when the user taps Buy Now:
_10<TouchableOpacity_10 onPress={() => {_10 afHandlePress();_10 navigation.navigate('/checkout');_10 }}_10>_10 <Text>Buy Now</Text>_10</TouchableOpacity>
View event
Use useViewObserver to track when a component has been visible on screen. The event fires after the component is at least 50% visible for 1 second (per IAB standards). The hook returns an afViewRef that can be attached to the target View.
_10import { useViewObserver } from '@vtex/activity-flow';_10_10const { afViewRef } = useViewObserver({_10 elementSource: 'product-card',_10 productId: '123',_10});
The example below attaches afViewRef to a View component to track when the Product Card element becomes visible on screen:
_10<View ref={afViewRef}>_10 <Text>Product Card</Text>_10</View>
Impression event
Use useImpressionObserver to track when a screen or component is rendered. The event fires on first render and re-fires only when the tracked attributes (or elementSource) change, matching the web script's attribute-based deduplication. Viewport visibility and screen focus are intentionally excluded: an impression is counted when the content is rendered. This hook is a pure side effect and returns nothing.
_10import { useImpressionObserver } from '@vtex/activity-flow';_10_10useImpressionObserver({_10 elementSource: 'promotion-banner',_10 screen: 'promotion',_10 campaignId: 'summer-2026',_10});
Combining multiple observers
The hooks can be combined on the same screen or component to track the full interaction funnel: whether the element was rendered (impression), actually seen by the user (view), and clicked (click).
The example below tracks all three on a product detail screen:
_29const ProductDetailScreen = ({ route }) => {_29 const { productId } = route.params;_29_29 useImpressionObserver({_29 elementSource: 'product-detail-screen',_29 productId,_29 });_29_29 const { afViewRef } = useViewObserver({_29 elementSource: 'product-image',_29 productId,_29 });_29_29 const { afHandlePress } = useClickObserver({_29 elementSource: 'add-to-cart-btn',_29 productId,_29 });_29_29 return (_29 <View>_29 <View ref={afViewRef}>_29 <Text>Product Image</Text>_29 </View>_29 <TouchableOpacity onPress={afHandlePress}>_29 <Text>Add to Cart</Text>_29 </TouchableOpacity>_29 </View>_29 );_29};