[federated_plugin_sample] modify the sample to expose battery API instead of geolocation API (#526)

pull/536/head
Ayush Bherwani 5 years ago committed by GitHub
parent 033ae11733
commit 60691d00c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,6 +1,6 @@
# federated_plugin # federated_plugin
A Flutter plugin sample that shows how to implement a federated plugin to fetch the device location on different platforms. A Flutter plugin sample that shows how to implement a federated plugin to retrieve current battery level on different platforms.
This sample is currently being built. Not all platforms and functionality are in place. This sample is currently being built. Not all platforms and functionality are in place.

@ -40,5 +40,4 @@ android {
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.google.android.gms:play-services-location:17.0.0'
} }

@ -1,5 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="dev.flutter.federated_plugin"> package="dev.flutter.federated_plugin">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest> </manifest>

@ -4,109 +4,55 @@
package dev.flutter.federated_plugin package dev.flutter.federated_plugin
import android.Manifest
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build
import androidx.annotation.NonNull import androidx.annotation.NonNull
import androidx.core.app.ActivityCompat.requestPermissions
import androidx.core.content.ContextCompat
import com.google.android.gms.location.LocationServices.getFusedLocationProviderClient
import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry
class FederatedPlugin : FlutterPlugin, MethodCallHandler {
class FederatedPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegistry.RequestPermissionsResultListener {
private lateinit var channel: MethodChannel private lateinit var channel: MethodChannel
private lateinit var context: Context private var context: Context? = null
private lateinit var activity: Activity
private lateinit var result: Result
private val REQUEST_CODE = 1001
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "location") channel = MethodChannel(flutterPluginBinding.binaryMessenger, "battery")
channel.setMethodCallHandler(this) channel.setMethodCallHandler(this)
context = flutterPluginBinding.applicationContext context = flutterPluginBinding.applicationContext
} }
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
this.result = result if (call.method == "getBatteryLevel") {
if (call.method == "getLocation") { val batteryLevel: Int
// Check for the runtime permission if SDK version is greater than 23. If permissions
// are granted, send the location data back to Dart. If permissions are not granted, batteryLevel = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// request for the runtime permissions. val batteryManager = context?.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.LOLLIPOP && !checkPermissions()) { batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
requestPermissions(activity, arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
), REQUEST_CODE)
} else {
provideLocation()
}
} else { } else {
result.notImplemented() val intent = IntentFilter(Intent.ACTION_BATTERY_CHANGED).let { intentFilter ->
context?.registerReceiver(null, intentFilter)
} }
intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
} }
// Method to fetch and send the last known location of the device to Dart. if (batteryLevel < 0) {
private fun provideLocation() { result.error("STATUS_UNAVAILABLE", "Not able to determine battery level.", null)
getFusedLocationProviderClient(context).lastLocation
.addOnSuccessListener { location ->
if (location != null) {
result.success(listOf(location.longitude, location.latitude))
} else { } else {
result.error("NOT_DETERMINED", "Not able to determine location", null) result.success(batteryLevel)
}
}.addOnFailureListener { exception ->
result.error("Error", exception.message, null)
} }
} else {
result.notImplemented()
} }
// Method to check permissions to access the location data.
private fun checkPermissions(): Boolean {
val fineLocationPermission = ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
val coarseLocationPermission = ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)
return fineLocationPermission == PackageManager.PERMISSION_GRANTED &&
coarseLocationPermission == PackageManager.PERMISSION_GRANTED
} }
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null) channel.setMethodCallHandler(null)
} context = null
override fun onDetachedFromActivity() {}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
binding.addRequestPermissionsResultListener(this)
}
override fun onDetachedFromActivityForConfigChanges() {}
// Callback for the result after requesting for runtime permissions. If permissions
// are granted, send the location data, or send an error back to Dart if permissions
// are not granted.
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>?, grantResults: IntArray): Boolean {
if (requestCode == REQUEST_CODE && grantResults.isNotEmpty()) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
provideLocation()
} else {
result.error("PERMISSION_DENIED", "Permission denied from User", null)
}
}
return false
} }
} }

