diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..071ea15 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +yarn lint && yarn typescript diff --git a/README.md b/README.md index 4d92df4..86fc0d4 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,49 @@ To set the page name ```js SiftReactNative.setPageName("HomePage"); ``` +## Track Screen Navigation + +##### 1. If you don't have root.js, create it, which file serves as the root component of the React Native application. It sets up the navigation using React Navigation and integrates with the `sift-react-native` library for tracking screen views. Install and import neccessary dependencies. + +##### 2. Create a stack navigator using createNativeStackNavigator() from @react-navigation/native-stack + `const Stack = createNativeStackNavigator();` + +##### 3. Define the Root component and set up the navigation container + ` import { NavigationContainer } from '@react-navigation/native';` + +##### 4. Inside the Root component, the useEffect hook is used to track the initial screen view by setting the page name with `SiftReactNative.setPageName()` and uploading the event with `SiftReactNative.upload()`. + `SiftReactNative.setPageName(`screen_${currentRouteName}`);` + ` SiftReactNative.upload();` + +##### 5. The ref and event handlers are used to track and update the current screen name dynamically. + ` const routeNameRef = React.useRef();` + ` const navigationRef = React.useRef();` + +##### 6. The NavigationContainer component wraps the stack navigator and provides the navigation context. + ` + (routeNameRef.current = navigationRef.current.getCurrentRoute().name) + } + onStateChange={() => { + const previousRouteName = routeNameRef.current; + const currentRouteName = navigationRef.current.getCurrentRoute().name; + + if (previousRouteName !== currentRouteName) { + console.log('Screen focused is:', currentRouteName); + SiftReactNative.setPageName(`screen_${currentRouteName}`); + SiftReactNative.upload(); + } + + routeNameRef.current = currentRouteName; + }}> + + + + + ` + + ## Example To see `sift-react-native` in action you can check out the source in the `example` folder. diff --git a/example/App.js b/example/App.js index 0e70f93..14dd63e 100644 --- a/example/App.js +++ b/example/App.js @@ -1,192 +1,12 @@ /** * Sample React Native App * https://github.com/facebook/react-native - * * @format * @flow strict-local - */ - -import React, {useState} from 'react'; -import { - Alert, - Platform, - StyleSheet, - View, - TextInput, - Text, - KeyboardAvoidingView, - Keyboard, - TouchableOpacity, - ScrollView, - SafeAreaView, -} from 'react-native'; -import SiftReactNative from 'sift-react-native'; + **/ +import React from 'react'; +import Root from './root'; const App = () => { - let [accountId, setAccountId] = useState(''); - let [beaconKey, setBeaconKey] = useState(''); - let defaultUrl = - Platform.OS === 'ios' - ? 'https://api3.siftscience.com/v3/accounts/%@/mobile_events' - : 'https://api3.siftscience.com/v3/accounts/%s/mobile_events'; - let [serverUrlFormat, setServerUrlFormat] = useState(defaultUrl); - let [userId, setUserId] = useState(''); - let [errortext, setErrortext] = useState(''); - const handleSubmitButton = () => { - setErrortext(''); - if (!accountId) { - Alert.alert('Missing Fields', 'Please fill Account ID'); - return; - } - if (!beaconKey) { - Alert.alert('Missing Fields', 'Please fill Beacon Key'); - return; - } - SiftReactNative.setSiftConfig(accountId, beaconKey, true, serverUrlFormat); - SiftReactNative.setUserId(userId); - }; - return ( - - - - - Sift SDK Example - - setAccountId(AccountId)} - placeholder="Enter Account ID" - placeholderTextColor="gray" - autoCapitalize="sentences" - onSubmitEditing={Keyboard.dismiss} - blurOnSubmit={false} - /> - - - setBeaconKey(BeaconKey)} - placeholder="Enter Beacon Key" - placeholderTextColor="gray" - onSubmitEditing={Keyboard.dismiss} - blurOnSubmit={false} - /> - - - setUserId(UserId)} - placeholder="Enter User ID" - placeholderTextColor="gray" - keyboardType="email-address" - onSubmitEditing={Keyboard.dismiss} - blurOnSubmit={false} - /> - - - - setServerUrlFormat(ServerUrlFormat) - } - placeholder="Enter Server URL Format" - placeholderTextColor="gray" - multiline={true} - numberOfLines={2} - onSubmitEditing={Keyboard.dismiss} - blurOnSubmit={false} - /> - - Default: {defaultUrl} - {errortext !== '' ? ( - {errortext} - ) : null} - - UPLOAD - - - - - - ); + return ; }; -const styles = StyleSheet.create({ - container: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - }, - SectionStyle: { - flexDirection: 'row', - height: 40, - marginTop: 20, - marginLeft: 35, - marginRight: 35, - margin: 10, - }, - SectionStyleMultiline: { - flexDirection: 'row', - height: 50, - marginTop: 20, - marginLeft: 35, - marginRight: 35, - margin: 10, - }, - buttonStyle: { - backgroundColor: '#307ecc', - borderWidth: 0, - color: '#FFFFFF', - borderColor: '#7DE24E', - height: 40, - alignItems: 'center', - borderRadius: 30, - marginLeft: 35, - marginRight: 35, - marginTop: 20, - marginBottom: 20, - }, - buttonTextStyle: { - fontWeight: 'bold', - color: 'white', - paddingVertical: 10, - fontSize: 16, - }, - inputStyle: { - flex: 1, - color: 'black', - paddingLeft: 15, - paddingRight: 15, - borderWidth: 1, - borderRadius: 30, - borderColor: '#307ecc', - }, - titleTextStyle: { - color: '#307ecc', - textAlign: 'center', - fontSize: 40, - }, - hintTextStyle: { - color: 'black', - textAlign: 'center', - fontSize: 10, - }, - errorTextStyle: { - color: 'red', - textAlign: 'center', - fontSize: 14, - }, - successTextStyle: { - color: '#307ecc', - textAlign: 'center', - fontSize: 18, - padding: 30, - }, -}); - export default App; diff --git a/example/android/build.gradle b/example/android/build.gradle index 8569fee..eaba435 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -2,6 +2,7 @@ buildscript { ext { + kotlinVersion = "1.6.0" buildToolsVersion = "31.0.0" minSdkVersion = 21 compileSdkVersion = 31 diff --git a/example/babel.config.js b/example/babel.config.js index f842b77..f3b7ace 100644 --- a/example/babel.config.js +++ b/example/babel.config.js @@ -1,3 +1 @@ -module.exports = { - presets: ['module:metro-react-native-babel-preset'], -}; +export const presets = ['module:metro-react-native-babel-preset']; diff --git a/example/package.json b/example/package.json index ea19c0e..3c71ef6 100644 --- a/example/package.json +++ b/example/package.json @@ -11,8 +11,15 @@ "lint": "eslint ." }, "dependencies": { + "@react-native-community/masked-view": "^0.1.11", + "@react-navigation/native": "^6.1.6", + "@react-navigation/native-stack": "^6.9.12", "react": "18.1.0", "react-native": "0.70.6", + "react-native-gesture-handler": "^2.9.0", + "react-native-reanimated": "^3.1.0", + "react-native-safe-area-context": "^4.5.2", + "react-native-screens": "^3.20.0", "sift-react-native": "^0.1.8" }, "devDependencies": { diff --git a/example/root.js b/example/root.js new file mode 100644 index 0000000..4dd51bf --- /dev/null +++ b/example/root.js @@ -0,0 +1,49 @@ +import React, {useEffect} from 'react'; +import {NavigationContainer} from '@react-navigation/native'; +import {createNativeStackNavigator} from '@react-navigation/native-stack'; +import SiftReactNative from 'sift-react-native'; +import ScreenTwo from './screens/screentwo'; +import ScreenOne from './screens/screenone'; + +const Stack = createNativeStackNavigator(); + +const Root = () => { + const routeNameRef = React.useRef(); + const navigationRef = React.useRef(); + + useEffect(() => { + const initialRouteName = routeNameRef.current; + console.log('Initial screen is:', initialRouteName); + SiftReactNative.setPageName(`screen_${initialRouteName}`); + SiftReactNative.upload(); + }, []); + + return ( + + (routeNameRef.current = navigationRef.current.getCurrentRoute().name) + } + onStateChange={() => { + const previousRouteName = routeNameRef.current; + const currentRouteName = navigationRef.current.getCurrentRoute().name; + + if (previousRouteName !== currentRouteName) { + // Replace the line below to add the tracker from a mobile analytics SDK + //alert(`The route changed to ${currentRouteName}`); + console.log('Screen focused is:', currentRouteName); + SiftReactNative.setPageName(`screen_${currentRouteName}`); + SiftReactNative.upload(); + } + + // Save the current route name for later comparison + routeNameRef.current = currentRouteName; + }}> + + + + + + ); +}; +export default Root; diff --git a/example/screens/screenone.js b/example/screens/screenone.js new file mode 100644 index 0000000..810c5f4 --- /dev/null +++ b/example/screens/screenone.js @@ -0,0 +1,194 @@ +import React, {useState} from 'react'; +import { + Alert, + Platform, + StyleSheet, + View, + TextInput, + Text, + KeyboardAvoidingView, + Keyboard, + TouchableOpacity, + ScrollView, + SafeAreaView, +} from 'react-native'; +import SiftReactNative from 'sift-react-native'; + +const ScreenOne = ({navigation}) => { + const handleButtonPress = () => { + navigation.navigate('ScreenTwo'); + }; + + let [accountId, setAccountId] = useState(''); + let [beaconKey, setBeaconKey] = useState(''); + let defaultUrl = + Platform.OS === 'ios' + ? 'https://api3.siftscience.com/v3/accounts/%@/mobile_events' + : 'https://api3.siftscience.com/v3/accounts/%s/mobile_events'; + let [serverUrlFormat, setServerUrlFormat] = useState(defaultUrl); + let [userId, setUserId] = useState(''); + let [errortext, setErrortext] = useState(''); + const handleSubmitButton = () => { + setErrortext(''); + if (!accountId) { + Alert.alert('Missing Fields', 'Please fill Account ID'); + return; + } + if (!beaconKey) { + Alert.alert('Missing Fields', 'Please fill Beacon Key'); + return; + } + SiftReactNative.setSiftConfig(accountId, beaconKey, true, serverUrlFormat); + SiftReactNative.setUserId(userId); + }; + return ( + + + + + Sift SDK Example + + setAccountId(AccountId)} + placeholder="Enter Account ID" + placeholderTextColor="gray" + autoCapitalize="sentences" + onSubmitEditing={Keyboard.dismiss} + blurOnSubmit={false} + /> + + + setBeaconKey(BeaconKey)} + placeholder="Enter Beacon Key" + placeholderTextColor="gray" + onSubmitEditing={Keyboard.dismiss} + blurOnSubmit={false} + /> + + + setUserId(UserId)} + placeholder="Enter User ID" + placeholderTextColor="gray" + keyboardType="email-address" + onSubmitEditing={Keyboard.dismiss} + blurOnSubmit={false} + /> + + + + setServerUrlFormat(ServerUrlFormat) + } + placeholder="Enter Server URL Format" + placeholderTextColor="gray" + multiline={true} + numberOfLines={2} + onSubmitEditing={Keyboard.dismiss} + blurOnSubmit={false} + /> + + Default: {defaultUrl} + {errortext !== '' ? ( + {errortext} + ) : null} + + UPLOAD + + + Next Screen + + + + + + ); +}; +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + SectionStyle: { + flexDirection: 'row', + height: 40, + marginTop: 20, + marginLeft: 35, + marginRight: 35, + margin: 10, + }, + SectionStyleMultiline: { + flexDirection: 'row', + height: 50, + marginTop: 20, + marginLeft: 35, + marginRight: 35, + margin: 10, + }, + buttonStyle: { + backgroundColor: '#307ecc', + borderWidth: 0, + color: '#FFFFFF', + borderColor: '#7DE24E', + height: 40, + alignItems: 'center', + borderRadius: 30, + marginLeft: 35, + marginRight: 35, + marginTop: 20, + marginBottom: 20, + }, + buttonTextStyle: { + fontWeight: 'bold', + color: 'white', + paddingVertical: 10, + fontSize: 16, + }, + inputStyle: { + flex: 1, + color: 'black', + paddingLeft: 15, + paddingRight: 15, + borderWidth: 1, + borderRadius: 30, + borderColor: '#307ecc', + }, + titleTextStyle: { + color: '#307ecc', + textAlign: 'center', + fontSize: 40, + }, + hintTextStyle: { + color: 'black', + textAlign: 'center', + fontSize: 10, + }, + errorTextStyle: { + color: 'red', + textAlign: 'center', + fontSize: 14, + }, + successTextStyle: { + color: '#307ecc', + textAlign: 'center', + fontSize: 18, + padding: 30, + }, +}); + +export default ScreenOne; diff --git a/example/screens/screentwo.js b/example/screens/screentwo.js new file mode 100644 index 0000000..8bbfe0d --- /dev/null +++ b/example/screens/screentwo.js @@ -0,0 +1,25 @@ +import React from 'react'; + +import {Button, Text, View, StyleSheet} from 'react-native'; + +const ScreenTwo = ({navigation}) => { + const handleButtonPress = () => { + navigation.navigate('ScreenOne'); + }; + + return ( + + Second screen + +