JOIN Stories Flutter Plugin
A Flutter plugin to integrate JOIN Stories widgets and standalone player in Flutter apps.
🚀 Features
- Bubble, Card List, Card Grid widgets
- Unified standalone player API (
JOINStories.startPlayer
) - Centralized event routing (triggers, player, analytics)
- Refresh support via controllers (Bubble/Card)
- Custom fonts on Android and iOS
- iOS and Android support
Compatibility
- Flutter: 3.0.0+
- Dart SDK: 2.17.0+
- iOS: 12.0+
- Android: API 21+
See COMPATIBILITY.md and COMPATIBILITY_SUMMARY.md for details.
Installation
Add to your app’s pubspec.yaml
:
dependencies:
join_stories_flutter: ^0.0.1
iOS
- Set platform to 12.0+ in
ios/Podfile
:
platform :ios, '12.0'
- Then run in
ios/
:pod repo update && pod install
Android
- No special setup required.
Initialization
import 'package:join_stories_flutter/join_stories_flutter.dart';
await JOINStories.initialize(teamId: 'your-team-id');
// optional if provided by JOIN
await JOINStories.initialize(teamId: 'your-team-id', apiKey: 'your-api-key');
Widgets
import 'package:flutter/material.dart';
import 'package:join_stories_flutter/join_stories_flutter.dart';
class MyStoriesPage extends StatefulWidget {
const MyStoriesPage({super.key});
@override
State<MyStoriesPage> createState() => _MyStoriesPageState();
}
class _MyStoriesPageState extends State<MyStoriesPage> {
final BubbleController _bubbleCtrl = BubbleController();
final CardController _cardListCtrl = CardController();
final CardController _cardGridCtrl = CardController();
Future<void> _pullToRefresh() async {
await _bubbleCtrl.refresh();
await _cardListCtrl.refresh();
await _cardGridCtrl.refresh();
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: _pullToRefresh,
child: ListView(
children: [
BubbleWidget(
alias: 'bubble-alias',
controller: _bubbleCtrl,
configuration: const BubbleConfiguration(
showLabel: true,
labelFontSize: 14,
fontName: 'Avenir',
// player customization available here as well
),
onTriggerFetchSuccess: (count) {},
onTriggerFetchEmpty: () {},
onTriggerFetchError: (msg) {},
onPlayerFetchSuccess: () {},
onPlayerLoaded: () {},
onPlayerFetchError: (msg) {},
onPlayerDismissed: (type) {}, // 'auto' | 'manual'
onContentLinkClick: (link) {},
onAnalyticsEvent: (name, payload) {},
),
CardListWidget(
alias: 'card-list-alias',
controller: _cardListCtrl,
configuration: const CardConfiguration(
showLabel: true,
cardRadius: 12,
),
),
CardGridWidget(
alias: 'card-grid-alias',
controller: _cardGridCtrl,
configuration: const CardConfiguration(
numberOfColumns: 2,
cardSize: 150,
),
),
],
),
);
}
}
Standalone Player
Single unified method (customization + callbacks):
await JOINStories.startPlayer(
'widget-alias',
standaloneOrigin: 'top', // 'top'|'topLeft'|'topRight'|'bottom'|'bottomLeft'|'bottomRight'
playerBackgroundColor: 0xFF2C3E50,
playerVerticalAnchor: 'center',
playerHorizontalMargins: 20.0,
playerCornerRadius: 12.0,
playerProgressBarDefaultColor: 0xFF7F8C8D,
playerProgressBarFillColor: 0xFF3498DB,
playerProgressBarThickness: 4.0,
playerProgressBarRadius: 8.0,
onPlayerFetchSuccess: () {},
onPlayerLoaded: () {},
onPlayerFetchError: (msg) {},
onPlayerDismissed: (type) {},
onContentLinkClick: (link) {},
onAnalyticsEvent: (eventName, payload) {},
);
Analytics & Identification
await JOINStories.setTrackingUserId('user_123');
await JOINStories.sendConversion('purchase', 'ecommerce');
await JOINStories.setSegmentationKey('premium_user');
All analytics events are routed to Flutter via:
onAnalyticsEvent(String eventName, Map<String, dynamic> payload)
Note (iOS widgets): until per-widget IDs are available, analytics include viewId: -1
(global). The plugin broadcasts to mounted widgets so callbacks still fire.
Custom Fonts
JOIN widgets are native views. Make the font available on both Flutter and native sides.
Flutter
Add fonts under assets/fonts/
and declare in pubspec.yaml
:
flutter:
uses-material-design: true
fonts:
- family: Parisienne
fonts:
- asset: assets/fonts/Parisienne-Regular.ttf
Optional global theme:
MaterialApp(theme: ThemeData(fontFamily: 'Parisienne'))
iOS
- Copy the font into
ios/Runner/Fonts/Parisienne-Regular.ttf
(Target Membership: Runner) - Add in
ios/Runner/Info.plist
underUIAppFonts
:
<string>Fonts/Parisienne-Regular.ttf</string>
- Use the exact PostScript name in JOIN configs:
fontName: 'Parisienne-Regular'
Android
- Either put the font under
android/app/src/main/res/font/
or keep it in Flutter assets. - Pass the same
fontName
as iOS; the bridge will try assets first, thenres/font
, then system font.
Refresh Widgets
Recommended: use controllers and call refresh()
(see example above).
Advanced: call the API directly if you have a viewId
:
await JOINStories.refreshWidget(viewId: someViewId, type: 'bubble'); // or 'card'
Example
See the example/
directory for a complete example application.
License
This project is licensed under the MIT License - see the LICENSE file for details.