Skip to content

Commit

Permalink
Merge pull request #6 from lytics/intermediary/interface/5-type-script
Browse files Browse the repository at this point in the history
Add TypeScript Interface
  • Loading branch information
mgacy authored Jan 17, 2024
2 parents 32ae625 + f319405 commit bf58295
Show file tree
Hide file tree
Showing 14 changed files with 481 additions and 114 deletions.
75 changes: 38 additions & 37 deletions android/src/main/java/com/lytics/react_native/LyticsModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,53 +54,62 @@ class LyticsModule(reactContext: ReactApplicationContext) :

@ReactMethod
fun user(promise: Promise) {
promise.resolve(Lytics.currentUser?.toHashMap())
promise.resolve(Lytics.currentUser?.toReadableMap())
}

// Configuration

@ReactMethod
fun start(apiToken: String, options: ReadableMap) {
val uploadInterval = options.getDoubleOrNull("uploadInterval") ?: DEFAULT_UPLOAD_INTERVAL
val sessionTimeout = options.getDoubleOrNull("sessionTimeout") ?: DEFAULT_SESSION_TIMEOUT
fun start(configuration: ReadableMap, promise: Promise) {
val apiToken = configuration.getString("apiToken")
if (apiToken == null) {
promise.reject("MISSING_API_TOKEN", "The API token is missing in the configuration.")
return
}
val uploadInterval =
configuration.getDoubleOrNull("uploadInterval") ?: DEFAULT_UPLOAD_INTERVAL
val sessionTimeout =
configuration.getDoubleOrNull("sessionTimeout") ?: DEFAULT_SESSION_TIMEOUT
val requestTimeout =
options.getDoubleOrNull("networkRequestTimeout") ?: DEFAULT_NETWORK_REQUEST_TIMEOUT
configuration.getDoubleOrNull("networkRequestTimeout")
?: DEFAULT_NETWORK_REQUEST_TIMEOUT
val logLevel =
options.getString("logLevel")?.let { LogLevel::class.byNameIgnoreCaseOrNull(it) }
configuration.getIntOrNull("logLevel")?.let { LogLevel::class.fromLevelOrNull(it) }
?: LogLevel.NONE

val config = LyticsConfiguration(
apiKey = apiToken,
defaultStream = options.getString("defaultStream") ?: DEFAULT_STREAM,
primaryIdentityKey = options.getString("primaryIdentityKey")
defaultStream = configuration.getString("defaultStream") ?: DEFAULT_STREAM,
primaryIdentityKey = configuration.getString("primaryIdentityKey")
?: LyticsConfiguration.DEFAULT_PRIMARY_IDENTITY_KEY,
anonymousIdentityKey = options.getString("anonymousIdentityKey")
anonymousIdentityKey = configuration.getString("anonymousIdentityKey")
?: LyticsConfiguration.DEFAULT_ANONYMOUS_IDENTITY_KEY,
defaultTable = options.getString("defaultTable")
defaultTable = configuration.getString("defaultTable")
?: LyticsConfiguration.DEFAULT_ENTITY_TABLE,
requireConsent = options.getBooleanOrNull("requireConsent") ?: false,
autoTrackActivityScreens = options.getBooleanOrNull("autoTrackActivityScreens")
requireConsent = configuration.getBooleanOrNull("requireConsent") ?: false,
autoTrackActivityScreens = configuration.getBooleanOrNull("autoTrackActivityScreens")
?: false,
autoTrackFragmentScreens = options.getBooleanOrNull("autoTrackFragmentScreens")
autoTrackFragmentScreens = configuration.getBooleanOrNull("autoTrackFragmentScreens")
?: false,
autoTrackAppOpens = options.getBooleanOrNull("autoTrackAppOpens") ?: false,
maxQueueSize = options.getIntOrNull("maxQueueSize") ?: DEFAULT_MAX_QUEUE_SIZE,
maxUploadRetryAttempts = options.getIntOrNull("maxUploadRetryAttempts")
autoTrackAppOpens = configuration.getBooleanOrNull("autoTrackAppOpens") ?: false,
maxQueueSize = configuration.getIntOrNull("maxQueueSize") ?: DEFAULT_MAX_QUEUE_SIZE,
maxUploadRetryAttempts = configuration.getIntOrNull("maxUploadRetryAttempts")
?: DEFAULT_MAX_UPLOAD_RETRY_ATTEMPTS,
maxLoadRetryAttempts = options.getIntOrNull("maxLoadRetryAttempts")
maxLoadRetryAttempts = configuration.getIntOrNull("maxLoadRetryAttempts")
?: DEFAULT_MAX_LOAD_RETRY_ATTEMPTS,
uploadInterval = TimeUnit.SECONDS.toMillis(uploadInterval.toLong()),
sessionTimeout = TimeUnit.MINUTES.toMillis(sessionTimeout.toLong()),
logLevel = logLevel,
sandboxMode = options.getBooleanOrNull("sandboxMode") ?: false,
collectionEndpoint = options.getString("collectionEndpoint")
sandboxMode = configuration.getBooleanOrNull("sandboxMode") ?: false,
collectionEndpoint = configuration.getString("collectionEndpoint")
?: LyticsConfiguration.DEFAULT_COLLECTION_ENDPOINT,
entityEndpoint = options.getString("entityEndpoint")
entityEndpoint = configuration.getString("entityEndpoint")
?: LyticsConfiguration.DEFAULT_ENTITY_ENDPOINT,
networkRequestTimeout = TimeUnit.SECONDS.toMillis(requestTimeout.toLong()).toInt()
)

Lytics.init(context = context, configuration = config)
promise.resolve(null)
}

// Events
Expand Down Expand Up @@ -182,24 +191,16 @@ class LyticsModule(reactContext: ReactApplicationContext) :
// Personalization

@ReactMethod
fun getProfile(promise: Promise) {
getProfile(
identifier = null,
promise = promise
)
}

@ReactMethod
fun getProfile(name: String, value: String, promise: Promise) {
getProfile(
identifier = EntityIdentifier(name = name, value = value),
promise = promise
)
}

private fun getProfile(identifier: EntityIdentifier?, promise: Promise) {
fun getProfile(name: String? = null, value: String? = null, promise: Promise) {
var identifier: EntityIdentifier? = null
if (name != null && value != null) {
identifier = EntityIdentifier(
name = name,
value = value
)
}
scope.launch {
promise.resolve(Lytics.getProfile(identifier)?.toHashMap())
promise.resolve(Lytics.getProfile(identifier)?.toReadableMap())
}
}

Expand Down
56 changes: 47 additions & 9 deletions android/src/main/java/com/lytics/react_native/Util.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.lytics.react_native

import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.lytics.android.LyticsUser
import com.lytics.android.logging.LogLevel
Expand Down Expand Up @@ -29,15 +31,51 @@ fun ReadableMap.getIntOrNull(key: String): Int? {
}
}

fun KClass<LogLevel>.byNameIgnoreCaseOrNull(level: String): LogLevel? {
return LogLevel.values().firstOrNull { it.name.equals(level, true) }
fun KClass<LogLevel>.fromLevelOrNull(level: Int): LogLevel? {
return LogLevel.values().firstOrNull { it.ordinal == level }
}

fun LyticsUser.toHashMap(): HashMap<String, Any?> {
return hashMapOf(
"identifiers" to this.identifiers,
"attributes" to this.attributes,
"consent" to this.consent,
"profile" to this.profile
)
fun LyticsUser.toReadableMap(): ReadableMap {
val readableMap = Arguments.createMap()
this.identifiers?.let {
readableMap.putMap("identifiers", createReadableMapFromMap(it))
}
this.attributes?.let {
readableMap.putMap("attributes", createReadableMapFromMap(it))
}
this.consent?.let {
readableMap.putMap("consent", createReadableMapFromMap(it))
}
this.profile?.let {
readableMap.putMap("profile", createReadableMapFromMap(it))
}
return readableMap
}

fun createReadableMapFromMap(map: Map<String, Any?>): ReadableMap = Arguments.createMap().apply {
map.forEach { (key, value) ->
when (value) {
is Int -> putInt(key, value)
is Double -> putDouble(key, value)
is String -> putString(key, value)
is Boolean -> putBoolean(key, value)
is Map<*, *> -> putMap(key, createReadableMapFromMap(value as Map<String, Any?>))
is List<*> -> putArray(key, createReadableArrayFromList(value))
else -> putNull(key)
}
}
}

fun createReadableArrayFromList(list: List<*>): ReadableArray = Arguments.createArray().apply {
list.forEach { item ->
when (item) {
is Int -> pushInt(item)
is Double -> pushDouble(item)
is String -> pushString(item)
is Boolean -> pushBoolean(item)
is Map<*, *> -> pushMap(createReadableMapFromMap(item as Map<String, Any?>))
is List<*> -> pushArray(createReadableArrayFromList(item))
else -> pushNull()
}
}
}
2 changes: 2 additions & 0 deletions example/ios/SdkExample/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,7 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>NSUserTrackingUsageDescription</key>
<string>Your data will be used to deliver personalized ads to you.</string>
</dict>
</plist>
40 changes: 25 additions & 15 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react';
import React, { useEffect } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { start, hasStarted } from 'react-native-sdk';
import { start, hasStarted, LogLevel } from 'react-native-sdk';

import { EventsTabNavigator } from './navigation/EventsTabNavigator';
import { LoginTabNavigator } from './navigation/LoginTabNavigator';
Expand All @@ -11,34 +11,44 @@ import { SettingsScreen } from './screens/Settings';
const Tab = createBottomTabNavigator();

export default function App() {
const [result, setResult] = React.useState<boolean | undefined>();

// WARNING: this is not a safe way to store your API token!
const apiToken = 'xxxxxx';

React.useEffect(() => {
useEffect(() => {
// Configure the SDK at startup
start(apiToken, {});
hasStarted().then(setResult);
}, []);
start({
apiToken: apiToken,
logLevel: LogLevel.debug,
});

console.log('hasStarted:', result);
hasStarted().then((result) => {
console.log('hasStarted:', result);
});
}, []);

return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen
name="Events"
name="EventsTab"
component={EventsTabNavigator}
options={{ headerShown: false }}
options={{ headerShown: false, title: 'Events' }}
/>
<Tab.Screen
name="Login"
name="LoginTab"
component={LoginTabNavigator}
options={{ headerShown: false }}
options={{ headerShown: false, title: 'Login' }}
/>
<Tab.Screen
name="ProfileTab"
component={ProfileScreen}
options={{ title: 'Profile' }}
/>
<Tab.Screen
name="SettingsTab"
component={SettingsScreen}
options={{ title: 'Settings' }}
/>
<Tab.Screen name="Profile" component={ProfileScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
</NavigationContainer>
);
Expand Down
15 changes: 14 additions & 1 deletion example/src/screens/EventDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
import * as React from 'react';
import React, { useEffect } from 'react';
import { View, Text, Button } from 'react-native';
import { screen, track } from 'react-native-sdk';

import { styles } from '../components/Styles';

export function EventDetailScreen() {
useEffect(() => {
console.log('Load Event Detail');
screen({
name: 'Event_Detail',
properties: { artistID: 123, eventID: 345 },
});
}, []);

const handleBuy = () => {
console.log('Buy');
track({
name: 'Buy_Tickets',
properties: { artistID: 123, eventID: 345 },
});
};

return (
Expand Down
2 changes: 2 additions & 0 deletions example/src/screens/Events.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import { View, Text, Button } from 'react-native';
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import { track } from 'react-native-sdk';

import { styles } from '../components/Styles';
import type { EventsStackParams } from '../navigation/EventsStackParams';
Expand All @@ -10,6 +11,7 @@ export function EventsScreen({
}: NativeStackScreenProps<EventsStackParams, 'Events'>) {
const handleSelect = () => {
console.log('Select');
track({ name: 'Event_Tap', properties: { event: 'Event_Tap' } });
navigation.navigate('EventDetail');
};

Expand Down
2 changes: 2 additions & 0 deletions example/src/screens/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { View, Text, TextInput, Button } from 'react-native';
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import { identify } from 'react-native-sdk';

import { styles } from '../components/Styles';
import type { LoginStackParams } from '../navigation/LoginStackParams';
Expand All @@ -13,6 +14,7 @@ export function LoginScreen({

const handleLogin = () => {
console.log('Login');
identify({ identifiers: { email: email } });
};

const handleRegister = () => {
Expand Down
21 changes: 18 additions & 3 deletions example/src/screens/Profile.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
import * as React from 'react';
import React, { useEffect, useState } from 'react';
import { View, Text, ScrollView } from 'react-native';
import { getProfile } from 'react-native-sdk';

import { styles } from '../components/Styles';

export function ProfileScreen() {
const [data, setData] = React.useState({});
const [data, setData] = useState({});

const loadProfile = async () => {
try {
const profile = await getProfile();
console.log('Profile:', profile);
setData(profile);
} catch (error) {
console.error(error);
}
};

useEffect(() => {
loadProfile();
}, []);

return (
<View style={styles.container}>
Expand All @@ -13,7 +28,7 @@ export function ProfileScreen() {
format:
</Text>
<ScrollView style={styles.scrollView}>
<Text>{JSON.stringify(data)}</Text>
<Text>{JSON.stringify(data, null, 2)}</Text>
</ScrollView>
</View>
);
Expand Down
Loading

0 comments on commit bf58295

Please sign in to comment.