snapshot of https://github.com/flutter/samples.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Main Branch CI / Test Flutter beta on ubuntu-latest (push) Has been skipped
Main Branch CI / Test Flutter stable on ubuntu-latest (push) Has been skipped
Main Branch CI / Test Flutter beta on macos-latest (push) Has been cancelled
Main Branch CI / Test Flutter stable on macos-latest (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-16 01:43:26 +05:00
commit 9ed6a80afa
2886 changed files with 140184 additions and 0 deletions

79
add_to_app/README.md Normal file
View File

@@ -0,0 +1,79 @@
# Add-to-App Samples
This directory contains Android and iOS projects that import and use a Flutter
module. They're designed to show recommended approaches for [adding Flutter to
existing Android and iOS apps](https://docs.flutter.dev/add-to-app).
## Samples Listing
* [`fullscreen`](./fullscreen) — Embeds a full screen instance of
Flutter into an existing iOS or Android app.
* [`prebuilt_module`](./prebuilt_module) — Embeds a full screen
instance of Flutter as a prebuilt library that can be loaded into an existing
iOS or Android app.
* [`plugin`](./plugin) — Embeds a full screen Flutter instance that
is using plugins into an existing iOS or Android app.
* [`books`](./books) — Mimics a real world use-case of embedding Flutter into an
existing Android app and demonstrates using
[Pigeon](https://pub.dev/packages/pigeon) to communicate between Flutter and
the host application.
* [`multiple_flutters`](./multiple_flutters) — Shows the usage of the Flutter
Engine Group APIs to embed multiple instances of Flutter into an existing app
with low memory cost.
* [`android_view`](./android_view) — Shows how to integrate a Flutter
add-to-app module at a view level for Android.
## Goals for these samples
* Show developers how to add Flutter to their existing applications.
* Show the following options:
* Whether to build the Flutter module from source each time the app builds or
rely on a separately pre-built module.
* Whether plugins are needed by the Flutter module used in the app.
* Show Flutter being integrated ergonomically with applications with existing
middleware and business logic data classes.
## Installing Cocoapods
The iOS samples in this repo require the latest version of Cocoapods. To make
sure you've got it, run the following command on a macOS machine:
```bash
sudo gem install cocoapods
```
See https://guides.cocoapods.org/using/getting-started.html for more details.
## Debugging
You can `flutter attach` to the running host application to [debug the Flutter
module](https://docs.flutter.dev/add-to-app/debugging). This will
allow you to hot reload, set breakpoints, and use DevTools and other debugging
functionality, similar to a full Flutter app.
## Issues
If you run into an issue with the sample itself, please file an issue
in the [Flutter samples repo](https://github.com/flutter/samples/issues).
## Updating Android samples and its dependencies
1. Open the top level build.gradle file in Android Studio
2. Open “Upgrade Assistant” and click upgrade
### Troubleshooting Android updates
* If after upgrading it fails to build, try upgrading to a slightly less new version
* If there's an "Unknown class version exception <VERSION NUMBER>" try setting the version of Java used by modifying JAVA_HOME
* If it still fails to build, check that the Flutter code referenced in the build is up-to-date and doesn't use discontinued plugins. (Common cases include “namespace”)
* The 'project structure' -> 'modules' view can be helpful in understanding the dependency tree.
* Once the app builds with the latest gradle/agp, update any deprecated usages in app/build.gradle
* compileSdkVersion -> sdkVersion
* Update the target sdk version and read through each target sdk update
* Export broadcast receivers
* Update the way flutter is imported to use flutter gradle plugin
* https://flutter.dev/go/flutter-gradle-plugin-apply
* **When updating an app that uses AAR as a Flutter module** -- In android studio update any android dependencies that are indicated as in yellow as old. Keep `androidx.test:runner`, `androidx.test.espresso:espresso-core`, and `androidx.test:core`, as defined in https://github.com/flutter/packages/tree/main/packages/espresso

View File

@@ -0,0 +1,31 @@
# android_view
An example of an Android app that integrates a Flutter add-to-app module at a
view level.
## tl;dr
If you're just looking to get up and running quickly, these bash commands will
fetch packages and set up dependencies (note that the above commands assume
you're building for both iOS and Android, with both toolchains installed):
```bash
#!/bin/bash
set -e
cd flutter_module_using_plugin/
flutter pub get
# For Android builds:
open -a "Android Studio" ../android_view # macOS only
# Or open the ../android_fullscreen folder in Android Studio for other platforms.
```
## Getting Started
For more information about Flutter, check out
[flutter.dev](https://flutter.dev).
For instructions on how to integrate Flutter modules into your existing
applications, see Flutter's
[add-to-app documentation](https://flutter.dev/docs/development/add-to-app).

View File

@@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

View File

@@ -0,0 +1,4 @@
# android_view
An example of an Android app that integrates a Flutter add-to-app module at a
view level. For more information see [../README.md](../README.md).

View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,58 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
compileSdk 36
lint {
baseline = file("lint-baseline.xml")
}
defaultConfig {
applicationId "dev.flutter.example.androidView"
minSdkVersion 24
targetSdk 36
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding true
}
namespace 'dev.flutter.example.androidView'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.vectordrawable:vectordrawable:1.2.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.4'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7'
implementation 'androidx.navigation:navigation-ui-ktx:2.7.7'
implementation "androidx.recyclerview:recyclerview:1.3.2"
implementation project(path: ':flutter')
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
}

View File

@@ -0,0 +1,279 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 7.4.2" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.2)" variant="all" version="7.4.2">
<issue
id="RedundantLabel"
message="Redundant label can be removed"
errorLine1=" android:label=&quot;@string/app_name&quot;>"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/AndroidManifest.xml"
line="14"
column="13"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of org.jetbrains.kotlin:kotlin-stdlib than 1.8.22 is available: 1.9.20"
errorLine1="}"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="39"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.core:core-ktx than 1.3.2 is available: 1.13.1"
errorLine1=""
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="40"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.appcompat:appcompat than 1.2.0 is available: 1.7.0"
errorLine1="dependencies {"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="41"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of com.google.android.material:material than 1.2.1 is available: 1.12.0"
errorLine1=" implementation &quot;org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="42"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.constraintlayout:constraintlayout than 2.0.4 is available: 2.1.4"
errorLine1=" implementation &apos;androidx.core:core-ktx:1.3.2&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="43"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.vectordrawable:vectordrawable than 1.1.0 is available: 1.2.0"
errorLine1=" implementation &apos;androidx.appcompat:appcompat:1.2.0&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="44"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.lifecycle:lifecycle-livedata-ktx than 2.2.0 is available: 2.8.2"
errorLine1=" implementation &apos;com.google.android.material:material:1.2.1&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="45"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.lifecycle:lifecycle-viewmodel-ktx than 2.2.0 is available: 2.8.2"
errorLine1=" implementation &apos;androidx.constraintlayout:constraintlayout:2.0.4&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="46"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.navigation:navigation-fragment-ktx than 2.3.2 is available: 2.7.7"
errorLine1=" implementation &apos;androidx.vectordrawable:vectordrawable:1.1.0&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="47"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.navigation:navigation-ui-ktx than 2.3.2 is available: 2.7.7"
errorLine1=" implementation &apos;androidx.lifecycle:lifecycle-livedata-ktx:2.2.0&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="48"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.recyclerview:recyclerview than 1.1.0 is available: 1.3.2"
errorLine1=" implementation &apos;androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="49"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.test.ext:junit than 1.1.2 is available: 1.1.5"
errorLine1=" implementation &quot;androidx.recyclerview:recyclerview:1.1.0&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="52"
column="31"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.test.espresso:espresso-core than 3.3.0 is available: 3.5.1"
errorLine1=" implementation project(path: &apos;:flutter&apos;)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="53"
column="31"/>
</issue>
<issue
id="GradleDynamicVersion"
message="Avoid using + in version numbers; can lead to unpredictable and unrepeatable builds (junit:junit:4.+)"
errorLine1=" implementation &apos;androidx.navigation:navigation-ui-ktx:2.3.2&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="51"
column="24"/>
</issue>
<issue
id="ExpiredTargetSdkVersion"
message="Google Play requires that apps target API level 31 or higher.&#xA;"
errorLine1=" defaultConfig {"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="12"
column="9"/>
</issue>
<issue
id="ObsoleteSdkInt"
message="Unnecessary; SDK_INT is always >= 21"
errorLine1=" &lt;item name=&quot;android:statusBarColor&quot; tools:targetApi=&quot;l&quot;>?attr/colorPrimaryVariant&lt;/item>"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values-night/themes.xml"
line="13"
column="45"/>
</issue>
<issue
id="ObsoleteSdkInt"
message="Unnecessary; SDK_INT is always >= 21"
errorLine1=" &lt;item name=&quot;android:statusBarColor&quot; tools:targetApi=&quot;l&quot;>?attr/colorPrimaryVariant&lt;/item>"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/themes.xml"
line="13"
column="45"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.dimen.activity_horizontal_margin` appears to be unused"
errorLine1=" &lt;dimen name=&quot;activity_horizontal_margin&quot;>16dp&lt;/dimen>"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/dimens.xml"
line="3"
column="12"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.dimen.activity_vertical_margin` appears to be unused"
errorLine1=" &lt;dimen name=&quot;activity_vertical_margin&quot;>16dp&lt;/dimen>"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/dimens.xml"
line="4"
column="12"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.drawable.ic_dashboard_black_24dp` appears to be unused"
errorLine1="&lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
errorLine2="^">
<location
file="src/main/res/drawable/ic_dashboard_black_24dp.xml"
line="1"
column="1"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.drawable.ic_home_black_24dp` appears to be unused"
errorLine1="&lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
errorLine2="^">
<location
file="src/main/res/drawable/ic_home_black_24dp.xml"
line="1"
column="1"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.drawable.ic_notifications_black_24dp` appears to be unused"
errorLine1="&lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
errorLine2="^">
<location
file="src/main/res/drawable/ic_notifications_black_24dp.xml"
line="1"
column="1"/>
</issue>
<issue
id="MonochromeLauncherIcon"
message="The application adaptive icon is missing a monochrome tag"
errorLine1="&lt;adaptive-icon xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;>"
errorLine2="^">
<location
file="src/main/res/mipmap-anydpi-v26/ic_launcher.xml"
line="2"
column="1"/>
</issue>
<issue
id="MonochromeLauncherIcon"
message="The application adaptive roundIcon is missing a monochrome tag"
errorLine1="&lt;adaptive-icon xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;>"
errorLine2="^">
<location
file="src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml"
line="2"
column="1"/>
</issue>
</issues>

View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.FlutterViewIntegration">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,243 @@
// Copyright 2019 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.
package dev.flutter.example.androidView
import android.app.Activity
import android.content.Intent
import androidx.activity.ComponentActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import io.flutter.embedding.android.ExclusiveAppComponent
import io.flutter.embedding.android.FlutterView
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.platform.PlatformPlugin
/**
* This is an application-specific wrapper class that exists to expose the intersection of an
* application's active activity and an application's visible view to a [FlutterEngine] for
* rendering.
*
* Omitted features from the [io.flutter.embedding.android.FlutterActivity] include:
* * **State restoration**. If you're integrating at the view level, you should handle activity
* state restoration yourself.
* * **Engine creations**. At this level of granularity, you must make an engine and attach.
* and all engine features like initial route etc must be configured on the engine yourself.
* * **Splash screens**. You must implement it yourself. Read from
* `addOnFirstFrameRenderedListener` as needed.
* * **Transparency, surface/texture**. These are just [FlutterView] level APIs. Set them on the
* [FlutterView] directly.
* * **Intents**. This doesn't do any translation of intents into actions in the [FlutterEngine].
* you must do them yourself.
* * **Back buttons**. You must decide whether to send it to Flutter via
* [FlutterEngine.getNavigationChannel.popRoute()], or consume it natively. Though that
* decision may be difficult due to https://github.com/flutter/flutter/issues/67011.
* * **Low memory signals**. You're strongly encouraged to pass the low memory signals (such
* as from the host `Activity`'s `onTrimMemory` callbacks) to the [FlutterEngine] to let
* Flutter and the Dart VM cull its own memory usage.
*
* Your own [FlutterView] integrating application may need a similar wrapper but you must decide on
* what the appropriate intersection between the [FlutterView], the [FlutterEngine] and your
* `Activity` should be for your own application.
*/
class FlutterViewEngine(val engine: FlutterEngine) : LifecycleObserver, ExclusiveAppComponent<Activity>{
private var flutterView: FlutterView? = null
private var activity: ComponentActivity? = null
private var platformPlugin: PlatformPlugin? = null
/**
* This is the intersection of an available activity and of a visible [FlutterView]. This is
* where Flutter would start rendering.
*/
private fun hookActivityAndView() {
// Assert state.
activity!!.let { activity ->
flutterView!!.let { flutterView ->
platformPlugin = PlatformPlugin(activity, engine.platformChannel)
engine.activityControlSurface.attachToActivity(this, activity.lifecycle)
flutterView.attachToFlutterEngine(engine)
activity.lifecycle.addObserver(this)
}
}
}
/**
* Lost the intersection of either an available activity or a visible
* [FlutterView].
*/
private fun unhookActivityAndView() {
// Stop reacting to activity events.
activity!!.lifecycle.removeObserver(this)
// Plugins are no longer attached to an activity.
engine.activityControlSurface.detachFromActivity()
// Release Flutter's control of UI such as system chrome.
platformPlugin!!.destroy()
platformPlugin = null
// Set Flutter's application state to detached.
engine.lifecycleChannel.appIsDetached();
// Detach rendering pipeline.
flutterView!!.detachFromFlutterEngine()
}
/**
* Signal that a host `Activity` is now ready. If there is no [FlutterView] instance currently
* attached to the view hierarchy and visible, Flutter is not yet rendering.
*
* You can also choose at this point whether to notify the plugins that an `Activity` is
* attached or not. You can also choose at this point whether to connect a Flutter
* [PlatformPlugin] at this point which allows your Dart program to trigger things like
* haptic feedback and read the clipboard. This sample arbitrarily chooses no for both.
*/
fun attachToActivity(activity: ComponentActivity) {
this.activity = activity
if (flutterView != null) {
hookActivityAndView()
}
}
/**
* Signal that a host `Activity` now no longer connected. If there were a [FlutterView] in
* the view hierarchy and visible at this moment, that [FlutterView] will stop rendering.
*
* You can also choose at this point whether to notify the plugins that an `Activity` is
* no longer attached or not. You can also choose at this point whether to disconnect Flutter's
* [PlatformPlugin] at this point which stops your Dart program being able to trigger things
* like haptic feedback and read the clipboard. This sample arbitrarily chooses yes for both.
*/
fun detachActivity() {
if (flutterView != null) {
unhookActivityAndView()
}
activity = null
}
/**
* Signal that a [FlutterView] instance is created and attached to a visible Android view
* hierarchy.
*
* If an `Activity` was also previously provided, this puts Flutter into the rendering state
* for this [FlutterView]. This also connects this wrapper class to listen to the `Activity`'s
* lifecycle to pause rendering when the activity is put into the background while the
* view is still attached to the view hierarchy.
*/
fun attachFlutterView(flutterView: FlutterView) {
this.flutterView = flutterView
if (activity != null) {
hookActivityAndView()
}
}
/**
* Signal that the attached [FlutterView] instance destroyed or no longer attached to a visible
* Android view hierarchy.
*
* If an `Activity` was attached, this stops Flutter from rendering. It also makes this wrapper
* class stop listening to the `Activity`'s lifecycle since it's no longer rendering.
*/
fun detachFlutterView() {
unhookActivityAndView()
flutterView = null
}
/**
* Callback to let Flutter respond to the `Activity`'s resumed lifecycle event while both an
* `Activity` and a [FlutterView] are attached.
*/
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
private fun resumeActivity() {
if (activity != null) {
engine.lifecycleChannel.appIsResumed()
}
platformPlugin?.updateSystemUiOverlays()
}
/**
* Callback to let Flutter respond to the `Activity`'s paused lifecycle event while both an
* `Activity` and a [FlutterView] are attached.
*/
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
private fun pauseActivity() {
if (activity != null) {
engine.lifecycleChannel.appIsInactive()
}
}
/**
* Callback to let Flutter respond to the `Activity`'s stopped lifecycle event while both an
* `Activity` and a [FlutterView] are attached.
*/
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
private fun stopActivity() {
if (activity != null) {
engine.lifecycleChannel.appIsPaused()
}
}
// These events aren't used but would be needed for Flutter plugins consuming
// these events to function.
/**
* Pass through the `Activity`'s `onRequestPermissionsResult` signal to plugins that may be
* listening to it while the `Activity` and the [FlutterView] are connected.
*/
fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (activity != null && flutterView != null) {
engine
.activityControlSurface
.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
/**
* Pass through the `Activity`'s `onActivityResult` signal to plugins that may be
* listening to it while the `Activity` and the [FlutterView] are connected.
*/
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (activity != null && flutterView != null) {
engine.activityControlSurface.onActivityResult(requestCode, resultCode, data);
}
}
/**
* Pass through the `Activity`'s `onUserLeaveHint` signal to plugins that may be
* listening to it while the `Activity` and the [FlutterView] are connected.
*/
fun onUserLeaveHint() {
if (activity != null && flutterView != null) {
engine.activityControlSurface.onUserLeaveHint();
}
}
/**
* Called when another App Component is about to become attached to the [ ] this App Component
* is currently attached to.
*
*
* This App Component's connections to the [io.flutter.embedding.engine.FlutterEngine]
* are still valid at the moment of this call.
*/
override fun detachFromFlutterEngine() {
// Do nothing here
}
/**
* Retrieve the App Component behind this exclusive App Component.
*
* @return The app component.
*/
override fun getAppComponent(): Activity {
return activity!!;
}
}

View File

@@ -0,0 +1,112 @@
// Copyright 2019 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.
package dev.flutter.example.androidView
import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import dev.flutter.example.androidView.databinding.AndroidCardBinding
import io.flutter.embedding.android.FlutterView
import io.flutter.plugin.common.MethodChannel
import java.util.*
import kotlin.random.Random
/**
* A demo-specific implementation of a [RecyclerView.Adapter] to setup the demo environment used
* to display view-level Flutter cells inside a list.
*
* The only instructional parts of this class are to show when to call
* [FlutterViewEngine.attachFlutterView] and [FlutterViewEngine.detachActivity] on a
* [FlutterViewEngine] equivalent class that you may want to create in your own application.
*/
class ListAdapter(context: Context, private val flutterViewEngine: FlutterViewEngine) : RecyclerView.Adapter<ListAdapter.Cell>() {
// Save the previous cells determined to be Flutter cells to avoid a confusing visual effect
// that the Flutter cells change position when scrolling back.
var previousFlutterCells = TreeSet<Int>();
private val matchParentLayout = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
private val random = Random.Default
private val flutterView = FlutterView(context)
private val flutterChannel = MethodChannel(flutterViewEngine.engine.dartExecutor, "dev.flutter.example/cell")
private var flutterCell: Cell? = null
/**
* A [RecyclerView.ViewHolder] based on the `android_card` layout XML.
*/
inner class Cell(val binding: AndroidCardBinding) : RecyclerView.ViewHolder(binding.root) {
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): Cell {
val binding = AndroidCardBinding.inflate(LayoutInflater.from(viewGroup.context), viewGroup, false)
// Let the default view holder have an "Android card" inflated from the layout XML. When
// needed, hide the Android card and show a Flutter one instead.
return Cell(binding)
}
override fun onBindViewHolder(cell: Cell, position: Int) {
// While scrolling forward, if no Flutter is presently showing, let the next one have a 1/3
// chance of being Flutter.
//
// While scrolling backward, let it be deterministic, and only show cells that were
// previously Flutter cells as Flutter cells.
if (previousFlutterCells.contains(position)
|| ((previousFlutterCells.isEmpty() || position > previousFlutterCells.last())
&& flutterCell == null
&& random.nextInt(3) == 0)) {
// If we're restoring a cell at a previous location, the current cell may not have
// recycled yet since that JVM timing is indeterministic. Yank it from the current one.
//
// This shouldn't produce any visual glitches since in the forward direction,
// Flutter cells were only introduced once the previous Flutter cell recycled.
if (flutterCell != null) {
Log.w("FeedAdapter", "While restoring a previous Flutter cell, a current "
+ "yet to be recycled Flutter cell was detached.")
flutterCell!!.binding.root.removeView(flutterView)
flutterViewEngine.detachFlutterView()
flutterCell = null
}
// Add the Flutter card and hide the Android card for the cells chosen to be Flutter
// cells.
cell.binding.root.addView(flutterView, matchParentLayout)
cell.binding.androidCard.visibility = View.GONE
// Keep track of the cell so we know which one to restore back to the "Android cell"
// state when the view gets recycled.
flutterCell = cell
// Keep track that this position has once been a Flutter cell. Let it be a Flutter cell
// again when scrolling back to this position.
previousFlutterCells.add(position)
// This is what makes the Flutter cell start rendering.
flutterViewEngine.attachFlutterView(flutterView)
// Tell Flutter which index it's at so Flutter could show the cell number too in its
// own widget tree.
flutterChannel.invokeMethod("setCellNumber", position)
} else {
// If it's not selected as a Flutter cell, just show the Android card.
cell.binding.androidCard.visibility = View.VISIBLE
cell.binding.cellNumber.text = position.toString();
}
}
override fun getItemCount() = 100
override fun onViewRecycled(cell: Cell) {
if (cell == flutterCell) {
cell.binding.root.removeView(flutterView)
flutterViewEngine.detachFlutterView()
flutterCell = null
}
super.onViewRecycled(cell)
}
}

View File

@@ -0,0 +1,118 @@
// Copyright 2019 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.
package dev.flutter.example.androidView
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.BundleCompat
import androidx.recyclerview.widget.LinearLayoutManager
import dev.flutter.example.androidView.databinding.ActivityMainBinding
import io.flutter.FlutterInjector
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor
import java.util.*
import kotlin.collections.ArrayList
// There are 3 files in this sample. MainActivity and ListAdapter are just
// fictional setups. FlutterViewEngine is instructional and demonstrates the
// various plumbing needed for a functioning FlutterView integration.
/**
* Main activity for this demo that shows a page with a `RecyclerView`.
*
* There are 3 files in this sample. MainActivity and ListAdapter are just fictional setups.
* FlutterViewEngine is instructional and demonstrates the various plumbing needed for a functioning
* FlutterView integration.
*/
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var flutterViewEngine: FlutterViewEngine
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// TODO: create a multi-engine version after
// https://github.com/flutter/flutter/issues/72009 is built.
val engine = FlutterEngine(applicationContext)
engine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(),
"showCell"))
flutterViewEngine = FlutterViewEngine(engine)
// The activity and FlutterView have different lifecycles.
// Attach the activity right away but only start rendering when the
// view is also scrolled into the screen.
flutterViewEngine.attachToActivity(this)
val layoutManager = LinearLayoutManager(this)
val recyclerView = binding.recyclerView
val adapter = ListAdapter(this, flutterViewEngine)
recyclerView.layoutManager = layoutManager
recyclerView.adapter = adapter
// If the activity was restarted, keep track of the previous scroll
// position and of the previous cell indices that were randomly selected
// as Flutter cells to preserve immersion.
if (savedInstanceState != null) {
val state = BundleCompat.getParcelable<Parcelable>(
savedInstanceState,
"layoutManager",
Parcelable::class.java
)
layoutManager.onRestoreInstanceState(state)
}
val previousFlutterCellsArray = savedInstanceState?.getIntegerArrayList("adapter")
if (previousFlutterCellsArray != null) {
adapter.previousFlutterCells = TreeSet(previousFlutterCellsArray)
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelable("layoutManager", binding.recyclerView.layoutManager?.onSaveInstanceState())
val previousFlutterCells = (binding.recyclerView.adapter as? ListAdapter)?.previousFlutterCells
if (previousFlutterCells != null) {
outState.putIntegerArrayList(
"adapter",
ArrayList(previousFlutterCells)
)
}
}
override fun onDestroy() {
super.onDestroy()
flutterViewEngine.detachActivity()
}
// These below aren't used here in this demo but would be needed for Flutter plugins that may
// consume these events.
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
flutterViewEngine.onRequestPermissionsResult(requestCode, permissions, grantResults)
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
flutterViewEngine.onActivityResult(requestCode, resultCode, data)
super.onActivityResult(requestCode, resultCode, data)
}
override fun onUserLeaveHint() {
flutterViewEngine.onUserLeaveHint()
super.onUserLeaveHint()
}
}

View File

@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M3,13h8L11,3L3,3v10zM3,21h8v-6L3,15v6zM13,21h8L21,11h-8v10zM13,3v6h8L21,3h-8z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z" />
</vector>

View File

@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z" />
</vector>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="160dp">
<com.google.android.material.card.MaterialCardView
android:id="@+id/androidCard"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginHorizontal="36dp"
android:layout_marginVertical="24dp"
app:cardBackgroundColor="#F1F1F1"
app:cardCornerRadius="16dp"
app:cardElevation="16dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/cellNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAppearance="?attr/textAppearanceHeadline3"/>
</FrameLayout>
</com.google.android.material.card.MaterialCardView>
</FrameLayout>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.FlutterViewIntegration" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@@ -0,0 +1,5 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Flutter View Integration</string>
</resources>

View File

@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.FlutterViewIntegration" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,26 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.8.22'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.9.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
tasks.register('clean', Delete) {
delete rootProject.buildDir
}

View File

@@ -0,0 +1,21 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
android.nonTransitiveRClass=false
android.nonFinalResIds=false

View File

@@ -0,0 +1,6 @@
#Fri Jul 26 11:31:21 EDT 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

172
add_to_app/android_view/android_view/gradlew vendored Executable file
View File

@@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

View File

@@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1,7 @@
rootProject.name = "Flutter View Integration"
include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir.parentFile,
'flutter_module_using_plugin_android_view/.android/include_flutter.groovy'
))

View File

@@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

View File

@@ -0,0 +1,4 @@
# android_view
An example of an Android app that integrates a Flutter add-to-app module at a
view level. For more information see [../README.md](../README.md).

View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,58 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
compileSdk 36
lint {
baseline = file("lint-baseline.xml")
}
defaultConfig {
applicationId "dev.flutter.example.androidView"
minSdkVersion 24
targetSdk 36
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding true
}
namespace 'dev.flutter.example.androidView'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.vectordrawable:vectordrawable:1.2.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.4'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7'
implementation 'androidx.navigation:navigation-ui-ktx:2.7.7'
implementation "androidx.recyclerview:recyclerview:1.3.2"
implementation project(path: ':flutter')
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
}

View File

@@ -0,0 +1,279 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 7.4.2" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.2)" variant="all" version="7.4.2">
<issue
id="RedundantLabel"
message="Redundant label can be removed"
errorLine1=" android:label=&quot;@string/app_name&quot;>"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/AndroidManifest.xml"
line="14"
column="13"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of org.jetbrains.kotlin:kotlin-stdlib than 1.8.22 is available: 1.9.20"
errorLine1="}"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="39"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.core:core-ktx than 1.3.2 is available: 1.13.1"
errorLine1=""
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="40"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.appcompat:appcompat than 1.2.0 is available: 1.7.0"
errorLine1="dependencies {"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="41"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of com.google.android.material:material than 1.2.1 is available: 1.12.0"
errorLine1=" implementation &quot;org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="42"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.constraintlayout:constraintlayout than 2.0.4 is available: 2.1.4"
errorLine1=" implementation &apos;androidx.core:core-ktx:1.3.2&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="43"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.vectordrawable:vectordrawable than 1.1.0 is available: 1.2.0"
errorLine1=" implementation &apos;androidx.appcompat:appcompat:1.2.0&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="44"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.lifecycle:lifecycle-livedata-ktx than 2.2.0 is available: 2.8.2"
errorLine1=" implementation &apos;com.google.android.material:material:1.2.1&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="45"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.lifecycle:lifecycle-viewmodel-ktx than 2.2.0 is available: 2.8.2"
errorLine1=" implementation &apos;androidx.constraintlayout:constraintlayout:2.0.4&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="46"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.navigation:navigation-fragment-ktx than 2.3.2 is available: 2.7.7"
errorLine1=" implementation &apos;androidx.vectordrawable:vectordrawable:1.1.0&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="47"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.navigation:navigation-ui-ktx than 2.3.2 is available: 2.7.7"
errorLine1=" implementation &apos;androidx.lifecycle:lifecycle-livedata-ktx:2.2.0&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="48"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.recyclerview:recyclerview than 1.1.0 is available: 1.3.2"
errorLine1=" implementation &apos;androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="49"
column="20"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.test.ext:junit than 1.1.2 is available: 1.1.5"
errorLine1=" implementation &quot;androidx.recyclerview:recyclerview:1.1.0&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="52"
column="31"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.test.espresso:espresso-core than 3.3.0 is available: 3.5.1"
errorLine1=" implementation project(path: &apos;:flutter&apos;)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="53"
column="31"/>
</issue>
<issue
id="GradleDynamicVersion"
message="Avoid using + in version numbers; can lead to unpredictable and unrepeatable builds (junit:junit:4.+)"
errorLine1=" implementation &apos;androidx.navigation:navigation-ui-ktx:2.3.2&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="51"
column="24"/>
</issue>
<issue
id="ExpiredTargetSdkVersion"
message="Google Play requires that apps target API level 31 or higher.&#xA;"
errorLine1=" defaultConfig {"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="12"
column="9"/>
</issue>
<issue
id="ObsoleteSdkInt"
message="Unnecessary; SDK_INT is always >= 21"
errorLine1=" &lt;item name=&quot;android:statusBarColor&quot; tools:targetApi=&quot;l&quot;>?attr/colorPrimaryVariant&lt;/item>"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values-night/themes.xml"
line="13"
column="45"/>
</issue>
<issue
id="ObsoleteSdkInt"
message="Unnecessary; SDK_INT is always >= 21"
errorLine1=" &lt;item name=&quot;android:statusBarColor&quot; tools:targetApi=&quot;l&quot;>?attr/colorPrimaryVariant&lt;/item>"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/themes.xml"
line="13"
column="45"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.dimen.activity_horizontal_margin` appears to be unused"
errorLine1=" &lt;dimen name=&quot;activity_horizontal_margin&quot;>16dp&lt;/dimen>"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/dimens.xml"
line="3"
column="12"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.dimen.activity_vertical_margin` appears to be unused"
errorLine1=" &lt;dimen name=&quot;activity_vertical_margin&quot;>16dp&lt;/dimen>"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/dimens.xml"
line="4"
column="12"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.drawable.ic_dashboard_black_24dp` appears to be unused"
errorLine1="&lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
errorLine2="^">
<location
file="src/main/res/drawable/ic_dashboard_black_24dp.xml"
line="1"
column="1"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.drawable.ic_home_black_24dp` appears to be unused"
errorLine1="&lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
errorLine2="^">
<location
file="src/main/res/drawable/ic_home_black_24dp.xml"
line="1"
column="1"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.drawable.ic_notifications_black_24dp` appears to be unused"
errorLine1="&lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
errorLine2="^">
<location
file="src/main/res/drawable/ic_notifications_black_24dp.xml"
line="1"
column="1"/>
</issue>
<issue
id="MonochromeLauncherIcon"
message="The application adaptive icon is missing a monochrome tag"
errorLine1="&lt;adaptive-icon xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;>"
errorLine2="^">
<location
file="src/main/res/mipmap-anydpi-v26/ic_launcher.xml"
line="2"
column="1"/>
</issue>
<issue
id="MonochromeLauncherIcon"
message="The application adaptive roundIcon is missing a monochrome tag"
errorLine1="&lt;adaptive-icon xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;>"
errorLine2="^">
<location
file="src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml"
line="2"
column="1"/>
</issue>
</issues>

View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.FlutterViewIntegration">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data
android:name="io.flutter.embedding.android.EnableContentSizing"
android:value="true" />
</application>
</manifest>

View File

@@ -0,0 +1,243 @@
// Copyright 2019 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.
package dev.flutter.example.androidView
import android.app.Activity
import android.content.Intent
import androidx.activity.ComponentActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import io.flutter.embedding.android.ExclusiveAppComponent
import io.flutter.embedding.android.FlutterView
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.platform.PlatformPlugin
/**
* This is an application-specific wrapper class that exists to expose the intersection of an
* application's active activity and an application's visible view to a [FlutterEngine] for
* rendering.
*
* Omitted features from the [io.flutter.embedding.android.FlutterActivity] include:
* * **State restoration**. If you're integrating at the view level, you should handle activity
* state restoration yourself.
* * **Engine creations**. At this level of granularity, you must make an engine and attach.
* and all engine features like initial route etc must be configured on the engine yourself.
* * **Splash screens**. You must implement it yourself. Read from
* `addOnFirstFrameRenderedListener` as needed.
* * **Transparency, surface/texture**. These are just [FlutterView] level APIs. Set them on the
* [FlutterView] directly.
* * **Intents**. This doesn't do any translation of intents into actions in the [FlutterEngine].
* you must do them yourself.
* * **Back buttons**. You must decide whether to send it to Flutter via
* [FlutterEngine.getNavigationChannel.popRoute()], or consume it natively. Though that
* decision may be difficult due to https://github.com/flutter/flutter/issues/67011.
* * **Low memory signals**. You're strongly encouraged to pass the low memory signals (such
* as from the host `Activity`'s `onTrimMemory` callbacks) to the [FlutterEngine] to let
* Flutter and the Dart VM cull its own memory usage.
*
* Your own [FlutterView] integrating application may need a similar wrapper but you must decide on
* what the appropriate intersection between the [FlutterView], the [FlutterEngine] and your
* `Activity` should be for your own application.
*/
class FlutterViewEngine(val engine: FlutterEngine) : LifecycleObserver, ExclusiveAppComponent<Activity>{
private var flutterView: FlutterView? = null
private var activity: ComponentActivity? = null
private var platformPlugin: PlatformPlugin? = null
/**
* This is the intersection of an available activity and of a visible [FlutterView]. This is
* where Flutter would start rendering.
*/
private fun hookActivityAndView() {
// Assert state.
activity!!.let { activity ->
flutterView!!.let { flutterView ->
platformPlugin = PlatformPlugin(activity, engine.platformChannel)
engine.activityControlSurface.attachToActivity(this, activity.lifecycle)
flutterView.attachToFlutterEngine(engine)
activity.lifecycle.addObserver(this)
}
}
}
/**
* Lost the intersection of either an available activity or a visible
* [FlutterView].
*/
private fun unhookActivityAndView() {
// Stop reacting to activity events.
activity!!.lifecycle.removeObserver(this)
// Plugins are no longer attached to an activity.
engine.activityControlSurface.detachFromActivity()
// Release Flutter's control of UI such as system chrome.
platformPlugin!!.destroy()
platformPlugin = null
// Set Flutter's application state to detached.
engine.lifecycleChannel.appIsDetached();
// Detach rendering pipeline.
flutterView!!.detachFromFlutterEngine()
}
/**
* Signal that a host `Activity` is now ready. If there is no [FlutterView] instance currently
* attached to the view hierarchy and visible, Flutter is not yet rendering.
*
* You can also choose at this point whether to notify the plugins that an `Activity` is
* attached or not. You can also choose at this point whether to connect a Flutter
* [PlatformPlugin] at this point which allows your Dart program to trigger things like
* haptic feedback and read the clipboard. This sample arbitrarily chooses no for both.
*/
fun attachToActivity(activity: ComponentActivity) {
this.activity = activity
if (flutterView != null) {
hookActivityAndView()
}
}
/**
* Signal that a host `Activity` now no longer connected. If there were a [FlutterView] in
* the view hierarchy and visible at this moment, that [FlutterView] will stop rendering.
*
* You can also choose at this point whether to notify the plugins that an `Activity` is
* no longer attached or not. You can also choose at this point whether to disconnect Flutter's
* [PlatformPlugin] at this point which stops your Dart program being able to trigger things
* like haptic feedback and read the clipboard. This sample arbitrarily chooses yes for both.
*/
fun detachActivity() {
if (flutterView != null) {
unhookActivityAndView()
}
activity = null
}
/**
* Signal that a [FlutterView] instance is created and attached to a visible Android view
* hierarchy.
*
* If an `Activity` was also previously provided, this puts Flutter into the rendering state
* for this [FlutterView]. This also connects this wrapper class to listen to the `Activity`'s
* lifecycle to pause rendering when the activity is put into the background while the
* view is still attached to the view hierarchy.
*/
fun attachFlutterView(flutterView: FlutterView) {
this.flutterView = flutterView
if (activity != null) {
hookActivityAndView()
}
}
/**
* Signal that the attached [FlutterView] instance destroyed or no longer attached to a visible
* Android view hierarchy.
*
* If an `Activity` was attached, this stops Flutter from rendering. It also makes this wrapper
* class stop listening to the `Activity`'s lifecycle since it's no longer rendering.
*/
fun detachFlutterView() {
unhookActivityAndView()
flutterView = null
}
/**
* Callback to let Flutter respond to the `Activity`'s resumed lifecycle event while both an
* `Activity` and a [FlutterView] are attached.
*/
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
private fun resumeActivity() {
if (activity != null) {
engine.lifecycleChannel.appIsResumed()
}
platformPlugin?.updateSystemUiOverlays()
}
/**
* Callback to let Flutter respond to the `Activity`'s paused lifecycle event while both an
* `Activity` and a [FlutterView] are attached.
*/
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
private fun pauseActivity() {
if (activity != null) {
engine.lifecycleChannel.appIsInactive()
}
}
/**
* Callback to let Flutter respond to the `Activity`'s stopped lifecycle event while both an
* `Activity` and a [FlutterView] are attached.
*/
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
private fun stopActivity() {
if (activity != null) {
engine.lifecycleChannel.appIsPaused()
}
}
// These events aren't used but would be needed for Flutter plugins consuming
// these events to function.
/**
* Pass through the `Activity`'s `onRequestPermissionsResult` signal to plugins that may be
* listening to it while the `Activity` and the [FlutterView] are connected.
*/
fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (activity != null && flutterView != null) {
engine
.activityControlSurface
.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
/**
* Pass through the `Activity`'s `onActivityResult` signal to plugins that may be
* listening to it while the `Activity` and the [FlutterView] are connected.
*/
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (activity != null && flutterView != null) {
engine.activityControlSurface.onActivityResult(requestCode, resultCode, data);
}
}
/**
* Pass through the `Activity`'s `onUserLeaveHint` signal to plugins that may be
* listening to it while the `Activity` and the [FlutterView] are connected.
*/
fun onUserLeaveHint() {
if (activity != null && flutterView != null) {
engine.activityControlSurface.onUserLeaveHint();
}
}
/**
* Called when another App Component is about to become attached to the [ ] this App Component
* is currently attached to.
*
*
* This App Component's connections to the [io.flutter.embedding.engine.FlutterEngine]
* are still valid at the moment of this call.
*/
override fun detachFromFlutterEngine() {
// Do nothing here
}
/**
* Retrieve the App Component behind this exclusive App Component.
*
* @return The app component.
*/
override fun getAppComponent(): Activity {
return activity!!;
}
}

View File

@@ -0,0 +1,106 @@
// Copyright 2019 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.
package dev.flutter.example.androidView
import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import dev.flutter.example.androidView.databinding.AndroidCardBinding
import io.flutter.embedding.android.FlutterView
import java.util.*
import kotlin.random.Random
/**
* A demo-specific implementation of a [RecyclerView.Adapter] to setup the demo environment used
* to display view-level Flutter cells inside a list.
*
* The only instructional parts of this class are to show when to call
* [FlutterViewEngine.attachFlutterView] and [FlutterViewEngine.detachActivity] on a
* [FlutterViewEngine] equivalent class that you may want to create in your own application.
*/
class ListAdapter(context: Context, private val flutterViewEngine: FlutterViewEngine) : RecyclerView.Adapter<ListAdapter.Cell>() {
// Save the previous cells determined to be Flutter cells to avoid a confusing visual effect
// that the Flutter cells change position when scrolling back.
var previousFlutterCells = TreeSet<Int>();
private val wrapContentLayout = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
private val random = Random.Default
private val flutterView = FlutterView(context)
private var flutterCell: Cell? = null
/**
* A [RecyclerView.ViewHolder] based on the `android_card` layout XML.
*/
inner class Cell(val binding: AndroidCardBinding) : RecyclerView.ViewHolder(binding.root) {
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): Cell {
val binding = AndroidCardBinding.inflate(LayoutInflater.from(viewGroup.context), viewGroup, false)
// Let the default view holder have an "Android card" inflated from the layout XML. When
// needed, hide the Android card and show a Flutter one instead.
return Cell(binding)
}
override fun onBindViewHolder(cell: Cell, position: Int) {
// While scrolling forward, if no Flutter is presently showing, let the next one have a 1/3
// chance of being Flutter.
//
// While scrolling backward, let it be deterministic, and only show cells that were
// previously Flutter cells as Flutter cells.
if (previousFlutterCells.contains(position)
|| ((previousFlutterCells.isEmpty() || position > previousFlutterCells.last())
&& flutterCell == null
&& random.nextInt(3) == 0)) {
// If we're restoring a cell at a previous location, the current cell may not have
// recycled yet since that JVM timing is indeterministic. Yank it from the current one.
//
// This shouldn't produce any visual glitches since in the forward direction,
// Flutter cells were only introduced once the previous Flutter cell recycled.
if (flutterCell != null) {
Log.w("FeedAdapter", "While restoring a previous Flutter cell, a current "
+ "yet to be recycled Flutter cell was detached.")
flutterCell!!.binding.root.removeView(flutterView)
flutterViewEngine.detachFlutterView()
flutterCell = null
}
// Add the Flutter card and hide the Android card for the cells chosen to be Flutter
// cells.
cell.binding.root.addView(flutterView, wrapContentLayout)
cell.binding.androidCard.visibility = View.GONE
// Keep track of the cell so we know which one to restore back to the "Android cell"
// state when the view gets recycled.
flutterCell = cell
// Keep track that this position has once been a Flutter cell. Let it be a Flutter cell
// again when scrolling back to this position.
previousFlutterCells.add(position)
// This is what makes the Flutter cell start rendering.
flutterViewEngine.attachFlutterView(flutterView)
} else {
// If it's not selected as a Flutter cell, just show the Android card.
cell.binding.androidCard.visibility = View.VISIBLE
cell.binding.cellNumber.text = position.toString();
}
}
override fun getItemCount() = 100
override fun onViewRecycled(cell: Cell) {
if (cell == flutterCell) {
cell.binding.root.removeView(flutterView)
flutterViewEngine.detachFlutterView()
flutterCell = null
}
super.onViewRecycled(cell)
}
}

View File

@@ -0,0 +1,118 @@
// Copyright 2019 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.
package dev.flutter.example.androidView
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.BundleCompat
import androidx.recyclerview.widget.LinearLayoutManager
import dev.flutter.example.androidView.databinding.ActivityMainBinding
import io.flutter.FlutterInjector
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor
import java.util.*
import kotlin.collections.ArrayList
// There are 3 files in this sample. MainActivity and ListAdapter are just
// fictional setups. FlutterViewEngine is instructional and demonstrates the
// various plumbing needed for a functioning FlutterView integration.
/**
* Main activity for this demo that shows a page with a `RecyclerView`.
*
* There are 3 files in this sample. MainActivity and ListAdapter are just fictional setups.
* FlutterViewEngine is instructional and demonstrates the various plumbing needed for a functioning
* FlutterView integration.
*/
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var flutterViewEngine: FlutterViewEngine
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// TODO: create a multi-engine version after
// https://github.com/flutter/flutter/issues/72009 is built.
val engine = FlutterEngine(applicationContext)
engine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(),
"main"))
flutterViewEngine = FlutterViewEngine(engine)
// The activity and FlutterView have different lifecycles.
// Attach the activity right away but only start rendering when the
// view is also scrolled into the screen.
flutterViewEngine.attachToActivity(this)
val layoutManager = LinearLayoutManager(this)
val recyclerView = binding.recyclerView
val adapter = ListAdapter(this, flutterViewEngine)
recyclerView.layoutManager = layoutManager
recyclerView.adapter = adapter
// If the activity was restarted, keep track of the previous scroll
// position and of the previous cell indices that were randomly selected
// as Flutter cells to preserve immersion.
if (savedInstanceState != null) {
val state = BundleCompat.getParcelable<Parcelable>(
savedInstanceState,
"layoutManager",
Parcelable::class.java
)
layoutManager.onRestoreInstanceState(state)
}
val previousFlutterCellsArray = savedInstanceState?.getIntegerArrayList("adapter")
if (previousFlutterCellsArray != null) {
adapter.previousFlutterCells = TreeSet(previousFlutterCellsArray)
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelable("layoutManager", binding.recyclerView.layoutManager?.onSaveInstanceState())
val previousFlutterCells = (binding.recyclerView.adapter as? ListAdapter)?.previousFlutterCells
if (previousFlutterCells != null) {
outState.putIntegerArrayList(
"adapter",
ArrayList(previousFlutterCells)
)
}
}
override fun onDestroy() {
super.onDestroy()
flutterViewEngine.detachActivity()
}
// These below aren't used here in this demo but would be needed for Flutter plugins that may
// consume these events.
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
flutterViewEngine.onRequestPermissionsResult(requestCode, permissions, grantResults)
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
flutterViewEngine.onActivityResult(requestCode, resultCode, data)
super.onActivityResult(requestCode, resultCode, data)
}
override fun onUserLeaveHint() {
flutterViewEngine.onUserLeaveHint()
super.onUserLeaveHint()
}
}

View File

@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M3,13h8L11,3L3,3v10zM3,21h8v-6L3,15v6zM13,21h8L21,11h-8v10zM13,3v6h8L21,3h-8z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z" />
</vector>

View File

@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z" />
</vector>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
android:id="@+id/androidCard"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginHorizontal="36dp"
android:layout_marginVertical="24dp"
app:cardBackgroundColor="#F1F1F1"
app:cardCornerRadius="16dp"
app:cardElevation="16dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/cellNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAppearance="?attr/textAppearanceHeadline3"/>
</FrameLayout>
</com.google.android.material.card.MaterialCardView>
</FrameLayout>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.FlutterViewIntegration" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@@ -0,0 +1,5 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Flutter View Content Sizing Integration</string>
</resources>

View File

@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.FlutterViewIntegration" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,26 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.8.22'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.9.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
tasks.register('clean', Delete) {
delete rootProject.buildDir
}

View File

@@ -0,0 +1,21 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
android.nonTransitiveRClass=false
android.nonFinalResIds=false

View File

@@ -0,0 +1,6 @@
#Fri Jul 26 11:31:21 EDT 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

View File

@@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1,7 @@
rootProject.name = "Flutter View Integration"
include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir.parentFile,
'flutter_module_using_plugin_content_sizing_android_view/.android/include_flutter.groovy'
))

