Flutter - Writing Android Specific Code
Flutter - Writing Android Platform-Specific Code
Section titled “Flutter - Writing Android Platform-Specific Code”While Flutter allows building cross-platform UIs from a single codebase, sometimes you need to access native platform features (like device sensors, specific OS services, or existing native libraries). Flutter provides Platform Channels for this communication.
Platform Channel Architecture
Section titled “Platform Channel Architecture”Platform channels enable communication between your Dart code (Client) and the native platform code (Host - Android/Java/Kotlin or iOS/ObjC/Swift).
- Message Channel: A named channel established between Dart and the native side.
- Method Calls: Dart code invokes methods on the channel, optionally passing arguments.
- Message Codec: Data (arguments and results) is serialized/deserialized automatically using a standard codec (
StandardMethodCodecis common, supporting basic types like numbers, strings, booleans, lists, maps). - Host Implementation: Native code listens for method calls on the same channel name, executes the required platform-specific logic, and sends back a result (success or error).
- Async Communication: Communication is asynchronous using Dart’s
Futures, preventing the UI from freezing while waiting for native code.
Think of it like sending messages back and forth over a specific communication line identified by a unique name.
Example: Opening a URL in the Native Browser (Android)
Section titled “Example: Opening a URL in the Native Browser (Android)”Let’s create an app with a button that uses Android’s native Intent system to open a web URL. (Note: For this specific task, the url_launcher package is the recommended, easier approach. This example demonstrates the manual platform channel mechanism.)
- Create Project: Create a new Flutter application:
flutter create android_platform_channel_app. - Dart Code (
lib/main.dart):
import 'package:flutter/material.dart';import 'package:flutter/services.dart'; // Required for Platform Channelsimport 'dart:async';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key);
@override Widget build(BuildContext context) { return MaterialApp( title: 'Platform Channel Demo', home: const MyHomePage(title: 'Android Platform Channel'), ); }}
class MyHomePage extends StatelessWidget { const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
// 1. Define the MethodChannel // Use a unique name, typically reverse domain notation static const platform = MethodChannel('com.example.android_platform_channel_app/browser');
// 2. Method to invoke the native code Future<void> _openBrowser(String url) async { try { // Use invokeMethod with the method name expected by the native side // Pass arguments as a Map final bool result = await platform.invokeMethod('openBrowser', {'url': url}); if (result) { print('Browser opened successfully (Android response)'); } else { print('Failed to open browser (Android response)'); } } on PlatformException catch (e) { // Handle errors coming from the native side print("Failed to open browser: '${e.message}'."); } catch (e) { // Handle other potential errors print("An error occurred: $e"); } }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(title), ), body: Center( // Replace deprecated RaisedButton with ElevatedButton child: ElevatedButton( child: const Text('Open Flutter Dev'), onPressed: () { // 3. Call the method when the button is pressed _openBrowser('https://flutter.dev'); }, ), ), ); }}- Android Native Code (Kotlin - Recommended): Open
android/app/src/main/kotlin/com/your_org/your_app/MainActivity.kt(path might vary slightly).
package com.example.android_platform_channel_app // Use your package name
import androidx.annotation.NonNullimport io.flutter.embedding.android.FlutterActivityimport io.flutter.embedding.engine.FlutterEngineimport io.flutter.plugin.common.MethodChannelimport android.content.Intentimport android.net.Uriimport android.os.Bundleimport android.util.Log
class MainActivity: FlutterActivity() { // 1. Define the same channel name used in Dart private val CHANNEL = "com.example.android_platform_channel_app/browser"
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine)
// 2. Set up the MethodChannel MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { // This lambda function is invoked when Dart calls invokeMethod call, result ->
// 3. Check which method was called if (call.method == "openBrowser") { // 4. Extract arguments val url = call.argument<String>("url") if (url != null) { // 5. Call the native function val success = openBrowser(url) // 6. Send result back to Dart result.success(success) } else { result.error("INVALID_ARGUMENT", "URL cannot be null.", null) } } else { // Indicate that the method is not implemented on the native side result.notImplemented() } } }
// Native function to open the browser using an Intent private fun openBrowser(url: String): Boolean { return try { val intent = Intent(Intent.ACTION_VIEW) intent.data = Uri.parse(url) // Make sure context is not null - activity should be available activity?.startActivity(intent) true // Indicate success } catch (e: Exception) { Log.e("MainActivity", "Error opening browser", e) false // Indicate failure } }}- (Alternative) Android Native Code (Java): If using Java, open
android/app/src/main/java/com/your_org/your_app/MainActivity.java.
package com.example.android_platform_channel_app; // Use your package name
import androidx.annotation.NonNull;import io.flutter.embedding.android.FlutterActivity;import io.flutter.embedding.engine.FlutterEngine;import io.flutter.plugin.common.MethodChannel;import android.content.Intent;import android.net.Uri;import android.os.Bundle;import android.util.Log;
public class MainActivity extends FlutterActivity { // 1. Define the same channel name used in Dart private static final String CHANNEL = "com.example.android_platform_channel_app/browser";
@Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine);
// 2. Set up the MethodChannel new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL) .setMethodCallHandler( (call, result) -> { // This lambda function is invoked when Dart calls invokeMethod // 3. Check which method was called if (call.method.equals("openBrowser")) { // 4. Extract arguments String url = call.argument("url"); if (url != null) { // 5. Call the native function boolean success = openBrowser(url); // 6. Send result back to Dart result.success(success); } else { result.error("INVALID_ARGUMENT", "URL cannot be null.", null); } } else { // Indicate that the method is not implemented on the native side result.notImplemented(); } } ); }
// Native function to open the browser using an Intent private boolean openBrowser(String url) { try { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(url)); // Use getActivity() to get the context getActivity().startActivity(intent); return true; // Indicate success } catch (Exception e) { Log.e("MainActivity", "Error opening browser", e); return false; // Indicate failure } }}- Run the App: Stop and restart the application (a hot reload might not pick up native code changes). Click the ‘Open Flutter Dev’ button. It should launch the default browser on your Android device/emulator and navigate to
https://flutter.dev.
Important Considerations
Section titled “Important Considerations”- Error Handling: Always wrap Dart’s
invokeMethodin atry-catchblock forPlatformExceptionand handle potential errors on the native side. - Uniqueness: Channel names must be unique within your application.
- Data Types: Ensure the data types you pass and receive are supported by the chosen
MethodCodec(usuallyStandardMethodCodec). - Thread Safety (Android): Platform channel methods are executed on the Android main thread by default. Perform long-running tasks on background threads to avoid blocking the UI.
- Packages: Before writing custom platform channels, check pub.dev (e.g., for
url_launcher,camera,battery_plus) as a package might already provide the functionality you need.