Flutterexperts

Crafting Next-Gen Apps with Flutter Power
Exploring Event and Method Channels in Flutter: A Comprehensive Guide

Flutter, the powerful toolkit from Google, seamlessly blends Dart and native code. Let’s dive into the essential communication tools it offers: Event Channels and Method Channels. This guide aims to simplify it, breaking down what these channels do, and their benefits, and even revealing a nifty trick to bypass using a device_info plugin with Event Channels.

If you’re looking for the best Flutter app development company for your mobile application then feel free to contact us at — support@flutterdevs.com.


Table of Contents:

Event Channel

Method Channel

Decoding Bidirectional Interaction

Scenarios to use Event Channels

Scenarios to use Method Channels

A Practical Demonstration

Showcasing with a Live Demo

Conclusion

Github

Reference


Event Channel:

Bridging the Gap in Real-Time Communication

Event Channels in Flutter are a powerful mechanism for real-time communication between Dart and native code. They are particularly useful for scenarios where continuous streams of data need to be shared. Let’s break down the key aspects of Event Channels:

  • Pros:
  1. Real-time Updates: Event Channels facilitate the continuous flow of data from native code to Dart, ensuring that your Flutter UI remains up-to-date with the latest information.
  2. Bi-Directional Communication: Events can flow in both directions, allowing communication from Dart to native code and vice versa.
  • Cons:
  1. Complexity: Implementing and managing Event Channels can be more complex than other communication methods, especially for beginners.
  2. Overhead: Continuous communication might introduce some performance overhead, so it’s crucial to optimize for efficiency.

Differences from Method Channels:

  1. Continuous Streams: While Method Channels are designed for single invocations, Event Channels are suitable for continuous data streams.
  2. Bi-Directional Communication: Event Channels are more suited for scenarios where both Dart and native code need to send updates back and forth continuously.

Method Channel:

Precise and Controlled Communication

Method Channels provide a straightforward way to call native methods from Dart code and receive the results. This one-time communication is beneficial for scenarios where a single piece of data or action needs to be conveyed. Let’s explore the key aspects of Method Channels:

  • Pros:
  1. Simplicity: Method Channels offer a simpler and more controlled approach compared to Event Channels, making them suitable for one-off interactions.
  2. Efficiency: The overhead is lower compared to Event Channels, making Method Channels more efficient for singular data transfers.
  • Cons:
  1. Limited Real-Time Updates: If real-time updates are needed, Event Channels might be a more suitable choice.
  2. Complexity for Continuous Streams: Implementing continuous data streams with Method Channels can be less intuitive and might require additional workarounds.

Differences from Event Channels:

  1. Single Invocation: Method Channels are designed for single method invocations and are ideal for scenarios where a specific action needs to be performed.
  2. One-Way Communication: While Event Channels support bi-directional communication, Method Channels follow a one-way communication pattern.

Decoding Bidirectional Interaction:

Both Event Channels and Method Channels in Flutter support bidirectional communication, but they are often used in different scenarios. Event Channels excel in continuous streams of data, providing real-time updates in both directions. On the other hand, Method Channels are more suited for one-time method invocations, even though they can be used bidirectionally by handling multiple method calls independently.


Scenarios to use Event Channels

  1. Custom Hardware Integration:

Scenario: You’re working with a specific hardware component for which there is no existing Flutter plugin.

Reason to Use Event Channel: Event Channels allow you to establish a continuous communication stream between Dart and native code, making it suitable for scenarios where you need real-time updates from custom hardware.

2. Low Dependency Footprint:

Scenario: You want to minimize the number of external dependencies in your Flutter project.

Reason to Use Event Channel: By using Event Channels, you can reduce your reliance on third-party plugins, keeping your project lightweight and giving you more control over the codebase.

3. Real-Time Data Streams:

Scenario: Your application requires real-time updates from the native side, and existing plugins do not provide the necessary continuous data stream.

Reason to Use Event Channel: Event Channels are designed for scenarios where you need continuous streams of data. If the existing plugins don’t support real-time updates, Event Channels offer a customizable solution.

