-
Notifications
You must be signed in to change notification settings - Fork 1
Experimental: Create Flutter Plugin
These instructions are for the ecosystem team as they implement parallel plugin support for v2 embedding and be testable on Firebase TestLab. See current version of the battery
plugin for an example: https://github.com/flutter/plugins/tree/master/packages/battery
-
Update the main plugin class (
*Plugin.java
) to implementFlutterPlugin
[source]. For more complex plugins, you can separate theFlutterPlugin
andMethodCallHandler
into two classes. See the next section, Basic Plugin, for more details on accessing app resources with the v2 embedding.
Also, note that the plugin should still contain the staticregisterWith()
method to remain compatible with apps that don't use v2 embedding. The easiest thing to do is move the logic fromregisterWith()
into a private method that bothregisterWith()
andonAttachedToEngine()
can call if possible. EitherregisterWith()
oronAttachToEngine()
will be called, not both.
If you are creating channels in youronAttachToEngine()
, there is no need to cleanup those creations inonDetachFromEngine()
and creating them again the second timeonAttachToEngine()
is called is fine.
In addition, you should document all non-overridden public members within the plugin. In an add-to-app scenario, these classes will be accessible to a developer and require documentation. -
(Optional) If your plugin needs an
Activity
reference, also implementActivityAware
. -
(Optional) If your plugin is expected to be held in a background
Service
at any point in time, implementServiceAware
. -
Update example app
MainActivity.java
to use the v2 embeddingFlutterActivity
. You may have to make a public constructor for you plugin class if one didn't exist already. e.g.
package io.flutter.plugins.firebasecoreexample;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugins.firebase.core.FirebaseCorePlugin;
public class MainActivity extends FlutterActivity {
// TODO(<github-username>): Remove this once v2 of GeneratedPluginRegistrant rolls to stable. https://github.com/flutter/flutter/issues/42694
@Override
public void configureFlutterEngine(FlutterEngine flutterEngine) {
flutterEngine.getPlugins().add(new FirebaseCorePlugin());
}
}
-
(Optional) Use
ShimPluginRegistry
to add plugins that don’t yet support the v2 embedding. e.g.
ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine);
PathProviderPlugin.registerWith(
shimPluginRegistry.registrarFor("io.flutter.plugins.pathprovider.PathProviderPlugin"));
VideoPlayerPlugin.registerWith(
shimPluginRegistry.registrarFor("io.flutter.plugins.videoplayer.VideoPlayerPlugin"));
- Create an
EmbeddingV1Activity.java
that uses the v1 embedding in the same folder asMainActivity
. e.g.
package io.flutter.plugins.firebasecoreexample;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class EmbeddingV1Activity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}
- Add the
EmbeddingV1Activity
to the <plugin_name>/example/android/app/src/main/AndroidManifest.xml. e.g.
<activity
android:name=".EmbeddingV1Activity"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
</activity>
- To have the plugin support Flutter on branches master and stable, include this gradle script in <plugin_name>/android/build.gradle.
// TODO(<github-username>): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348
afterEvaluate {
def containsEmbeddingDependencies = false
for (def configuration : configurations.all) {
for (def dependency : configuration.dependencies) {
if (dependency.group == 'io.flutter' &&
dependency.name.startsWith('flutter_embedding') &&
dependency.isTransitive())
{
containsEmbeddingDependencies = true
break
}
}
}
if (!containsEmbeddingDependencies) {
android {
dependencies {
def lifecycle_version = "1.1.1"
compileOnly "android.arch.lifecycle:runtime:$lifecycle_version"
compileOnly "android.arch.lifecycle:common:$lifecycle_version"
compileOnly "android.arch.lifecycle:common-java8:$lifecycle_version"
}
}
}
}
- Update <plugin_name>/example/android/app/build.gradle to replace references to
android.support.test
withandroidx.test
:
defaultConfig {
...
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
...
}
dependencies {
...
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
...
}
- Add tests files for
MainActivity
andEmbeddingV1Activity
in <plugin_name>/example/android/app/src/androidTest/java/<plugin_path>/. You will need to create these directories. e.g.
package io.flutter.plugins.firebase.core;
import androidx.test.rule.ActivityTestRule;
import dev.flutter.plugins.e2e.FlutterRunner;
import io.flutter.plugins.firebasecoreexample.MainActivity;
import org.junit.Rule;
import org.junit.runner.RunWith;
@RunWith(FlutterRunner.class)
public class MainActivityTest {
@Rule public ActivityTestRule<MainActivity> rule = new ActivityTestRule<>(MainActivity.class);
}
package io.flutter.plugins.firebase.core;
import androidx.test.rule.ActivityTestRule;
import dev.flutter.plugins.e2e.FlutterRunner;
import io.flutter.plugins.firebasecoreexample.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);
}
- Add
e2e
andflutter_driver
dev_dependencies to <plugin_name>/pubspec.yaml and <plugin_name>/example/pubspec.yaml.
e2e: ^0.2.1
flutter_driver:
sdk: flutter
- Manually register the E2E plugin in MainActivity.java alongside any other plugins used by the example app.
package io.flutter.plugins.packageinfoexample;
import dev.flutter.plugins.e2e.E2EPlugin;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugins.packageinfo.PackageInfoPlugin;
public class MainActivity extends FlutterActivity {
// TODO(jackson): Remove this once v2 of GeneratedPluginRegistrant rolls to stable.
// https://github.com/flutter/flutter/issues/42694
@Override
public void configureFlutterEngine(FlutterEngine flutterEngine) {
flutterEngine.getPlugins().add(new PackageInfoPlugin());
flutterEngine.getPlugins().add(new E2EPlugin());
}
}
- Update minimum Flutter version of environment in <plugin_name>/pubspec.yaml. All plugins moving forward will set the minimum version to
1.9.1+hotfix.4
which is the minimum version we can guarantee support for .e.g.
environment:
sdk: ">=2.0.0-dev.28.0 <3.0.0"
flutter: ">=1.9.1+hotfix.4 <2.0.0"
- Create a simple test in <plugin_name>/test/<plugin_name>_e2e.dart. For the purpose of testing the PR that adds the v2 embedding support, we're trying to test some very basic functionality of the plugin. This is a smoke test to ensure that the plugin properly registers with the new embedder. e.g.
import 'package:flutter_test/flutter_test.dart';
import 'package:battery/battery.dart';
import 'package:e2e/e2e.dart';
void main() {
E2EWidgetsFlutterBinding.ensureInitialized();
testWidgets('Can get battery level', (WidgetTester tester) async {
final Battery battery = Battery();
final int batteryLevel = await battery.batteryLevel;
expect(batteryLevel, isNotNull);
});
}
- Test run the e2e tests locally. In a terminal:
cd <plugin_name>/example
flutter build apk
cd android
./gradlew app:connectedAndroidTest -Ptarget=`pwd`/../../test/<plugin_name>_e2e.dart
To get started with a Flutter Android plugin in code, start by implementing FlutterPlugin
.
public class MyPlugin implements FlutterPlugin {
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
// TODO: your plugin is now attached to a Flutter experience.
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
// TODO: your plugin is no longer attached to a Flutter experience.
}
}
As shown above, your plugin may or may not be associated with a given Flutter experience at any given moment in time. You should take care to initialize your plugin's behavior in onAttachedToEngine()
, and then cleanup your plugin's references in onDetachedFromEngine()
.
The FlutterPluginBinding
provides your plugin with a few important references:
-
binding.getFlutterEngine()
: Returns theFlutterEngine
that your plugin is attached to, providing access to components like theDartExecutor
,FlutterRenderer
, and more. -
binding.getApplicationContext()
: Returns the Android application'sContext
for the running app. -
binding.getLifecycle()
: Returns a reference that can be used to obtain aLifecycle
object. If you need to use this lifecycle reference then you need add a project dependency on Flutter's Android lifecycle package.
If your plugin needs to interact with the UI, such as requesting permissions, or altering Android UI chrome, then you need to take additional steps to define your plugin. You must implement the ActivityAware
interface.
public class MyPlugin implements FlutterPlugin, ActivityAware {
//...normal plugin behavior is hidden...
@Override
public void onAttachedToActivity(ActivityPluginBinding activityPluginBinding) {
// TODO: your plugin is now attached to an Activity
}
@Override
public void onDetachedFromActivityForConfigChanges() {
// TODO: the Activity your plugin was attached to was destroyed to change configuration.
// This call will be followed by onReattachedToActivityForConfigChanges().
}
@Override
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding activityPluginBinding) {
// TODO: your plugin is now attached to a new Activity after a configuration change.
}
@Override
public void onDetachedFromActivity() {
// TODO: your plugin is no longer associated with an Activity. Clean up references.
}
}
To interact with an Activity
, your ActivityAware
plugin must implement appropriate behavior at 4 stages. First, your plugin is attached to an Activity
. You can access that Activity
and a number of its callbacks through the provided ActivityPluginBinding
.
Since Activity
s can be destroyed during configuration changes, you must cleanup any references to the given Activity
in onDetachedFromActivityForConfigChanges()
, and then re-establish those references in onReattachedToActivityForConfigChanges()
.
Finally, in onDetachedFromActivity()
your plugin should clean up all references related to Activity
behavior and return to a non-UI configuration.
TODO
TODO
- Home of the Wiki
- Roadmap
- API Reference (stable)
- API Reference (master)
- Glossary
- Contributor Guide
- Chat on Discord
- Code of Conduct
- Issue triage reports
- Our Values
- Tree hygiene
- Issue hygiene and Triage
- Style guide for Flutter repo
- Project teams
- Contributor access
- What should I work on?
- Running and writing tests
- Release process
- Rolling Dart
- Manual Engine Roll with Breaking Commits
- Updating Material Design Fonts & Icons
- Postmortems
- Setting up the Framework development environment
- The Framework architecture
- The flutter tool
- API Docs code block generation
- Running examples
- Using the Dart analyzer
- The flutter run variants
- Test coverage for package:flutter
- Writing a golden-file test for package:flutter
- Setting up the Engine development environment
- Compiling the engine
- Debugging the engine
- Using Sanitizers with the Flutter Engine
- Testing the engine
- The Engine architecture
- Flutter's modes
- Engine disk footprint
- Comparing AOT Snapshot Sizes
- Custom Flutter engine embedders
- Custom Flutter Engine Embedding in AOT Mode
- Flutter engine operation in AOT Mode
- Engine-specific Service Protocol extensions
- Crashes
- Supporting legacy platforms
- Metal on iOS FAQ
- Engine Clang Tidy Linter
- Why we have a separate engine repo
- Reduce Flutter engine size with MLGO
- Setting up the Plugins development environment
- Setting up the Packages development environment
- Plugins and Packages repository structure
- Plugin Tests
- Contributing to Plugins and Packages
- Releasing a Plugin or Package
- Unexpected Plugins and Packages failures