Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/flutter_android_lifecycle/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.DS_Store
.dart_tool/

.packages
.pub/

build/
10 changes: 10 additions & 0 deletions packages/flutter_android_lifecycle/.metadata
Original file line number Diff line number Diff line change
@@ -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: 0e605cc4dd83137f785769dea5e8ae7da1afb361
channel: master

project_type: plugin
4 changes: 4 additions & 0 deletions packages/flutter_android_lifecycle/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## 1.0.0

* Introduces a `FlutterLifecycleAdapter`, which can be used by other plugins to obtain a `Lifecycle`
reference from a `FlutterPluginBinding`.
1 change: 1 addition & 0 deletions packages/flutter_android_lifecycle/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TODO: Add your license here.
41 changes: 41 additions & 0 deletions packages/flutter_android_lifecycle/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Flutter Android Lifecycle Plugin

[![pub package](https://img.shields.io/pub/v/flutter_android_lifecycle.svg)](https://pub.dartlang.org/packages/flutter_android_lifecycle)

A Flutter plugin for Android to allow other Flutter plugins to access an Android `Lifecycle` object
in the plugin's binding.

*Note*: This plugin is still under development, and some APIs might not be available yet.

## Installation

Add `flutter_android_lifecycle` as a [dependency in your pubspec.yaml file](https://flutter.io/using-packages/).

## Example

Use a `FlutterLifecycleAdapter` within another Flutter plugin's Android implementation, as shown
below:

```java
import androidx.lifecycle.Lifecycle;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding;
import io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter;

public class MyPlugin implements FlutterPlugin {
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
Object lifecycleReference = binding.getLifecycle();
Lifecycle lifecycle = new FlutterLifecycleAdapter(lifecycleReference).getLifecycle();

// Use lifecycle as desired.
}

//...
}
```

*Note*: This plugin is still under development, and some APIs might not be available yet.
[Feedback welcome](https://github.com/flutter/flutter/issues) and
[Pull Requests](https://github.com/flutter/plugins/pulls) are most welcome!
8 changes: 8 additions & 0 deletions packages/flutter_android_lifecycle/android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
42 changes: 42 additions & 0 deletions packages/flutter_android_lifecycle/android/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
group 'io.flutter.plugins.flutter_android_lifecycle'
version '1.0'

buildscript {
repositories {
google()
jcenter()
}

dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
}
}

rootProject.allprojects {
repositories {
google()
jcenter()
}
}

apply plugin: 'com.android.library'

android {
compileSdkVersion 28

defaultConfig {
minSdkVersion 16
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
lintOptions {
disable 'InvalidPackage'
}

dependencies {
implementation "androidx.annotation:annotation:1.1.0"
implementation "androidx.lifecycle:lifecycle-runtime:2.0.0-rc01"
api 'android.arch.lifecycle:runtime:1.1.1'
api 'android.arch.lifecycle:common:1.1.1'
api 'android.arch.lifecycle:common-java8:1.1.1'
}
}
4 changes: 4 additions & 0 deletions packages/flutter_android_lifecycle/android/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
1 change: 1 addition & 0 deletions packages/flutter_android_lifecycle/android/settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rootProject.name = 'flutter_android_lifecycle'
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.flutter.plugins.flutter_android_lifecycle">
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.embedding.engine.plugins.lifecycle;

import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;
import io.flutter.Log;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class FlutterLifecycleAdapter {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a nit, but this is still needed for this class to comply with Google Java style.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

private static final String TAG = "FlutterLifecycleAdapter";

@NonNull private Lifecycle lifecycle = null;

public FlutterLifecycleAdapter(@NonNull Object reference) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mklim @matthew-carroll

Currently the usage of this API looks like:

      FlutterLifecycleAdapter adapter =
          new FlutterLifecycleAdapter(flutterPluginBinding.getLifecycle());
      Lifecycle lifecycle = adapter.getLifecycle();

What do you think about making the getLifecycle method static instead, I see how usually we want to avoid static methods for testability, but in this case I don't see how this is particularly useful especially without us adding some FlutterLifecycleAdapterFactory method which will potentially allow tests to replace the concrete implementation of FlutterLifecycleAdapter.

Second question - what do yo think about making the adapter be the one that calls getLifecycle?

If we do both of these we may end up with the call site looking like:

  Lifecycle lifecycle = FlutterLifecycleAdapter.getLifecycle(flutterPluginBinding);

What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I avoid any static call that I don't absolutely need, but it's up to you. Seems like you can also achieve the second goal whether you go static or not.

Copy link
Contributor Author

@amirh amirh Oct 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I avoid any static call that I don't absolutely need, but it's up to you.

I'm looking for feedback 😄 do you see any issue with using static here?

Seems like you can also achieve the second goal whether you go static or not.

Yes, that's why I separated them to discuss separately, what is your opinion on that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the second suggestion is fine.

My feedback on the static suggestion is that I never make something static if I don't have to. I would personally not make it static. But I'm also not going to block this PR if you choose to do so. I don't see anything that will break by making it static. Then again, I could say the same thing for many instance methods that aren't static.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is totally subjective so I think it's up to you.

My personal opinion on static is that it's preferable if the method is truly immutable, doesn't require any kind of state or precondition, and truly doesn't have any kind of side effects. Using static in that way is a soft indicator that a method is purely functional. Good examples are Arrays#asList or TextUtils#isEmpty. A "bad" counter example of static is our own FlutterJNI, which has a bunch of "static" methods that don't depend on any member variables but do depend on there being this magic global c library loaded in the program's global runtime. The danger in methods like that is first that it's extremely easy to call them out of sync, and then second that they're really hard to work around when testing.

The generic problem with any static method is that it can't be mocked in unit tests. I generally prefer testing real behavior to using mocks whenever possible so that's not a huge concern to me but it can be a gigantic headache in the moment when the method really does need to be avoided in a test context (again, like FlutterJNI). Static also prevents subclasses from overriding the method, but that's a non-issue for me because I generally prefer composition to inheritance.

I think in this specific case it would be fine to make this static, because it's basically just returning the object that's passed into it. In a test context if you really needed to mock anything you would be returning a mock from the input parameter. I again prefer static interfaces because they're a soft signal that the function doesn't have silent side effects so I'd be happy to use one here, but it's totally subjective.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated to static, I think there's an upside (less cluttered call site) and no downside.

try {
Class hiddenLifecycleClass =
Class.forName("io.flutter.embedding.engine.plugins.lifecycle.HiddenLifecycleReference");

if (!reference.getClass().equals(hiddenLifecycleClass)) {
throw new IllegalArgumentException(
"The reference argument must be of type HiddenLifecycleReference. Was actually "
+ reference);
}

Method getLifecycle = reference.getClass().getMethod("getLifecycle");
this.lifecycle = (Lifecycle) getLifecycle.invoke(reference);
} catch (ClassNotFoundException
| NoSuchMethodException
| IllegalAccessException
| InvocationTargetException e) {
Log.w(
TAG,
"You are attempting to use Flutter plugins that are newer than your"
+ " version of Flutter. Plugins may not work as expected.");
}
}

@NonNull
public Lifecycle getLifecycle() {
return lifecycle;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
package io.flutter.plugins.flutter_android_lifecycle;

import io.flutter.plugin.common.PluginRegistry.Registrar;

/**
* Plugin class that exists because the Flutter tool expects such a class to exist for every Android
* plugin.
*
* <p><strong>DO NOT USE THIS CLASS.</strong>
*/
public class FlutterAndroidLifecyclePlugin {
public static void registerWith(Registrar registrar) {
// no-op
}
}
36 changes: 36 additions & 0 deletions packages/flutter_android_lifecycle/example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 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
.packages
.pub-cache/
.pub/
/build/

# Web related
lib/generated_plugin_registrant.dart

# Exceptions to above rules.
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
10 changes: 10 additions & 0 deletions packages/flutter_android_lifecycle/example/.metadata
Original file line number Diff line number Diff line change
@@ -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: 0e605cc4dd83137f785769dea5e8ae7da1afb361
channel: master

project_type: app
16 changes: 16 additions & 0 deletions packages/flutter_android_lifecycle/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# flutter_android_lifecycle_example

Demonstrates how to use the flutter_android_lifecycle plugin.

## Getting Started

This project is a starting point for a Flutter application.

A few resources to get you started if this is your first Flutter project:

- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)

For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
7 changes: 7 additions & 0 deletions packages/flutter_android_lifecycle/example/android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}

def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}

def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}

def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}

apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
compileSdkVersion 28

lintOptions {
disable 'InvalidPackage'
}

defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "io.flutter.plugins.flutter_android_lifecycle_example"
minSdkVersion 16
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}

flutter {
source '../..'
}

dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.flutter_android_lifecycle_example;

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 EmbeddingV1ActivityTest {
@Rule
public ActivityTestRule<EmbeddingV1Activity> rule =
new ActivityTestRule<>(EmbeddingV1Activity.class);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.flutter_android_lifecycle_example;

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 MainActivityTest {
@Rule public ActivityTestRule<MainActivity> rule = new ActivityTestRule<>(MainActivity.class);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.flutter.plugins.flutter_android_lifecycle_example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
Loading