5

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.

halfer
  • 19,824
  • 17
  • 99
  • 186
Anthony Sette
  • 777
  • 1
  • 10
  • 26
  • What is the `package` attribute in your plugin's `manifest` tag? – Nitrodon Sep 24 '20 at 21:56
  • @Nitrodon for the plugin it is `package="agency.sparc.flutter_radar_io"` but for the example app it is `package="agency.sparc.flutter_radar_io_example"` – Anthony Sette Sep 24 '20 at 22:06
  • Is there a way for me to reference the class from the plugins package? Because if I can then perhaps I can add the receiver to the app XML file and then it will be received by the plugin. Or maybe I could add something to the plugin XML, however when I do that I get an error of overriding the apps android label... `Attribute application@label value=(flutter_radar_io_example) from AndroidManifest.xml:16:9-49 is also present at [:flutter_radar_io] AndroidManifest.xml:9:9-41` – Anthony Sette Sep 24 '20 at 22:09

2 Answers2

5

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.

Ben Butterworth
  • 22,056
  • 10
  • 114
  • 167
  • This looks promising, but if `backgroundMethodChannel.invokeMethod` is called, it is landing nowhere. What should be on the Flutter/Dart side to catch the call? – mspnr Jun 17 '23 at 00:12
  • I think you misunderstand the question, this one is about listening to a `BroadcastReceiver` in Android Flutter plugins, not `MethodChannel` in Dart/Flutter apps. – Ben Butterworth Jun 17 '23 at 07:09
  • OK. My interest can be not directly related to the main question, but I find your suggestion very useful. Could you take a look into my question https://stackoverflow.com/questions/76496662/how-to-start-flutter-processing-from-android-broadcastreceiver I suppose you can say if it is possible. – mspnr Jun 17 '23 at 14:38
1

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.

halfer
  • 19,824
  • 17
  • 99
  • 186
Anthony Sette
  • 777
  • 1
  • 10
  • 26