View File

@@ -0,0 +1,42 @@
.DS_Store
.dart_tool/
.packages
.pub/
.idea/
.vagrant/
.sconsign.dblite
.svn/
*.swp
profile
DerivedData/
.generated/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
xcuserdata
*.moved-aside
*.pyc
*sync/
Icon?
.tags*
build/
.android/
.ios/
.flutter-plugins
.flutter-plugins-dependencies

View File

@@ -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: 532a8fed41a4f6595965f02f3edf9666ba5ebf44
channel: master
project_type: module

View File

@@ -0,0 +1,14 @@
# flutter_module_using_plugin
An example Flutter module that uses a native plugin, intended for use in the
Flutter add-to-app samples. For more information on how to use it, see the
[README.md](../README.md) parent directory.
## Getting Started
For more information about Flutter, check out
[flutter.dev](https://flutter.dev).
For instructions on how to integrate Flutter modules into your existing
applications, see Flutter's
[add-to-app documentation](https://flutter.dev/docs/development/add-to-app).

View File

@@ -0,0 +1 @@
include: ../../../analysis_options.yaml

View File

@@ -0,0 +1,154 @@
// Copyright 2019 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 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:sensors_plus/sensors_plus.dart';
// This is on alternate entrypoint for this module to display Flutter UI in
// a (multi-)view integration scenario.
void main() {
runApp(const Cell());
}
class Cell extends StatefulWidget {
const Cell({super.key});
@override
State<StatefulWidget> createState() => _CellState();
}
class _CellState extends State<Cell> with WidgetsBindingObserver {
static const double gravity = 9.81;
static final AccelerometerEvent defaultPosition = AccelerometerEvent(
0,
0,
0,
DateTime.now(),
);
int cellNumber = 0;
Random? _random;
AppLifecycleState? appLifecycleState;
@override
void initState() {
const channel = MethodChannel('dev.flutter.example/cell');
channel.setMethodCallHandler((call) async {
if (call.method == 'setCellNumber') {
setState(() {
cellNumber = call.arguments as int;
_random = Random(cellNumber);
});
}
});
// Keep track of what the current platform lifecycle state is.
WidgetsBinding.instance.addObserver(this);
super.initState();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
setState(() {
appLifecycleState = state;
});
}
// Show a random bright color.
Color randomLightColor() {
_random ??= Random(cellNumber);
return Color.fromARGB(
255,
_random!.nextInt(50) + 205,
_random!.nextInt(50) + 205,
_random!.nextInt(50) + 205,
);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
// The Flutter cells will be noticeably different (due to background color
// and the Flutter logo). The banner breaks immersion.
debugShowCheckedModeBanner: false,
home: Container(
color: Colors.white,
child: Builder(
builder: (context) {
return Card(
// Mimic the platform Material look.
margin: const EdgeInsets.symmetric(
horizontal: 36,
vertical: 24,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
elevation: 16,
color: randomLightColor(),
child: Stack(
children: [
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
// Show a number provided by the platform based on
// the cell's index.
cellNumber.toString(),
style: Theme.of(context).textTheme.displaySmall,
),
],
),
),
Positioned(
left: 42,
top: 0,
bottom: 0,
child: Opacity(
opacity: 0.2,
child: StreamBuilder<AccelerometerEvent>(
// Don't continuously rebuild for nothing when the
// cell isn't visible.
stream: appLifecycleState == AppLifecycleState.resumed
? accelerometerEventStream()
: Stream.value(defaultPosition),
initialData: defaultPosition,
builder: (context, snapshot) {
return Transform(
// Figure out the phone's orientation relative
// to gravity's direction. Ignore the z vector.
transform:
Matrix4.rotationX(
snapshot.data!.y / gravity * pi / 2,
)..multiply(
Matrix4.rotationY(
snapshot.data!.x / gravity * pi / 2,
),
),
alignment: Alignment.center,
child: const FlutterLogo(size: 72),
);
},
),
),
),
],
),
);
},
),
),
);
}
}