4. Performance Optimization:

Scenario: You are working on a performance-critical application, and existing plugins introduce unnecessary overhead.

Reason to Use Event Channel: Event Channels allow you to optimize performance by tailoring the communication process to your specific needs, avoiding any unnecessary abstraction layers that might be present in plugins.

5. Integration with Proprietary Systems:

cenario: Your Flutter app needs to integrate with a proprietary or specialized system that lacks community-supported plugins.

Reason to Use Event Channel: Event Channels provide a flexible way to communicate with native code, allowing you to integrate with proprietary systems for which no public plugins are available.

6. Custom UI Components:

Scenario: You’re building custom UI components that require continuous updates or complex interactions.

Reason to Use Event Channel: Event Channels offer the flexibility to send and receive data between Dart and native code in real-time, making them suitable for scenarios where custom UI components demand dynamic updates.

Remember that while Event Channels provide flexibility and control, they also come with added responsibility for managing the communication protocol and ensuring efficient data transfer between Dart and native code. Carefully assess your project requirements and consider the trade-offs before opting for Event Channels over existing plugins.


Scenarios to use Method Channels:

1. Custom Functionality:

Scenario: You need to implement specific functionalities that are not covered by existing Flutter plugins.

Reason to Use Method Channel: Method Channels allow you to define custom methods in native code and invoke them from Dart. This is useful when you need to implement tailored functionality that doesn’t fit within the scope of existing plugins.

2. Direct Native API Access:

Scenario: You require direct access to native APIs without relying on the abstractions provided by plugins.

Reason to Use Method Channel: Method Channels enable direct communication between Dart and native code, allowing you to call native methods and access APIs without the additional layer of abstraction introduced by plugins.

3. Performance Optimization:

Scenario: Your application demands optimal performance, and existing plugins introduce unnecessary overhead.

Reason to Use Method Channel: By using Method Channels, you have more control over the communication process, enabling you to fine-tune performance and avoid any performance bottlenecks introduced by generic plugins.

4. Integration with Native Libraries:

Scenario: Your project requires integration with native libraries that are not covered by existing plugins.

Reason to Use Method Channel: Method Channels provide a way to bridge the gap between Flutter and native code, allowing you to directly interface with native libraries that may not have dedicated Flutter plugins.

5. Legacy System Integration:

Scenario: You need to integrate with a legacy system or a platform that lacks community-supported plugins.

Reason to Use Method Channel: Method Channels offer a direct and customizable communication bridge, making them suitable for integrating with legacy systems or platforms for which no suitable plugins are available.

6. Complex Business Logic:

Scenario: Your application involves complex business logic that is better handled in native code.

Reason to Use Method Channel: Method Channels enable you to encapsulate and execute complex business logic on the native side, providing a more efficient and tailored solution compared to generic plugins.

7. Specific UI Implementations:

Scenario: You are implementing custom UI components with complex interactions that aren’t supported by existing plugins.

Reason to Use Method Channel: Method Channels offer the flexibility to trigger specific UI interactions or behaviors directly from Dart, allowing you to implement custom UI components with the desired native behavior.

When choosing between Method Channels and plugins, carefully evaluate the project requirements, considering factors such as performance, customizability, and the availability of existing solutions. Method Channels provide a powerful way to establish communication between Flutter and native code, giving you the flexibility to implement features that may not be covered by standard plugins.


A Practical Demonstration:

Avoiding Plugin Dependency with Event Channels:

As a Flutter developer, you might want to reduce your reliance on plugins, and Event Channels provide an elegant solution for scenarios like retrieving device information. This approach helps maintain greater control over the codebase and can lead to a more lightweight application. I also set up an Event Channel for sending a string to Flutter

Add code in MainActivity.kt

class MainActivity : FlutterActivity() {
private val handler = Handler(Looper.getMainLooper())

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// Set up an EventChannel for device information
val deviceEventChannel = EventChannel(
flutterEngine?.dartExecutor?.binaryMessenger,
DEVICE_EVENT_CHANNEL_NAME
)

// Send device information to Flutter
handler.postDelayed({
val deviceInfo = getDeviceInfo()
deviceEventChannel.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, eventSink: EventChannel.EventSink) {
eventSink.success(deviceInfo)
}

override fun onCancel(arguments: Any?) {
// Handle cancellation
}
})
}, 1000)

