Using BLE technology when working with beacon in React Native
15 minutes
Mobile
Jun 28, 2024
14 minutes
const UnityScreen: React.FC<RootStackScreenProps<RootRoutes.UNITY>> = ({
route,
}) => {
// Start
const unityRef = useRef<UnityView>(null);
const {messageToUnity} = route.params;
useEffect(() => {
if (messageToUnity) {
unityRef.current?.postMessage('', '', messageToUnity);
}
}, [messageToUnity]);
const handleUnityMessage = (message: string) => {
console.log(message);
};
//End
return (
<UnityView
ref={unityRef}
//@ts-expect-error UnityView needs a 'flex: 1' style to show full screen view
style={styles.flex}
onUnityMessage={e => handleUnityMessage(e.nativeEvent.message)} // and this line
/>
);
};
type RootStackParamList = {
[RootRoutes.HOME]: undefined;
[RootRoutes.UNITY]: {messageToUnity?: string}; // added messageToUnity
};
// score data type
type Score = {
date: string;
score: number;
};
const HomeScreen: React.FC<RootStackScreenProps<RootRoutes.HOME>> = ({
navigation,
}) => {
const [scores, setScores] = useState<Score[]>([]); // scores to display in list
const insets = useSafeAreaInsets();
//List item to render
const renderScore: ListRenderItem<Score> = useCallback(({item, index}) => {
return (
<View style={styles.score}>
<Text style={styles.scoreText}>{index + 1}.</Text>
<Text style={[styles.scoreText, styles.flex]}>{item.score}</Text>
<Text style={styles.scoreDate}>{item.date}</Text>
</View>
);
}, []);
return (
<View style={[styles.screen, {paddingBottom: Math.max(insets.bottom, 15)}]}>
<Text style={styles.welcomeText}>
Hello, from{' '}
<Text style={[styles.welcomeText, styles.purple]}>dev.family</Text> team
</Text>
{/** scoreboard */}
<Text style={styles.welcomeText}>Scores 🏆:</Text>
<FlatList
data={scores}
renderItem={renderScore}
keyExtractor={i => i.date}
style={styles.list}
contentContainerStyle={styles.listContent}
ListEmptyComponent={<Text>You have no scoreboard yet</Text>}
/>
<TouchableOpacity
style={styles.button}
onPress={() => {
navigation.navigate(RootRoutes.UNITY, {messageToUnity: ''});
}}>
<Text style={styles.buttonText}>Go Unity</Text>
</TouchableOpacity>
</View>
);
};
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine.UI;
using UnityEngine;
public class NativeAPI {
#if UNITY_IOS && !UNITY_EDITOR
[DllImport("__Internal")]
public static extern void sendMessageToMobileApp(string message);
#endif
}
public class ButtonBehavior : MonoBehaviour
{
public void ButtonPressed()
{
if (Application.platform == RuntimePlatform.Android)
{
using (AndroidJavaClass jc = new AndroidJavaClass("com.azesmwayreactnativeunity.ReactNativeUnityViewManager"))
{
jc.CallStatic("sendMessageToMobileApp", "The button has been tapped!");
}
}
else if (Application.platform == RuntimePlatform.IPhonePlayer)
{
#if UNITY_IOS && !UNITY_EDITOR
NativeAPI.sendMessageToMobileApp("The button has been tapped!");
#endif
}
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine.UI;
using UnityEngine;
public class NativeAPI
{
#if UNITY_IOS && !UNITY_EDITOR
[DllImport("__Internal")]
public static extern void sendMessageToMobileApp(string message);
#endif
}
//Score class
public class Score
{
public int score;
public string date;
public Score(int score, string date)
{
this.score = score;
this.date = date;
}
}
public class MessageToReactScript : MonoBehaviour
{
private Text _score;
public void ButtonPressed()
{
//getting current date in ISO format
var date = DateTime.Now.ToString("o");
//getting current score from UI
_score = GameObject.FindGameObjectWithTag("Score").GetComponent();
//creating an instance of Score class
Score score = new(int.Parse(_score.text), date);
//pare score object to json (we sending a string)
string scoreJSON = JsonUtility.ToJson(score);
print(scoreJSON);
if (Application.platform == RuntimePlatform.Android)
{
using (AndroidJavaClass jc = new AndroidJavaClass("com.azesmwayreactnativeunity.ReactNativeUnityViewManager"))
{
//send message to android
jc.CallStatic("sendMessageToMobileApp", scoreJSON);
}
}
else if (Application.platform == RuntimePlatform.IPhonePlayer)
{
#if UNITY_IOS && !UNITY_EDITOR
//send message to iOS
NativeAPI.sendMessageToMobileApp(scoreJSON);
#endif
}
}
}
const handleUnityMessage = (message: string) => {
//alert to show Unity message data
alert(message);
const score = JSON.parse(message) as Score;
console.log({score});
};
yarn add @react-native-async-storage/async-storage
npx pod-install или cd ios && pod install && cd ..
const handleUnityMessage = (message: string) => {
//alert to show Unity message data
const score = JSON.parse(message) as Score; //parse message to Score Object
if (score) {
unityRef.current?.unloadUnity();//unload Unity View
navigation.navigate(RootRoutes.HOME, {score}); //going to Home Screen with recent score
}
};
type RootStackParamList = {
[RootRoutes.HOME]: {score?: Score}; // can get Score from Unity Screen
[RootRoutes.UNITY]: {messageToUnity?: string}; // added messageToUnity
};
//func to setup scores from async storage on app open (we have no scores)
const setupScores = async () => {
const scoresJSON = await AsyncStorage.getItem('scores');
if (scoresJSON) {
setScores(JSON.parse(scoresJSON) as Score[]);
}
};
//setting up existed scores
useEffect(() => {
if (!scores.length) {
setupScores();
}
});
const setNewScores = async (score: Score) => {
//creating new scores with new one, includes filter & sort to show only 10 best results
const newScores = [...scores, score]
.sort((a, b) => b.score - a.score)
.slice(0, 10);
//setting new scores to async storage
await AsyncStorage.setItem('scores', JSON.stringify(newScores));
//setting new scores to scores' state
setScores(newScores);
//clean navigation score param
navigation.setParams({score: undefined});
};
useEffect(() => {
if (route.params?.score) {
setNewScores(route.params.score);
}
}, [route.params]);
const {messageToUnity} = route.params;
useEffect(() => {
if (messageToUnity) {
unityRef.current?.postMessage('', '', messageToUnity);// right here
}
}, [messageToUnity]);
(gameObject: string, methodName: string, message: string)
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using TMPro;
public class LogicManagerScript : MonoBehaviour
{
private int _score;
[SerializeField]
private Text _scoreText;
[SerializeField]
private GameObject _gameOverScreen;
//best score field where we write value from RN part
public TextMeshProUGUI bestScoreText;
[SerializeField]
private GameObject _startScreen;
[SerializeField]
private GameObject _game;
[ContextMenu("Increase Score")]
public void IncreaseScore(int number)
{
_score += number;
_scoreText.text = _score.ToString();
}
//rewrite bestScoreText value with RN message
public void SetBestScore(string message)
{
bestScoreText.text = message;
}
public void RestartGame()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
public void GameOver()
{
_gameOverScreen.SetActive(true);
}
public void StartGame()
{
_startScreen.SetActive(false);
_game.SetActive(true);
}
}
const goUnity = () => {
let messageToUnity = '0';// set default value to 0
if (scores.length) {
// if we have scores select max value
// element with 0 index is the highest because we’ve sorted our scores
messageToUnity = scores[0].score.toString();
}
//go to Unity with max score = messageToUnity
navigation.navigate(RootRoutes.UNITY, {messageToUnity});
};
<TouchableOpacity style={styles.button} onPress={goUnity}>//added a method
<Text style={styles.buttonText}>Go Unity</Text>
</TouchableOpacity>
const {messageToUnity} = route.params; // getting our message from route params
//creating message object (not necessary)
const message = {
gameObject: 'LogicManager',
method: 'SetBestScore',
message: messageToUnity,
};
//on getting message from route posting it to Unity
useEffect(() => {
if (messageToUnity) {
unityRef.current?.postMessage(
message.gameObject,
message.method,
message.message,
);
}
}, [messageToUnity]);
import React, {useCallback, useEffect, useRef, useState} from 'react';
import {NavigationContainer} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import {
FlatList,
ListRenderItem,
StatusBar,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import {
SafeAreaProvider,
useSafeAreaInsets,
} from 'react-native-safe-area-context';
import UnityView from '@azesmway/react-native-unity';
import AsyncStorage from '@react-native-async-storage/async-storage';
import type {NativeStackScreenProps} from '@react-navigation/native-stack';
enum RootRoutes {
HOME = 'Home',
UNITY = 'Unity',
}
type RootStackParamList = {
[RootRoutes.HOME]: {score?: Score};
[RootRoutes.UNITY]: {messageToUnity: string}; // added messageToUnity
};
type RootStackScreenProps<RN extends keyof RootStackParamList = RootRoutes> =
NativeStackScreenProps<RootStackParamList, RN>;
const Stack = createNativeStackNavigator<RootStackParamList>();
// score data type
type Score = {
date: string;
score: number;
};
const HomeScreen: React.FC<RootStackScreenProps<RootRoutes.HOME>> = ({
navigation,
route,
}) => {
const [scores, setScores] = useState<Score[]>([]); // scores to display in list
const insets = useSafeAreaInsets();
//func to setup scores from async storage on app open (we have no scores)
const setupScores = async () => {
const scoresJSON = await AsyncStorage.getItem('scores');
if (scoresJSON) {
setScores(JSON.parse(scoresJSON) as Score[]);
}
};
//setting up existed scores
useEffect(() => {
if (!scores.length) {
setupScores();
}
});
const setNewScores = async (score: Score) => {
//creating new scores with new one, includes filter & sort to show only 10 best results
const newScores = [...scores, score]
.sort((a, b) => b.score - a.score)
.slice(0, 10);
//setting new scores to async storage
await AsyncStorage.setItem('scores', JSON.stringify(newScores));
//setting new scores to scores' state
setScores(newScores);
//clean navigation score param
navigation.setParams({score: undefined});
};
useEffect(() => {
if (route.params?.score) {
setNewScores(route.params.score);
}
}, [route.params]);
const goUnity = () => {
let messageToUnity = '0';
if (scores.length) {
messageToUnity = scores[0].score.toString();
}
navigation.navigate(RootRoutes.UNITY, {messageToUnity});
};
//List item to render
const renderScore: ListRenderItem<Score> = useCallback(({item, index}) => {
return (
<View style={styles.score}>
<Text style={styles.scoreText}>{index + 1}.</Text>
<Text style={[styles.scoreText, styles.flex]}>{item.score}</Text>
<Text style={styles.scoreDate}>
{new Date(item.date).toLocaleString()}
</Text>
</View>
);
}, []);
return (
<View style={[styles.screen, {paddingBottom: Math.max(insets.bottom, 15)}]}>
<Text style={styles.welcomeText}>
Hello, from{' '}
<Text style={[styles.welcomeText, styles.purple]}>dev.family</Text> team
</Text>
{/** scoreboard */}
<Text style={styles.welcomeText}>Scores 🏆:</Text>
{!!scores.length && (
<View style={[styles.row, styles.scoreInfo]}>
<Text style={[styles.scoreText, styles.flex]}>Score</Text>
<Text style={styles.scoreText}>Date</Text>
</View>
)}
<FlatList
data={scores}
renderItem={renderScore}
keyExtractor={i => i.date}
style={styles.list}
contentContainerStyle={styles.listContent}
ListEmptyComponent={<Text>You have no scoreboard yet</Text>}
/>
<TouchableOpacity style={styles.button} onPress={goUnity}>
<Text style={styles.buttonText}>Go Unity</Text>
</TouchableOpacity>
</View>
);
};
const UnityScreen: React.FC<RootStackScreenProps<RootRoutes.UNITY>> = ({
route,
navigation,
}) => {
// Start
const unityRef = useRef<UnityView>(null);
const {messageToUnity} = route.params;
const message = {
gameObject: 'LogicManager',
method: 'SetBestScore',
message: messageToUnity,
};
useEffect(() => {
if (messageToUnity) {
unityRef.current?.postMessage(
message.gameObject,
message.method,
message.message,
);
}
}, [messageToUnity]);
const handleUnityMessage = (json: string) => {
//alert to show Unity message data
const score = JSON.parse(json) as Score;
if (score) {
// unityRef.current?.unloadUnity();
navigation.navigate(RootRoutes.HOME, {score});
unityRef.current?.unloadUnity();
}
};
//End
return (
<View style={styles.flex}>
<UnityView
ref={unityRef}
//@ts-expect-error UnityView needs a 'flex: 1' style to show full screen view
style={styles.flex}
onUnityMessage={e => handleUnityMessage(e.nativeEvent.message)} // and this line
/>
</View>
);
};
const App = () => {
return (
<View style={styles.flex}>
<StatusBar backgroundColor={'#FFF'} barStyle="dark-content" />
<SafeAreaProvider>
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerTintColor: 'purple',
headerTitleStyle: {color: 'black'},
}}>
<Stack.Screen name={RootRoutes.HOME} component={HomeScreen} />
<Stack.Screen name={RootRoutes.UNITY} component={UnityScreen} />
</Stack.Navigator>
</NavigationContainer>
</SafeAreaProvider>
</View>
);
};
const styles = StyleSheet.create({
screen: {
flex: 1,
paddingHorizontal: 16,
gap: 30,
paddingTop: 25,
},
button: {
width: '100%',
backgroundColor: 'purple',
justifyContent: 'center',
alignItems: 'center',
height: 50,
borderRadius: 16,
marginTop: 'auto',
},
purple: {color: 'purple'},
buttonText: {
color: '#FFF',
fontSize: 16,
fontWeight: '600',
},
welcomeText: {
fontSize: 24,
color: 'black',
fontWeight: '600',
},
flex: {
flex: 1,
},
row: {flexDirection: 'row'},
scoreInfo: {paddingHorizontal: 14},
score: {
flexDirection: 'row',
alignItems: 'center',
gap: 6,
paddingBottom: 6,
borderBottomWidth: 1,
borderColor: '#bcbcbc',
},
scoreText: {
fontSize: 18,
fontWeight: '500',
color: 'black',
},
scoreDate: {
color: '#262626',
fontSize: 16,
fontWeight: '400',
},
list: {
flex: 1,
},
listContent: {
flexGrow: 1,
paddingBottom: 20,
gap: 12,
},
});
export default App;