How do you use receiver (BroadcastReceiver) in a flutter plugin?

6,918

Solution 1

Solution

After seeing the comment by Nitrodon I took a different look at the problem and found a solution. I am not sure why the programmatic Receiver initialization didn't work, however, here is what I put in my plugin's Android Manifest.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.youorganization.yourproject">
  <application
    android:name="io.flutter.app.FlutterApplication">
    <receiver
      android:name=".MyRadarReceiver"
      android:enabled="true"
      android:exported="false">
        <intent-filter>
            <action android:name="io.radar.sdk.RECEIVED" />
        </intent-filter>
    </receiver>
  </application>
</manifest>

You cannot add an android label or anything else to the application tags or it will throw an error out of conflict with the application's Android Manifest. For this reason, you just need to declare the receiver in the simple application tags that only contain the name which is io.flutter.app.FlutterApplication. I hope this helps someone else! If anyone has anything to add please do but it finally works!

Solution 2

Android basics

A useful resource would be Broadcasts overview.

It depends if you want register your broadcast receiver at runtime, or declare it statically in the manifest. Read Dynamic Registration vs Static Registration of BroadcastReceiver. If you want an intent to launch your receiver, it has to be declared statically. If you want it to listen only when your app is already running, register it at runtime.

This is all normal Android stuff.


Flutter specifics

In a broadcast receiver, you're not guaranteed that a Flutter application, Flutter Engine or DartVM are running. If you want to run Flutter, you need to spawn an Isolate.

flutterEngine = new FlutterEngine(context, null);
DartExecutor executor = flutterEngine.getDartExecutor();
backgroundMethodChannel = new MethodChannel(executor.getBinaryMessenger(), "your channel name");
backgroundMethodChannel.setMethodCallHandler(this);
// Get and launch the users app isolate manually:
executor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault());

Then, you could inform plugins/ Flutter application by calling:

// Even though lifecycle parameter is @NonNull, the implementation `FlutterEngineConnectionRegistry`
// does not use it, because it is a bug in the API design. See https://github.com/flutter/flutter/issues/90316
flutterEngine.getBroadcastReceiverControlSurface().attachToBroadcastReceiver(this, null); // This is the class you are in, the broadcast receiver

and when you're about to finish the broadcast receiver execution:

flutterEngine.getBroadcastReceiverControlSurface().detachFromBroadcastReceiver();

This will make sure BroadcastReceiverAware plugins will be informed when broadcast receivers are attached and detached.

Share:
6,918
Anthony Sette
Author by

Anthony Sette

Apparently, this user prefers to keep an air of mystery about them... sometimes. These are my opinions, get triggered... Top five languages are as follows (and probably changed between the time I am writing this and the time you are reading it): Python, JS, Dart, TypeScript, C++ PHP is the worst language ever invented. Nevermind Java might be worse. Vue.js is the best Web Framework. Flutter is the best cross-platform development framework. Now time for a stupid joke since I'm writing this at 3:00 am after a long coding session: Why does Python live on land? Because its above C level. Ok, Goodnight my doods. I just realized all of this amazing profile work will have to be rewritten when I graduate college.

Updated on December 24, 2022

Comments

  • Anthony Sette
    Anthony Sette over 1 year

    Problem

    I am building an application that requires some of the functionality of the Radar.io SDK, and so I decided to create plugin so after this project is over I can share the plugin with the rest of the Flutter Community. The problem is, I am not able to receive events in this plugin. This is important for the plugin since in order to receive events such as geolocation triggers and other background events this receiver needs to be receiving realtime information.

    I have successfully implemented every function in their SDK for Android (using Kotlin) seen here https://radar.io/documentation/sdk, however, the receiver is not getting called when an event takes place. I know the device is being tracked since the location is updated in the Radar.io dashboard, however, the receiver is not executed at all when various events take place.

    What I have tried

    Here is how the documentation tells you to register the receiver in the Manifest.

      <receiver
          android:name=".MyRadarReceiver"
          android:enabled="true"
          android:exported="false">
          <intent-filter>
              <action android:name="io.radar.sdk.RECEIVED" />
          </intent-filter>
      </receiver>
    

    This would work for a standard application, however, when I register the receiver in the manifest the application does not look in the plugins directory which is where the receiver is located. Instead it looks in the applications android directory and returns an error that the data path could not be found. This causes the application to terminate.

    To combat this issue I discovered that the receiver could be set programmatically so I decided to give that a shot.

    Inside the Plugin's main Kotlin file I wrote the following lines to be executed in the registerWith and the onAttachedToEngine. It is my understanding that these are basically the onStart functions. registerWith is an older function kept for compatibility with older devices.

    val myRadarReceiver = MyRadarReceiver()
    val intentFilter = IntentFilter()
    intentFilter.addAction("io.radar.sdk.RECEIVED")
    ContextWrapper(this.context).registerReceiver(myRadarReceiver, intentFilter)
    

    The class referenced as MyRadarReceiver() is an extension of the RadarReceiver class in the SDK which extends BroadcastReceiver meaning this is my Broadcast Receiver. Here is the class:

    public class MyRadarReceiver: RadarReceiver() {
    
      override fun onEventsReceived(context: Context, events: Array<RadarEvent>, user: RadarUser) {
        println("test: " + "an event was received")
      }
    
      override fun onLocationUpdated(context: Context, location: Location, user: RadarUser) {
        println("test: " + "location was updated")
      }
    
      override fun onClientLocationUpdated(context: Context, location: Location, stopped: Boolean, source: Radar.RadarLocationSource) {
        println("test: " + "client location updated")
      }
    
      override fun onError(context: Context, status: Radar.RadarStatus) {
        println("test: " + status)
      }
    
      override fun onLog(context: Context, message: String) {
        println("test: " + message)
      }
    
    }
    

    When I started background tracking on the emulated device I expected one of the functions in MyRadarReceiver to execute, but nothing was printed to the terminal. There was no errors, but nothing was received. This makes me wonder if I set the intent incorrectly or if maybe the Receiver was written improperly. I don't know another way to set the intent so I decided to rewrite the MyRadarReceiver in a simpler form like so:

    public class MyRadarReceiver: BroadcastReceiver() {
      override fun onReceive(context: Context, intent: Intent) {
        println("works! Something was received")
      }
    }
    

    Unfortunately nothing was printed to the console so I now believe it has something to do with my intent.

    Summary

    The only solution I have left to try is to write the receiver in the application instead of in the plugin. This may work, however, I was hoping to share this plugin with the Flutter community when I was finished. Radar is a very powerful platform and bringing this plugin to the Flutter community could have an outstanding impact.