Skip to content

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 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 (StandardMethodCodec is 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.)

  1. Create Project: Create a new Flutter application: flutter create android_platform_channel_app.
  2. Dart Code (lib/main.dart):
import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; // Required for Platform Channels
import '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');
},
),
),
);
}
}
  1. 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.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
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
}
}
}
  1. (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
}
}
}
  1. 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.
  • Error Handling: Always wrap Dart’s invokeMethod in a try-catch block for PlatformException and 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 (usually StandardMethodCodec).
  • 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.