View File

@@ -0,0 +1,180 @@
// Copyright 2019 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:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart' as launcher;
import 'cell.dart';
/// The entrypoint for the flutter module.
void main() {
// This call ensures the Flutter binding has been set up before creating the
// MethodChannel-based model.
WidgetsFlutterBinding.ensureInitialized();
final model = CounterModel();
runApp(ChangeNotifierProvider.value(value: model, child: const MyApp()));
}
/// This is on alternate entrypoint for this module to display Flutter UI in
/// a (multi-)view integration scenario.
// This is unfortunately in this file due to
// https://github.com/flutter/flutter/issues/72630.
@pragma("vm:entry-point")
void showCell() {
runApp(const Cell());
}
/// A simple model that uses a [MethodChannel] as the source of truth for the
/// state of a counter.
///
/// Rather than storing app state data within the Flutter module itself (where
/// the native portions of the app can't access it), this module passes messages
/// back to the containing app whenever it needs to increment or retrieve the
/// value of the counter.
class CounterModel extends ChangeNotifier {
CounterModel() {
_channel.setMethodCallHandler(_handleMessage);
_channel.invokeMethod<void>('requestCounter');
}
final _channel = const MethodChannel('dev.flutter.example/counter');
int _count = 0;
int get count => _count;
void increment() {
_channel.invokeMethod<void>('incrementCounter');
}
Future<dynamic> _handleMessage(MethodCall call) async {
if (call.method == 'reportCounter') {
_count = call.arguments as int;
notifyListeners();
}
}
}
/// The "app" displayed by this module.
///
/// It offers two routes, one suitable for displaying as a full screen and
/// another designed to be part of a larger UI.
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Module Title',
theme: ThemeData(colorSchemeSeed: Colors.blue),
routes: {
'/': (context) => const FullScreenView(),
'/mini': (context) => const Contents(),
},
);
}
}
/// Wraps [Contents] in a Material [Scaffold] so it looks correct when displayed
/// full-screen.
class FullScreenView extends StatelessWidget {
const FullScreenView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Full-screen Flutter with plugin')),
body: const Contents(showExit: true),
);
}
}
/// The actual content displayed by the module.
///
/// This widget displays info about the state of a counter and how much room (in
/// logical pixels) it's been given. It also offers buttons to increment the
/// counter, opening the Flutter documentation via the url_launcher plugin, and
/// (optionally) close the Flutter view.
class Contents extends StatelessWidget {
final bool showExit;
const Contents({this.showExit = false, super.key});
@override
Widget build(BuildContext context) {
final mediaInfo = MediaQuery.of(context);
return SizedBox.expand(
child: Stack(
children: [
Positioned.fill(
child: DecoratedBox(
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
),
),
),
const Positioned.fill(
child: Opacity(
opacity: .25,
child: FittedBox(fit: BoxFit.cover, child: FlutterLogo()),
),
),
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Window is ${mediaInfo.size.width.toStringAsFixed(1)} x '
'${mediaInfo.size.height.toStringAsFixed(1)}',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 16),
Consumer<CounterModel>(
builder: (context, model, child) {
return Text(
'Taps: ${model.count}',
style: Theme.of(context).textTheme.headlineSmall,
);
},
),
const SizedBox(height: 16),
Consumer<CounterModel>(
builder: (context, model, child) {
return FilledButton(
onPressed: () => model.increment(),
child: const Text('Tap me!'),
);
},
),
FilledButton(
onPressed: () async {
// Use the url_launcher plugin to open the Flutter docs in
// a browser.
final url = Uri.parse('https://flutter.dev/docs');
if (await launcher.canLaunchUrl(url)) {
await launcher.launchUrl(url);
}
},
child: const Text('Open Flutter Docs'),
),
if (showExit) ...[
const SizedBox(height: 16),
FilledButton(
onPressed: () => SystemNavigator.pop(),
child: const Text('Exit this screen'),
),
],
],
),
),
],
),
);
}
}

