Skip to content

Commit

Permalink
Support v2 android embedding and add smoke test.
Browse files Browse the repository at this point in the history
This update adds support for Flutter's version 2 Android embedder.
In order to update a project created prior to flutter version 1.12,
follow this guide: https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects

Initial e2e test is included as a template for adding future tests.
Follow the section for testing here:
https://flutter.dev/docs/development/packages-and-plugins/plugin-api-migration#testing-your-plugin
  • Loading branch information
pauldemarco committed Mar 31, 2020
1 parent e7d3f97 commit 419c17b
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import android.app.Activity;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Application;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
Expand Down Expand Up @@ -42,29 +43,43 @@

import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

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.BinaryMessenger;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.EventChannel.EventSink;
import io.flutter.plugin.common.EventChannel.StreamHandler;
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;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener;


/** FlutterBluePlugin */
public class FlutterBluePlugin implements MethodCallHandler, RequestPermissionsResultListener {
public class FlutterBluePlugin implements FlutterPlugin, ActivityAware, MethodCallHandler, RequestPermissionsResultListener {
private static final String TAG = "FlutterBluePlugin";
private static FlutterBluePlugin instance;
private Object initializationLock = new Object();
private Context context;
private MethodChannel channel;
private static final String NAMESPACE = "plugins.pauldemarco.com/flutter_blue";

private EventChannel stateChannel;
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;

private FlutterPluginBinding pluginBinding;
private ActivityPluginBinding activityBinding;
private Application application;
private Activity activity;

private static final int REQUEST_FINE_LOCATION_PERMISSIONS = 1452;
static final private UUID CCCD_ID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
private final Registrar registrar;
private final Activity activity;
private final MethodChannel channel;
private final EventChannel stateChannel;
private final BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private final Map<String, BluetoothDeviceCache> mDevices = new HashMap<>();
private LogLevel logLevel = LogLevel.EMERGENCY;

Expand All @@ -74,21 +89,97 @@ public class FlutterBluePlugin implements MethodCallHandler, RequestPermissionsR

/** Plugin registration. */
public static void registerWith(Registrar registrar) {
final FlutterBluePlugin instance = new FlutterBluePlugin(registrar);
registrar.addRequestPermissionsResultListener(instance);
if (instance == null) {
instance = new FlutterBluePlugin();
}
Activity activity = registrar.activity();
Application application = null;
if (registrar.context() != null) {
application = (Application) (registrar.context().getApplicationContext());
}
instance.setup(registrar.messenger(), application, activity, registrar, null);
}

public FlutterBluePlugin() {}

@Override
public void onAttachedToEngine(FlutterPluginBinding binding) {
pluginBinding = binding;
}

@Override
public void onDetachedFromEngine(FlutterPluginBinding binding) {
pluginBinding = null;

}

@Override
public void onAttachedToActivity(ActivityPluginBinding binding) {
activityBinding = binding;
setup(
pluginBinding.getBinaryMessenger(),
(Application) pluginBinding.getApplicationContext(),
activityBinding.getActivity(),
null,
activityBinding);
}

@Override
public void onDetachedFromActivity() {
tearDown();
}

FlutterBluePlugin(Registrar r){
this.registrar = r;
this.activity = r.activity();
this.channel = new MethodChannel(registrar.messenger(), NAMESPACE+"/methods");
this.stateChannel = new EventChannel(registrar.messenger(), NAMESPACE+"/state");
this.mBluetoothManager = (BluetoothManager) r.activity().getSystemService(Context.BLUETOOTH_SERVICE);
this.mBluetoothAdapter = mBluetoothManager.getAdapter();
channel.setMethodCallHandler(this);
stateChannel.setStreamHandler(stateHandler);
@Override
public void onDetachedFromActivityForConfigChanges() {
onDetachedFromActivity();
}

@Override
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
onAttachedToActivity(binding);
}

private void setup(
final BinaryMessenger messenger,
final Application application,
final Activity activity,
final PluginRegistry.Registrar registrar,
final ActivityPluginBinding activityBinding) {
synchronized (initializationLock) {
Log.i(TAG, "setup");
this.activity = activity;
this.application = application;
channel = new MethodChannel(messenger, NAMESPACE + "/methods");
channel.setMethodCallHandler(this);
stateChannel = new EventChannel(messenger, NAMESPACE + "/state");
stateChannel.setStreamHandler(stateHandler);
mBluetoothManager = (BluetoothManager) application.getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (registrar != null) {
// V1 embedding setup for activity listeners.
registrar.addRequestPermissionsResultListener(this);
} else {
// V2 embedding setup for activity listeners.
activityBinding.addRequestPermissionsResultListener(this);
}
}
}

private void tearDown() {
Log.i(TAG, "teardown");
context = null;
activityBinding.removeRequestPermissionsResultListener(this);
activityBinding = null;
channel.setMethodCallHandler(null);
channel = null;
stateChannel.setStreamHandler(null);
stateChannel = null;
mBluetoothAdapter = null;
mBluetoothManager = null;
application = null;
}


@Override
public void onMethodCall(MethodCall call, Result result) {
if(mBluetoothAdapter == null && !"isAvailable".equals(call.method)) {
Expand Down
4 changes: 2 additions & 2 deletions example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,6 @@ flutter {

dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.flutter.plugins.firebase.core;

import androidx.test.rule.ActivityTestRule;
import dev.flutter.plugins.e2e.FlutterRunner;
import com.pauldemarco.flutter_blue_example.EmbeddingV1Activity;
import org.junit.Rule;
import org.junit.runner.RunWith;

@RunWith(FlutterRunner.class)
public class EmbeddingV1ActivityTest {
@Rule
public ActivityTestRule<EmbeddingV1Activity> rule =
new ActivityTestRule<>(EmbeddingV1Activity.class);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.flutter.plugins.firebase.core;

import androidx.test.rule.ActivityTestRule;
import dev.flutter.plugins.e2e.FlutterRunner;
import org.junit.Rule;
import org.junit.runner.RunWith;

@RunWith(FlutterRunner.class)
public class FlutterActivityTest {
// Replace `MainActivity` with `io.flutter.embedding.android.FlutterActivity` if you removed `MainActivity`.
@Rule public ActivityTestRule<io.flutter.embedding.android.FlutterActivity> rule = new ActivityTestRule<>(io.flutter.embedding.android.FlutterActivity.class);
}
7 changes: 0 additions & 7 deletions example/android/app/src/debug/AndroidManifest.xml

This file was deleted.

10 changes: 9 additions & 1 deletion example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
android:label="flutter_blue_example"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:name="io.flutter.embedding.android.FlutterActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
Expand All @@ -28,5 +28,13 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".EmbeddingV1Activity"
android:theme="@android:style/Theme.Black.NoTitleBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
</activity>
<meta-data android:name="flutterEmbedding" android:value="2"/>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.pauldemarco.flutter_blue_example;

import android.os.Bundle;
import dev.flutter.plugins.e2e.E2EPlugin;
import io.flutter.app.FlutterActivity;
import com.pauldemarco.flutter_blue.FlutterBluePlugin;

public class EmbeddingV1Activity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FlutterBluePlugin.registerWith(registrarFor("com.pauldemarco.flutter_blue.FlutterBluePlugin"));
E2EPlugin.registerWith(registrarFor("dev.flutter.plugins.e2e.E2EPlugin"));
}
}

This file was deleted.

7 changes: 0 additions & 7 deletions example/android/app/src/profile/AndroidManifest.xml

This file was deleted.

8 changes: 5 additions & 3 deletions example/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: flutter_blue_example
description: Demonstrates how to use the flutter_blue plugin.
publish_to: 'none'
publish_to: "none"

environment:
sdk: ">=2.1.0 <3.0.0"
Expand All @@ -12,9 +12,11 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter

flutter_blue:
path: ../
e2e: ^0.2.1
flutter_driver:
sdk: flutter

flutter:
uses-material-design: true
uses-material-design: true
6 changes: 5 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ author: Paul DeMarco <paulmdemarco@gmail.com>
homepage: https://github.com/pauldemarco/flutter_blue

environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.0.0-dev.28.0 <3.0.0"
flutter: ">=1.12.13+hotfix.6 <2.0.0"

dependencies:
flutter:
Expand All @@ -19,6 +20,9 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
e2e: ^0.2.1
flutter_driver:
sdk: flutter

flutter:
plugin:
Expand Down
13 changes: 13 additions & 0 deletions test/flutter_blue_test_e2e.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_blue/flutter_blue.dart';
import 'package:e2e/e2e.dart';

void main() {
E2EWidgetsFlutterBinding.ensureInitialized();

testWidgets('Is bluetooth available?', (WidgetTester tester) async {
final FlutterBlue blue = FlutterBlue.instance;
final bool isAvail = await blue.isAvailable;
expect(isAvail, true);
});
}

0 comments on commit 419c17b

Please sign in to comment.