// Set up an EventChannel for sending a string to Flutter
val stringEventChannel = EventChannel(
flutterEngine?.dartExecutor?.binaryMessenger,
STRING_EVENT_CHANNEL_NAME
)

// Example: send a string from native to Flutter every 2 seconds
handler.postDelayed({
sendStringToFlutter("Hello from native!")
// You can change the string or the frequency as needed
}, 2000)
}

private fun getDeviceInfo(): Map<String, Any> {
val deviceInfo = HashMap<String, Any>()
deviceInfo["model"] = Build.MODEL
deviceInfo["brand"] = Build.BRAND
deviceInfo["version"] = Build.VERSION.RELEASE
// Add more information as needed

return deviceInfo
}

private fun sendStringToFlutter(message: String) {
val stringEventChannel = EventChannel(
flutterEngine?.dartExecutor?.binaryMessenger,
STRING_EVENT_CHANNEL_NAME
)
stringEventChannel.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, eventSink: EventChannel.EventSink) {
eventSink.success(message)
}

override fun onCancel(arguments: Any?) {
// Handle cancellation
}
})
}

companion object {
private const val DEVICE_EVENT_CHANNEL_NAME = "deviceEventChannel"
private const val STRING_EVENT_CHANNEL_NAME = "stringEventChannel"
}

}

Call Event Channel in Ui

import 'package:event_channel_demo/core/constants/icon_constants.dart';
import 'package:event_channel_demo/core/constants/string_constants.dart';
import 'package:event_channel_demo/core/custom_elevated_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class EventChannelView extends StatefulWidget {
const EventChannelView({super.key});

@override
State<EventChannelView> createState() => _EventChannelViewState();
}

class _EventChannelViewState extends State<EventChannelView> {

static const EventChannel _deviceEventChannel =
EventChannel('deviceEventChannel');
static const EventChannel _stringEventChannel =
EventChannel('stringEventChannel');

Map<String, dynamic>? _deviceInfo;
String _stringFromNative = StringConstants.notReceivedString;

void _stopListeningAndClearEventChannelData() {
setState(() {
_deviceInfo = null;
_stringFromNative = StringConstants.notReceivedString;
});
}

void _sendDeviceInfoEvent() {
_deviceEventChannel.receiveBroadcastStream().listen((dynamic event) {
setState(() {
_deviceInfo = Map<String, dynamic>.from(event);
});
});
}

void _sendStringEvent() {
_stringEventChannel.receiveBroadcastStream().listen((dynamic event) {
setState(() {
_stringFromNative = event.toString();
});
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.black,
title: Text(StringConstants.eventChannel),
),
body: Column(
// mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
height: 100,
width: 100,
margin: const EdgeInsets.all(12),
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(AppIcons.bgImage),
alignment: Alignment.center,
fit: BoxFit.fill)),
)
],
),
SizedBox(height: MediaQuery.of(context).size.height*0.12),
if (_deviceInfo != null)
for (var entry in _deviceInfo!.entries)
Text(
'${entry.key}: ${entry.value}',
style: const TextStyle(fontSize: 16),
),
Text(
'${StringConstants.stringFromNative}: $_stringFromNative',
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 20),
CustomElevatedButton(title: StringConstants.getDeviceInfoNative,
onTap: _sendDeviceInfoEvent
),
const SizedBox(height: 10),
CustomElevatedButton(title: StringConstants.getStringNative,
onTap: _sendStringEvent
),
const SizedBox(height: 10),
CustomElevatedButton(title: StringConstants.clearData,
onTap: _stopListeningAndClearEventChannelData
),

],
),
);
}
}

Calculating the Sum with Method Channel:

To further showcase the usage of Method Channels, we’ve implemented a simple demo where Dart calls a native method to calculate the sum of two numbers. This provides a practical example of how Method Channels can be employed for specific tasks.