@ -18,15 +18,15 @@ class MyApp extends StatelessWidget {
} }
} }
/// Demonstrates how to use the getLocation method from federated_plugin to access /// Demonstrates how to use the getBatteryLevel method from federated_plugin to retrieve
/// location data. /// current battery level of device.
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
@override @override
_HomePageState createState() => _HomePageState(); _HomePageState createState() => _HomePageState();
} }
class _HomePageState extends State<HomePage> { class _HomePageState extends State<HomePage> {
Location location; int batteryLevel;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -40,21 +40,20 @@ class _HomePageState extends State<HomePage> {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
location == null batteryLevel == null
? SizedBox.shrink() ? SizedBox.shrink()
: Text( : Text(
'Latitude: ${location.latitude}\n' 'Battery Level: $batteryLevel',
'Longitude: ${location.longitude}',
style: Theme.of(context).textTheme.headline5, style: Theme.of(context).textTheme.headline5,
), ),
SizedBox(height: 16), SizedBox(height: 16),
RaisedButton( RaisedButton(
child: Text('Get Location'), child: Text('Get Battery Level'),
onPressed: () async { onPressed: () async {
try { try {
final result = await getLocation(); final result = await getBatteryLevel();
setState(() { setState(() {
location = result; batteryLevel = result;
}); });
} catch (error) { } catch (error) {
Scaffold.of(context).showSnackBar( Scaffold.of(context).showSnackBar(

@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:federated_plugin/federated_plugin.dart';
import 'package:federated_plugin_example/main.dart'; import 'package:federated_plugin_example/main.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -10,27 +9,23 @@ import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
group('federated plugin demo tests', () { group('federated plugin demo tests', () {
final location = Location(latitude: 131.0, longitude: 221.0); final batteryLevel = 45;
setUpAll(() { setUpAll(() {
MethodChannel('location').setMockMethodCallHandler((call) async { MethodChannel('battery').setMockMethodCallHandler((call) async {
if (call.method == 'getLocation') { if (call.method == 'getBatteryLevel') {
return [location.longitude, location.latitude]; return batteryLevel;
} }
}); });
}); });
testWidgets('get location from platform', (tester) async { testWidgets('get current battery level from platform', (tester) async {
await tester.pumpWidget(MyApp()); await tester.pumpWidget(MyApp());
// Tap button to get location from platform. // Tap button to retrieve current battery level from platform.
await tester.tap(find.byType(RaisedButton)); await tester.tap(find.byType(RaisedButton));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect( expect(find.text('Battery Level: $batteryLevel'), findsOneWidget);
find.text('Latitude: ${location.latitude}\n'
'Longitude: ${location.longitude}'),
findsOneWidget,
);
}); });
}); });
} }

@ -5,12 +5,10 @@
import 'dart:async'; import 'dart:async';
import 'package:federated_plugin_platform_interface/federated_plugin_platform_interface.dart'; import 'package:federated_plugin_platform_interface/federated_plugin_platform_interface.dart';
import 'package:federated_plugin_platform_interface/location_model.dart';
export 'package:federated_plugin_platform_interface/location_model.dart';
/// Returns [Location] to provide latitude and longitude. /// Returns the current battery level of device.
/// ///
/// It uses [FederatedPluginInterface] interface to provide location. /// It uses [FederatedPluginInterface] interface to provide current battery level.
Future<Location> getLocation() async { Future<int> getBatteryLevel() async {
return await FederatedPluginInterface.instance.getLocation(); return await FederatedPluginInterface.instance.getBatteryLevel();
} }

@ -10,17 +10,16 @@ void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
group('Federated Plugin Test', () { group('Federated Plugin Test', () {
final location = Location(latitude: 131.0, longitude: 221.0); final batteryLevel = 34;
MethodChannel('location').setMockMethodCallHandler((call) async { MethodChannel('battery').setMockMethodCallHandler((call) async {
if (call.method == 'getLocation') { if (call.method == 'getBatteryLevel') {
return [location.longitude, location.latitude]; return batteryLevel;
} }
}); });
test('getLocation method test', () async { test('getBatteryLevel method test', () async {
final result = await getLocation(); final result = await getBatteryLevel();
expect(result.longitude, location.longitude); expect(result, batteryLevel);
expect(result.latitude, location.latitude);
}); });
}); });
} }

@ -0,0 +1,17 @@
// 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:federated_plugin_platform_interface/federated_plugin_platform_interface.dart';
import 'package:flutter/services.dart';
/// Implements [FederatedPluginInterface] using [MethodChannel] to fetch
/// battery level from platform.
class BatteryMethodChannel extends FederatedPluginInterface {
static const MethodChannel _methodChannel = MethodChannel('battery');
@override
Future<int> getBatteryLevel() async {
return await _methodChannel.invokeMethod<int>('getBatteryLevel');
}
}

@ -2,8 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:federated_plugin_platform_interface/location_method_channel.dart'; import 'package:federated_plugin_platform_interface/battery_method_channel.dart';
import 'package:federated_plugin_platform_interface/location_model.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart';
/// Interface which allows all the platform plugins to implement the same /// Interface which allows all the platform plugins to implement the same
@ -12,11 +11,11 @@ abstract class FederatedPluginInterface extends PlatformInterface {
FederatedPluginInterface() : super(token: _object); FederatedPluginInterface() : super(token: _object);
static FederatedPluginInterface _federatedPluginInterface = static FederatedPluginInterface _federatedPluginInterface =
LocationMethodChannel(); BatteryMethodChannel();
static final Object _object = Object(); static final Object _object = Object();
/// Provides instance of [LocationMethodChannel] to invoke platform calls. /// Provides instance of [BatteryMethodChannel] to invoke platform calls.
static FederatedPluginInterface get instance => _federatedPluginInterface; static FederatedPluginInterface get instance => _federatedPluginInterface;
static set instance(FederatedPluginInterface instance) { static set instance(FederatedPluginInterface instance) {
@ -24,8 +23,8 @@ abstract class FederatedPluginInterface extends PlatformInterface {
_federatedPluginInterface = instance; _federatedPluginInterface = instance;
} }
/// Returns [Location] to provide latitude and longitude. /// Returns the current battery level of device.
Future<Location> getLocation() async { Future<int> getBatteryLevel() async {
throw UnimplementedError('getLocation() has not been implemented.'); throw UnimplementedError('getBatteryLevel() has not been implemented.');
} }
} }

@ -1,24 +0,0 @@
// 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:federated_plugin_platform_interface/federated_plugin_platform_interface.dart';
import 'package:federated_plugin_platform_interface/location_model.dart';
import 'package:flutter/services.dart';
/// Implements [FederatedPluginInterface] using [MethodChannel] to fetch
/// location from platform.
class LocationMethodChannel extends FederatedPluginInterface {
static const MethodChannel _methodChannel = MethodChannel('location');
@override
Future<Location> getLocation() async {
final result =
await _methodChannel.invokeMethod<List<dynamic>>('getLocation');
return Location(
longitude: result.first as double,
latitude: result.last as double,
);
}
}

@ -1,11 +0,0 @@
// 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.
/// Model to hold the incoming latitude and longitude values from platform.
class Location {
final double latitude;
final double longitude;
Location({this.latitude, this.longitude});
}

@ -2,8 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:federated_plugin_platform_interface/location_method_channel.dart'; import 'package:federated_plugin_platform_interface/battery_method_channel.dart';
import 'package:federated_plugin_platform_interface/location_model.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
@ -11,18 +10,17 @@ void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
group('MethodChannel test', () { group('MethodChannel test', () {
final location = Location(latitude: 1.0, longitude: 2.0); final batteryLevel = 89;
MethodChannel('location').setMockMethodCallHandler((call) async { MethodChannel('battery').setMockMethodCallHandler((call) async {
if (call.method == 'getLocation') { if (call.method == 'getBatteryLevel') {
return [location.longitude, location.latitude]; return batteryLevel;
} }
}); });
test('getLocation method test', () async { test('getBatteryLevel method test', () async {
final locationMethodChannel = LocationMethodChannel(); final locationMethodChannel = BatteryMethodChannel();
final result = await locationMethodChannel.getLocation(); final result = await locationMethodChannel.getBatteryLevel();
expect(result.longitude, location.longitude); expect(result, batteryLevel);
expect(result.latitude, result.latitude);
}); });
}); });
} }

@ -67,6 +67,9 @@ build/
**/ios/ServiceDefinitions.json **/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.* **/ios/Runner/GeneratedPluginRegistrant.*
# Web related
lib/generated_plugin_registrant.dart
# Exceptions to above rules. # Exceptions to above rules.
!**/ios/**/default.mode1v3 !**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3 !**/ios/**/default.mode2v3

@ -5,17 +5,17 @@
import 'dart:html' as html; import 'dart:html' as html;
import 'package:federated_plugin_platform_interface/federated_plugin_platform_interface.dart'; import 'package:federated_plugin_platform_interface/federated_plugin_platform_interface.dart';
import 'package:federated_plugin_platform_interface/location_model.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart';
/// Web Implementation of [FederatedPluginInterface] to provide location. /// Web Implementation of [FederatedPluginInterface] to retrieve current battery
/// level of device.
class FederatedPlugin extends FederatedPluginInterface { class FederatedPlugin extends FederatedPluginInterface {
final html.Geolocation _geolocation; final html.Navigator _navigator;
/// Constructor to override the geolocation object for testing purpose. /// Constructor to override the navigator object for testing purpose.
FederatedPlugin({html.Geolocation geolocation}) FederatedPlugin({html.Navigator navigator})
: _geolocation = geolocation ?? html.window.navigator.geolocation; : _navigator = navigator ?? html.window.navigator;
/// Method to register the plugin which sets [FederatedPlugin] to be the default /// Method to register the plugin which sets [FederatedPlugin] to be the default
/// instance of [FederatedPluginInterface]. /// instance of [FederatedPluginInterface].
@ -23,21 +23,20 @@ class FederatedPlugin extends FederatedPluginInterface {
FederatedPluginInterface.instance = FederatedPlugin(); FederatedPluginInterface.instance = FederatedPlugin();
} }
/// Returns [Location] to provide latitude and longitude. /// Returns the current battery level of device.
/// ///
/// If any error, it's assume that user has denied the permission forever. /// If any error, it's assume that the BatteryManager API is not supported by
/// browser.
@override @override
Future<Location> getLocation() async { Future<int> getBatteryLevel() async {
try { try {
final geoPosition = await _geolocation.getCurrentPosition(); final battery = await _navigator.getBattery() as html.BatteryManager;
return Location( // The battery level retrieved is in range of 0.0 to 1.0.
longitude: geoPosition.coords.longitude.toDouble(), return battery.level * 100 as int;
latitude: geoPosition.coords.latitude.toDouble(),
);
} catch (error) { } catch (error) {
throw PlatformException( throw PlatformException(
code: 'PERMISSION_DENIED', code: 'STATUS_UNAVAILABLE',
message: 'Permission denied from User', message: 'The plugin is not supported by the browser.',
details: null, details: null,
); );
} }

@ -1,5 +1,5 @@
name: federated_plugin_web name: federated_plugin_web
description: Web implementation of federated_plugin to provide location. description: Web implementation of federated_plugin to retrieve current battery level.
version: 0.0.1 version: 0.0.1
homepage: homepage:
publish_to: none publish_to: none

@ -9,45 +9,35 @@ import 'package:federated_plugin_web/federated_plugin_web.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
const kLatitude = 4; const kBatteryLevel = 0.49;
const kLongitude = 3;
class GeoLocationMock extends Mock implements Geolocation {} class NavigatorMock extends Mock implements Navigator {}
class GeoPositionMock extends Mock implements Geoposition { class BatteryManagerMock extends Mock implements BatteryManager {
@override @override
Coordinates get coords => MockCoordinates(); num get level => kBatteryLevel;
}
class MockCoordinates extends Mock implements Coordinates {
@override
num get latitude => kLatitude;
@override
num get longitude => kLongitude;
} }
void main() { void main() {
E2EWidgetsFlutterBinding.ensureInitialized(); E2EWidgetsFlutterBinding.ensureInitialized();
group('FederatedPlugin test', () { group('FederatedPlugin test', () {
final geoLocationMock = GeoLocationMock(); final navigatorMock = NavigatorMock();
setUp(() { setUp(() {
when(geoLocationMock.getCurrentPosition()) when(navigatorMock.getBattery())
.thenAnswer((realInvocation) async => GeoPositionMock()); .thenAnswer((realInvocation) async => BatteryManagerMock());
}); });
testWidgets('getLocation Method', (tester) async { testWidgets('getBatteryLevel Method', (tester) async {
final federatedPlugin = FederatedPlugin(geolocation: geoLocationMock); final federatedPlugin = FederatedPlugin(navigator: navigatorMock);
final location = await federatedPlugin.getLocation(); final batteryLevel = await federatedPlugin.getBatteryLevel();
// Verify that getCurrentPosition was called. // Verify that getBattery was called.
verify(geoLocationMock.getCurrentPosition()); verify(navigatorMock.getBattery());
// Verify the values of Location.longitude and Location.latitude. // Verify the battery level.
expect(location.longitude, kLongitude); expect(batteryLevel, kBatteryLevel * 100);
expect(location.latitude, kLatitude);
}); });
}); });
} }

Loading…
Cancel
Save