App Integration
App
To run Flarie Studio games in your app you need to create a webview that supports custom interfaces and load your game url in the webview. WKWebView would be our recommended option for iOS.
[recommendation] - in Flarie Studio you can enable Instruction Views. These Instruction View may include tutorial videos. set allowsInlineMediaPlayback in order to prevent the video going into full screen mode.
[App] iOS - Swift
import WebKit
class webGames: UIViewController, WKScriptMessageHandler, WKUIDelegate {
var webView: WKWebView!
var contentController = WKUserContentController()
override func viewDidLoad() {
// this will add support for the callbacks, meaning you will receive a notification with json data for all callbacks
contentController.add(self, name: "callback")
let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true // recommended to prevent full screen video
config.userContentController = contentController
webView = WKWebView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: viewm.frame.height), configuration: config)
self.view.addSubview(webView)
// don’t forget to set the WKUIDelegate otherwise the target='_blank' won’t work
webView.uiDelegate = self
let gameUrl = Foundation.URL(string: "YOUR_GAME_URL")
let gameUrlRequest = URLRequest(url: gameUrl!, cachePolicy: NSURLRequest.CachePolicy.reloadIgnoringCacheData, timeoutInterval: 20)
webView.load(gameUrlRequest)
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print("data: \(message.body)")
}
// allow target _blank for external urls
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
if let frame = navigationAction.targetFrame,
frame.isMainFrame {
return nil
}
if let url = navigationAction.request.url {
// this will allow target _blank on urls
UIApplication.shared.open(URL(string: "\(url)")!)
} else {
webView.load(navigationAction.request)
}
return nil
}
}
[APP] Android - Java
public class Demo {
WebView webView;
private void setupWebView() {
webView = findViewById(R.id.webView);
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new WebAppInterface(context), "Android");
webView.loadUrl("YOUR_GAME_URL");
}
private Handler handler = new Handler();
public class WebAppInterface {
@JavascriptInterface
public void callback(final String message) {
handler.post(new Runnable() {
@Override
public void run() {
Log.d("", "Data: " + message);
}
});
}
}
}
[APP] React Native
import React, { useRef } from 'react';
import { SafeAreaView, StyleSheet, Linking } from 'react-native';
import { WebView, WebViewNavigation } from 'react-native-webview';
const App = () => {
const webViewRef = useRef(null);
// Handle post messages (callbacks) sent from the webView
const onMessage = (event: any) => {
const message = event.nativeEvent.data;
console.log(message);
if (message === 'game') {
console.log('the game has ended');
}
};
// allows external links (target='_blank') always open in the default browser
const handleExternalLink = (event: WebViewNavigation) => {
if (event.navigationType === 'click') {
Linking.openURL(event.url);
return false;
}
return true;
};
return (
<SafeAreaView style={styles.container}>
<WebView
ref={webViewRef}
source= // Replace with your actual game URL
onMessage={onMessage}
onShouldStartLoadWithRequest={handleExternalLink}
javaScriptEnabled={true}
domStorageEnabled={true}
allowsInlineMediaPlayback={true}
style=
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
});
export default App;
[APP] Flutter
// in pubspec.yaml add dependency for webview
// webview_flutter: ^4.10.0
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
class MyGameView extends StatefulWidget {
const MyGameView({super.key});
@override
State<MyGameView> createState() => MyGameViewState();
}
class MyGameViewState extends State<MyGameView> {
late final WebViewController controller;
late final WebViewWidget webViewWidget;
@override
void initState() {
super.initState();
initializeWebView();
loadGameUrl();
}
void initializeWebView() {
final PlatformWebViewControllerCreationParams params = getWebViewParams();
controller = WebViewController.fromPlatformCreationParams(params)
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel("Flutter", onMessageReceived: handleCallback)
..enableZoom(false);
webViewWidget = WebViewWidget(controller: controller);
}
PlatformWebViewControllerCreationParams getWebViewParams() {
// For ios devices
if (WebViewPlatform.instance is WebKitWebViewPlatform) {
return WebKitWebViewControllerCreationParams(
allowsInlineMediaPlayback: true,
mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
);
}
// For android devices
return const PlatformWebViewControllerCreationParams();
}
Future<void> loadGameUrl() async {
const String url = "{YOUR_GAME_URL}";
await controller.clearCache();
controller.loadRequest(Uri.parse(url));
}
void handleCallback(JavaScriptMessage message) {
try {
final Map<String, dynamic> data = jsonDecode(message.message);
final String callback = data.keys.first;
switch (callback) {
case 'loading':
loadingCallback(data['loading']);
break;
case 'game':
gameOverCallback(data['game']);
break;
default:
print('callback: $callback');
break;
}
} catch (e) {
print('Error parsing JavaScript callback: $e');
}
}
void loadingCallback(dynamic payload) {
final Map<String, dynamic>? loadProgress = payload as Map<String, dynamic>?;
final int? progress = parseInt(loadProgress?['progress']);
if (progress != null) {
if (progress == 100) {
print('callback loading: game finished loading, progress 100');
} else {
print('callback loading: progress: $progress');
}
}
}
void gameOverCallback(dynamic payload) {
final Map<String, dynamic>? gameData = payload as Map<String, dynamic>?;
final int? time = parseInt(gameData?['time']);
final int? score = parseInt(gameData?['score']);
print('Game Over callback: Time: $time, Score: $score');
}
int? parseInt(dynamic value) {
if (value is int) return value;
if (value is double) return value.floor();
if (value is String) return int.tryParse(value);
return null;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: webViewWidget,
);
}
}
Best Practices
Since the games are integrated within a WebView, you have complete control to add optional elements and functionalities on top of the WebView to enhance the overall experience. For instance, you can introduce custom controls to improve user interaction during gameplay, such as a custom close button or dynamic functionality that adapts based on whether the user is actively playing the game. See example below.
Learn more how to work with Callbacks and Methods here.