grovs_flutter_plugin 3.0.2
grovs_flutter_plugin: ^3.0.2 copied to clipboard
Flutter plugin for Grovs SDK - Deep linking, smart links, user messaging, and campaign tracking for mobile apps.
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:grovs_flutter_plugin/grovs.dart';
import 'package:grovs_flutter_plugin/models/grovs_link.dart';
import 'package:url_launcher/url_launcher.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Grovs SDK
final grovs = Grovs();
try {
await grovs.setDebugLevel('info');
// Set user information (optional)
await grovs.setUserIdentifier('demo-user-123');
await grovs.setUserAttributes({
'name': 'Demo User',
'email': 'demo@example.com',
'app': 'Flutter Demo',
});
} catch (e) {
debugPrint('Failed to configure Grovs: $e');
}
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
String _generatedLink = '';
String _lastDeeplinkReceived = 'None';
final _grovs = Grovs();
StreamSubscription<DeeplinkDetails>? _deeplinkSubscription;
bool _isGenerating = false;
bool _sdkEnabled = true;
bool _isChangingConsent = false;
String _lastError = 'None';
StreamSubscription<GrovsError>? _errorSubscription;
@override
void initState() {
super.initState();
initPlatformState();
setupDeeplinkListener();
_errorSubscription = _grovs.onError.listen((error) {
if (!mounted) return;
setState(() {
_lastError = '${error.code.nativeName}: ${error.message}';
});
});
}
@override
void dispose() {
_deeplinkSubscription?.cancel();
_errorSubscription?.cancel();
super.dispose();
}
Future<void> _setSDKEnabled(bool enabled) async {
setState(() => _isChangingConsent = true);
try {
await _grovs.setSDK(enabled);
if (!mounted) return;
setState(() => _sdkEnabled = enabled);
} on GrovsException catch (error) {
if (!mounted) return;
setState(() => _lastError = error.toString());
} finally {
if (mounted) setState(() => _isChangingConsent = false);
}
}
void setupDeeplinkListener() {
_deeplinkSubscription = _grovs.onDeeplinkReceived.listen((deeplinkDetails) {
setState(() {
_lastDeeplinkReceived =
'Link: ${deeplinkDetails.link}\nData: ${deeplinkDetails.data}\nTracking: ${deeplinkDetails.tracking?.toMap()}';
});
// Show a dialog when deeplink is received
if (mounted) {
// Defer the dialog to the next frame to ensure Material localizations are available
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Deeplink Received'),
content: Text(
'Link: ${deeplinkDetails.link}\n\nPayload: ${deeplinkDetails.data}',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
}
});
}
});
}
Future<void> initPlatformState() async {
String platformVersion;
try {
platformVersion =
await _grovs.getPlatformVersion() ?? 'Unknown platform version';
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
}
Future<void> generateLink() async {
setState(() {
_isGenerating = true;
});
try {
final link = await _grovs.generateLink(
GenerateLinkParams(
title: 'Check out this Flutter app!',
subtitle: 'Built with Grovs SDK',
imageURL:
'https://flutter.dev/assets/images/shared/brand/flutter/logo/flutter-lockup.png',
data: {
'screen': 'flutter-home',
'userId': 'demo-user-123',
'timestamp': DateTime.now().toIso8601String(),
},
tags: ['demo', 'flutter'],
customRedirects: CustomRedirects(
ios: CustomLinkRedirect(
url: 'https://google.com/ios',
openAppIfInstalled: true,
),
android: CustomLinkRedirect(
url: 'https://google.com/android',
openAppIfInstalled: false,
),
desktop: CustomLinkRedirect(
url: 'https://google.com/desktop',
openAppIfInstalled: false,
),
),
showPreviewIos: false,
showPreviewAndroid: true,
tracking: TrackingParams(
utmCampaign: 'spring_sale',
utmSource: 'newsletter',
utmMedium: 'email',
),
),
);
if (mounted) {
setState(() {
_generatedLink = link;
_isGenerating = false;
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Link generated successfully!')),
);
}
} on GrovsException catch (e) {
if (mounted) {
setState(() {
_isGenerating = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to generate link: ${e.message}')),
);
}
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
navigatorObservers: [Grovs.navigatorObserver],
home: Scaffold(
appBar: AppBar(title: const Text('Grovs Flutter Example'), actions: []),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Platform Info',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text('Running on: $_platformVersion'),
],
),
),
),
const SizedBox(height: 16),
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Generate Link',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _isGenerating ? null : generateLink,
child: _isGenerating
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: const Text('Generate Grovs Link'),
),
if (_generatedLink.isNotEmpty) ...[
const SizedBox(height: 16),
const Text(
'Generated Link:',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
SelectableText(
_generatedLink,
style: const TextStyle(
fontSize: 12,
color: Colors.blue,
),
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: () {
Clipboard.setData(
ClipboardData(text: _generatedLink),
);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Link copied to clipboard!'),
),
);
},
icon: const Icon(Icons.copy),
label: const Text('Copy Link'),
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: () async {
try {
if (await canLaunchUrl(
Uri.parse(_generatedLink),
)) {
await launchUrl(
Uri.parse(_generatedLink),
mode: LaunchMode.externalApplication,
);
} else {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Could not launch link'),
),
);
}
}
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error opening link: $e'),
),
);
}
}
},
icon: const Icon(Icons.open_in_browser),
label: const Text('Open Link'),
),
],
],
),
),
),
const SizedBox(height: 16),
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Revenue Tracking',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () async {
try {
await _grovs.logCustomPurchase(
type: TransactionType.buy,
priceInCents: 999,
currency: 'USD',
productId: 'premium_monthly',
);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Custom purchase logged!'),
),
);
}
} on GrovsException catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Failed to log purchase: ${e.message}',
),
),
);
}
}
},
child: const Text('Log Custom Purchase (\$9.99)'),
),
],
),
),
),
const SizedBox(height: 16),
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Consent',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: const Text('SDK enabled'),
value: _sdkEnabled,
onChanged: _isChangingConsent ? null : _setSDKEnabled,
),
const SizedBox(height: 8),
const Text(
'Last SDK error',
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(_lastError, style: const TextStyle(fontSize: 12)),
],
),
),
),
const SizedBox(height: 16),
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Analytics',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () async {
await Grovs().track(
'demo_button_tapped',
properties: {'source': 'example_app'},
tags: ['demo'],
);
},
child: const Text('Track custom event'),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
settings: const RouteSettings(
name: 'DemoSecondScreen',
),
builder: (_) => Scaffold(
appBar: AppBar(
title: const Text('Second screen'),
),
body: const Center(
child: Text('Auto screen view fired'),
),
),
),
);
},
child: const Text('Open second screen'),
),
],
),
),
),
const SizedBox(height: 16),
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Last Deeplink Received',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
_lastDeeplinkReceived,
style: const TextStyle(fontSize: 12),
),
],
),
),
),
],
),
),
),
);
}
}