flutter_platform_channels_plus 0.1.0
flutter_platform_channels_plus: ^0.1.0 copied to clipboard
Simplified platform channel communication for Flutter with support for all 6 platforms (iOS, Android, Web, Windows, macOS, Linux) and WASM compatibility.
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_platform_channels_plus/flutter_platform_channels_plus.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Platform Channels Plus Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const FeatureDemoPage(),
);
}
}
class FeatureDemoPage extends StatefulWidget {
const FeatureDemoPage({super.key});
@override
State<FeatureDemoPage> createState() => _FeatureDemoPageState();
}
class _FeatureDemoPageState extends State<FeatureDemoPage> {
int _selectedIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Platform Channels Plus Demo'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: Row(
children: [
NavigationRail(
selectedIndex: _selectedIndex,
onDestinationSelected: (index) {
setState(() {
_selectedIndex = index;
});
},
labelType: NavigationRailLabelType.all,
destinations: const [
NavigationRailDestination(
icon: Icon(Icons.info_outline),
selectedIcon: Icon(Icons.info),
label: Text('Platform Info'),
),
NavigationRailDestination(
icon: Icon(Icons.code_outlined),
selectedIcon: Icon(Icons.code),
label: Text('Method Channel'),
),
NavigationRailDestination(
icon: Icon(Icons.event_outlined),
selectedIcon: Icon(Icons.event),
label: Text('Event Channel'),
),
NavigationRailDestination(
icon: Icon(Icons.message_outlined),
selectedIcon: Icon(Icons.message),
label: Text('Message Channel'),
),
],
),
const VerticalDivider(thickness: 1, width: 1),
Expanded(
child: _buildContent(),
),
],
),
);
}
Widget _buildContent() {
switch (_selectedIndex) {
case 0:
return const PlatformInfoDemo();
case 1:
return const MethodChannelDemo();
case 2:
return const EventChannelDemo();
case 3:
return const MessageChannelDemo();
default:
return const SizedBox();
}
}
}
// Platform Information Demo
class PlatformInfoDemo extends StatefulWidget {
const PlatformInfoDemo({super.key});
@override
State<PlatformInfoDemo> createState() => _PlatformInfoDemoState();
}
class _PlatformInfoDemoState extends State<PlatformInfoDemo> {
String _platformInfo = 'Loading...';
@override
void initState() {
super.initState();
_loadPlatformInfo();
}
Future<void> _loadPlatformInfo() async {
final platform = PlatformDetector.currentPlatform;
final capabilities = PlatformDetector.supportedCapabilities;
setState(() {
_platformInfo = '''
Platform: ${platform.name}
Platform Description: ${PlatformDetector.platformDescription}
Platform Type:
- Is Web: ${PlatformDetector.isWeb}
- Is Mobile: ${PlatformDetector.isMobile}
- Is Desktop: ${PlatformDetector.isDesktop}
Capabilities:
- Native Code: ${PlatformDetector.supportsNativeCode}
- WASM: ${PlatformDetector.supportsWasm}
Supported Capabilities: ${capabilities.join(', ')}
Platform Checks:
- Supports 'web': ${PlatformDetector.supportsCapability('web')}
- Supports 'mobile': ${PlatformDetector.supportsCapability('mobile')}
- Supports 'native_code': ${PlatformDetector.supportsCapability('native_code')}
- Supports 'wasm': ${PlatformDetector.supportsCapability('wasm')}
- Supports '${platform.name.toLowerCase()}': ${PlatformDetector.supportsCapability(platform.name.toLowerCase())}
''';
});
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Platform Detection',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 16),
Text(
_platformInfo,
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
PlatformDetector.reset();
_loadPlatformInfo();
},
child: const Text('Refresh Platform Info'),
),
],
),
),
),
],
),
);
}
}
// Method Channel Demo
class MethodChannelDemo extends StatefulWidget {
const MethodChannelDemo({super.key});
@override
State<MethodChannelDemo> createState() => _MethodChannelDemoState();
}
class _MethodChannelDemoState extends State<MethodChannelDemo> {
final Map<String, String> _results = {};
Future<void> _testInvokeMethod() async {
final channel = MethodChannelPlus('demo_methods');
final result = await channel.invokeMethod('getData', {'test': 'invoke'});
setState(() {
_results['invoke'] = result.isSuccess
? 'Success: ${result.data}'
: 'Error: ${result.error}';
});
}
Future<void> _testInvokeMethodStrict() async {
final channel = MethodChannelPlus('demo_methods');
try {
final result = await channel.invokeMethodStrict<Map<String, dynamic>>(
'getData', {'test': 'strict'});
setState(() {
_results['strict'] = 'Success: $result';
});
} catch (e) {
setState(() {
_results['strict'] = 'Exception: $e';
});
}
}
Future<void> _testInvokeMethodWithRetry() async {
final channel = MethodChannelPlus('demo_methods',
config: const ChannelConfig(retryCount: 2));
final result =
await channel.invokeMethodWithRetry('getData', {'test': 'retry'}, 2);
setState(() {
_results['retry'] = result.isSuccess
? 'Success: ${result.data}'
: 'Error: ${result.error}';
});
}
Future<void> _testInvokeMethodWithTimeout() async {
final channel = MethodChannelPlus('demo_methods',
config: const ChannelConfig(timeout: Duration(seconds: 5)));
final result = await channel.invokeMethodWithTimeout(
'getData', {'test': 'timeout'}, const Duration(seconds: 3));
setState(() {
_results['timeout'] = result.isSuccess
? 'Success: ${result.data}'
: 'Error: ${result.error}';
});
}
Future<void> _testCustomConfig() async {
final config = const ChannelConfig(
timeout: Duration(seconds: 10),
retryCount: 5,
enableLogging: true,
);
final channel = MethodChannelPlus('demo_methods', config: config);
final result = await channel.invokeMethod('getData', {'test': 'custom'});
setState(() {
_results['custom'] = result.isSuccess
? 'Success: ${result.data}'
: 'Error: ${result.error}';
});
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildTestCard(
context,
'Basic invokeMethod',
'Test basic method invocation',
_results['invoke'] ?? 'Not tested',
_testInvokeMethod,
),
const SizedBox(height: 16),
_buildTestCard(
context,
'invokeMethodStrict',
'Test strict method invocation (throws on error)',
_results['strict'] ?? 'Not tested',
_testInvokeMethodStrict,
),
const SizedBox(height: 16),
_buildTestCard(
context,
'invokeMethodWithRetry',
'Test method invocation with retry logic',
_results['retry'] ?? 'Not tested',
_testInvokeMethodWithRetry,
),
const SizedBox(height: 16),
_buildTestCard(
context,
'invokeMethodWithTimeout',
'Test method invocation with timeout',
_results['timeout'] ?? 'Not tested',
_testInvokeMethodWithTimeout,
),
const SizedBox(height: 16),
_buildTestCard(
context,
'Custom Configuration',
'Test with custom config (timeout: 10s, retries: 5, logging: true)',
_results['custom'] ?? 'Not tested',
_testCustomConfig,
),
],
),
);
}
Widget _buildTestCard(BuildContext context, String title, String description,
String result, VoidCallback onTest) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 4),
Text(
description,
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 8),
Text(
result,
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: onTest,
child: Text('Test $title'),
),
],
),
),
);
}
}
// Event Channel Demo
class EventChannelDemo extends StatefulWidget {
const EventChannelDemo({super.key});
@override
State<EventChannelDemo> createState() => _EventChannelDemoState();
}
enum _EventChannelMode {
broadcast,
single,
highFrequency,
debug,
}
class _EventChannelDemoState extends State<EventChannelDemo> {
_EventChannelMode _mode = _EventChannelMode.debug;
String _eventData = 'No events received';
String _sendEventResult = 'Not tested';
StreamSubscription<dynamic>? _subscription;
@override
void initState() {
super.initState();
_startStream(_mode);
}
Future<void> _startStream(_EventChannelMode mode) async {
await _safeCancel(_subscription);
setState(() {
_eventData = 'Listening...';
_mode = mode;
});
EventChannelPlus channel;
switch (mode) {
case _EventChannelMode.broadcast:
channel = EventChannelPlus('demo_events');
_subscription = channel.receiveBroadcastStream().listen((event) {
setState(() {
_eventData = 'Broadcast event: $event';
});
});
break;
case _EventChannelMode.single:
channel = EventChannelPlus('demo_events_single');
_subscription = channel.receiveStream().listen((event) {
setState(() {
_eventData = 'Single stream event: $event';
});
});
break;
case _EventChannelMode.highFrequency:
channel = EventChannelPlus.forHighFrequency('sensor_data');
_subscription = channel.receiveBroadcastStream().listen((event) {
setState(() {
_eventData = 'High frequency event: $event';
});
});
break;
case _EventChannelMode.debug:
channel = EventChannelPlus.forDebug('debug_events');
_subscription = channel.receiveBroadcastStream().listen((event) {
setState(() {
_eventData = 'Debug event: $event';
});
});
break;
}
}
Future<void> _testSendEvent() async {
final eventChannel = EventChannelPlus('demo_events');
final result = await eventChannel
.sendEvent({'type': 'test', 'data': 'Hello from Flutter!'});
setState(() {
_sendEventResult = result.isSuccess
? 'Event sent successfully'
: 'Error: ${result.error}';
});
}
@override
void dispose() {
_safeCancel(_subscription);
super.dispose();
}
Future<void> _safeCancel(StreamSubscription<dynamic>? sub) async {
try {
await sub?.cancel();
} catch (_) {
// Ignore cancellation errors from platform streams
}
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
spacing: 8,
runSpacing: 8,
children: [
ChoiceChip(
label: const Text('Broadcast'),
selected: _mode == _EventChannelMode.broadcast,
onSelected: (_) => _startStream(_EventChannelMode.broadcast),
),
ChoiceChip(
label: const Text('Single Stream'),
selected: _mode == _EventChannelMode.single,
onSelected: (_) => _startStream(_EventChannelMode.single),
),
ChoiceChip(
label: const Text('High Frequency'),
selected: _mode == _EventChannelMode.highFrequency,
onSelected: (_) =>
_startStream(_EventChannelMode.highFrequency),
),
ChoiceChip(
label: const Text('Debug'),
selected: _mode == _EventChannelMode.debug,
onSelected: (_) => _startStream(_EventChannelMode.debug),
),
],
),
const SizedBox(height: 16),
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Active Stream: ${_mode.name}',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
Text(
_eventData,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
),
const SizedBox(height: 16),
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Send Event',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
Text(
_sendEventResult,
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: _testSendEvent,
child: const Text('Send Event to Platform'),
),
],
),
),
),
],
),
);
}
}
// Message Channel Demo
class MessageChannelDemo extends StatefulWidget {
const MessageChannelDemo({super.key});
@override
State<MessageChannelDemo> createState() => _MessageChannelDemoState();
}
class _MessageChannelDemoState extends State<MessageChannelDemo> {
String _sendResult = 'Not tested';
String _sendAndReceiveResult = 'Not tested';
String _handlerResult = 'No messages received';
String _textChannelResult = 'Not tested';
String _binaryChannelResult = 'Not tested';
@override
void initState() {
super.initState();
_setupMessageHandler();
}
Future<void> _setupMessageHandler() async {
final messageChannel = BasicMessageChannelPlus<String>('demo_messages');
await messageChannel.setMessageHandler((message) async {
setState(() {
_handlerResult = 'Received: $message';
});
return 'Echo: $message';
});
}
Future<void> _testSend() async {
final messageChannel = BasicMessageChannelPlus<String>('demo_messages');
final result = await messageChannel.send('Hello from Flutter!');
setState(() {
_sendResult = result.isSuccess
? 'Message sent successfully'
: 'Error: ${result.error}';
});
}
Future<void> _testSendAndReceive() async {
final messageChannel = BasicMessageChannelPlus<String>('demo_messages');
final result = await messageChannel.sendAndReceive('ping');
setState(() {
_sendAndReceiveResult = result.isSuccess
? 'Response: ${result.data}'
: 'Error: ${result.error}';
});
}
Future<void> _testTextChannel() async {
final textChannel = BasicMessageChannelPlus.forText('text_messages');
final result = await textChannel.sendAndReceive('Hello from text channel!');
setState(() {
_textChannelResult = result.isSuccess
? 'Response: ${result.data}'
: 'Error: ${result.error}';
});
}
Future<void> _testBinaryChannel() async {
final binaryChannel = BasicMessageChannelPlus.forBinary('binary_messages');
final data = Uint8List.fromList([1, 2, 3, 4, 5]);
final result = await binaryChannel.send(data);
setState(() {
_binaryChannelResult = result.isSuccess
? 'Binary data sent successfully'
: 'Error: ${result.error}';
});
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildTestCard(
context,
'Send Message',
'Send a message without waiting for response',
_sendResult,
_testSend,
),
const SizedBox(height: 16),
_buildTestCard(
context,
'Send and Receive',
'Send a message and wait for response',
_sendAndReceiveResult,
_testSendAndReceive,
),
const SizedBox(height: 16),
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Message Handler',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
Text(
'Handler is set up to echo messages',
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 8),
Text(
_handlerResult,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
),
const SizedBox(height: 16),
_buildTestCard(
context,
'Text Channel (forText)',
'Pre-configured text message channel',
_textChannelResult,
_testTextChannel,
),
const SizedBox(height: 16),
_buildTestCard(
context,
'Binary Channel (forBinary)',
'Pre-configured binary data channel',
_binaryChannelResult,
_testBinaryChannel,
),
],
),
);
}
Widget _buildTestCard(BuildContext context, String title, String description,
String result, VoidCallback onTest) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 4),
Text(
description,
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 8),
Text(
result,
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: onTest,
child: Text('Test $title'),
),
],
),
),
);
}
}