Flutter Integration
This example demonstrates how to integrate the Hubble Gift Card Store SDK in a Flutter application.
Parameters
| Parameter | Required | Description |
|---|---|---|
clientId | Yes | Client ID provided by the Hubble team |
appSecret | Yes | App secret provided by the Hubble team |
token | No | Auth token that uniquely identifies your user. Optional if lazy login is enabled. |
wrap-plt | Yes | Platform identifier. Use flutter. |
appVersion | No | App version string. Defaults to "10000". |
deviceId | No | Device identifier. |
Deep Links
To navigate directly to a specific page, append the route path before the query parameters. See the Deep Links page for the full route mapping.
| Page | URL Format |
|---|---|
| Home (default) | https://sdk.myhubble.money/?clientId=... |
| Brand purchase | https://sdk.myhubble.money/buy/{brandId}?clientId=... |
| Search | https://sdk.myhubble.money/search?clientId=... |
| Transactions | https://sdk.myhubble.money/transactions?clientId=... |
| Help | https://sdk.myhubble.money/help?clientId=... |
Events
The SDK communicates with the host app via a JavaScript channel named FlutterHost. Events are JSON objects with a type field:
Action events — SDK lifecycle and navigation:
{"type": "action", "action": "close"}
{"type": "action", "action": "app_ready"}
{"type": "action", "action": "error"}
Analytics events — user interaction tracking:
{ "type": "analytics", "event": "event_name", "properties": { "key": "value" } }
| Action | Description |
|---|---|
close | User tapped close. Dismiss the WebView. |
app_ready | SDK loaded successfully. |
error | SDK failed to load. Show an error state. |
See the Events page for the full list of analytics events.
Events Not Reaching Analytics
If you are not seeing SDK events in your analytics dashboard, check the following:
- Verify your event listener is set up BEFORE loading the SDK (events fire immediately on load).
- On native platforms, ensure the JavaScript channel is registered before the WebView loads the URL.
- Check for CORS issues if your analytics provider requires domain whitelisting.
Platform-Specific Configuration
Flutter runs on both iOS and Android, so you may need platform-specific configuration for UPI payments:
- iOS: See the iOS Integration Guide for
Info.plistconfiguration to detect installed UPI apps. - Android: See the Android Integration Guide for
AndroidManifest.xmlconfiguration required for UPI intents on Android 11+.
Full Example
import 'dart:convert'; // Required for jsonDecode
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:url_launcher/url_launcher.dart';
class HubbleWebView extends StatefulWidget {
const HubbleWebView({Key? key}) : super(key: key);
State<HubbleWebView> createState() => _HubbleWebViewState();
}
class _HubbleWebViewState extends State<HubbleWebView> {
late final WebViewController _controller;
bool _isWebViewReady = false;
void initState() {
super.initState();
_initWebView();
}
void _initWebView() {
final params = {
'token': 'your_auth_token', // your auth token (optional for lazy login)
'clientId': 'id_given_by_hubble',
'appSecret': 'secret_given_by_hubble',
'wrap-plt': 'flutter',
// 'appVersion': '10000', // optional, defaults to 10000
// 'deviceId': 'device_id', // optional
};
final baseUrl = 'https://sdk.dev.myhubble.money/';
// prod baseUrl is https://sdk.myhubble.money/
final sourceUrl = '$baseUrl?clientId=${params['clientId']}&appSecret=${params['appSecret']}&token=${params['token']}&wrap-plt=${params['wrap-plt']}';
// Deep link example: to open a brand page directly, append the route path:
// final sourceUrl = '${baseUrl}buy/uber?clientId=${params['clientId']}&appSecret=${params['appSecret']}&token=${params['token']}&wrap-plt=${params['wrap-plt']}';
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(Colors.white)
..setNavigationDelegate(
NavigationDelegate(
onPageStarted: (String url) {
print('Page started loading: $url');
},
onPageFinished: (String url) {
setState(() {
_isWebViewReady = true;
});
print('Page finished loading: $url');
},
onWebResourceError: (WebResourceError error) {
print('Web resource error: ${error.description}');
},
onNavigationRequest: (NavigationRequest request) {
if (request.url.startsWith(baseUrl) ||
request.url.startsWith('https://api.razorpay.com')) {
return NavigationDecision.navigate;
} else {
_launchURL(request.url);
return NavigationDecision.prevent;
}
},
),
)
..addJavaScriptChannel(
'FlutterHost',
onMessageReceived: (JavaScriptMessage message) {
_handleEvent(message.message);
},
)
..loadRequest(Uri.parse(sourceUrl));
}
Future<void> _launchURL(String url) async {
final uri = Uri.parse(url);
if (await canLaunchUrl(uri)) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
} else {
print('Could not launch $url');
}
}
void _handleEvent(String jsonString) {
try {
final eventData = jsonDecode(jsonString) as Map<String, dynamic>;
final String? type = eventData['type'] as String?;
if (type == 'action') {
final String? action = eventData['action'] as String?;
if (action == 'close') {
print("Received 'close' action from WebView. Popping screen.");
Navigator.of(context).pop();
} else if (action == 'app_ready') {
print('Hubble SDK is ready');
} else if (action == 'error') {
print('Hubble SDK encountered an error');
}
} else if (type == 'analytics') {
final String? event = eventData['event'] as String?;
final Map<String, dynamic>? properties =
eventData['properties'] as Map<String, dynamic>?;
if (event != null) {
_logEvent(event, properties ?? {});
}
}
} catch (e) {
print('Error handling event from WebView: $e');
print('Received message: $jsonString');
}
}
void _logEvent(String eventType, Map<String, dynamic> eventParams) {
// Your implementation of sending events to analytics
print('Analytics Event Type: $eventType');
print('Analytics Event Params: $eventParams');
}
Widget build(BuildContext context) {
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) async {
if (didPop) return;
if (!_isWebViewReady) {
Navigator.of(context).pop();
return;
}
if (await _controller.canGoBack()) {
await _controller.goBack();
} else {
if (context.mounted) {
Navigator.of(context).pop();
}
}
},
child: Scaffold(
body: SafeArea(
child: WebViewWidget(controller: _controller),
),
),
);
}
}