class MainActivity : FlutterActivity() {
private val handler = Handler(Looper.getMainLooper())
private val CHANNEL = "exampleMethodChannel"

override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)

MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"getMessage" -> result.success("Hello from Android!")
"calculateSum" -> {
val a = call.argument<Int>("a") ?: 0
val b = call.argument<Int>("b") ?: 0
val sum = calculateSum(a, b)
result.success(sum)
}
else -> result.notImplemented()
}
}
}

private fun calculateSum(a: Int, b: Int): Int {
return a + b
}
}

Call Method Channel in Ui

import 'package:event_channel_demo/core/constants/icon_constants.dart';
import 'package:event_channel_demo/core/constants/string_constants.dart';
import 'package:event_channel_demo/core/custom_elevated_button.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class MethodChannelView extends StatefulWidget {
const MethodChannelView({super.key});

@override
State<MethodChannelView> createState() => _MethodChannelViewState();
}

class _MethodChannelViewState extends State<MethodChannelView> {
static const MethodChannel _methodChannel =
MethodChannel('exampleMethodChannel');

int _result = 0;

Future<void> _calculateSum(int a, int b) async {
try {
final dynamic result = await _methodChannel
.invokeMethod('calculateSum', {'a': a, 'b': b});
setState(() {
_result = result as int;
});
} on PlatformException catch (e) {
setState(() {
_result = 0;
});
if (kDebugMode) {
print('Error: ${e.message}');
}
}
}
void _stopListeningAndClearMethodChannelData() {
setState(() {
_result = 0;
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.black,
title: Text(StringConstants.methodChannel),
),
body: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
height: 100,
width: 100,
margin: const EdgeInsets.all(12),
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(AppIcons.bgImage),
alignment: Alignment.center,
fit: BoxFit.fill)),
)
],
),
SizedBox(height: MediaQuery.of(context).size.height*0.15),
Text(
'${StringConstants.result}: $_result',
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 10),
CustomElevatedButton(title: '${StringConstants.calculateSum} (10 + 20)',
onTap: () => _calculateSum(10, 20),
),
const SizedBox(height: 10),
CustomElevatedButton(title: '${StringConstants.calculateSum} (5 + 7)',
onTap: () => _calculateSum(5, 7),
),
const SizedBox(height: 10),
CustomElevatedButton(title: StringConstants.clearData,
onTap: _stopListeningAndClearMethodChannelData,
),
],
),
);
}
}


Showcasing with a Live Demo

Event Channel and Method Channel in Action

Conclusion:

Choosing the Right Channel for the Job

In conclusion, both Event and Method Channels play crucial roles in Flutter development, offering versatile solutions for communication between Dart and native code. The choice between them depends on the specific requirements of your application.

When dealing with real-time updates or continuous data streams, Event Channels shine. For controlled and one-time interactions, Method Channels provide a simpler and more efficient solution.

By understanding the strengths and weaknesses of each channel, Flutter developers can make informed decisions, ensuring the seamless integration of their applications with native code. Happy coding!


Github:

Explore the entire codebase for this demonstration through the provided link

GitHub – RitutoshAeologic/event_method_channel_demo
Contribute to RitutoshAeologic/event_method_channel_demo development by creating an account on GitHub.github.com


❤ ❤ Thanks for reading this article ❤❤

If I got something wrong? Let me know in the comments. I would love to improve.

Clap 👏 If this article helps you.


Reference:

Writing custom platform-specific code
Learn how to write custom platform-specific code in your app.docs.flutter.dev


Feel free to connect with us:
And read more articles from FlutterDevs.com.

FlutterDevs team of Flutter developers to build high-quality and functionally-rich apps. Hire a Flutter developer for your cross-platform Flutter mobile app project hourly or full-time as per your requirement! For any flutter-related queries, you can connect with us on Facebook, GitHub, Twitter, and LinkedIn.

We welcome feedback and hope that you share what you’re working on using #FlutterDevs. We truly enjoy seeing how you use Flutter to build beautiful, interactive web experiences.


Leave comment

Your email address will not be published. Required fields are marked with *.