This guide explains how to use the NativeAPI pattern for Unity-Flutter communication in the Game Framework.
The NativeAPI system provides three levels of abstraction for Unity-Flutter communication:
- NativeAPI.cs - Low-level platform bridge (similar to flutter-unity-view-widget)
- UnityMessageManager.cs - High-level message routing with callbacks
- MessageHandler.cs - Type-safe message handling
Flutter (Dart)
↕
Native Bridge (Kotlin/Swift)
↕
Unity C# Scripts
├── NativeAPI.cs (Low-level)
├── UnityMessageManager.cs (Manager)
└── MessageHandler.cs (Type-safe)
Direct platform-specific communication with minimal overhead. Based on Unity as a Library pattern.
- Platform-specific native calls (iOS/Android)
- Lifecycle event management
- Direct message passing
- Unity player control (pause, quit, unload)
using Xraph.GameFramework.Unity;
public class GameBootstrap : MonoBehaviour
{
void Start()
{
// Initialize NativeAPI (call once)
NativeAPI.Initialize();
// Notify Flutter when ready
NativeAPI.NotifyUnityReady();
}
}// Simple message
NativeAPI.SendMessageToFlutter(
"Flutter",
"onGameEvent",
"{\"event\":\"levelComplete\",\"score\":1000}"
);
// Structured message
NativeAPI.SendMessageToFlutter(
target: "GameManager",
method: "onPlayerAction",
data: JsonUtility.ToJson(actionData)
);void Start()
{
// Subscribe to messages
NativeAPI.OnMessageReceived += HandleMessage;
}
void HandleMessage(string message)
{
Debug.Log($"Received from Flutter: {message}");
// Parse and handle message
var msg = JsonUtility.FromJson<MessageData>(message);
// ... process message
}
void OnDestroy()
{
// Unsubscribe
NativeAPI.OnMessageReceived -= HandleMessage;
}// Subscribe to lifecycle events
NativeAPI.OnUnityReady += () => Debug.Log("Unity ready!");
NativeAPI.OnUnityPaused += (paused) => Debug.Log($"Unity paused: {paused}");
NativeAPI.OnSceneLoaded += (name, index) => Debug.Log($"Scene loaded: {name}");
// Control Unity
NativeAPI.Pause(true); // Pause Unity
NativeAPI.Pause(false); // Resume Unity
NativeAPI.ShowHostMainWindow(); // Show Flutter window (iOS)
NativeAPI.QuitUnity(); // Quit Unity// Called from Unity C#
extern "C" {
void _sendMessageToFlutter(const char* message);
void _notifyUnityReady();
void _showHostMainWindow();
void _unloadUnity();
void _quitUnity();
}// Called via JNI from Unity
fun onUnityMessage(target: String, method: String, data: String) {
sendEventToFlutter("onMessage", mapOf(
"target" to target,
"method" to method,
"data" to data
))
}Provides message routing, callbacks, event system, and thread-safe queuing.
- Singleton pattern for centralized communication
- Request-response pattern with callbacks
- Event listener system
- Thread-safe message queuing
- Automatic JSON serialization
using Xraph.GameFramework.Unity;
public class GameController : MonoBehaviour
{
void Start()
{
// Send message to Flutter
UnityMessageManager.Instance.SendToFlutter(
"Flutter",
"onGameStart",
"Level 1"
);
// Send typed object
var gameState = new GameState { score = 100, level = 1 };
UnityMessageManager.Instance.SendToFlutter(
"Flutter",
"onGameState",
gameState
);
}
}// Send message and wait for response
int callbackId = UnityMessageManager.Instance.SendToFlutterWithCallback(
"Flutter",
"getPlayerData",
"{\"playerId\":\"123\"}",
(response) =>
{
Debug.Log($"Received response: {response}");
var playerData = JsonUtility.FromJson<PlayerData>(response);
// Process player data
}
);void Start()
{
// Register event listener
UnityMessageManager.Instance.AddEventListener(
"Flutter:Command",
HandleFlutterCommand
);
// Listen to Unity events
UnityMessageManager.Instance.AddEventListener(
"Unity:Ready",
(data) => Debug.Log("Unity ready!")
);
}
void HandleFlutterCommand(string data)
{
Debug.Log($"Command from Flutter: {data}");
}
void OnDestroy()
{
// Unregister listener
UnityMessageManager.Instance.RemoveEventListener(
"Flutter:Command",
HandleFlutterCommand
);
}// Trigger event to all listeners
UnityMessageManager.Instance.TriggerEvent(
"Game:LevelComplete",
"{\"level\":1,\"score\":1000}"
);Type-safe message handling with automatic deserialization and validation.
- Generic type handlers
- Automatic JSON deserialization
- Message queuing and throttling
- Built-in message types
- Request-response with types
using Xraph.GameFramework.Unity;
public class GameManager : MonoBehaviour
{
private MessageHandler messageHandler;
void Start()
{
messageHandler = gameObject.AddComponent<MessageHandler>();
messageHandler.enableDebugLogging = true;
// Register typed handlers
messageHandler.RegisterHandler<PlayerAction>("PlayerAction", HandlePlayerAction);
messageHandler.RegisterHandler<GameConfig>("Config", HandleConfig);
messageHandler.RegisterHandler<string>("SimpleMessage", HandleSimpleMessage);
}
void HandlePlayerAction(PlayerAction action)
{
Debug.Log($"Player action: {action.type} at {action.position}");
// Respond with typed object
var response = new ActionResponse
{
success = true,
result = $"Action '{action.type}' executed"
};
messageHandler.SendMessage("Flutter", "onActionResponse", response);
}
void HandleConfig(GameConfig config)
{
QualitySettings.SetQualityLevel(config.quality);
AudioListener.volume = config.volume;
}
void HandleSimpleMessage(string message)
{
Debug.Log($"Message: {message}");
}
}
[Serializable]
public class PlayerAction
{
public string type;
public Vector3 position;
public float timestamp;
}
[Serializable]
public class GameConfig
{
public int quality;
public float volume;
}
[Serializable]
public class ActionResponse
{
public bool success;
public string result;
}// Send typed message
var gameState = new GameState
{
score = 1000,
level = 5,
health = 75.0f
};
messageHandler.SendMessage("Flutter", "onGameState", gameState);var request = new DataRequest
{
requestId = Guid.NewGuid().ToString(),
dataType = "playerStats"
};
messageHandler.SendMessageWithResponse<DataRequest, DataResponse>(
"Flutter",
"requestData",
request,
(response) =>
{
if (response.success)
{
Debug.Log($"Data: {response.data}");
}
else
{
Debug.LogError($"Error: {response.error}");
}
}
);// Game state message
var gameState = new MessageHandler.GameStateMessage
{
isPlaying = true,
isPaused = false,
score = 100,
level = 1,
health = 100.0f
};
// Event message
var eventMsg = new MessageHandler.EventMessage
{
eventType = "achievement",
eventName = "first_win",
timestamp = Time.time,
data = "{\"achievementId\":\"001\"}"
};
// Response message
var response = new MessageHandler.ResponseMessage
{
success = true,
message = "Operation completed",
data = JsonUtility.ToJson(resultData)
};See NativeAPIExample.cs for a comprehensive example demonstrating all three levels:
using Xraph.GameFramework.Unity;
using Xraph.GameFramework.Unity.Examples;
public class MyGame : MonoBehaviour
{
private MessageHandler messageHandler;
void Start()
{
// Level 1: NativeAPI - Initialize and notify ready
NativeAPI.Initialize();
NativeAPI.NotifyUnityReady();
// Level 2: UnityMessageManager - Event listeners
UnityMessageManager.Instance.AddEventListener(
"Flutter:Command",
HandleCommand
);
// Level 3: MessageHandler - Type-safe handling
messageHandler = gameObject.AddComponent<MessageHandler>();
messageHandler.RegisterHandler<PlayerAction>(
"PlayerAction",
HandlePlayerAction
);
}
void HandleCommand(string data)
{
// Process command
}
void HandlePlayerAction(PlayerAction action)
{
// Handle player action
}
void SendGameState()
{
// Send to Flutter
var state = new GameState { score = 100 };
messageHandler.SendMessage("Flutter", "onGameState", state);
}
}// Send message to Unity
await controller.sendMessage(
target: 'GameManager',
method: 'StartGame',
data: '{"level": 1}',
);// Listen for Unity messages
controller.onMessage.listen((message) {
print('Target: ${message.target}');
print('Method: ${message.method}');
print('Data: ${message.data}');
});- NativeAPI: Low-level control, platform-specific features
- UnityMessageManager: Callbacks, event system, general messaging
- MessageHandler: Type-safe business logic, complex data structures
void Awake()
{
if (FindObjectsOfType<MyManager>().Length > 1)
{
Destroy(gameObject);
return;
}
DontDestroyOnLoad(gameObject);
NativeAPI.Initialize();
}void OnDestroy()
{
// Unsubscribe from events
NativeAPI.OnMessageReceived -= HandleMessage;
UnityMessageManager.Instance.RemoveEventListener("Event", Handler);
messageHandler.ClearHandlers();
}try
{
NativeAPI.SendMessageToFlutter("target", "method", data);
}
catch (Exception e)
{
Debug.LogError($"Failed to send message: {e.Message}");
// Send error to Flutter
NativeAPI.SendMessageToFlutter(
"Unity",
"onError",
$"{{\"error\":\"{e.Message}\"}}"
);
}All three systems handle threading automatically:
- NativeAPI: Platform-specific threading
- UnityMessageManager: Queues actions for main thread
- MessageHandler: Processes queue on main thread
- Message Size: Keep messages small, use references instead of embedding large data
- Frequency: Batch updates when possible (e.g., game state every 100ms, not every frame)
- Serialization: Cache serialized data if sending repeatedly
- Callbacks: Clean up callbacks when no longer needed
- Check NativeAPI is initialized:
NativeAPI.Initialize() - Verify Unity is ready:
NativeAPI.IsReady() - Check event subscriptions are active
- Enable debug logging:
messageHandler.enableDebugLogging = true
- Ensure
SetFlutterBridgeControlleris called from Swift - Check FlutterBridge.mm is included in build
- Verify Activity is available
- Check UnityEngineController is accessible
- Ensure proper lifecycle management
- Unity as a Library Documentation
- flutter-unity-view-widget
- Game Framework Documentation