Handling permission requests in Android for Flutter plugins

Updated for the new plugin registration process in Flutter 1.12.

While building the Android functionality for a plugin recently I had to handle a system permission request. Although all the parts of that are simple it took me a while to figure out how to put them together. In hopes of making it simpler for others looking to do this here’s all the bits in one place. Note: Code examples are Kotlin, there’s a full gist here. The gist is for pre 1.12, I’ll try to get a post 1.12 gist up in the next little while but until then see the details below for the registration changes.

Handling the callback

The part that I had to search a bit to pull together was how to handle the Android permission callback in my plugin. The Android documentation for it is here but doing it in a plugin is a bit different. Here’s the relevant code for a Flutter plugin.

Implement the callback interface

First add the PluginRegistry.RequestPermissionsResultListener interface to the plugin class declaration. I also define the request code to use, myPermissionCode and a variable to hold the result of the permission check, permissionGranted. The request code value can be anything you want. It is an identifier that you give to the requestPermissions call and that is then given back in the result handler so that you know if the result matches your request.

class MyPlugin ( activity: Activity): MethodCallHandler, 
  PluginRegistry.RequestPermissionsResultListener {
    private val myPermissionCode = 72
    private var permissionGranted: Boolean = false

Then implement the method from that interface. Note the test for the matching myPermissionCode to ensure that the result is for the permission request you made. If it is then I store the granted state into a boolean then call a method on the plugin to do something with the new permission. That part depends on what your plugin wants to do with the permission.

override fun onRequestPermissionsResult( requestCode: Int, 
  permissions: Array<out String>?, 
  grantResults: IntArray?): Boolean {

  when (requestCode) {
    myPermissionCode -> {
      if ( null != grantResults ) {
        permissionGranted = grantResults.isNotEmpty() &&
        grantResults.get(0) == PackageManager.PERMISSION_GRANTED
        }
      // do something here to handle the permission state
      updatePluginState()
      // only return true if handling the request code
      return true
      }
   }
   return false
}

Register the callback

Flutter 1.12 and later

Now that the interface is implemented it has to be registered so that it will be invoked. In the post 1.12 world there is no guarantee that your plugin will have any access to the UI. Plugins that are written for background services for example never use the UI. Any plugins you write should be aware of this and ensure that they only perform UI operations when the UI is available. The new Plugin structure makes it fairly easy to respond to changes in the availability of the UI. There’s a new interface ActivityAware to implement. Note that unlike before 1.12 this is now separate from the plugin registration and could happen at any time after the plugin has registered.

Here’s the new plugin declaration with the new interface and it’s implementation. The important part to note is the call to binding.addRequestPermissionsResultListener

class MyPlugin ( activity: Activity): MethodCallHandler, 
  PluginRegistry.RequestPermissionsResultListener, FlutterPlugin,
        ActivityAware {
  private var currentActivity: Activity? = null

  override fun onDetachedFromActivity() {
      currentActivity = null
  }

  override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
      currentActivity = binding.activity
      binding.addRequestPermissionsResultListener(this)
  }

  override fun onAttachedToActivity(binding: ActivityPluginBinding) {
      currentActivity = binding.activity
      binding.addRequestPermissionsResultListener(this)
  }

  override fun onDetachedFromActivityForConfigChanges() {
      currentActivity = null
  }

...
}

Prior to Flutter 1.12

Now that the interface is implemented it has to be registered with the Flutter plugin registrar so that it will be invoked. I add this code to the generated registerWith method that is already in the plugin. The new line is the call to registrar.addRequestPermissionsResultListener.

companion object {
@JvmStatic
fun registerWith(registrar: Registrar) {
  val channel = MethodChannel(registrar.messenger(), "myPluginChannel")
  val myPlugin = MyPlugin()
  registrar.addRequestPermissionsResultListener(myPlugin)
  channel.setMethodCallHandler(myPlugin)
  }
}

Check for permission

With this code in place you can now successfully check for a permission and if not yet granted ask the user to provide it. This example shows checking if a single Android permission, READ_EXTERNAL_STORAGE, has already been granted and if not showing the system dialog requesting permission from the user. Note that the system prompt and the user response is non-blocking and the response is returned via the callback.

private fun checkPermission(context: Application) {
  permissionGranted = ContextCompat.checkSelfPermission(context,
    Manifest.permission.READ_EXTERNAL_STORAGE) == 
    PackageManager.PERMISSION_GRANTED
  if ( !permissionGranted ) {
    ActivityCompat.requestPermissions(pluginActivity,
      arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),   
      myPermissionCode )
  }
}

Leave a Reply