Skip to content

Commit

Permalink
Added sample for multiple flutters for android
Browse files Browse the repository at this point in the history
  • Loading branch information
gaaclarke committed Feb 2, 2021
1 parent 447c2b6 commit 25f82c1
Show file tree
Hide file tree
Showing 38 changed files with 1,083 additions and 0 deletions.
15 changes: 15 additions & 0 deletions add_to_app/multiple_flutters/multiple_flutters_android/.gitignore
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions add_to_app/multiple_flutters/multiple_flutters_android/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# multiple_flutters_android

This is an add-to-app sample that uses the Flutter engine group API to host
multiple instances of Flutter in the app.

## Getting Started

```sh
cd ../multiple_flutters_module
flutter pub get
cd -
open -a "Android Studio" multiple_flutters_android/ # macOS command
# (build and run)
```

For more information see
[multiple_flutters_module](../multiple_flutters_module/README.md).
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
}

android {
signingConfigs {
self {
}
}
compileSdkVersion 30

defaultConfig {
applicationId "dev.flutter.multipleflutters"
minSdkVersion 24
targetSdkVersion 30
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig debug.signingConfig
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}

dependencies {

implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation project(':flutter')
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="dev.flutter.multipleflutters">

<application
android:name=".App"
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.MultipleFlutters">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SingleFlutterActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
/>
<activity
android:name=".DoubleFlutterActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
/>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dev.flutter.multipleflutters

import android.app.Application
import io.flutter.embedding.engine.FlutterEngineGroup

/**
* Application class for this app.
*
* This holds onto our engine group.
*/
class App : Application() {
lateinit var engines: FlutterEngineGroup

override fun onCreate() {
super.onCreate()
engines = FlutterEngineGroup(this)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package dev.flutter.multipleflutters

import java.lang.ref.WeakReference

/**
* Interface for getting notifications when the DataModel is updated.
*/
interface DataModelObserver {
fun onCountUpdate(newCount: Int)
}

/**
* A singleton/observable data model for the data shared between Flutter and the host platform.
*
* This is the definitive source of truth for all data.
*/
class DataModel {
companion object {
val instance = DataModel()
}

private val observers = mutableListOf<WeakReference<DataModelObserver>>()

public var counter = 0
set(value) {
field = value
for (observer in observers) {
observer.get()?.onCountUpdate(value)
}
}

fun addObserver(observer: DataModelObserver) {
observers.add(WeakReference(observer))
}

fun removeObserver(observer: DataModelObserver) {
observers.removeIf {
if (it.get() != null) it.get() == observer else true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package dev.flutter.multipleflutters

import android.content.Intent
import android.os.Bundle
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import io.flutter.embedding.android.FlutterFragment
import io.flutter.embedding.engine.FlutterEngineCache

/**
* An activity that displays 2 FlutterFragments vertically.
*/
class DoubleFlutterActivity : FragmentActivity(), EngineBindingsDelegate {
private val topBindings: EngineBindings by lazy {
EngineBindings(activity = this, delegate = this, entrypoint = "topMain")
}
private val bottomBindings: EngineBindings by lazy {
EngineBindings(activity = this, delegate = this, entrypoint = "bottomMain")
}
private val numberOfFlutters = 2

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val root = LinearLayout(this)
root.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
)
root.orientation = LinearLayout.VERTICAL
root.weightSum = numberOfFlutters.toFloat()

val fragmentManager: FragmentManager = supportFragmentManager

setContentView(root)

val app = applicationContext as App

for (i in 0 until numberOfFlutters) {
val flutterContainer = FrameLayout(this)
root.addView(flutterContainer)
flutterContainer.id = 12345 + i
flutterContainer.layoutParams = LinearLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT,
1.0f
)
val engine = if (i == 0) topBindings.engine else bottomBindings.engine
FlutterEngineCache.getInstance().put(i.toString(), engine)
val flutterFragment =
FlutterFragment.withCachedEngine(i.toString()).build<FlutterFragment>()
fragmentManager
.beginTransaction()
.add(
12345 + i,
flutterFragment
)
.commit()
}

topBindings.attach()
bottomBindings.attach()
}

override fun onDestroy() {
topBindings.detach()
topBindings.detach()

for (i in 0 until numberOfFlutters) {
FlutterEngineCache.getInstance().remove(i.toString())
}

super.onDestroy()
}

override fun onNext() {
val flutterIntent = Intent(this, MainActivity::class.java)
startActivity(flutterIntent)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package dev.flutter.multipleflutters

import android.app.Activity
import io.flutter.FlutterInjector
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.plugin.common.MethodChannel

/**
* This interface represents the notifications an EngineBindings may be receiving from the Flutter
* instance.
*
* What methods this interface has depends on the messages that are sent over the EngineBinding's
* channel in `main.dart`. Messages that interact with the DataModel are handled automatically
* by the EngineBindings.
*
* @see main.dart for what messages are getting sent from Flutter.
*/
interface EngineBindingsDelegate {
fun onNext()
}

/**
* This binds a FlutterEngine instance with the DataModel and a channel for communicating with that
* engine.
*
* Messages involving the DataModel are handled by the EngineBindings, other messages are forwarded
* to the EngineBindingsDelegate.
*
* @see main.dart for what messages are getting sent from Flutter.
*/
class EngineBindings(activity: Activity, delegate: EngineBindingsDelegate, entrypoint: String) :
DataModelObserver {
val channel: MethodChannel
val engine: FlutterEngine
val delegate: EngineBindingsDelegate

init {
val app = activity.applicationContext as App
// This has to be lazy to avoid creation before the FlutterEngineGroup.
val dartEntrypoint =
DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(), entrypoint
)
engine = app.engines.createAndRunEngine(activity, dartEntrypoint)
this.delegate = delegate
channel = MethodChannel(engine.dartExecutor.binaryMessenger, "multiple-flutters")
}

/**
* This setups the messaging connections on the platform channel and the DataModel.
*/
fun attach() {
DataModel.instance.addObserver(this)
channel.invokeMethod("setCount", DataModel.instance.counter)
channel.setMethodCallHandler { call, result ->
when (call.method) {
"incrementCount" -> {
DataModel.instance.counter = DataModel.instance.counter + 1
result.success(null)
}
"next" -> {
this.delegate.onNext()
result.success(null)
}
else -> {
result.notImplemented()
}
}
}
}

/**
* This tears down the messaging connections on the platform channel and the DataModel.
*/
fun detach() {
DataModel.instance.removeObserver(this)
channel.setMethodCallHandler(null)
}

override fun onCountUpdate(newCount: Int) {
channel.invokeMethod("setCount", newCount)
}
}
Loading

0 comments on commit 25f82c1

Please sign in to comment.