diff --git a/platform_channels/android/app/src/main/kotlin/dev/flutter/platform_channels/AccelerometerStreamHandler.kt b/platform_channels/android/app/src/main/kotlin/dev/flutter/platform_channels/AccelerometerStreamHandler.kt new file mode 100644 index 000000000..cd3371ed7 --- /dev/null +++ b/platform_channels/android/app/src/main/kotlin/dev/flutter/platform_channels/AccelerometerStreamHandler.kt @@ -0,0 +1,37 @@ +// Copyright 2020 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.platform_channels + +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager +import io.flutter.plugin.common.EventChannel + +class AccelerometerStreamHandler(sManager: SensorManager, s: Sensor) : EventChannel.StreamHandler, SensorEventListener { + private val sensorManager: SensorManager = sManager + private val accelerometerSensor: Sensor = s + private lateinit var eventSink: EventChannel.EventSink + + override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { + if (events != null) { + eventSink = events + sensorManager.registerListener(this, accelerometerSensor, SensorManager.SENSOR_DELAY_UI) + } + } + + override fun onCancel(arguments: Any?) { + sensorManager.unregisterListener(this) + } + + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {} + + override fun onSensorChanged(sensorEvent: SensorEvent?) { + if (sensorEvent != null) { + val axisValues = listOf(sensorEvent.values[0], sensorEvent.values[1], sensorEvent.values[2]) + eventSink.success(axisValues) + } + } +} diff --git a/platform_channels/android/app/src/main/kotlin/dev/flutter/platform_channels/MainActivity.kt b/platform_channels/android/app/src/main/kotlin/dev/flutter/platform_channels/MainActivity.kt index 5f7786ff9..537ba1146 100644 --- a/platform_channels/android/app/src/main/kotlin/dev/flutter/platform_channels/MainActivity.kt +++ b/platform_channels/android/app/src/main/kotlin/dev/flutter/platform_channels/MainActivity.kt @@ -1,7 +1,15 @@ +// Copyright 2020 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package dev.flutter.platform_channels +import android.content.Context +import android.hardware.Sensor +import android.hardware.SensorManager import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MethodChannel class MainActivity : FlutterActivity() { @@ -27,5 +35,10 @@ class MainActivity : FlutterActivity() { } } } + + val sensorManger: SensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager + val accelerometerSensor: Sensor = sensorManger.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) + EventChannel(flutterEngine.dartExecutor, "eventChannelDemo") + .setStreamHandler(AccelerometerStreamHandler(sensorManger, accelerometerSensor)) } } diff --git a/platform_channels/lib/main.dart b/platform_channels/lib/main.dart index 00de80336..fdef8d478 100644 --- a/platform_channels/lib/main.dart +++ b/platform_channels/lib/main.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; +import 'package:platform_channels/src/event_channel_demo.dart'; import 'package:platform_channels/src/method_channel_demo.dart'; void main() { @@ -15,6 +16,7 @@ class PlatformChannelSample extends StatelessWidget { return MaterialApp( routes: { '/methodChannelDemo': (context) => MethodChannelDemo(), + '/eventChannelDemo': (context) => EventChannelDemo(), }, title: 'Platform Channel Sample', home: HomePage(), @@ -33,9 +35,13 @@ class DemoInfo { List demoList = [ DemoInfo( - 'MethodChannelDemo', + 'MethodChannel Demo', '/methodChannelDemo', ), + DemoInfo( + 'EventChannel Demo', + '/eventChannelDemo', + ) ]; class HomePage extends StatelessWidget { diff --git a/platform_channels/lib/src/accelerometer_event_channel.dart b/platform_channels/lib/src/accelerometer_event_channel.dart new file mode 100644 index 000000000..1655a8332 --- /dev/null +++ b/platform_channels/lib/src/accelerometer_event_channel.dart @@ -0,0 +1,37 @@ +// Copyright 2020 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/services.dart'; + +/// This class includes the implementation for [EventChannel] to listen to value +/// changes from the Accelerometer sensor from native side. It has a [readings] +/// getter to provide a stream of [AccelerometerReadings]. +class Accelerometer { + static final _eventChannel = const EventChannel('eventChannelDemo'); + + /// Method responsible for providing a stream of [AccelerometerReadings] to listen + /// to value changes from the Accelerometer sensor. + static Stream get readings { + return _eventChannel.receiveBroadcastStream().map( + (dynamic event) => AccelerometerReadings( + event[0] as double, + event[1] as double, + event[2] as double, + ), + ); + } +} + +class AccelerometerReadings { + /// Acceleration force along the x-axis. + final double x; + + /// Acceleration force along the y-axis. + final double y; + + /// Acceleration force along the z-axis. + final double z; + + AccelerometerReadings(this.x, this.y, this.z); +} diff --git a/platform_channels/lib/src/event_channel_demo.dart b/platform_channels/lib/src/event_channel_demo.dart new file mode 100644 index 000000000..355f0fba1 --- /dev/null +++ b/platform_channels/lib/src/event_channel_demo.dart @@ -0,0 +1,58 @@ +// Copyright 2020 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:platform_channels/src/accelerometer_event_channel.dart'; + +/// Demonstrates how to use [EventChannel] to listen continuous values +/// of Accelerometer Sensor from platform. +/// +/// The widget uses a [StreamBuilder] to rebuild it's descendant whenever it +/// listens a new value from the [Accelerometer.readings] stream. It has three +/// [Text] widgets to display the value of [AccelerometerReadings.x], +/// [AccelerometerReadings.y], and [AccelerometerReadings.z] respectively. +class EventChannelDemo extends StatelessWidget { + @override + Widget build(BuildContext context) { + final textStyle = Theme.of(context).textTheme.headline5; + return Scaffold( + appBar: AppBar( + title: Text('EventChannel Demo'), + ), + body: Center( + child: StreamBuilder( + stream: Accelerometer.readings, + builder: (context, snapshot) { + if (snapshot.hasError) { + return Text(snapshot.error.toString()); + } else if (snapshot.hasData) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'x axis: ' + snapshot.data.x.toStringAsFixed(3), + style: textStyle, + ), + Text( + 'y axis: ' + snapshot.data.y.toStringAsFixed(3), + style: textStyle, + ), + Text( + 'z axis: ' + snapshot.data.z.toStringAsFixed(3), + style: textStyle, + ) + ], + ); + } + + return Text( + 'No Data Available', + style: textStyle, + ); + }, + ), + ), + ); + } +} diff --git a/platform_channels/test/src/event_channel_demo_test.dart b/platform_channels/test/src/event_channel_demo_test.dart new file mode 100644 index 000000000..9082b14aa --- /dev/null +++ b/platform_channels/test/src/event_channel_demo_test.dart @@ -0,0 +1,73 @@ +// Copyright 2020 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:platform_channels/src/event_channel_demo.dart'; + +void main() { + group('EventChannel Demo tests', () { + final sensorValues = [1.3556, 2.3, -0.12]; + setUpAll(() { + // By default EventChannel uses StandardMethodCodec to communicate with + // platform. + const standardMethod = StandardMethodCodec(); + + // This function handles the incoming messages from the platform. It + // calls the BinaryMessenger.setMessageHandler registered for the EventChannel + // and add the incoming message to the StreamController used by the EventChannel + // after decoding the message with codec used by the EventChannel. + void emitValues(ByteData event) { + ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( + 'eventChannelDemo', + event, + (reply) {}, + ); + } + + // Register a mock for EventChannel. EventChannel under the hood uses + // MethodChannel to listen and cancel the created stream. + ServicesBinding.instance.defaultBinaryMessenger + .setMockMessageHandler('eventChannelDemo', (message) async { + // Decode the message into MethodCallHandler. + final methodCall = standardMethod.decodeMethodCall(message); + + if (methodCall.method == 'listen') { + // Emit new sensor values. + emitValues(standardMethod.encodeSuccessEnvelope(sensorValues)); + emitValues(null); + return standardMethod.encodeSuccessEnvelope(null); + } else if (methodCall.method == 'cancel') { + return standardMethod.encodeSuccessEnvelope(null); + } else { + fail('Expected listen or cancel'); + } + }); + }); + + testWidgets('EventChannel AccelerometerReadings Stream test', + (tester) async { + await tester.pumpWidget(MaterialApp( + home: EventChannelDemo(), + )); + + await tester.pumpAndSettle(); + + // Check the values of axis. The value is rounded to 3 decimal places. + expect( + find.text('x axis: ' + sensorValues[0].toStringAsFixed(3)), + findsOneWidget, + ); + expect( + find.text('y axis: ' + sensorValues[1].toStringAsFixed(3)), + findsOneWidget, + ); + expect( + find.text('z axis: ' + sensorValues[2].toStringAsFixed(3)), + findsOneWidget, + ); + }); + }); +}