Implement Native code in Flutter

Ahmed Allam
4 min readJan 10, 2023

--

Flutter is a popular open-source framework for developing mobile and web applications. One of the key features of Flutter is the ability to write code that is native to both Android and iOS platforms. This allows developers to create high-performance and visually stunning applications without the need for separate codebases for each platform. In this article, we’ll explore how to implement native code in flutter.

Few examples you might use native code in a Flutter project:

  1. Camera access: You can use native code to access the device’s camera and capture images or videos.

2. Geolocation: You can use native code to access the device’s GPS and get the current location..

3. Push Notifications: You can use native code to implement push notifications in your app.

4. Sensors: You can use native code to access the device’s sensors, such as the accelerometer or gyroscope.

5. Third-party Libraries: You might need to use a library or an SDK that is only available on one of the platform.

These are just a few examples of how you might use native code in a Flutter project. The possibilities are virtually endless, and you can use native code to access virtually any platform-specific functionality you need to create your app.

Platform channels:

Messages are passed between the client (UI) and host (platform) using platform channels as illustrated in this diagram:

How to Implement Native code?

Step 1: Create the Flutter platform client

static const channelName = MethodChannel('channelName');

Step 2: Add an Android platform-specific implementation in MainActivity.java or MainActivity.kt:

private static final String channelName= "channelName";


@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler(
(call, result) -> {
if (call.method.equals("methodName")) {
yourMethod(result);
}
});
}

step3: Add an iOS platform-specific implementation in AppDelegate.swift

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let channelName = FlutterMethodChannel(name: "channelName",
binaryMessenger: controller.binaryMessenger)
channelName.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
...code
})

GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

Examples:

  1. Camera access:

In the Flutter code:

static const cameraChannel = MethodChannel('com.example/camera');
Future<String> takePicture() async {
final String path = await cameraChannel.invokeMethod('takePicture');
return path;
}

In the Android native code:

public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "com.example/camera";
private String currentPhotoPath;

@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler(
(call, result) -> {
if (call.method.equals("takePicture")) {
takePicture(result);
}
});
}
private void takePicture(MethodCall.Result result) {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
// Error occurred while creating the File
}
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(this,
"com.example.android.fileprovider",
photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
}
}
}
// ... more code ...
}

In the iOS native code :

@objc class SwiftCameraHandler: NSObject, FlutterPlugin {
var result: FlutterResult?
func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if(call.method == "takePicture") {
self.result = result;
let imagePicker = UIImagePickerController()
imagePicker.delegate = self;
imagePicker.sourceType = .camera
UIApplication.shared.keyWindow?.rootViewController?.present(imagePicker, animated: true, completion: nil)
}
}
// ... more code ...
}

2. Geolocation:

In the Flutter code:

static const locationChannel = MethodChannel('com.example/location');

Future<Position> getCurrentLocation() async {
final Map<String, double> locationMap = await locationChannel.invokeMethod('getLocation');
final double latitude = locationMap['latitude'];
final double longitude = locationMap['longitude'];
return Position(latitude: latitude, longitude: longitude);
}

In the Android native code:

public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "com.example/location";
private FusedLocationProviderClient fusedLocationClient;

@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler(
(call, result) -> {
if (call.method.equals("getLocation")) {
getLastLocation(result);
}
});
}

private void getLastLocation(MethodCall.Result result) {
fusedLocationClient.getLastLocation()
.addOnSuccessListener(this, location -> {
if (location != null) {
Map<String, Double> locationMap = new HashMap<>();
locationMap.put("latitude", location.getLatitude());
locationMap.put("longitude", location.getLongitude());
result.success(locationMap);
} else {
result.error("UNAVAILABLE", "Location not available.", null);
}
})
.addOnFailureListener(this, e -> {
Log.e("LOCATION", "Error getting location", e);
result.error("ERROR", e.getMessage(), null);
});
}
// ... more code ...

In the iOS native code:


@objc class SwiftLocationHandler: NSObject, FlutterPlugin {
var locationManager: CLLocationManager!
var result: FlutterResult?
func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if(call.method == "getLocation") {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
self.result = result;
}
}
// ... more code ...
}

Keep in mind that you may need additional error handling and permission checks.

3. Battery:

you can see the full example in the official docs from this link:
Click Here

In conclusion, integrating native code in Flutter is a great way to take advantage of the platform’s underlying capabilities and optimizations, to improve performance and user experience. However, it’s important to keep in mind that it requires some additional setup, testing, and handling the possible failure scenarios. By following the steps outlined in this article, you can add native code to your Flutter project and start taking advantage of its capabilities.

--

--