Flutter v1.1.0

Flutter Native Bridge: Real-Time Streams from Native Code

Stream sensor data, counters, and live updates from native Android & iOS to Flutter with EventChannel support.

In my previous post, I introduced Flutter Native Bridge — a plugin that eliminates MethodChannel boilerplate for calling native code.

I'm excited to announce version 1.1.0 with a major new feature: EventChannel support for real-time streams.

What's new? Stream continuous data from native code to Flutter using Stream<T> instead of Future<T>.


Upgrade to v1.1.0

Update your pubspec.yaml:

YAML
dependencies:
  flutter_native_bridge: ^1.1.0

Then regenerate your Dart bindings:

Bash
dart run flutter_native_bridge:generate

Backwards Compatible: All your existing MethodChannel code continues to work. Streams are purely additive.


MethodChannel vs EventChannel

Before diving in, let's understand when to use each:

Aspect MethodChannel EventChannel
Pattern Request → Response Subscribe → Continuous Events
Dart Type Future<T> Stream<T>
Use Case Get device model, fetch data Sensor updates, live counters
Lifecycle One-time call Ongoing subscription

When to Use Streams

Sensor Data

Accelerometer, gyroscope, proximity sensors that emit continuous readings.

Location Updates

GPS coordinates that change as the user moves.

Bluetooth/BLE

Device discovery, connection state, and characteristic notifications.

System Events

Battery level changes, network connectivity, app lifecycle.


The Traditional Way (Pain)

Setting up EventChannel manually requires significant boilerplate:

Kotlin
class MainActivity : FlutterActivity() {
    private var eventSink: EventChannel.EventSink? = null

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        EventChannel(flutterEngine.dartExecutor, "counter_channel")
            .setStreamHandler(object : EventChannel.StreamHandler {
                override fun onListen(args: Any?, sink: EventChannel.EventSink?) {
                    eventSink = sink
                    startCounter()
                }
                override fun onCancel(args: Any?) {
                    stopCounter()
                    eventSink = null
                }
            })
    }
    // Plus all the counter logic...
}

And the same amount of code for iOS. Then wire it up in Dart. Exhausting!


The Flutter Native Bridge Way

With v1.1.0, streaming is as simple as adding one annotation:

Android (Kotlin)

Kotlin
import io.nativebridge.*

@NativeBridge
class CounterService {
    private val handler = Handler(Looper.getMainLooper())
    private var counter = 0
    private var runnable: Runnable? = null

    @NativeStream  // That's it! This method now streams to Flutter
    fun counterUpdates(sink: StreamSink) {
        counter = 0
        runnable = object : Runnable {
            override fun run() {
                sink.success(mapOf(
                    "count" to counter,
                    "timestamp" to System.currentTimeMillis()
                ))
                counter++
                handler.postDelayed(this, 1000)
            }
        }
        handler.post(runnable!!)
    }

    fun stopCounter() {
        runnable?.let { handler.removeCallbacks(it) }
        runnable = null
        counter = 0
    }
}

iOS (Swift)

Swift
import flutter_native_bridge

class CounterService: NSObject {
    private var timer: Timer?
    private var counter = 0
    private var activeSink: StreamSink?

    // Method name must end with "WithSink:" for streams
    @objc func counterUpdatesWithSink(_ sink: StreamSink) {
        activeSink = sink
        counter = 0

        DispatchQueue.main.async { [weak self] in
            self?.timer = Timer.scheduledTimer(
                withTimeInterval: 1.0,
                repeats: true
            ) { [weak self] _ in
                guard let self = self else { return }
                self.activeSink?.success([
                    "count": self.counter,
                    "timestamp": Date().timeIntervalSince1970 * 1000
                ])
                self.counter += 1
            }
        }
    }

    @objc func stopCounter() {
        timer?.invalidate()
        timer = nil
        activeSink = nil
    }
}

Dart (Flutter)

