journiq_flutter_sdk 0.6.3
journiq_flutter_sdk: ^0.6.3 copied to clipboard
Journiq deep linking, attribution, and analytics SDK for Flutter. Wraps native Android and iOS SDKs via platform channels.
example/lib/main.dart
import 'dart:async';
import 'package:app_links/app_links.dart';
import 'package:flutter/material.dart';
import 'package:journiq_flutter_sdk/journiq_flutter_sdk.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Journiq.configure(apiKey: 'jq_pub_example_key');
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Journiq SDK Example',
theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final _appLinks = AppLinks();
StreamSubscription<Uri>? _linkSub;
String _status = 'Initializing...';
String? _deepLinkPath;
String? _attributedLinkId;
final List<String> _events = [];
@override
void initState() {
super.initState();
_init();
}
@override
void dispose() {
_linkSub?.cancel();
super.dispose();
}
Future<void> _init() async {
// 1. Check for deferred deep link (first install attribution)
final match = await Journiq.deepLinks.checkDeferredDeepLink();
if (match.matched) {
setState(() {
_status = 'Deferred link matched!';
_deepLinkPath = match.deepLinkPath;
_attributedLinkId = Journiq.attributedDeepLinkId;
});
} else {
setState(() => _status = 'No deferred link');
}
// 2. Listen for incoming URL scheme links
// Handle link that launched the app (cold start)
final initialUri = await _appLinks.getInitialLink();
if (initialUri != null) _handleIncomingLink(initialUri);
// Handle links while app is running (warm start)
_linkSub = _appLinks.uriLinkStream.listen(_handleIncomingLink);
}
void _handleIncomingLink(Uri uri) {
// Extract attribution (jq_link, jq_click) and get clean URI
final cleanUri = Journiq.deepLinks.handleIncomingLink(uri);
setState(() {
_status = 'Deep link received!';
_deepLinkPath = cleanUri.path.replaceFirst('/', '');
_attributedLinkId = Journiq.attributedDeepLinkId;
});
}
Future<void> _trackPurchase() async {
// deepLinkId is automatically attached from attribution
await Journiq.events.track(
'PURCHASE',
metadata: {'amount': '29.99', 'currency': 'USD'},
);
setState(() {
_events.add(
'PURCHASE (attributed to: ${Journiq.attributedDeepLinkId ?? "none"})',
);
});
}
Future<void> _trackSignUp() async {
await Journiq.events.track('SIGN_UP');
setState(() {
_events.add(
'SIGN_UP (attributed to: ${Journiq.attributedDeepLinkId ?? "none"})',
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Journiq SDK Example')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
// Status card
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Status',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
Text(_status),
if (_deepLinkPath != null) ...[
const SizedBox(height: 4),
Text(
'Path: $_deepLinkPath',
style: const TextStyle(fontFamily: 'monospace'),
),
],
if (_attributedLinkId != null) ...[
const SizedBox(height: 4),
Text(
'Attributed Link ID: $_attributedLinkId',
style: const TextStyle(
fontFamily: 'monospace',
fontSize: 12,
),
),
],
],
),
),
),
const SizedBox(height: 16),
// Actions
Text('Track Events', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: FilledButton(
onPressed: _trackPurchase,
child: const Text('Purchase'),
),
),
const SizedBox(width: 8),
Expanded(
child: OutlinedButton(
onPressed: _trackSignUp,
child: const Text('Sign Up'),
),
),
],
),
const SizedBox(height: 16),
// Event log
if (_events.isNotEmpty) ...[
Text('Event Log', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
..._events.reversed.map(
(e) => Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Text(
'• $e',
style: const TextStyle(fontFamily: 'monospace', fontSize: 12),
),
),
),
],
],
),
);
}
}