[federated_plugin] adds platform interface implementation and plugin implementation for Android (#503)

pull/509/head
Ayush Bherwani 4 years ago committed by GitHub
parent 3bc860f5df
commit 6d909874db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

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

@ -4,37 +4,109 @@
package dev.flutter.federated_plugin
import androidx.annotation.NonNull;
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
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.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar
/** FederatedPlugin */
public class FederatedPlugin: FlutterPlugin, MethodCallHandler {
/// The MethodChannel that will the communication between Flutter and native Android
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
private lateinit var channel : MethodChannel
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "federated_plugin")
channel.setMethodCallHandler(this);
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else {
result.notImplemented()
}
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
import io.flutter.plugin.common.PluginRegistry
class FederatedPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegistry.RequestPermissionsResultListener {
private lateinit var channel: MethodChannel
private lateinit var context: Context
private lateinit var activity: Activity
private lateinit var result: Result
private val REQUEST_CODE = 1001
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "location")
channel.setMethodCallHandler(this)
context = flutterPluginBinding.applicationContext
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
this.result = result
if (call.method == "getLocation") {
// 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,
// request for the runtime permissions.
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.LOLLIPOP && !checkPermissions()) {
requestPermissions(activity, arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
), REQUEST_CODE)
} else {
provideLocation()
}
} else {
result.notImplemented()
}
}
// Method to fetch and send the last known location of the device to Dart.
private fun provideLocation() {
getFusedLocationProviderClient(context).lastLocation
.addOnSuccessListener { location ->
if (location != null) {
result.success(listOf(location.longitude, location.latitude))
} else {
result.error("NOT_DETERMINED", "Not able to determine location", null)
}
}.addOnFailureListener { exception ->
result.error("Error", exception.message, null)
}
}
// 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) {
channel.setMethodCallHandler(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
}
}

@ -2,60 +2,74 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:federated_plugin/federated_plugin.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
class MyApp extends StatelessWidget {
@override
_MyAppState createState() => _MyAppState();
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
/// Demonstrates how to use the getLocation method from federated_plugin to access
/// location data.
class HomePage extends StatefulWidget {
@override
void initState() {
super.initState();
initPlatformState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
String platformVersion;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
platformVersion = await FederatedPlugin.platformVersion;
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
_HomePageState createState() => _HomePageState();
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
}
class _HomePageState extends State<HomePage> {
Location location;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Text('Running on: $_platformVersion\n'),
),
return Scaffold(
appBar: AppBar(
title: Text('Federated Plugin Demo'),
),
body: Builder(
builder: (context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
location == null
? SizedBox.shrink()
: Text(
'Latitude: ${location.latitude}\n'
'Longitude: ${location.longitude}',
style: Theme.of(context).textTheme.headline5,
),
SizedBox(height: 16),
RaisedButton(
child: Text('Get Location'),
onPressed: () async {
try {
final result = await getLocation();
setState(() {
location = result;
});
} catch (error) {
Scaffold.of(context).showSnackBar(
SnackBar(
backgroundColor: Theme.of(context).primaryColor,
content: Text(error.message as String),
),
);
}
},
),
],
),
);
},
),
);
}

@ -64,6 +64,13 @@ packages:
relative: true
source: path
version: "0.0.1"
federated_plugin_platform_interface:
dependency: transitive
description:
path: "../../federated_plugin_platform_interface"
relative: true
source: path
version: "0.0.1"
flutter:
dependency: "direct main"
description: flutter
@ -102,6 +109,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.2"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
sky_engine:
dependency: transitive
description: flutter

