flutter_platform_channels_plus 0.1.0 copy "flutter_platform_channels_plus: ^0.1.0" to clipboard
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.

example/lib/main.dart

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'),
            ),
          ],
        ),
      ),
    );
  }
}
2
likes
160
points
85
downloads

Publisher

verified publisherbechattaoui.dev

Weekly Downloads

Simplified platform channel communication for Flutter with support for all 6 platforms (iOS, Android, Web, Windows, macOS, Linux) and WASM compatibility.

Repository (GitHub)
View/report issues

Topics

#platform-channels #flutter-plugin #cross-platform #desktop #wasm

Documentation

API reference

Funding

Consider supporting this project:

github.com

License

MIT (license)

Dependencies

flutter

More

Packages that depend on flutter_platform_channels_plus

Packages that implement flutter_platform_channels_plus