Overview
WPM Typing Tutor extends to mobile platforms through React Native with Expo Go , providing a native mobile experience for Android and iOS devices. The mobile app wraps the Unity WebGL build within a WebView, enabling cross-platform gameplay with native mobile integration.
Framework : React Native with Expo SDKDeployment : Expo Go appPlatforms : Android (available), iOS (coming soon)Unity Integration : WebView wrapper
Architecture Overview
The mobile app uses a hybrid architecture:
React Native Shell
A React Native application provides:
Native mobile app structure
Navigation and UI framework
Platform-specific capabilities
App lifecycle management
WebView Container
The game runs inside a WebView component:
Loads Unity WebGL build from hosted URL
Provides JavaScript bridge for native communication
Handles touch input and gestures
Manages viewport and orientation
Unity WebGL Game
The same Unity build used for web:
No modifications needed to Unity code
Touch events work automatically
Responsive to viewport changes
Same gameplay experience
Expo Configuration
Project Setup
The app is configured using Expo’s managed workflow:
{
"expo" : {
"name" : "WPM Typing Tutor" ,
"slug" : "wpm-typing-tutor" ,
"version" : "1.0.0" ,
"orientation" : "landscape" ,
"icon" : "./assets/icon.png" ,
"userInterfaceStyle" : "dark" ,
"splash" : {
"image" : "./assets/splash.png" ,
"resizeMode" : "contain" ,
"backgroundColor" : "#0d0d0d"
},
"platforms" : [ "ios" , "android" ],
"android" : {
"package" : "com.wpm.typingtutor" ,
"adaptiveIcon" : {
"foregroundImage" : "./assets/adaptive-icon.png" ,
"backgroundColor" : "#0d0d0d"
},
"permissions" : []
},
"ios" : {
"bundleIdentifier" : "com.wpm.typingtutor" ,
"supportsTablet" : true
}
}
}
The mobile app uses landscape orientation by default to match the Unity WebGL game’s design and provide the best typing experience.
WebView Integration
Basic Implementation
The core of the mobile app is a WebView component that loads the Unity game:
App.js - Basic WebView
App.js - Advanced with Loading
import React from 'react' ;
import { StyleSheet , View } from 'react-native' ;
import { WebView } from 'react-native-webview' ;
import { StatusBar } from 'expo-status-bar' ;
export default function App () {
return (
< View style = { styles . container } >
< StatusBar hidden />
< WebView
source = { { uri: 'https://your-domain.com' } }
style = { styles . webview }
javaScriptEnabled = { true }
domStorageEnabled = { true }
startInLoadingState = { true }
scalesPageToFit = { true }
allowsFullscreenVideo = { false }
/>
</ View >
);
}
const styles = StyleSheet . create ({
container: {
flex: 1 ,
backgroundColor: '#0d0d0d' ,
},
webview: {
flex: 1 ,
},
});
WebView Configuration Options
Critical WebView props for Unity WebGL:
JavaScript
Storage
Scaling
Loading State
Required for Unity WebGL to run. Unity uses JavaScript extensively for WebGL initialization and game logic.Enables localStorage for save data, settings, and Unity’s PlayerPrefs system. Automatically scales the web content to fit the mobile viewport. startInLoadingState = { true }
Shows a loading indicator while the Unity game initializes.
Unity Touch Events
Unity WebGL automatically handles touch input:
// Unity's Input system maps touch to mouse events:
// - Touch = Mouse click
// - Swipe = Mouse drag
// - Pinch = Not supported (not needed for typing game)
// In Unity C# code, use standard input:
if ( Input . GetMouseButtonDown ( 0 )) {
// Handles both mouse clicks and touch
}
// Or use Unity's Touch API directly:
if ( Input . touchCount > 0 ) {
Touch touch = Input . GetTouch ( 0 );
if ( touch . phase == TouchPhase . Began ) {
// Handle touch
}
}
The typing game requires a physical or virtual keyboard. On mobile devices:
Android: Virtual keyboard automatically appears
iOS: Virtual keyboard can be triggered programmatically
Best experience: External Bluetooth keyboard
Keyboard Management
HTML Input Focus
import React , { useEffect } from 'react' ;
import { Keyboard } from 'react-native' ;
import { WebView } from 'react-native-webview' ;
export default function GameView () {
useEffect (() => {
// Keep keyboard visible during gameplay
const showSubscription = Keyboard . addListener ( 'keyboardDidShow' , () => {
console . log ( 'Keyboard shown' );
});
const hideSubscription = Keyboard . addListener ( 'keyboardDidHide' , () => {
console . log ( 'Keyboard hidden' );
});
return () => {
showSubscription . remove ();
hideSubscription . remove ();
};
}, []);
return (
< WebView
source = { { uri: 'https://your-domain.com' } }
// ... other props
/>
);
}
JavaScript Bridge
Communicate between React Native and Unity using the WebView JavaScript bridge:
React Native to Unity
Unity C# to React Native
JavaScript Bridge Setup
import React , { useRef } from 'react' ;
import { View , Button } from 'react-native' ;
import { WebView } from 'react-native-webview' ;
export default function App () {
const webViewRef = useRef ( null );
// Send message to Unity
const sendToUnity = ( message ) => {
webViewRef . current ?. injectJavaScript ( `
if (window.unityInstance) {
window.unityInstance.SendMessage('GameManager', 'ReceiveMessage', ' ${ message } ');
}
true;
` );
};
return (
< View style = { { flex: 1 } } >
< WebView
ref = { webViewRef }
source = { { uri: 'https://your-domain.com' } }
onMessage = { ( event ) => {
// Receive messages from Unity
const data = JSON . parse ( event . nativeEvent . data );
console . log ( 'Message from Unity:' , data );
} }
/>
< Button
title = "Start Game"
onPress = { () => sendToUnity ( 'START' ) }
/>
</ View >
);
}
Development Workflow
Local Development with Expo
Create Expo Project
npx create-expo-app wpm-typing-tutor-mobile
cd wpm-typing-tutor-mobile
Install Dependencies
npm install react-native-webview
npx expo install expo-splash-screen expo-status-bar
Configure App
Edit app.json with your app configuration (see above examples).
Start Development Server
This opens the Expo Developer Tools in your browser.
Test on Device
Android:
Install Expo Go from Play Store
Scan QR code from Expo Dev Tools
iOS:
Install Expo Go from App Store
Scan QR code with Camera app
During development, you can point the WebView to localhost if running the web server locally, or use a tunneling service like ngrok for remote testing.
Testing with Local Unity Build
Development Configuration
// Point to local development server
const DEV_MODE = __DEV__ ; // Expo provides this automatically
const GAME_URL = DEV_MODE
? 'http://192.168.1.100:3000' // Your local IP
: 'https://your-production-domain.com' ;
export default function App () {
return (
< WebView
source = { { uri: GAME_URL } }
// ... other props
/>
);
}
Android Configuration
Permissions
Adaptive Icon
Build Settings
{
"android" : {
"permissions" : [
// Add only required permissions
// WPM Typing Tutor doesn't need any special permissions
]
}
}
{
"android" : {
"adaptiveIcon" : {
"foregroundImage" : "./assets/adaptive-icon.png" ,
"backgroundColor" : "#0d0d0d"
}
}
}
Create a 1024x1024px icon with transparent background. {
"android" : {
"package" : "com.wpm.typingtutor" ,
"versionCode" : 1 ,
"buildToolsVersion" : "34.0.0" ,
"compileSdkVersion" : 34 ,
"targetSdkVersion" : 34
}
}
iOS Configuration (Coming Soon)
iOS support is planned but not yet available. The configuration is prepared for future release.
{
"ios" : {
"bundleIdentifier" : "com.wpm.typingtutor" ,
"buildNumber" : "1.0.0" ,
"supportsTablet" : true ,
"infoPlist" : {
"UIRequiresFullScreen" : true ,
"UIStatusBarHidden" : true
}
}
}
Hardware Acceleration Enable hardware acceleration in WebView for better Unity performance: < WebView
androidLayerType = "hardware"
// ... other props
/>
Cache Management Enable caching to speed up subsequent loads: < WebView
cacheEnabled = { true }
cacheMode = "LOAD_CACHE_ELSE_NETWORK"
/>
Memory Management Unity WebGL can be memory-intensive. Monitor memory usage and reload WebView if needed.
Loading Optimization Show splash screen during initial load: import * as SplashScreen from 'expo-splash-screen' ;
SplashScreen . preventAutoHideAsync ();
// Hide when WebView loads:
SplashScreen . hideAsync ();
Unity WebGL Mobile Optimization
Unity Mobile Optimizations
// Detect mobile in Unity and adjust settings
if ( Application . platform == RuntimePlatform . WebGLPlayer ) {
// Reduce quality for mobile
if ( IsMobileDevice ()) {
QualitySettings . SetQualityLevel ( 1 ); // Low quality
Application . targetFrameRate = 30 ; // 30 FPS instead of 60
}
}
bool IsMobileDevice () {
string userAgent = Application . platform . ToString ();
return userAgent . Contains ( "Android" ) || userAgent . Contains ( "iOS" );
}
Building for Production
Build Android APK
Configure Build
Ensure app.json has correct Android configuration.
Build with EAS
# Install EAS CLI
npm install -g eas-cli
# Login to Expo
eas login
# Configure build
eas build:configure
# Build APK
eas build --platform android --profile preview
Download APK
EAS Build will provide a download link when complete.
Test APK
Install the APK on Android device:
Publish to Expo Go
# Publish update to Expo Go
npx expo publish
# Users can access via Expo Go app
# using your published URL
For production release to Google Play Store or Apple App Store, use EAS Build to create native binaries.
Troubleshooting
Symptoms : Blank WebView or error messageSolutions :
Check network connection
Verify game URL is accessible
Check if JavaScript is enabled
Look for CORS errors in logs
Try clearing app cache
Unity game not responsive
Symptoms : Touch input doesn’t workSolutions :
Verify javaScriptEnabled={true}
Check Unity build for mobile compatibility
Test with scalesPageToFit={true}
Review Unity Input settings
Symptoms : Virtual keyboard doesn’t showSolutions :
Add hidden input element in HTML
Use Keyboard API to show keyboard
Ensure input is focused
Check device keyboard settings
Next Steps
Deployment Guide Deploy the web build for the mobile app to load
Unity Setup Review Unity WebGL configuration details
Android Platform User guide for Android platform
iOS Platform Coming soon: iOS platform guide