Flutter Callkit — Handle actions in the killed state

Aayush Sharma
4 min readMar 7, 2024

--

Hi! Myself Aayush, a fellow Flutter developer. I’d like to share an unusual yet crucial challenge that I came across and how I solved it.

The Problem

If you’ve ever worked on an app that required implementing Call notifications and handling the actions on it then you might have tried using the flutter_callkit_incoming plugin by Hien Nguyen. But a problem with this plugin is that it does not provide callbacks when the App is killed.

So suppose you want your user to re-direct to a certain screen upon accepting the call but it won’t work when your app is killed. You can still manage to show the call notification.

I’ve managed to solve this issue on Android and will be helping you with the same in this article. Also to summarize this article, You will be learning how you can intercept the Intent that launched your app and pass on the data from that intent to Flutter.

Flutter Callkit Incoming

This is a plugin provided by Hien Nguyen that makes use of the Android Telecom framework for handling calls on Android, and normal Notification building and handling.

To make it work you have to initialize a CallKitParams object and provide it with the necessary attributes. Use UUID for providing an ID. You can modify other attributes like the name of the caller, number, photo, etc.

After this is done, pass the CallKitParams object to the showCallkitIncomingmethod of FlutterCallkitIncoming. This is it! Now you’ll receive a call on your device.

You can combine this with FirebaseMessaging to tackle real-life problems. Now the problem with this is, that if the App is in a killed state and you’ve rendered the Notification then after accepting the call it will launch your App but that is it. You’re clueless about what to do next as there was no way you could’ve identified that the App was opened by accepting the call.

Solution

In Android, you can intercept the intent that launched the Application by calling getIntent (in Java) or intent (in Kotlin). If this intent is not null and its action is equal to the Call accepted action specified by flutter_callkit_incoming in the intent to launch the app then we will process it.

Now we have the intent, we can extract the data using the extras getter on it. This is of the type Bundle, we will parse this bundle get the actual payload in the form of a HashMap, and pass it to Flutter.

To pass the data from Native to Flutter you will have to use MethodChannel.

Using a MethodChanel on Android, invoke a method, this will take a method name that should match the MethodChannel on the Flutter side and the data. On the Flutter side, you have to call setMethodCallHandler on the MethodChannel with the same name you have declared Natively. In this handler, you will handle the method invoked on Native.

Now the final part is getting this data, For this we will use a Completer. If the data received Natively is not null then we will complete the future with it else we will complete it with an error that says No data found. Define a method in your calling kit service that waits for the completer to complete and return the data.

Now you can call this method in the initState of your root widget and process the data accordingly. That’s it, It should work like a charm now.

Bonus Content

Handling accept action was made easy because it launches your App and you can intercept the intent there. But what if you want to handle the decline action? This is tricky because it doesn’t launch any activity and thus cannot be helped.

A workaround for this is to modify the plugin itself. What I did was add a Callback function in the CallKitParams class named onDecline. You will have to annotate it with JsonKey and specify includeFromJson and includeToJson to false.

@JsonKey(includeFromJson: false, includeToJson: false)
final Function(String reason)? onDecline;

Now in FlutterCallkitIncomingPlugin.kt you have to modify the sendEvent function. The plugin stores the active method channels in the list methodChannels. So we have to compare if the event is a Call decline action then iterate through methodChannels list and invoke a method that triggers the the callback on Flutter side. The logic is the same as described above.

In the showCallkitIncoming function of the plugin, you will have to define the setMethodCallHandler to trigger the onDecline callback.

_channel.setMethodCallHandler((call) async {
if (call.method == 'CALL_DECLINED_CUSTOM') {
params.onDecline?.call(call.arguments);
}
});

Thanks and hope it helped somebody, For any further queries feel free to comment or reach out to me on LinkedIn. Do visit my website made with Flutter.

--

--