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. 

Group 40 (1)

Learn more how to work with Callbacks and Methods here.