How to use native APIs in Flutter: iOS and Android Biometric Authentication [Part 2]
Use platform channels to build a full project with Flutter and native APIs
≈ 12 minutes readIntroduction
This is the second part in our series on using platform channels in Flutter to communicate with the host platform. If you have not read the first part, it is recommended that you read it first.
How often do you find yourself in a situation where you need to integrate a new native iOS/Android API, or a third-party SDK, and you think for a second it would have been better if I’m using native instead of Flutter? This does not happen very often, but when it does, it could be critical to the experience of your app.
In this part, you will get hands-on using native code from Swift and Kotlin in Dart.
Get the full code
Before diving in, you can get the full project and code used in this tutorial from this repository. The code is runnable, you can clone and play around with it on Android and iOS devices.
Preparing the project
Let’s start with creating a new Flutter project.
By default, the newly created project will use Kotlin for Android and Swift for iOS.
Once the project is created, and if you are on the stable channel, as of the time of writing, it is Flutter 3.0.2
. You can notice you got 6 folders with the following platform names: ios, android, web, macos, windows, and linux. In this tutorial, we are only interested in ios and android folders.
If you flutter run
, you can see the usual Flutter counter application. You won’t learn how to increment the counter with a platform channel, instead, you will look into a real use-case.
What are you building?
Before going further into code, let’s break down the sample you’re going to build in simple English.
The use-case
Some apps hold sensitive information about a user, such as financial, and health apps. It is recommended that you use biometrics as an option to protect the user’s privacy.
About Biometrics Native APIs
Both iOS and Android exposes an easy-to-use API that allows developers to authenticate users locally using their configured biometrics such as Face and Fingerprint, or fallback to passcode.
On iOS, you can communicate with the system to ask the user to authenticate with biometrics using LocalAuthentication framework. On Android, this can be achieved using BiometricPrompt package.
The Dart and Flutter side
You will start from main.dart
. Open the file and delete the MyHomePage
widget.
UI part
The app has a single view only, let’s name it AuthView
.
The AuthStatus
type is not yet defined, so let’s create it.
Lastly, add the new widget AuthView
as the home in MaterialApp
.
MethodChannel part
The very first step in defining a MethodChannel to use to communicate with the native side.
You will need flutter/services
package, go to the very top of your file and add this import:
Next, under _AuthViewState
, add the following.
Next, you will implement authenticateWithBiometrics
.
To break down what’s happening inside this function:
- You’re invoking a method named
authenticateWithBiometrics
on thesamples.invertase.io/biometrics
channel. - Then, you’re setting up a handler that will listen to any method calls on this channel that is coming from the native side of the app.
- Finally, you’re catching any exception of type
PlatformException
that might be thrown from the native side.
Results and data validation
After invoking a method on a channel, you might get a response back from the native side. The type returned by invokeMethod
is Future<dynamic>
, meaning you need to await it, and for the fact that the value of the future is dynamic
, you will need to validate the result before going any further in your code.
In this example, you won’t store the result, rather just await for the invocation to finish then proceed, since there’s no expected data to be returned from authenticateWithBiometrics
as you will see in the following sections.
The native side
In this section, you will add the native implementation for authenticateWithBiometrics
method. Even if you don’t have any native experience, don’t let this part scare you. The goal is to walk you through writing native code and debugging it step by step.
Writing the iOS implementation
The iOS part will be written in Swift language using Xcode, to make use of the native debugging, analyzing and formatting capabilities. If you’re using VS Code, right-click on the iOS folder, you can see Open in Xcode
, click on it. If you’re using Android Studio, you should see a similar option, right-click on iOS folder, then Flutter > Open iOS module in Xcode
.
Once Xcode window is open, expand Runner
folder, and open AppDelegate
.
The AppDelegate
class is the entry point of the Flutter app on iOS. You can override some of its methods to prepare Swift to handle calls from Dart.
First step is to setup the channel. We need 1 constant channelName
and 1 variable biometricsChannel
, add them inside the AppDelegate
class.
Next, paste the following code inside application
method.
Xcode will complain that authenticateWithBiometrics
does not exist yet, so let’s create it. inside the AppDelegate
class, paste this new method.
The last important part is to import LocalAuthentication
framework at the top of the file.
If you got lost, you can refer to the AppDelegat.swift file from the source code.
Sending results to Flutter
To send a result to Flutter from Swift, you will pass in result
that you got from setMethodCallHandler
to authenticateWithBiometrics
method. This object holds all the necessary information needed to send back a response to Flutter. You can send 2 types of responses:
Success with data:
Error with message, which is received by Dart as PlatformException
:
Run and debug
The reason it’s easier to write Swift using Xcode is because it allows us to debug it. In the following snippet, you can see how to run and start a debugging session of the app.
Writing the Android implementation
The Android part will be written in Kotlin using Android Studio.
Before writing the implementation, you need to add biometric package in app/build.gradle
.
Then you need to tell Android Studio to fetch the package. Go to File > Sync with Gradle Files
, then wait for it to finish syncing.
You also need to add the permission to access biometric fingerprint on the user’s phone. Open android/app/src/main/AndroidManifest.xml
, and add the following under application
tag:
If you’re using VS Code, right-click on the Android folder, then click on Open in Android Studio
. If you’re using Android Studio, you should see a similar option, right-click on Android folder, then Flutter > Open Android module in Android Studio
.
Once Android Studio window is open, expand app/java
folder, then open the folder named after your application ID, usually this would start with com.example
. Finally, the Android native code will be written inside MainActivity
.
In your case, the MainActivity
class would look like this:
It’s empty, you will start adding code inside of it.
First, add necessary imports.
Next, add the channel name constant and method channel variable inside MainActivity
class.
Next, override configureFlutterEngine
.
This method is overridden from the inherited class FlutterActivity
, this method is the counterpart of application
method in AppDelegate
in Swift. You can notice that the implementation of the MethodChannel here has a very similar API to Swift.
Next, paste the implementation of the method authenticateWithBiometrics
under configureFlutterEngine
.
At this point, Android Studio should show a similar warning about casting this
to FragmentActivity
.
The reason is that this class is a FlutterActivity
, not a FlutterFragmentctivity
. To fix it, go up to the class definition, and change FlutterActivity
to FlutterFragmentActivity
. You will also need to update the imports.
Sending results to Flutter
To send a result to Flutter from Kotlin, you will pass in result
that you got from setMethodCallHandler
to authenticateWithBiometrics
method. This object holds all the necessary information needed to send back a response to Flutter. You can send 2 types of responses:
Success with data:
Error with message, which is received by Dart as PlatformException
:
Run and debug
For the same reason Xcode was used to write the iOS code, you can run and debug the Android side of your Flutter app in Android Studio.
Putting it all together
Let’s break up the actual steps you need when creating any platform channel regardless of your use-case.
- Pick a String name for your channel, make sure it’s unique enough not to collide with other plugins in your project.In our example, the channel name was
samples.invertase.io/biometrics
. - Setup a MethodChannel in both sides, Dart and native, using the same name.
- From the native side, call
setMethodCallHandler
on the newly created MethodChannel. - Inside the handler, you can read the name of the method once it’s triggered from Flutter, if it matches the expected name, put your implementation, if the method name isn’t expected, throw a Not Implemented exception.
- From Dart, invoke the method by calling
platform.invokeMethod("methodName")
. The method name should match exactly the one which the native side is expecting. - To call Dart methods from native, on your Dart code, set a similar handler to the one in native by calling
platform.setMethodCallHandler
. - From the native side, invoke any method by calling
methodChannel.invokeMethod(”methodName”)
.
Final remarks
It is highly important as a Flutter developer to learn how to implement platform channels. In fact, you’re not going to use it on a daily basis, you might actually never use it. However, it is important to understand how it works, since most of the plugins in the ecosystem are built over native APIs and third-party SDKs.
If you ever came across a case where you had to implement your own platform channels, it is advised that you take your implementation out of you project to a separated plugin project, check this guide from the official docs on how to start your own plugin. Moreover, you should consider publishing your plugin to share it with the community, which is good since more people can try it out and help you improve it!
Finally, this series is still going on! in part 3, we will see how platform channels enable using native UI elements as Flutter widgets.
Follow us on Twitter, LinkedIn, and Youtube, and subscribe to our monthly newsletter to always stay up-to-date.
Acknowledgment
Special thanks to our interns in the period between August and June 2022, Çağla Loğoğlu and Fatimah Dagriri, for helping in polishing this tutorial.