
Security News
OWASP 2025 Top 10 Adds Software Supply Chain Failures, Ranked Top Community Concern
OWASP’s 2025 Top 10 introduces Software Supply Chain Failures as a new category, reflecting rising concern over dependency and build system risks.
@vtex/activity-flow
Advanced tools
VTEX Activity Flow Mobile RUM for React Native apps focused in Android and iOS.
VTEX Activity Flow Mobile RUM for React Native apps focused in Android and iOS.
This plugin coupled to an app, aims to:
The @react-native-async-storage/async-storage library is required because it provides a persistent key-value storage system that is essential for tracking and storing user navigation data locally. This ensures that the Activity Flow plugin can function correctly, even in scenarios where network connectivity is temporarily unavailable. Installing it directly ensures that the native dependencies are properly linked and available in your app.
The app must use React Navigation or Expo Router, as Activity Flow relies on the useFocusEffect hook from React Navigation to detect page changes. For guidance on testing with Jest and avoiding issues, refer to the React Navigation documentation.
npm install @vtex/activity-flow @react-native-async-storage/async-storage
# or
yarn add @vtex/activity-flow @react-native-async-storage/async-storage
And running pod install (for iOS) ensures the native dependencies are properly synced.
cd ios && pod install && cd ../
To use the Activity Flow plugin, you need to initialize it in your React Native application. This is typically done in the main entry file of your app (e.g., App.js or index.js).
import { initActivityFlow } from '@vtex/activity-flow';
function App(): React.JSX.Element {
// Initialize the Activity Flow plugin with your VTEX account name
initActivityFlow({
accountName: 'your-account-name', // Replace with your VTEX account name
});
return (
// Your app components
);
}
To use the usePageViewObserver hook, you need to integrate it with the NavigationContainer and ensure it is set up correctly to track navigation state changes.
import { usePageViewObserver } from '@vtex/activity-flow';
The NavigationContainer is the root component for React Navigation. You need to create a ref and pass it to the NavigationContainer so that the usePageViewObserver hook can access the navigation state.
Also, the observer needs the account name to identify the source of the event.
const navigationRef = useRef(null);
return (
<NavigationContainer ref={navigationRef}>
// Stack configurations
</NavigationContainer>
);
usePageViewObserver({
navigationRef: navigationRef,
});
The Activity Flow SDK now automatically captures query parameters from deep links and includes them in page view events. Also, you need to configure the deep links in your app, depending on the platform. For Android check the Android Setup section. For iOS check the iOS Setup section.
To enable deep link handling on Android, you need to add intent filters to your AndroidManifest.xml file. Each route that can be accessed via deep link requires its own intent filter.
Location: android/app/src/main/AndroidManifest.xml
Add intent filters inside the <activity> tag for your main activity:
<!-- Example for HTTPS deep links -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="{YOUR_DOMAIN}" android:pathPrefix="{YOUR_APP_ROUTE}" />
</intent-filter>
<!-- Example for custom URL scheme -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="{YOUR_CUSTOM_SCHEME}" />
</intent-filter>
Important: For each route in your app that should be accessible via deep link, add a new intent filter. The main difference between intent filters is the android:pathPrefix attribute, which specifies the route path.
Example with multiple routes:
<!-- Route for product pages -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="mystore.com" android:pathPrefix="/product" />
</intent-filter>
<!-- Route for checkout -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="mystore.com" android:pathPrefix="/checkout" />
</intent-filter>
iOS deep link configuration requires changes in two files:
Step 1: Configure Info.plist
Add the following configuration to your ios/{YourApp}/Info.plist file to register your custom URL scheme:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>{YOUR_BUNDLE_URL_SCHEME}</string>
</array>
<key>CFBundleURLName</key>
<string>{YOUR_BUNDLE_URL_NAME}</string>
</dict>
</array>
Example:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
<key>CFBundleURLName</key>
<string>com.mycompany.myapp</string>
</dict>
</array>
Step 2: Configure AppDelegate
Modify your ios/{YourApp}/AppDelegate.swift (or AppDelegate.mm if using Objective-C) to handle incoming deep links.
Important: The following code handles deep links for both cold starts (when the app is not running) and warm starts (when the app is already running).
Add the following code to your AppDelegate:
import UIKit
import React
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
// ... your existing setup code ...
// Capture initial URL if app was launched with a deep link (cold start)
if let initialURL = launchOptions?[UIApplication.LaunchOptionsKey.url] as? URL {
NotificationCenter.default.post(
name: NSNotification.Name("RCTOpenURLNotification"),
object: nil,
userInfo: ["url": initialURL]
)
}
return true
}
/**
* Handles incoming URLs from Custom URL Schemes (e.g., myapp://path)
* This is called when the app is opened via a custom URL scheme.
*/
func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]
) -> Bool {
// Post notification for Activity Flow's CaptureDeepLinkModule
NotificationCenter.default.post(
name: NSNotification.Name("RCTOpenURLNotification"),
object: nil,
userInfo: ["url": url]
)
// Pass the URL to React Native's standard linking manager
return RCTLinkingManager.application(app, open: url, options: options)
}
/**
* Handles incoming URLs from Universal Links (e.g., https://mystore.com/product)
* Universal Links allow your app to be opened from standard HTTPS URLs.
*/
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
// Check if the activity is a web browsing activity (Universal Link)
if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
// Post notification for Activity Flow's CaptureDeepLinkModule
if let url = userActivity.webpageURL {
NotificationCenter.default.post(
name: NSNotification.Name("RCTOpenURLNotification"),
object: nil,
userInfo: ["url": url]
)
}
// Pass the user activity to React Native's standard linking manager
return RCTLinkingManager.application(
application,
continue: userActivity,
restorationHandler: restorationHandler
)
}
return false
}
}
Note: Make sure to import RCTLinkingManager at the top of your AppDelegate file if it's not already imported.
Android: https://mystore.com/product?id=12345 → queryParams = { id: '12345' }
iOS: myapp://checkout?orderId=abc123&step=payment → queryParams = { orderId: 'abc123', step: 'payment' }
The query parameters are automatically captured and included in the page view events, providing better tracking and analytics for deep link attribution.
To track ad events Activity Flow, get the handlers from the useAdsTracker hook. The hook accepts the ad parameters as an Object. All the ad parameters, like adId, need to be passed on the hook like the example.
const { handlePress, viewRef } = useAdsTracker(
{
adId: '123',
// ... other ads parameters
},
true // enables the hook, optional, default value is true
);
To capture impression events, simply call useAdsTracker with the enabled parameter set to true (which is the default). This is helpful for components that may or may not serve as ads, enabling or disabling all the ad events capture. To capture click events, the handlePress needs to be called besides the press property of the component. To capture view events, use the viewRef on the' ref' property of the desired ad viewable component. Next, an example use of the useAdsTracker hook.
return (
<View ref={viewRef}>
<TouchableOpacity
style={styles.button}
onPress={() => {
navigation.navigate('/checkout');
handlePress();
}}
>
<Text style={styles.buttonText}>Buy</Text>
</TouchableOpacity>
</View>
);
Instead of manually using the hook with a ref and handlers, you can simply wrap your ad component with the AFAdsTracker component to capture the events automatically. Using this component, the previous example would look like this:
return (
<AFAdsTracker params={{ adId: '123', foo: 'bar' }}>
<TouchableOpacity
style={styles.button}
onPress={() => {
navigation.navigate('/checkout');
}}
>
<Text style={styles.buttonText}>Buy</Text>
</TouchableOpacity>
</AFAdsTracker>
);
The AFAdsTracker captures click events using onTouchEndCapture, which fires during the event's capture phase. To align your ad's own click logic with this, you should use the standard onTouchEnd prop, which fires during the subsequent bubble phase.
You can see the example with the implementation in App.jsx(tsx) here.
@react-native-async-storage/async-storage dependency[@RNC/AsyncStorage]: NativeModule: AsyncStorage is null.Problem:
The app fails to build or run because the @react-native-async-storage/async-storage dependency is not properly installed or linked.
Solution:
Ensure that your project's package.json explicitly includes @react-native-async-storage/async-storage as a dependency, even if it is indirectly required by other dependencies. Run the following commands to install and link the dependency:
yarn add @react-native-async-storage/async-storage
cd ios && pod install && cd ../
Problem: The Activity Flow plugin is not sending events as expected.
Solution:
accountName provided to the initActivityFlow function is correct.Problem: The iOS build fails after installing the dependencies.
Solution: Run the following commands to ensure all native dependencies are properly linked and synced:
cd ios && pod install && cd ../
If the issue persists, clean the build folder and rebuild the project:
cd ios && xcodebuild clean && cd ../
Problem: The Android build fails due to missing dependencies or incorrect configurations.
Solution:
@react-native-async-storage/async-storage dependency is installed.cd android && ./gradlew clean && cd ../
If you encounter any other issues, please refer to the official documentation or open an issue in the plugin's repository.
MIT
FAQs
VTEX Activity Flow Mobile RUM for React Native apps focused in Android and iOS.
We found that @vtex/activity-flow demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 54 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OWASP’s 2025 Top 10 introduces Software Supply Chain Failures as a new category, reflecting rising concern over dependency and build system risks.

Research
/Security News
Socket researchers discovered nine malicious NuGet packages that use time-delayed payloads to crash applications and corrupt industrial control systems.

Security News
Socket CTO Ahmad Nassri discusses why supply chain attacks now target developer machines and what AI means for the future of enterprise security.