Mobile Integration
Embed GoCharting SDK inside a React Native, Flutter, Android, or iOS WebView. The SDK posts chart events to your native shell; your app owns chrome (toolbars, sheets, order tickets) and drives the chart with injected JavaScript.
Overview
| Flag | What you get |
|---|---|
touchMode: true | Full JS mobile Chart-tab UX (TopBar + BottomBar + JS ContextMenu + Layers) |
isNativeApp: true | Mobile canvas only — JS bars hidden, JS ContextMenu suppressed, every TerminalMobile appCallback also posted via sendToNative |
Compare touchMode true vs false
Interactive lab with a TradingView-style Desktop / Mobile device preview:
- Desktop — single chart with the desktop shell (
touchMode: false) - Mobile — two phone frames side by side: left
touchMode: true, righttouchMode: false(same narrow size so the chrome difference is obvious)
Source: examples/touchmode-compare-lab.html in the SDK package.
For native shells, use isNativeApp: true. Prefer not combining with touchMode unless you also want the JS toolbars.
Bridge flow (native WebView):
- SDK (
isNativeApp: true) posts events viasendToNative({ type, ... }) - Native WebView bridge receives them and drives host toolbars / sheets / menus
- Host calls back into the chart with
evaluateJavascript/ inject JS
1. Web page loaded in the WebView
Host a minimal page that creates the chart. Keep a global handle so native code can call chart APIs.
<!DOCTYPE html>
<html>
<head>
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
/>
<style>
html,
body,
#chart {
margin: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="chart"></div>
<script src="https://gocharting.com/sdk/library/index.umd.js"></script>
<script>
const { createChart } = window.GoChartingSDK;
// Expose for native → web commands
window.chart = createChart("#chart", {
symbol: "BYBIT:FUTURE:BTCUSDT",
interval: "1m",
datafeed: window.myDatafeed, // your datafeed
licenseKey: "YOUR_LICENSE_KEY",
isNativeApp: true,
// Optional: also handle events in JS
appCallback: (type, msg) => {
console.log("[appCallback]", type, msg);
},
});
</script>
</body>
</html>With isNativeApp: true the SDK:
- Renders the mobile chart canvas (no JS TopBar / BottomBar)
- Suppresses the JS ContextMenu and emits
OPEN_CONTEXT_MENU - Keeps Layers sheets in JS (for now)
- Posts every TerminalMobile
appCallbackto the native bridge
2. How the bridge works (web → native)
Internally the SDK uses sendToNative / sendAppCallbackToNative:
// Pseudocode of what the SDK does on every TerminalMobile appCallback
function sendToNative(message) {
const payload = JSON.stringify(message);
if (window.ReactNativeWebView) {
window.ReactNativeWebView.postMessage(payload);
return;
}
if (window.Flutter) {
window.Flutter.postMessage(payload);
return;
}
if (window.Android) {
window.Android.postMessage(payload);
return;
}
if (window.webkit?.messageHandlers?.ios) {
window.webkit.messageHandlers.ios.postMessage(message);
return;
}
}Message shape
{
"type": "OPEN_CONTEXT_MENU",
"chartId": "MAIN_CHART",
"x": 120,
"y": 340,
"targetType": "object",
"objectId": "drawing-123",
"details": { "type": "object", "objectId": "drawing-123" },
"security": { "symbol": "BTCUSDT" },
"interval": "1m"
}typeis always the appCallback event name (OPEN_CONTEXT_MENU,PLACE_ORDER, …).- If the payload had its own
type(e.g. hit-test"chart"/"object"), it is preserved astargetType.
Common events
type | When |
|---|---|
OPEN_CONTEXT_MENU | Long-press / right-click on the chart |
DOWNLOAD_MORE_DATA | User pans past available history |
PLACE_ORDER | Buy / Sell from OHLC (or trading UI) |
CHART_SELECTED | Chart pane selected (multi-chart) |
OPEN_SYMBOL_SEARCH | Symbol tap requesting search UI |
See the Configuration API and Events for the full event surface.
3. Native host — receive messages
React Native (react-native-webview)
Inject window.ReactNativeWebView automatically. Listen with onMessage:
import { WebView } from "react-native-webview";
function ChartScreen() {
const onMessage = (event) => {
const msg = JSON.parse(event.nativeEvent.data);
switch (msg.type) {
case "OPEN_CONTEXT_MENU":
// Show your native action sheet at msg.x / msg.y
openContextMenu(msg);
break;
case "PLACE_ORDER":
openOrderTicket(msg);
break;
case "DOWNLOAD_MORE_DATA":
requestMoreBars(msg);
break;
default:
console.log("chart event", msg.type, msg);
}
};
return (
<WebView
source={{ uri: "https://your.app/chart.html" }}
onMessage={onMessage}
javaScriptEnabled
domStorageEnabled
allowsInlineMediaPlayback
style={{ flex: 1 }}
/>
);
}Flutter (webview_flutter + JS channel)
Register a channel named Flutter so window.Flutter.postMessage works:
import 'dart:convert';
import 'package:webview_flutter/webview_flutter.dart';
late final WebViewController controller;
void initChartWebView() {
controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel(
'Flutter',
onMessageReceived: (JavaScriptMessage message) {
final msg = jsonDecode(message.message) as Map<String, dynamic>;
switch (msg['type']) {
case 'OPEN_CONTEXT_MENU':
openContextMenu(msg);
break;
case 'PLACE_ORDER':
openOrderTicket(msg);
break;
default:
debugPrint('chart event ${msg['type']}');
}
},
)
..loadRequest(Uri.parse('https://your.app/chart.html'));
}Android (Kotlin — JavascriptInterface)
Expose an object named Android with postMessage(String):
class ChartBridge(private val activity: Activity) {
@JavascriptInterface
fun postMessage(payload: String) {
val msg = JSONObject(payload)
when (msg.getString("type")) {
"OPEN_CONTEXT_MENU" -> activity.runOnUiThread {
openContextMenu(msg)
}
"PLACE_ORDER" -> activity.runOnUiThread {
openOrderTicket(msg)
}
}
}
}
webView.settings.javaScriptEnabled = true
webView.addJavascriptInterface(ChartBridge(this), "Android")
webView.loadUrl("https://your.app/chart.html")iOS (Swift — WKScriptMessageHandler)
Register a handler named ios (window.webkit.messageHandlers.ios):
class ChartMessageHandler: NSObject, WKScriptMessageHandler {
func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
guard let body = message.body as? [String: Any],
let type = body["type"] as? String else { return }
switch type {
case "OPEN_CONTEXT_MENU":
openContextMenu(body)
case "PLACE_ORDER":
openOrderTicket(body)
default:
print("chart event", type)
}
}
}
let contentController = WKUserContentController()
contentController.add(ChartMessageHandler(), name: "ios")
let config = WKWebViewConfiguration()
config.userContentController = contentController
let webView = WKWebView(frame: .zero, configuration: config)
webView.load(URLRequest(url: URL(string: "https://your.app/chart.html")!))Note: On iOS the SDK posts the object (not a JSON string). On RN / Flutter / Android the payload is a JSON string.
4. Native → WebView (drive the SDK)
Keep window.chart (or your own global) from step 1. Inject JavaScript from the host:
React Native
const webRef = useRef(null);
const setSymbol = (symbol) => {
webRef.current?.injectJavaScript(`
window.chart && window.chart.setSymbol(${JSON.stringify(symbol)});
true;
`);
};
const setInterval = (interval) => {
webRef.current?.injectJavaScript(`
window.chart && window.chart.setInterval(${JSON.stringify(interval)});
true;
`);
};
<WebView ref={webRef} /* ... */ />;Flutter
Future<void> setSymbol(String symbol) async {
final js = 'window.chart && window.chart.setSymbol(${jsonEncode(symbol)});';
await controller.runJavaScript(js);
}Android
fun setSymbol(symbol: String) {
val escaped = JSONObject.quote(symbol)
webView.evaluateJavascript(
"window.chart && window.chart.setSymbol($escaped);",
null
)
}iOS
func setSymbol(_ symbol: String) {
let js = "window.chart && window.chart.setSymbol(\(String(reflecting: symbol)));"
webView.evaluateJavaScript(js, completionHandler: nil)
}Typical commands you’ll wire to your native chrome:
| Native UI action | Injected call |
|---|---|
| Symbol picked | window.chart.setSymbol("EXCHANGE:SYMBOL") |
| Interval changed | window.chart.setInterval("5m") |
| Theme toggle | window.chart.setTheme("dark") |
| Layout / chart type | Use the matching chart API methods from the Chart API |
5. End-to-end flow example
Long-press on a drawing
- User long-presses → SDK emits
OPEN_CONTEXT_MENU(JS menu not shown). sendToNativeposts{ type: "OPEN_CONTEXT_MENU", targetType: "object", objectId, x, y, ... }.- Native shows an action sheet (Edit / Delete / …).
- On “Delete”, native injects JS that calls your chart / layers API (or dispatches a custom event your page listens for).
Buy from OHLC
- User taps Buy →
PLACE_ORDERappCallback. - Native receives
{ type: "PLACE_ORDER", ... }and opens your order ticket. - After fill, native updates positions via your trading bridge / datafeed as usual.
6. Checklist
- Load chart HTML with
isNativeApp: true - Expose
window.chart(or equivalent) for injectJS - Register the bridge name your platform expects (
ReactNativeWebView/Flutter/Android/ios) - Parse JSON on RN / Flutter / Android; read object on iOS
- Handle at least
OPEN_CONTEXT_MENU,PLACE_ORDER,DOWNLOAD_MORE_DATA - Build native chrome for symbol search, intervals, drawings, orders