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.
Journiq Flutter SDK
Official Flutter plugin for Journiq — deep linking, deferred deep links, event tracking, attribution analytics, in-app notifications, and push.
Wraps the native Android SDK and iOS SDK via platform channels.
Features #
- Deferred Deep Links — Attribute installs to the link that drove them, even across the app store
- Link Management — Create, list, and retrieve short links programmatically
- Event Tracking — Track conversions and custom events with offline queue and automatic batching
- Analytics — Retrieve link click stats and app configuration
- In-App Notifications — Receive and display real-time in-app notifications with custom UI
- Push Registration — Register FCM device tokens for push notifications
- Cross-platform — Single Dart API backed by native SDKs on both Android and iOS
Requirements #
| Platform | Minimum Version |
|---|---|
| Android | API 24 (Android 7.0) |
| iOS | 13.0 |
| Flutter | 3.3.0+ |
| Dart | 3.11.0+ |
Installation #
Add to your pubspec.yaml:
dependencies:
journiq_flutter_sdk: ^0.3.0
Then run:
flutter pub get
Quick Start #
1. Initialize the SDK #
import 'package:journiq_flutter_sdk/journiq_flutter_sdk.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Journiq.configure(apiKey: 'jq_pub_your_key_here');
runApp(MyApp());
}
2. Check for Deferred Deep Links #
final match = await Journiq.deepLinks.checkDeferredDeepLink();
if (match.matched) {
// Attribution is stored automatically — events will be linked to this deep link
Navigator.pushNamed(context, match.deepLinkPath ?? '/');
}
3. Handle Incoming Universal / App Links #
import 'package:journiq_flutter_sdk/journiq_flutter_sdk.dart';
// When the app opens via a Universal Link or Android App Link
void handleUniversalLink(Uri uri) async {
try {
final resolved = await Journiq.deepLinks.resolveUniversalLink(uri);
// resolved.deepLinkPath — e.g. "product/123"
// resolved.parameters — custom key-value pairs
// resolved.utmSource, .utmMedium, .utmCampaign...
navigateTo(resolved.deepLinkPath, params: resolved.parameters);
} catch (e) {
// Fallback: parse locally (e.g. URL scheme links)
final cleanUri = Journiq.deepLinks.handleIncomingLink(uri);
navigateTo(cleanUri.path);
}
}
Note:
resolveUniversalLinkcalls the server to resolve the short URL, tracks the open, and stores attribution automatically. UsehandleIncomingLinkas a local-only fallback for URL scheme links.
4. Track Events #
// deepLinkId is automatically attached from the last attribution source
await Journiq.events.track(
'PURCHASE',
metadata: {'amount': '29.99', 'currency': 'USD'},
);
// Or pass explicitly if needed
await Journiq.events.track(
'PURCHASE',
deepLinkId: 'specific_link_id',
metadata: {'amount': '29.99', 'currency': 'USD'},
);
4. Create Links #
final link = await Journiq.links.create(LinkCreateRequest(
webUrl: 'https://example.com/product/123',
title: 'Cool Product',
deepLinkPath: '/product/123',
utmSource: 'app',
utmMedium: 'share',
));
// Share link.shortUrl
5. User Identity #
// After login
await Journiq.setIdentity('user_12345');
// On logout
await Journiq.logout();
6. Push Notifications #
Register the device's FCM token so the backend can send push and in-app notifications:
import 'package:firebase_messaging/firebase_messaging.dart';
final token = await FirebaseMessaging.instance.getToken();
if (token != null) {
await Journiq.push.registerToken(token);
}
// Re-register on token refresh
FirebaseMessaging.instance.onTokenRefresh.listen((newToken) {
Journiq.push.registerToken(newToken);
});
Handle incoming FCM data messages:
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
// Let the SDK handle Journiq messages (returns true if handled)
final handled = Journiq.push.handleDataMessage(message.data);
if (!handled) {
// Handle your own messages
}
});
7. In-App Notifications #
Listen for new notifications (stream-based)
Journiq.notifications.onNewNotification.listen((notification) {
print('New: ${notification.title}');
});
Custom Display Handler (recommended)
Register a handler to control exactly how notifications appear in your app:
Journiq.notifications.setDisplayHandler(context, (ctx, notification) {
// Return a widget to display, or null to suppress
return MaterialBanner(
content: Text(notification.title),
actions: [
TextButton(
onPressed: () {
Journiq.notifications.markAsRead(notification.id);
JourniqNotifications.reportAction(notification, NotificationAction.tapped);
},
child: Text('VIEW'),
),
TextButton(
onPressed: () {
JourniqNotifications.reportAction(notification, NotificationAction.dismissed);
},
child: Text('DISMISS'),
),
],
);
});
Fetch notifications
final result = await Journiq.notifications.getAll(page: 1, limit: 20);
for (final n in result.notifications) {
print('${n.title} - read: ${n.read}');
}
Mark as read & unread count
await Journiq.notifications.markAsRead(notificationId);
final count = await Journiq.notifications.getUnreadCount();
// Or listen to count changes
Journiq.notifications.onUnreadCountChanged.listen((count) {
updateBadge(count);
});
API Reference #
Journiq #
| Method | Description |
|---|---|
configure({apiKey, baseUrl?}) |
Initialize the SDK. Call once at app startup. |
isInitialized |
Whether the SDK has been configured. |
setIdentity(userId) |
Set user identity for cross-device attribution. |
logout() |
Remove user identity and clear attribution. |
onAppForegrounded() |
Trigger event queue flush (call from lifecycle handler). |
attributedDeepLinkId |
The currently stored deep link ID (read-only). |
attributedClickId |
The currently stored click ID (read-only). |
Journiq.deepLinks #
| Method | Description |
|---|---|
checkDeferredDeepLink() |
Check for a deferred deep link match on first open. Auto-stores attribution. Returns MatchResult. |
resolveUniversalLink(Uri, {source?}) |
Resolve a universal/app link URL via API (tracks open + stores attribution). Returns ResolvedLink. |
handleIncomingLink(Uri) |
Extract attribution from an incoming URL scheme link locally (no network call). Returns clean Uri for routing. |
Journiq.links #
| Method | Description |
|---|---|
create(LinkCreateRequest) |
Create a new deep link. Returns DeepLink. |
list({page, limit}) |
List deep links with pagination. |
get(linkId) |
Get a single deep link by ID. |
Journiq.events #
| Method | Description |
|---|---|
track(eventName, {deepLinkId?, metadata?}) |
Track a conversion or custom event. Auto-attaches stored deepLinkId if not provided. |
flush() |
Force flush queued events immediately. |
Journiq.analytics #
| Method | Description |
|---|---|
getLinkStats(linkId) |
Get click statistics for a link. Returns LinkStats. |
getAppConfig() |
Get app configuration. Returns AppConfig. |
Journiq.notifications #
| Method | Description |
|---|---|
onNewNotification |
Stream of new notifications as they arrive. |
onUnreadCountChanged |
Stream of unread count changes. |
getAll({page, limit, unreadOnly}) |
Fetch paginated notifications. Returns NotificationsResult. |
markAsRead(notificationId) |
Mark a notification as read. |
getUnreadCount() |
Get current unread count. |
setDisplayHandler(context, handler) |
Register a custom widget builder for incoming notifications. |
setActionHandler(handler) |
Register a callback for notification tap/dismiss actions. |
removeDisplayHandler() |
Remove the display handler. |
Journiq.push #
| Method | Description |
|---|---|
registerToken(fcmToken) |
Register the device FCM token with Journiq. |
handleDataMessage(data) |
Process an FCM data message. Returns true if handled by Journiq. |
Platform Setup #
1. Configure URL Schemes in Journiq Dashboard #
In the Journiq dashboard, navigate to Apps → your app → Edit and configure:
| Field | Example | Description |
|---|---|---|
| iOS URL Scheme | myapp://{{path}} |
Template used to open your iOS app. {{path}} is replaced with the deep link path at redirect time. |
| Android URL Scheme | myapp://{{path}} |
Template used to construct the Android intent URL. |
| Bundle ID | com.example.myapp |
Your iOS app bundle identifier |
| Package Name | com.example.myapp |
Your Android application ID |
| App Store URL | https://apps.apple.com/app/id123456 |
Fallback if app not installed (iOS) |
| Play Store URL | https://play.google.com/store/apps/details?id=com.example.myapp |
Fallback if app not installed (Android) |
Important: The
{{path}}placeholder is required. When a user clicks a link withdeepLinkPath: "product/123", the redirect service generatesmyapp://product/123.
2. iOS Setup #
Install native SDK
cd ios && pod install
Register your URL scheme
Add your custom scheme to ios/Runner/Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
<key>CFBundleURLName</key>
<string>com.example.myapp</string>
</dict>
</array>
Replace myapp with the scheme portion of your iOS URL Scheme (everything before ://).
3. Android Setup #
The SDK dependency is pulled from Maven Central automatically.
Register your URL scheme
Add an intent filter to your main activity in android/app/src/main/AndroidManifest.xml:
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
...>
<!-- Existing intent filters ... -->
<!-- Journiq deep link URL scheme -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" />
</intent-filter>
</activity>
Replace myapp with the scheme portion of your Android URL Scheme.
4. Handle Incoming Deep Links in Flutter #
Use the app_links package to listen for incoming URLs, then parse them with the Journiq SDK:
import 'package:app_links/app_links.dart';
import 'package:journiq_flutter_sdk/journiq_flutter_sdk.dart';
class DeepLinkHandler {
final _appLinks = AppLinks();
void init() {
// Handle link when app is already running
_appLinks.uriLinkStream.listen((Uri uri) {
_handleDeepLink(uri);
});
// Handle link that launched the app
_appLinks.getInitialLink().then((uri) {
if (uri != null) _handleDeepLink(uri);
});
}
void _handleDeepLink(Uri uri) {
final path = uri.path.replaceFirst('/', ''); // e.g. "product/123"
final params = uri.queryParameters; // e.g. {"ref": "campaign1"}
// Navigate to the appropriate screen
navigateTo(path, params);
}
}
Alternatively, if you use go_router, deep links are handled automatically via its route definitions.
How It Works #
User clicks Journiq link (e.g. https://links.example.com/abc123)
│
▼
Journiq redirect service detects platform
│
├── iOS mobile ──► Redirects to myapp://product/123
│ (falls back to App Store if not installed)
│
├── Android ────► Redirects via intent URL
│ intent:#Intent;scheme=myapp;package=com.example.myapp;
│ S.browser_fallback_url=<Play Store URL>;end
│ (falls back to Play Store if not installed)
│
└── Web/Desktop ► Redirects to webUrl
Every click goes through the redirect service, ensuring analytics are always recorded before the app opens.
License #
MIT