Dart
import 'native_bridge.g.dart';

// Subscribe to the stream
StreamSubscription? subscription;

void startListening() {
  subscription = CounterService.counterUpdates().listen((data) {
    if (data is Map) {
      print('Count: ${data['count']}');
      print('Time: ${data['timestamp']}');
    }
  });
}

void stopListening() {
  subscription?.cancel();
  CounterService.stopCounter();
}

That's it! The @NativeStream annotation + StreamSink parameter is all you need. No manual EventChannel setup.


Key Concepts

The StreamSink Object

StreamSink is the bridge that sends data to Flutter. It has three methods:

Method Description
success(data) Send data to the Flutter stream
error(code, message, details) Send an error to the stream
endOfStream() Close the stream (optional)

Platform-Specific Requirements

Platform Requirement
Android Add @NativeStream annotation + StreamSink parameter
iOS Method selector must end with WithSink: + StreamSink parameter

Real-World Example: Accelerometer

Here's a practical example streaming accelerometer data:

Android (Kotlin)

Kotlin
@NativeBridge
class SensorService(private val context: Context) : SensorEventListener {
    private var sensorManager: SensorManager? = null
    private var accelerometer: Sensor? = null
    private var sink: StreamSink? = null

    @NativeStream
    fun accelerometerUpdates(sink: StreamSink) {
        this.sink = sink
        sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
        accelerometer = sensorManager?.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
        sensorManager?.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI)
    }

    override fun onSensorChanged(event: SensorEvent) {
        sink?.success(mapOf(
            "x" to event.values[0],
            "y" to event.values[1],
            "z" to event.values[2]
        ))
    }

    fun stopAccelerometer() {
        sensorManager?.unregisterListener(this)
        sink = null
    }

    override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
}

Dart Usage

Dart
import 'native_bridge.g.dart';

class AccelerometerWidget extends StatefulWidget {
  @override
  State<AccelerometerWidget> createState() => _AccelerometerWidgetState();
}

class _AccelerometerWidgetState extends State<AccelerometerWidget> {
  StreamSubscription? _subscription;
  double x = 0, y = 0, z = 0;

  @override
  void initState() {
    super.initState();
    _subscription = SensorService.accelerometerUpdates().listen((data) {
      if (data is Map) {
        setState(() {
          x = (data['x'] as num).toDouble();
          y = (data['y'] as num).toDouble();
          z = (data['z'] as num).toDouble();
        });
      }
    });
  }

  @override
  void dispose() {
    _subscription?.cancel();
    SensorService.stopAccelerometer();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Text('X: ${x.toStringAsFixed(2)}, Y: ${y.toStringAsFixed(2)}, Z: ${z.toStringAsFixed(2)}');
  }
}

Runtime API for Streams

Don't want code generation? Use the runtime API:

Dart
import 'package:flutter_native_bridge/flutter_native_bridge.dart';

// Stream without code generation
FlutterNativeBridge.stream<Map>('CounterService', 'counterUpdates').listen((data) {
  print('Count: ${data['count']}');
});

// Discover available streams
final streams = await FlutterNativeBridge.discoverStreams();
// {'CounterService': ['counterUpdates'], 'SensorService': ['accelerometerUpdates']}

Quick Reference


Resources


Conclusion

Flutter Native Bridge v1.1.0 brings the same zero-boilerplate philosophy to real-time data streams. Whether you're building a fitness app with sensor data, a location-aware service, or any feature requiring continuous native events — you can now do it with minimal setup.

Annotate. Register. Stream.

Try it out and let me know what you build!

If you found this useful, give the package a like on pub.dev and star the repo on GitHub!

Share this article:
UP

Uttam Panchasara

Flutter Developer with 9+ years of mobile development experience. Skilled in Clean Architecture, Bloc, and performance optimization.

Want to discuss Flutter development?

Let's connect and talk about your mobile app project.

Get in Touch