Demonstrating bidirectional messaging between Flutter and a hosting web app
We're modifying the sample Flutter application to demonstrate bi-directional messaging between the HTML/Javascript and the Flutter code. The sample Flutter counter app is embedded in a web page. It communicates with Javascript via browser window messages.
- Demonstrates messaging Flutter to the web app via window messages whenever the counter increments.
- Demonstrates messaging from the web app to Flutter whenever a button is pressed on the HTML page. An "increment" message is passed to Flutter via the browser window messaging which causes the Flutter app to increment its counter.
Show me the code
Extracted snippets can be seen by scrolling down on this page.
- https://github.com/freemansoft/flutter-embedded
- https://github.com/freemansoft/flutter-embedded/tree/main/v1
Video on Joe's YouTube channel
The videos are always more expressive than anything I would write here.
Flutter Initiated message flow
Diagram used in the videos describing the action and message flow when one of the Flutter buttons is pressed. A counter is incremented by one in this flow.
A transcript will eventually be added here.
Javascript Initiated Message Flow
Diagram used in the videos describing the action and message flow when one of the HTML buttons is pressed. A counter is incremented by one in this flow.
A transcript will eventually be added here.
Index.html messaging code
The static site web app is a single HTML page with some JavaScript. The payloads of all messages are JSON.
Javascript receiving messages
window.addEventListener("message", handleMessage);
// The flutter app posts to window and window parent if available
// If running in iframe then message destined for parent is received here
// If running not in iframe then parent and window are the same
// and message destined for parent AND window is received here
// Using createTextNode which does not accept HTML markup
function handleMessage(e) {
var data = JSON.parse(e.data);
// Only interested in messages with an "action":"incremented".
// Ignore all the others
// should check origin also
if (data.action == "incremented"){
console.log("javascript Message Received from origin: "+ e.origin + " data: " + e.data);
var theDiv = document.getElementById("MESSAGE_RESULTS_DIV");
var content = document.createTextNode(e.data);
theDiv.appendChild(content);
} else {
// ignoring other messages
}
}
Javascript sending messages
<button id="button2" onclick="sendMessageWindowIFrame()">Increment flutter in iFrame</button>
<br/;>
<button id="button1" onclick="sendMessageWindow()">Increment flutter in window</button>
<script>
// sends a message to the flutter app in the element
function sendMessageWindow() {
const message = '{"action":"increment"}';
// Should target the domain instead of '*'
// Which one should we call out
window.postMessage(message, '*');
}
function sendMessageWindowIFrame() {
const message = '{"action":"increment"}';
var flutterIFrame = document.getElementById('FLUTTER-IFRAME');
// Should target the domain instead of '*'
// Which one should we call out
flutterIFrame.contentWindow.postMessage(message, '*');
}
</script>
Dart messaging code
The Flutter app is a single dart file. The payloads of all messages are JSON.
Dart receiving messages
html.window.onMessage.listen(
(html.MessageEvent event) => _listen(event),
);
Note that the dart code drops all messages other than the "increment" command
/// Called when a window message event is received
void _listen(html.MessageEvent event) {
final eventData = event.data;
// only increment if we receive the increment command
// should verify is string and ignore if not
// debugPrint('${eventData.runtimeType}');
final data = jsonDecode(eventData as String);
// Should check origin also
if (data['action'] == 'increment') {
debugPrint(
'Flutter received from origin: ${event.origin} data: $eventData');
_incrementCounter();
} else {
// Receive all window messages for that window and domain
// Ignore the onces we aren't interested in
}
// _incrementCounter calls setState
//setState(() {});
}
Dart sending messages
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
_postActionMessage(
action: "incremented", actionPayload: {"count": _counter});
});
}
The code in the repository has additional logic to determine if the Dart code is running in a web app or mobile native. There is a bunch of learning-related logic here with the window and the window.parent. A real app would use only one.
/// @param actionPayload can be antything. we use it for package info
void _postActionMessageWeb(
{required String action, required Map<String, dynamic> actionPayload}) {
Map<String, dynamic> data = {"action": action};
data.addAll(actionPayload);
// use html.window.parent if we are in an iframe
if (html.window.parent != null) {
data["location"] = html.window.parent!.location.toString();
final json = const JsonEncoder().convert(data);
debugPrint('flutter Message fired: $json to parent.');
html.window.parent!.postMessage(json, "*");
}
// if there is no parent or the parent and window are different
// This will be true if in iframe but no one will receive it
if (html.window.parent == null ||
html.window.parent!.location.toString() !=
html.window.location.toString()) {
data["location"] = html.window.location.toString();
final json = const JsonEncoder().convert(data);
debugPrint('flutter Message fired: $json to window.');
// should target a domain instead of global
html.window.postMessage(json, "*");
}
}
Video
Demonstrating bidirectional messaging between Flutter and a hosting web app
Showing the code for bidirectional messaging between Flutter and a hosting web app
Revision History
Created 2024 05
Comments
Post a Comment