Sameer Ahmad
Sameer Ahmad

Reputation: 63

How to Recieve a stream of data from a native Service's BroadcastReciever using EventChannel?

Let's pretend that I have a flutter application and I have a Foreground Service that has a worker thread and keeps sending me updates about user's location, this is the service's code which returns a random integer numbers for now :

Android Service code

public class LocationUpdatesService extends Service {
static final int NOTIFICATION_ID = 100;
static  final String NOTIFICATION = "com.example.fitness_app";
NotificationManagerCompat m_notificationManager;
private Intent m_broadcastInent;
private  final String TAG = this.getClass().getSimpleName();
private AtomicBoolean working = new AtomicBoolean(true);
private int steps = 0;
private Runnable runnable = new Runnable() {
    @Override
    public void run() {
        while(working.get()) {
            steps++;
            m_notificationManager.notify(NOTIFICATION_ID,
                    createNotification("Steps Counter" + steps ,
                            R.drawable.common_full_open_on_phone, 1));
            m_broadcastInent.putExtra("steps", steps);
            sendBroadcast(m_broadcastInent);
        }

    }
};



@Override
public void onCreate() {
    m_broadcastInent = new Intent(NOTIFICATION);
    m_notificationManager = NotificationManagerCompat.from(this);
    createNotificationChannel();
    startForeground(NOTIFICATION_ID, createNotification("Steps Counter" ,
            R.drawable.common_full_open_on_phone, 0));
    super.onCreate();

}

@Override
public IBinder onBind(Intent intent) {
    return null;
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    new Thread(runnable).start();
    return Service.START_STICKY;
}


@Override
public void onDestroy() {
    working.set(false);
    m_notificationManager.cancel(NOTIFICATION_ID);
    super.onDestroy();

}


private Notification createNotification(String title, int icon, int steps) {

    NotificationCompat.Builder builder = new  NotificationCompat.Builder(this,
            getString(R.string.BACKGROUND_SERVICE_NOTIFICATION_CHANNEL_ID));
    builder.setNumber(steps);
    builder.setSmallIcon(icon);
    builder.setContentTitle(title);
    builder.setOnlyAlertOnce(true);
    return builder.build();

}

private void createNotificationChannel() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        CharSequence name = getString(R.string.BACKGROUND_SERVICE_NOTIFICATION_CHANNEL_ID);
        String description =
                getString(R.string.BACKGROUND_SERVICE_NOTIFICATION_CHANNEL_DESCRIPTION);
        int importance = NotificationManager.IMPORTANCE_HIGH;
        NotificationChannel channel =
                new NotificationChannel(getString(
                        R.string.BACKGROUND_SERVICE_NOTIFICATION_CHANNEL_ID), name, importance);
        channel.setDescription(description);
        NotificationManager notificationManager = getSystemService(NotificationManager.class);
        notificationManager.createNotificationChannel(channel);
    }
}


}

In MainActivity.java I am recieving broadcasts from the service and I should send them to the flutter side:

MainActivity

public class MainActivity extends FlutterActivity {

private static final String TAG = MainActivity.class.getSimpleName();
private static final String ONE_TIME_BACKGROUND_METHOD_CHANNEL = "fitness_app/method_one_time_service";
private static final String EVENTS_STREAM_CHANNEL = "fitness_app/event_one_time_service";

private Intent m_serviceIntent;
private MethodChannel m_methodChannel;
private EventChannel m_eventchannel;
private EventChannel.EventSink m_stepsStreamSink;
private EventChannel.StreamHandler m_eventCallHandler;
private MethodChannel.Result m_result;
private EventChannel.EventSink m_eventSink;
private BroadcastReceiver m_serviceBroadcastReciever = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        // Log.d(TAG, "milliseconds " + intent.getIntExtra("steps", 0));

        Bundle bundle = intent.getExtras();
        if (bundle != null) {
            int steps = bundle.getInt("steps");
            /////////////////////////////////////////
            /////////////////////////////////////////
            // I need Here To add Data To the stream 
            /////////////////////////////////////////
            /////////////////////////////////////////

        }
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    GeneratedPluginRegistrant.registerWith(this);

    m_serviceIntent = new Intent(this, LocationUpdatesService.class);
    PendingIntent pendingIntent = PendingIntent.getService(this, 0, m_serviceIntent, 0);
    m_methodChannel = new MethodChannel(getFlutterView(), ONE_TIME_BACKGROUND_METHOD_CHANNEL);

    m_methodChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
        @Override
        public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
            if (methodCall.method.equals("START_STEPS_COUNTER")) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    startForegroundService(m_serviceIntent);
                } else {
                    startService(m_serviceIntent);
                }
            } else {
                stopService(m_serviceIntent);
            }
        }
    });
    m_eventchannel = new EventChannel(getFlutterView(), EVENTS_STREAM_CHANNEL);
    m_eventCallHandler = new EventChannel.StreamHandler() {
        @Override
        public void onListen(Object o, EventChannel.EventSink eventSink) {
            m_eventSink = eventSink;
        }

        @Override
        public void onCancel(Object o) {

        }
    };
    m_eventchannel.setStreamHandler(m_eventCallHandler);

}

@Override
protected void onStart() {
    super.onStart();

}

@Override
protected void onResume() {
    super.onResume();
    registerReceiver(m_serviceBroadcastReciever, new IntentFilter(LocationUpdatesService.NOTIFICATION));
}

@Override
protected void onDestroy() {
    super.onDestroy();

}

}

Flutter dart code

void start() async {
try {
  await _methodChannel.invokeMethod(PlatformMethods.STEPS_COUNTER_START);
  Stream<int> stream = _eventChannel.receiveBroadcastStream();
} on PlatformException catch (e) {
  print(
      " Faild to run native service with thrown exception : ${e.toString()}");
}

Everything here works fine. I can Trigger the service using the Methodchannel, I receive the data from the service using the BroadCastREciever. All I need to do is to return a stream from the native code using EventChannel.

Upvotes: 1

Views: 1925

Answers (1)

Ricardo Markiewicz
Ricardo Markiewicz

Reputation: 977

Create a class that extends BroadcastReceiver and pass a EventChannel.EventSink.

class SinkBroadcastReceiver(private val sink: EventChannel.EventSink) {
    override fun onReceive(context: Context, intent: Intent) {
        val bundle = intent.getExtras()
        if (bundle != null) {
            val steps = bundle.getInt("steps")
            sink.success(steps)
        }
    }
}

Then, instead od create the BroadcastReceiver in the declaration you can create it in the onListen and call registerReceiver there:

m_eventCallHandler = new EventChannel.StreamHandler() {
        @Override
        public void onListen(Object o, EventChannel.EventSink eventSink) {
            SinkBroadcastReceiver receiver = new SinkBroadcastReceiver(eventSink);
            registerReceiver(receiver, new IntentFilter(LocationUpdatesService.NOTIFICATION));
            // TODO : Save receiver in a list to call unregisterReceiver later
        }

        @Override
        public void onCancel(Object o) {

        }
    };

You may need to track all the receivers in a list because you may need to unregister when the activity stops. Also when you stop the service you may need to traverse the list of registered BroadcastReceiver to unregister all the instances.

This way you may also have more than one listener on the dart code for the same event.

Upvotes: 1

Related Questions