@ -2,23 +2,35 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:federated_plugin/federated_plugin.dart';
import 'package:federated_plugin_example/main.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:federated_plugin_example/main.dart';
void main() {
testWidgets('Verify Platform version', (tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
group('federated plugin demo tests', () {
final location = Location(latitude: 131.0, longitude: 221.0);
setUpAll(() {
MethodChannel('location').setMockMethodCallHandler((call) async {
if (call.method == 'getLocation') {
return [location.longitude, location.latitude];
}
});
});
testWidgets('get location from platform', (tester) async {
await tester.pumpWidget(MyApp());
// Tap button to get location from platform.
await tester.tap(find.byType(RaisedButton));
await tester.pumpAndSettle();
// Verify that platform version is retrieved.
expect(
find.byWidgetPredicate(
(widget) => widget is Text &&
widget.data.startsWith('Running on:'),
),
findsOneWidget,
);
expect(
find.text('Latitude: ${location.latitude}\n'
'Longitude: ${location.longitude}'),
findsOneWidget,
);
});
});
}

@ -4,13 +4,13 @@
import 'dart:async';
import 'package:flutter/services.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';
class FederatedPlugin {
static const MethodChannel _channel = MethodChannel('federated_plugin');
static Future<String> get platformVersion async {
final version = await _channel.invokeMethod<String>('getPlatformVersion');
return version;
}
/// Returns [Location] to provide latitude and longitude.
///
/// It uses [FederatedPluginInterface] interface to provide location.
Future<Location> getLocation() async {
return await FederatedPluginInterface.instance.getLocation();
}

@ -50,6 +50,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
federated_plugin_platform_interface:
dependency: "direct main"
description:
path: "../federated_plugin_platform_interface"
relative: true
source: path
version: "0.0.1"
flutter:
dependency: "direct main"
description: flutter
@ -88,6 +95,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.2"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
sky_engine:
dependency: transitive
description: flutter

@ -12,6 +12,8 @@ environment:
dependencies:
flutter:
sdk: flutter
federated_plugin_platform_interface:
path: ../federated_plugin_platform_interface
dev_dependencies:
flutter_test:

@ -7,21 +7,20 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:federated_plugin/federated_plugin.dart';
void main() {
const channel = MethodChannel('federated_plugin');
TestWidgetsFlutterBinding.ensureInitialized();
setUp(() {
channel.setMockMethodCallHandler((methodCall) async {
return '42';
group('Federated Plugin Test', () {
final location = Location(latitude: 131.0, longitude: 221.0);
MethodChannel('location').setMockMethodCallHandler((call) async {
if (call.method == 'getLocation') {
return [location.longitude, location.latitude];
}
});
});
tearDown(() {
channel.setMockMethodCallHandler(null);
});
test('getPlatformVersion', () async {
expect(await FederatedPlugin.platformVersion, '42');
test('getLocation method test', () async {
final result = await getLocation();
expect(result.longitude, location.longitude);
expect(result.latitude, location.latitude);
});
});
}

@ -0,0 +1,75 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 8bd2e6585b0eda5ccdc10c1e1d35105e11f84424
channel: master
project_type: package

@ -0,0 +1,31 @@
include: package:pedantic/analysis_options.yaml
analyzer:
strong-mode:
implicit-casts: false
implicit-dynamic: false
linter:
rules:
- avoid_types_on_closure_parameters
- avoid_void_async
- await_only_futures
- camel_case_types
- cancel_subscriptions
- close_sinks
- constant_identifier_names
- control_flow_in_finally
- directives_ordering
- empty_statements
- hash_and_equals
- implementation_imports
- non_constant_identifier_names
- package_api_docs
- package_names
- package_prefixed_library_names
- test_types_in_equals
- throw_in_finally
- unnecessary_brace_in_string_interps
- unnecessary_getters_setters
- unnecessary_new
- unnecessary_statements

@ -0,0 +1,31 @@
// 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/location_method_channel.dart';
import 'package:federated_plugin_platform_interface/location_model.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
/// Interface which allows all the platform plugins to implement the same
/// functionality.
abstract class FederatedPluginInterface extends PlatformInterface {
FederatedPluginInterface() : super(token: _object);
static FederatedPluginInterface _federatedPluginInterface =
LocationMethodChannel();
static final Object _object = Object();
/// Provides instance of [LocationMethodChannel] to invoke platform calls.
static FederatedPluginInterface get instance => _federatedPluginInterface;
static set instance(FederatedPluginInterface instance) {
PlatformInterface.verifyToken(instance, _object);
_federatedPluginInterface = instance;
}
/// Returns [Location] to provide latitude and longitude.
Future<Location> getLocation() async {
throw UnimplementedError('getLocation() has not been implemented.');
}
}

@ -0,0 +1,24 @@
// 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,
);
}
}

@ -0,0 +1,11 @@
// 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});
}

@ -0,0 +1,161 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.2"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0-nullsafety"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.3"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0-nullsafety"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.8"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0-nullsafety"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
pedantic:
dependency: "direct dev"
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.2"
plugin_platform_interface:
dependency: "direct main"
description:
name: plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.5"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.17"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0-nullsafety"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0-nullsafety"
sdks:
dart: ">=2.9.0-18.0 <2.9.0"
flutter: ">=1.17.0 <2.0.0"

@ -0,0 +1,20 @@
name: federated_plugin_platform_interface
description: A new Flutter package project.
version: 0.0.1
author:
homepage:
publish_to: none
environment:
sdk: ">=2.7.0 <3.0.0"
flutter: ">=1.17.0 <2.0.0"
dependencies:
flutter:
sdk: flutter
plugin_platform_interface: ^1.0.2
dev_dependencies:
flutter_test:
sdk: flutter
pedantic: ^1.9.0

@ -0,0 +1,28 @@
// 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/location_method_channel.dart';
import 'package:federated_plugin_platform_interface/location_model.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('MethodChannel test', () {
final location = Location(latitude: 1.0, longitude: 2.0);
MethodChannel('location').setMockMethodCallHandler((call) async {
if (call.method == 'getLocation') {
return [location.longitude, location.latitude];
}
});
test('getLocation method test', () async {
final locationMethodChannel = LocationMethodChannel();
final result = await locationMethodChannel.getLocation();
expect(result.longitude, location.longitude);
expect(result.latitude, result.latitude);
});
});
}
Loading…
Cancel
Save