View File

@@ -0,0 +1,34 @@
name: flutter_module_using_plugin_android_view
description: An example Flutter module that uses a plugin.
version: 1.0.0+1
resolution: workspace
environment:
sdk: ^3.8.1-0
dependencies:
flutter:
sdk: flutter
provider: ^6.1.5
url_launcher: ^6.3.2
sensors_plus: ^6.1.1
dev_dependencies:
analysis_defaults:
path: ../../../analysis_defaults
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
# This section identifies your Flutter project as a module meant for
# embedding in a native host app. These identifiers should _not_ ordinarily
# be changed after generation - they are used to ensure that the tooling can
# maintain consistency when adding or modifying assets and plugins.
# They also do not have any bearing on your native host application's
# identifiers, which may be completely independent or the same as these.
module:
androidX: true
androidPackage: dev.flutter.example.flutter_module_using_plugin
iosBundleIdentifier: dev.flutter.example.flutterModuleUsingPlugin

View File

@@ -0,0 +1,50 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_module_using_plugin_android_view/main.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
class MockCounterModel extends ChangeNotifier implements CounterModel {
int _count = 0;
@override
int get count => _count;
@override
void increment() {
_count++;
notifyListeners();
}
}
void main() {
testWidgets('MiniView smoke test', (tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(
MaterialApp(
home: ChangeNotifierProvider<CounterModel>.value(
value: MockCounterModel(),
child: const Contents(),
),
),
);
// Verify that our counter starts at 0.
expect(find.text('Taps: 0'), findsOneWidget);
expect(find.text('Taps: 1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.text('Tap me!'));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('Taps: 0'), findsNothing);
expect(find.text('Taps: 1'), findsOneWidget);
});
}

View File

@@ -0,0 +1,42 @@
.DS_Store
.dart_tool/
.packages
.pub/
.idea/
.vagrant/
.sconsign.dblite
.svn/
*.swp
profile
DerivedData/
.generated/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
xcuserdata
*.moved-aside
*.pyc
*sync/
Icon?
.tags*
build/
.android/
.ios/
.flutter-plugins
.flutter-plugins-dependencies

View File

@@ -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: 532a8fed41a4f6595965f02f3edf9666ba5ebf44
channel: master
project_type: module

View File

@@ -0,0 +1,14 @@
# flutter_module_using_plugin
An example Flutter module that uses a native plugin, intended for use in the
Flutter add-to-app samples. For more information on how to use it, see the
[README.md](../README.md) parent directory.
## Getting Started
For more information about Flutter, check out
[flutter.dev](https://flutter.dev).
For instructions on how to integrate Flutter modules into your existing
applications, see Flutter's
[add-to-app documentation](https://flutter.dev/docs/development/add-to-app).

View File

@@ -0,0 +1 @@
include: ../../../analysis_options.yaml

View File

@@ -0,0 +1,56 @@
// Copyright 2014 The Flutter Authors. 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:flutter/material.dart';
void main() {
runApp(const ResizeApp());
}
class ResizeApp extends StatefulWidget {
const ResizeApp({super.key});
@override
State<ResizeApp> createState() => _ResizeAppState();
}
class _ResizeAppState extends State<ResizeApp> {
int _listSize = 1;
void _addToList() {
setState(() {
_listSize++;
});
}
@override
Widget build(BuildContext context) {
return Center(
heightFactor: 1,
child: Directionality(
textDirection: TextDirection.ltr,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
for (int i = 0; i < _listSize; i++)
Container(
color: HSVColor.fromAHSV(1, (10.0 * i), 1, 1).toColor(),
height: 50,
width: 200,
child: Center(
child: Text(
'Flutter Widget $i',
style: const TextStyle(fontSize: 16, color: Colors.black),
),
),
),
TextButton(
onPressed: _addToList,
child: Text('Listception!'),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,34 @@
name: flutter_module_using_plugin_content_sizing_android_view
description: An example Flutter module that uses a plugin.
version: 1.0.0+1
resolution: workspace
environment:
sdk: ^3.8.1-0
dependencies:
flutter:
sdk: flutter
provider: ^6.1.5
url_launcher: ^6.3.2
sensors_plus: ^6.1.1
dev_dependencies:
analysis_defaults:
path: ../../../analysis_defaults
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
# This section identifies your Flutter project as a module meant for
# embedding in a native host app. These identifiers should _not_ ordinarily
# be changed after generation - they are used to ensure that the tooling can
# maintain consistency when adding or modifying assets and plugins.
# They also do not have any bearing on your native host application's
# identifiers, which may be completely independent or the same as these.
module:
androidX: true
androidPackage: dev.flutter.example.flutter_module_using_plugin
iosBundleIdentifier: dev.flutter.example.flutterModuleUsingPlugin

View File

@@ -0,0 +1,15 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
class MockCounterModel extends ChangeNotifier {}
void main() {
testWidgets('MiniView smoke test', (tester) async {});
}

View File

@@ -0,0 +1,87 @@
# books
Mimics a real world use-case of embedding Flutter into an existing Android app
and demonstrates using [Pigeon](https://pub.dev/packages/pigeon) to communicate
between Flutter and the host application.
## Description
These apps integrate the `flutter_books` module using the simpler build-together
project setup. They simulate a mock scenario where an existing book catalog
list app already exists. Flutter is used to implement an additional book details
page.
* Similar to [`fullscreen`](../fullscreen).
* An existing books catalog app is already implemented in Kotlin and Swift.
* The platform-side app has existing middleware constraints that should also
be the middleware foundation for the additional Flutter screen.
* On Android, the Kotlin app already uses GSON and OkHttp for networking and
references the Google Books API as a data source. These same libraries also
underpin the data fetched and shown in the Flutter screen.
* On iOS, the Swift app does a similar fetch and parse of the Google Books API
using iOS's built-in libraries.
* The platform application interfaces with the Flutter book details page using
idiomatic platform API conventions rather than Flutter conventions.
* On Android, the Flutter activity receives the book to show via activity
intent and returns the edited book by setting the result intent on the
activity. No Flutter concepts are leaked into the consumer activity.
* On iOS, Storyboards are used to design the presentation of the books, just
as one might do in a full UIKit app.
* The [pigeon](https://pub.dev/packages/pigeon) plugin is used to generate
interop APIs and data classes. The same `Book` model class is used within the
Kotlin/Swift program, the Dart program and in the interop between Kotlin/Swift
and Dart. No manual platform channel plumbing needed for interop.
* The `api.dart/java/mm` files generated from the
`flutter_module_books/pigeon/schema.dart` file are checked into source
control. Therefore `pigeon` is only a dev dependency with no runtime
requirements.
* If the `schema.dart` is modified, the generated classes can be updated with
```bash
dart run pigeon --input pigeon/schema.dart \
--dart_out lib/api.dart \
--objc_header_out ../ios_books/IosBooks/api.h \
--objc_source_out ../ios_books/IosBooks/api.m \
--objc_prefix BK \
--java_out ../android_books/app/src/main/java/dev/flutter/example/books/Api.java \
--java_package "dev.flutter.example.books"
```
in the `flutter_module_books` directory.
Once you've understood the basics of add-to-app with `android_fullscreen` and
`ios_fullscreen`, this is a good sample to demonstrate how to integrate Flutter
in a slightly more realistic setting with existing business logic.
## tl;dr
If you're just looking to get up and running quickly, these bash commands will
fetch packages and set up dependencies (note that the above commands assume
you're building for both iOS and Android, with both toolchains installed):
```bash
#!/bin/bash
set -e
cd flutter_module_books/
flutter pub get
# For Android builds:
open -a "Android Studio" ../android_books # macOS only
# Or open the ../android_books folder in Android Studio for other platforms.
# For iOS builds:
cd ../ios_books
pod install
open IosBooks.xcworkspace
```
## Requirements
* Flutter
* Android Studio
* Cocoapods (iOS)
## Questions/issues
See [add_to_app/README.md](../README.md) for further help.

View File

@@ -0,0 +1,14 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx

View File

@@ -0,0 +1,14 @@
# android_books
An example Android application used in the Flutter add-to-app samples. For more
information on how to use it, see the [README](../README.md) file located in the
[/add_to_app](/add_to_app) directory of this repo.
## Getting Started
For more information about Flutter, check out
[flutter.dev](https://flutter.dev).
For instructions on how to integrate Flutter modules into your existing
applications, see Flutter's
[add-to-app documentation](https://flutter.dev/docs/development/add-to-app).

Some files were not shown because too many files have changed in this diff Show More