Topic: 14 Understanding Navigation and Communication in Flutter
This is our fourteenth topic from Learn Flutter: Basic to Advance
Hello devs, In today's article, we discussed how widgets communicate with each other, How Flutter communicates with the Android or IOS module, and How navigation works in Flutter.
Start with the Navigation.
Navigation in Flutter
Navigation in Flutter involves moving between different routes, where each route represents a distinct screen or page within the app. Flutter provides a variety of navigation techniques, including:
Static Navigation: Using named routes defined in the MaterialApp widget's routes property.
Dynamic Navigation: Programmatically navigating between screens using the Navigator class.
Nested Navigation: Handling navigation within nested navigators, such as in tabbed or drawer-based layouts.
Basic Navigation Example
Let's start with a simple example of how to navigate between two screens in Flutter using static navigation:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/details': (context) => DetailsScreen(),
},
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Screen'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, '/details');
},
child: Text('Go to Details Screen'),
),
),
);
}
}
class DetailsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Details Screen'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back'),
),
),
);
}
}
In this example, we define two screens: HomeScreen and DetailsScreen. We use named routes to navigate between these screens using the Navigator class. When the user taps the button on the HomeScreen, they're taken to the DetailsScreen. Similarly, tapping the button on the DetailsScreen navigates back to the HomeScreen.
Passing Data Between Screens
You can also pass data between screens during navigation. For example, you can use the arguments parameter of Navigator.pushNamed to pass data to the destination screen:
Navigator.pushNamed(
context,
'/details',
arguments: 'Hello from HomeScreen!',
);
Then, you can retrieve the passed data in the destination screen using the ModalRoute settings:
class DetailsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final String data = ModalRoute.of(context)?.settings.arguments as String;
return Scaffold(
appBar: AppBar(
title: Text('Details Screen'),
),
body: Center(
child: Text(data),
),
);
}
}
Navigation is a crucial aspect of building Flutter applications. Whether you're navigating between screens, passing data between screens, or handling navigation events, Flutter provides a variety of techniques to help you create a seamless and intuitive user experience. By mastering the basics of navigation in Flutter, you can build robust and user-friendly apps that delight your users.
Alright devs, Now let's explore how Flutter Communicates with the Android or IOS module.
Communication between Flutter and Android/IOS module
Flutter provides a mechanism called Platform Channels that facilitates communication between Dart code (Flutter) and platform-specific code (Android/iOS). Platform Channels act as a bridge, allowing you to invoke platform-specific code from your Flutter app and receive responses back.
How it Works
Method Channels: Method Channels are the most common type of platform channel used in Flutter. They enable bidirectional communication between Flutter and native code by invoking platform-specific methods and passing data back and forth.
Event Channels: Event Channels are another type of platform channel used for listening to continuous streams of data or events from the native platform. They're particularly useful for scenarios like listening to sensor data or receiving real-time updates from the platform.
Communicating with Android Modules
When communicating with Android modules in Flutter, you typically follow these steps:
Define Method Channels: Define a Method Channel in your Flutter code to specify the communication channel between Flutter and Android.
Implement Platform-Specific Code: Write the platform-specific code in Java/Kotlin to handle method calls from Flutter. This code will reside in your Android module and will be responsible for executing the desired functionality.
Invoke Platform Methods: Use the Method Channel in your Flutter code to invoke platform-specific methods. Pass any required parameters to the platform methods, and handle the responses accordingly.
Communicating with iOS Modules
Similarly, when communicating with iOS modules in Flutter, you follow a similar process:
Define Method Channels: Define a Method Channel in your Flutter code to establish communication between Flutter and iOS.
Implement Platform-Specific Code: Write the platform-specific code in Objective-C/Swift to handle method calls from Flutter. This code will reside in your iOS module and will execute the desired functionality.
Invoke Platform Methods: Use the Method Channel in your Flutter code to invoke platform-specific methods. Pass any necessary parameters to the platform methods and handle the responses accordingly.
Example Code
Here's a simplified example demonstrating how to communicate with platform modules in Flutter:
import 'package:flutter/services.dart';
// Define Method Channel
const platform = MethodChannel('platform_channel');
// Invoke Platform Method
Future<void> invokePlatformMethod() async {
try {
final String result = await platform.invokeMethod('platform_method');
print('Result from platform: $result');
} on PlatformException catch (e) {
print('Failed to invoke platform method: ${e.message}');
}
}
// Android Platform-specific Code
public class MyPlugin implements MethodCallHandler {
Context context;
MyPlugin(Context context) {
this.context = context;
}
@Override
public void onMethodCall(MethodCall call, Result result) {
if (call.method.equals("platform_method")) {
// Execute platform-specific functionality
result.success("Data from Android");
} else {
result.notImplemented();
}
}
}
// iOS Platform-specific Code
import Flutter
public class MyPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "platform_channel", binaryMessenger: registrar.messenger())
let instance = MyPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if call.method == "platform_method" {
// Execute platform-specific functionality
result("Data from iOS")
} else {
result(FlutterMethodNotImplemented)
}
}
}
Okay, so devs I want you to tell one more thing that helps you to communicate between Flutter and Android/iOS. In one of my project, I used pigeon lib for this. Let's explore.
Pigeon
Pigeon is a code generator tool provided by the Flutter team that simplifies communication between Flutter and platform-specific code. It generates platform-specific code for you, saving you the hassle of manually defining method channels and handling communication boilerplate. With Pigeon, you define your communication protocol using a simple interface definition language (IDL), and Pigeon takes care of the rest.
How Pigeon Works
Pigeon works by generating platform-specific code from a single IDL file. This code includes Dart code for Flutter and platform-specific code for Android and iOS. Pigeon automatically handles message serialization and deserialization, method invocation, and error handling, making communication between Flutter and platform modules seamless and hassle-free.
Using Pigeon in Flutter
Using Pigeon in Flutter involves several steps:
Define an Interface: Define your communication protocol using the Pigeon IDL language. This includes defining messages and methods that you want to expose to Flutter and platform-specific code.
Generate Code: Use the Pigeon tool to generate platform-specific code from your IDL file. This generates Dart code for Flutter and platform-specific code for Android and iOS.
Implement Platform-Specific Code: Implement the platform-specific code in your Android and iOS modules according to the generated code. This code handles method invocation and communicates with your Flutter app.
Use Generated Code in Flutter: Use the generated Dart code in your Flutter app to communicate with the platform-specific code. This code provides a clean API for invoking methods and passing data between Flutter and platform modules.
Benefits of Using Pigeon
Using Pigeon offers several benefits:
Simplicity: Pigeon simplifies communication between Flutter and platform-specific code by generating code for you, reducing boilerplate and manual effort.
Consistency: Pigeon ensures consistency between Flutter and platform-specific code by generating code based on a single source of truth (the IDL file).
Type Safety: Pigeon provides type-safe communication between Flutter and platform modules, ensuring that data is properly serialized and deserialized.
Efficiency: Pigeon automates message serialization and deserialization, method invocation, and error handling, saving you time and effort.
Let's say we want to create a simple messaging app where the Flutter front-end needs to communicate with platform-specific code to send and receive messages. We can use Pigeon to define the communication protocol between Flutter and the platform.
First, we define our communication protocol using Pigeon's IDL language. We'll create a file called messaging.pigeon
:
// messaging.pigeon
// Define a Dart class representing a message
class Message {
String? text;
}
// Define a Dart class representing a response
class Response {
bool? success;
}
// Define a Dart class representing a platform interface
@HostApi()
abstract class MessagingApi {
Response send(Message message);
}
Next, we generate the platform-specific code using the Pigeon tool. We run the following command in the terminal:
flutter pub run pigeon --input messaging.pigeon --dart_out lib/messaging.dart --objc_header_out ios/Classes/Messaging.pigeon.h --objc_source_out ios/Classes/Messaging.pigeon.m
This command generates Dart code for Flutter (lib/messaging.dart
) and Objective-C code for iOS (ios/Classes/Messaging.pigeon.h
and ios/Classes/Messaging.pigeon.m
).
Now, let's take a look at how we can use the generated Dart code in our Flutter app:
// main.dart
import 'package:flutter/material.dart';
import 'package:messaging/messaging.dart'; // Import the generated Dart code
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MessagingPage(),
);
}
}
class MessagingPage extends StatefulWidget {
@override
_MessagingPageState createState() => _MessagingPageState();
}
class _MessagingPageState extends State<MessagingPage> {
MessagingApi messagingApi = MessagingApi(); // Instantiate the generated API class
void sendMessage() async {
Message message = Message()..text = "Hello from Flutter";
Response response = await messagingApi.send(message); // Call the send method
print('Response from platform: ${response.success}');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Messaging App'),
),
body: Center(
child: ElevatedButton(
onPressed: sendMessage,
child: Text('Send Message'),
),
),
);
}
}
In this Flutter code, we import the generated Dart code messaging.dart
, instantiate the generated API class MessagingApi
, and use it to send a message to the platform. The response from the platform is printed to the console.
On the platform-specific side (e.g., iOS), we would implement the MessagingApi
protocol according to the generated Objective-C code. We can then handle the message and send back a response. The communication between Flutter and the platform is now streamlined and type-safe thanks to Pigeon.
Pigeon is a powerful tool for simplifying communication between Flutter and platform-specific modules. By generating code based on a single interface definition language (IDL) file, Pigeon streamlines the process of defining method channels, handling communication, and ensuring consistency between Flutter and platform-specific code. Whether you're accessing device APIs, integrating native libraries, or implementing custom platform-specific features, Pigeon empowers you to build robust and versatile Flutter apps with ease.
Alright devs, This is the end of our blog i hope this blog helps you to easily understand Navigation and Communication in Flutter. Okay, then We catch up on our next topic TDD.
Connect with Me:
Hey there! If you enjoyed reading this blog and found it informative, why not connect with me on LinkedIn? π You can also follow my Instagram page for more mobile development-related content. π²π¨βπ» Letβs stay connected, share knowledge and have some fun in the exciting world of app development! π