Documentation
Feedback
Guides
API Reference

Guides
Guides

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:

  1. In your project directory, run:


    _10
    yarn add @vtex/activity-flow @react-native-async-storage/async-storage

    or


    _10
    npm install @vtex/activity-flow @react-native-async-storage/async-storage

  2. For iOS, sync native dependencies:


    _10
    cd 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:


_10
import { 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:


_10
function 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:

  1. Import the observer hook:


    _10
    import { usePageViewObserver } from '@vtex/activity-flow';

  2. Create a reference for the NavigationContainer:


    _10
    const navigationRef = useRef(null);

  3. Pass the reference to NavigationContainer:


    _10
    <NavigationContainer ref={navigationRef}>
    _10
    {/* Stack configurations */}
    _10
    </NavigationContainer>

  4. Use the observer hook to track page views:


    _10
    usePageViewObserver({ 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:

//checkout_screen.tsx

_11
<TouchableOpacity
_11
onPress={() =>
_11
navigation.navigate('/purchase_success', {
_11
orderGroup: '1234567890',
_11
orderId: '1234',
_11
// ...other params
_11
})
_11
}
_11
>
_11
<Text>Confirm Purchase</Text>
_11
</TouchableOpacity>

When a user completes a purchase, Activity Flow automatically captures orderGroup and other query parameters during the screen transition.

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.


_10
usePageViewObserver({
_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:

AndroidManifest.xml

_18
_18
<intent-filter>
_18
<action android:name="android.intent.action.VIEW" />
_18
<category android:name="android.intent.category.DEFAULT" />
_18
<category android:name="android.intent.category.BROWSABLE" />
_18
<data
_18
android:scheme="https"
_18
android:host="mystore.com"
_18
android:pathPrefix="{APP_ROUTE}"
_18
/>
_18
</intent-filter>
_18
_18
<intent-filter>
_18
<action android:name="android.intent.action.VIEW" />
_18
<category android:name="android.intent.category.DEFAULT" />
_18
<category android:name="android.intent.category.BROWSABLE" />
_18
<data android:scheme="{YOUR_CUSTOM_SCHEME}" />
_18
</intent-filter>

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:

  1. Register your custom URL scheme by adding the following configuration to your Info.plist file:

    Info.plist

    _11
    <key>CFBundleURLTypes</key>
    _11
    <array>
    _11
    <dict>
    _11
    <key>CFBundleURLSchemes</key>
    _11
    <array>
    _11
    <string>{YOUR_BUNDLE_URL_SCHEME}</string>
    _11
    </array>
    _11
    <key>CFBundleURLName</key>
    _11
    <string>{YOUR_BUNDLE_URL_NAME}</string>
    _11
    </dict>
    _11
    </array>

    In the Info.plist configuration, {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 to myapp makes links like myapp://path open 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.

  2. Handle incoming deep links by modifying your AppDelegate.swift (or AppDelegate.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).

    AppDelegate.swift

    _69
    import UIKit
    _69
    import React
    _69
    _69
    @UIApplicationMain
    _69
    class AppDelegate: UIResponder, UIApplicationDelegate {
    _69
    _69
    func application(
    _69
    _ application: UIApplication,
    _69
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
    _69
    ) -> Bool {
    _69
    _69
    // ... your existing setup code ...
    _69
    _69
    // Capture initial URL if app was launched with a deep link (cold start)
    _69
    if let initialURL = launchOptions?[UIApplication.LaunchOptionsKey.url] as? URL {
    _69
    NotificationCenter.default.post(
    _69
    name: NSNotification.Name("RCTOpenURLNotification"),
    _69
    object: nil,
    _69
    userInfo: ["url": initialURL]
    _69
    )
    _69
    }
    _69
    _69
    return true
    _69
    }
    _69
    _69
    // Handles incoming URLs from Custom URL Schemes (e.g., myapp://path)
    _69
    func application(
    _69
    _ app: UIApplication,
    _69
    open url: URL,
    _69
    options: [UIApplication.OpenURLOptionsKey : Any] = [:]
    _69
    ) -> Bool {
    _69
    // Post notification for Activity Flow's CaptureDeepLinkModule
    _69
    NotificationCenter.default.post(
    _69
    name: NSNotification.Name("RCTOpenURLNotification"),
    _69
    object: nil,
    _69
    userInfo: ["url": url]
    _69
    )
    _69
    _69
    // Pass the URL to React Native's standard linking manager
    _69
    return RCTLinkingManager.application(app, open: url, options: options)
    _69
    }
    _69
    _69
    // Handles incoming URLs from Universal Links (e.g., https://mystore.com/product)
    _69
    func application(
    _69
    _ application: UIApplication,
    _69
    continue userActivity: NSUserActivity,
    _69
    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
    _69
    ) -> Bool {
    _69
    // Check if the activity is a web browsing activity (Universal Link)
    _69
    if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
    _69
    // Post notification for Activity Flow's CaptureDeepLinkModule
    _69
    if let url = userActivity.webpageURL {
    _69
    NotificationCenter.default.post(
    _69
    name: NSNotification.Name("RCTOpenURLNotification"),
    _69
    object: nil,
    _69
    userInfo: ["url": url]
    _69
    )
    _69
    }
    _69
    _69
    // Pass the user activity to React Native's standard linking manager
    _69
    return RCTLinkingManager.application(
    _69
    application,
    _69
    continue: userActivity,
    _69
    restorationHandler: restorationHandler
    _69
    )
    _69
    }
    _69
    return false
    _69
    }
    _69
    }

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:

  1. Create the ad object

    To track events, provide an object containing all information relevant to the ad event. The accountName field is required.


    _10
    const adParams = {
    _10
    accountName: 'your-account-name',
    _10
    adId: 'main-banner-01',
    _10
    // ...other parameters
    _10
    };

  2. 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 useAdsTracker hook.

    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
    _10
    style={styles.button}
    _10
    onPress={() => {
    _10
    navigation.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-flow with React Navigation to track navigation across multiple screens and ad events from a single location:


    _94
    // App.tsx
    _94
    import React, { useRef, useEffect } from 'react';
    _94
    import { View, Text, Button, TouchableOpacity, StyleSheet } from 'react-native';
    _94
    import { NavigationContainer } from '@react-navigation/native';
    _94
    import { createNativeStackNavigator } from '@react-navigation/native-stack';
    _94
    import {
    _94
    initActivityFlow,
    _94
    usePageViewObserver,
    _94
    AFAdsTracker,
    _94
    } from '@vtex/activity-flow';
    _94
    _94
    type NavProps = { navigation: any };
    _94
    _94
    const HomeScreen = ({ navigation }: NavProps) => {
    _94
    return (
    _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
    _94
    const ProfileScreen = ({ navigation }: NavProps) => (
    _94
    <View>
    _94
    <Text>Profile</Text>
    _94
    <Button title="Go to Details" onPress={() => navigation.navigate('Details')} />
    _94
    </View>
    _94
    );
    _94
    _94
    const DetailsScreen = ({ navigation }: NavProps) => {
    _94
    return (
    _94
    <View>
    _94
    <Text>Details</Text>
    _94
    <Button title="Go Back" onPress={() => navigation.goBack()} />
    _94
    </View>
    _94
    );
    _94
    };
    _94
    _94
    const AdsScreen = () => {
    _94
    // Include your VTEX accountName here (required for sending ad events)
    _94
    const adParams = {
    _94
    accountName: 'your-account-name',
    _94
    adId: '12345',
    _94
    campaign: 'summer_campaign',
    _94
    };
    _94
    _94
    return (
    _94
    <View>
    _94
    <Text>Ads</Text>
    _94
    {/* Wrap your ad with AFAdsTracker. The wrapper wires impression/view/click. */}
    _94
    <AFAdsTracker params={adParams}>
    _94
    <TouchableOpacity
    _94
    onPress={() => {
    _94
    // Your CTA action goes here (e.g., navigate, open link)
    _94
    console.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
    _94
    const Stack = createNativeStackNavigator();
    _94
    _94
    export default function App() {
    _94
    const navigationRef = useRef(null);
    _94
    _94
    // Initialize Activity Flow once with your VTEX account name
    _94
    useEffect(() => {
    _94
    initActivityFlow({
    _94
    accountName: 'your-account-name',
    _94
    });
    _94
    }, []);
    _94
    _94
    // Track page views automatically (attach to the navigation ref)
    _94
    usePageViewObserver({ navigationRef });
    _94
    _94
    return (
    _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 initActivityFlow with your VTEX account name and enables automatic page view tracking by calling usePageViewObserver with a NavigationContainer ref.

    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 the useAdsTracker hook instead.

    To use this example in your project, remember to replace accountName based on your scenario.

    Using the useAdsTracker hook

    The useAdsTracker hook 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 uses onTouchEndCapture.

    Follow the steps below to use this hook:

    1. Import and call the hook:


      _10
      import { useAdsTracker } from '@vtex/activity-flow';
      _10
      _10
      const { handleLayout, handlePress, viewRef } = useAdsTracker(adParams);

    2. Attach the handlers and ref to your component. See the example below:


      _10
      <TouchableOpacity
      _10
      ref={viewRef} // Attach the ref to track visibility (view)
      _10
      onLayout={handleLayout} // Attach the layout handler (impression)
      _10
      onPress={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's onLayout prop. It fires the impression event.
    • handlePress: A function to be passed to a touch prop, like onPress or onTouchEndCapture. It fires the click event.
    • viewRef: A ref that must be attached to the ref prop 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 the useAdsTracker hook from the @vtex/activity-flow library to integrate with an ad tracking system for visibility and interaction.


    _24
    import { useAdsTracker } from "@vtex/activity-flow";
    _24
    import { Image, TouchableOpacity } from "react-native";
    _24
    _24
    export const CustomAd = () => {
    _24
    // 1. Define the parameters
    _24
    const adParams = {
    _24
    accountName: 'my-company-x',
    _24
    adId: 'sidebar-banner-02',
    _24
    };
    _24
    _24
    // 2. Call the hook to get the handlers and the ref
    _24
    const { handleLayout, handlePress, viewRef } = useAdsTracker(adParams);
    _24
    _24
    return (
    _24
    // 3. Attach the handlers and ref to your component
    _24
    <TouchableOpacity
    _24
    ref={viewRef} // Attach the ref to track visibility (view)
    _24
    onLayout={handleLayout} // Attach the layout handler (impression)
    _24
    onPress={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.

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 elementSource key 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.


_10
import { useClickObserver } from '@vtex/activity-flow';
_10
_10
const { 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.


_10
import { useViewObserver } from '@vtex/activity-flow';
_10
_10
const { 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.


_10
import { useImpressionObserver } from '@vtex/activity-flow';
_10
_10
useImpressionObserver({
_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:


_29
const 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
};

Contributors
1
Photo of the contributor
Was this helpful?
Yes
No
Suggest Edits (GitHub)
See also
VTEX Activity Flow
Guides
Contributors
1
Photo of the contributor
Was this helpful?
Suggest edits (GitHub)
On this page