Skip to content

Commit 1a2dcd0

Browse files
committed
Added sample for multiple flutters for android
1 parent 447c2b6 commit 1a2dcd0

38 files changed

+1078
-0
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
*.iml
2+
.gradle
3+
/local.properties
4+
/.idea/caches
5+
/.idea/libraries
6+
/.idea/modules.xml
7+
/.idea/workspace.xml
8+
/.idea/navEditor.xml
9+
/.idea/assetWizardSettings.xml
10+
.DS_Store
11+
/build
12+
/captures
13+
.externalNativeBuild
14+
.cxx
15+
local.properties
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# multiple_flutters_android
2+
3+
This is an add-to-app sample that uses the Flutter engine group API to host
4+
multiple instances of Flutter in the app.
5+
6+
## Getting Started
7+
8+
```sh
9+
cd ../multiple_flutters_module
10+
flutter pub get
11+
cd -
12+
open -a "Android Studio" multiple_flutters_android/ # macOS command
13+
# (build and run)
14+
```
15+
16+
For more information see
17+
[multiple_flutters_module](../multiple_flutters_module/README.md).
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
plugins {
2+
id 'com.android.application'
3+
id 'kotlin-android'
4+
}
5+
6+
android {
7+
signingConfigs {
8+
self {
9+
}
10+
}
11+
compileSdkVersion 30
12+
13+
defaultConfig {
14+
applicationId "dev.flutter.multipleflutters"
15+
minSdkVersion 24
16+
targetSdkVersion 30
17+
versionCode 1
18+
versionName "1.0"
19+
20+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
21+
}
22+
23+
buildTypes {
24+
release {
25+
minifyEnabled false
26+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
27+
signingConfig debug.signingConfig
28+
}
29+
}
30+
compileOptions {
31+
sourceCompatibility JavaVersion.VERSION_1_8
32+
targetCompatibility JavaVersion.VERSION_1_8
33+
}
34+
kotlinOptions {
35+
jvmTarget = '1.8'
36+
}
37+
}
38+
39+
dependencies {
40+
41+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
42+
implementation 'androidx.core:core-ktx:1.2.0'
43+
implementation 'androidx.appcompat:appcompat:1.1.0'
44+
implementation 'com.google.android.material:material:1.1.0'
45+
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
46+
testImplementation 'junit:junit:4.+'
47+
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
48+
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
49+
implementation project(':flutter')
50+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
package="dev.flutter.multipleflutters">
4+
5+
<application
6+
android:name=".App"
7+
android:allowBackup="true"
8+
android:icon="@mipmap/ic_launcher"
9+
android:label="@string/app_name"
10+
android:roundIcon="@mipmap/ic_launcher_round"
11+
android:supportsRtl="true"
12+
android:theme="@style/Theme.MultipleFlutters">
13+
<activity android:name=".MainActivity">
14+
<intent-filter>
15+
<action android:name="android.intent.action.MAIN" />
16+
17+
<category android:name="android.intent.category.LAUNCHER" />
18+
</intent-filter>
19+
</activity>
20+
<activity
21+
android:name=".SingleFlutterActivity"
22+
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
23+
android:hardwareAccelerated="true"
24+
android:windowSoftInputMode="adjustResize"
25+
/>
26+
<activity
27+
android:name=".DoubleFlutterActivity"
28+
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
29+
android:hardwareAccelerated="true"
30+
android:windowSoftInputMode="adjustResize"
31+
/>
32+
</application>
33+
34+
</manifest>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package dev.flutter.multipleflutters
2+
3+
import android.app.Application
4+
import io.flutter.embedding.engine.FlutterEngineGroup
5+
6+
/**
7+
* Application class for this app.
8+
*
9+
* This holds onto our engine group.
10+
*/
11+
class App : Application() {
12+
lateinit var engines : FlutterEngineGroup
13+
14+
override fun onCreate() {
15+
super.onCreate()
16+
engines = FlutterEngineGroup(this)
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package dev.flutter.multipleflutters
2+
3+
import java.lang.ref.WeakReference
4+
5+
/**
6+
* Interface for getting notifications when the DataModel is updated.
7+
*/
8+
interface DataModelObserver {
9+
fun onCountUpdate(newCount : Int)
10+
}
11+
12+
/**
13+
* A singleton/observable data model for the data shared between Flutter and the host platform.
14+
*
15+
* This is the definitive source of truth for all data.
16+
*/
17+
class DataModel {
18+
companion object {
19+
val instance = DataModel()
20+
}
21+
22+
private val observers = mutableListOf<WeakReference<DataModelObserver>>()
23+
24+
public var counter = 0
25+
set(value) {
26+
field = value
27+
for (observer in observers) {
28+
observer.get()?.onCountUpdate(value)
29+
}
30+
}
31+
32+
fun addObserver(observer : DataModelObserver) {
33+
observers.add(WeakReference(observer))
34+
}
35+
36+
fun removeObserver(observer : DataModelObserver) {
37+
observers.removeIf {
38+
if (it.get() != null) it.get() == observer else true
39+
}
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package dev.flutter.multipleflutters
2+
3+
import android.content.Intent
4+
import android.os.Bundle
5+
import android.widget.FrameLayout
6+
import android.widget.LinearLayout
7+
import androidx.fragment.app.FragmentActivity
8+
import androidx.fragment.app.FragmentManager
9+
import io.flutter.embedding.android.FlutterFragment
10+
import io.flutter.embedding.engine.FlutterEngineCache
11+
12+
/**
13+
* An activity that displays 2 FlutterFragments vertically.
14+
*/
15+
class DoubleFlutterActivity : FragmentActivity(), EngineBindingsDelegate {
16+
private val topBindings : EngineBindings by lazy {
17+
EngineBindings(activity = this, delegate = this, entrypoint = "topMain")
18+
}
19+
private val bottomBindings : EngineBindings by lazy {
20+
EngineBindings(activity = this, delegate = this, entrypoint = "bottomMain")
21+
}
22+
private val numberOfFlutters = 2
23+
24+
override fun onCreate(savedInstanceState: Bundle?) {
25+
super.onCreate(savedInstanceState)
26+
val root = LinearLayout(this)
27+
root.layoutParams = LinearLayout.LayoutParams(
28+
LinearLayout.LayoutParams.MATCH_PARENT,
29+
LinearLayout.LayoutParams.MATCH_PARENT
30+
)
31+
root.orientation = LinearLayout.VERTICAL
32+
root.weightSum = numberOfFlutters.toFloat()
33+
34+
val fragmentManager: FragmentManager = supportFragmentManager
35+
36+
setContentView(root)
37+
38+
val app = applicationContext as App
39+
40+
for (i in 0 until numberOfFlutters) {
41+
val flutterContainer = FrameLayout(this)
42+
root.addView(flutterContainer)
43+
flutterContainer.id = 12345 + i
44+
flutterContainer.layoutParams = LinearLayout.LayoutParams(
45+
FrameLayout.LayoutParams.MATCH_PARENT,
46+
FrameLayout.LayoutParams.MATCH_PARENT,
47+
1.0f
48+
)
49+
val engine = if (i == 0) topBindings.engine else bottomBindings.engine
50+
FlutterEngineCache.getInstance().put(i.toString(), engine)
51+
val flutterFragment =
52+
FlutterFragment.withCachedEngine(i.toString()).build<FlutterFragment>()
53+
fragmentManager
54+
.beginTransaction()
55+
.add(
56+
12345 + i,
57+
flutterFragment
58+
)
59+
.commit()
60+
}
61+
62+
topBindings.attach()
63+
bottomBindings.attach()
64+
}
65+
66+
override fun onDestroy() {
67+
topBindings.detach()
68+
topBindings.detach()
69+
70+
for (i in 0 until numberOfFlutters) {
71+
FlutterEngineCache.getInstance().remove(i.toString())
72+
}
73+
74+
super.onDestroy()
75+
}
76+
77+
override fun onNext() {
78+
val flutterIntent = Intent(this, MainActivity::class.java)
79+
startActivity(flutterIntent)
80+
}
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package dev.flutter.multipleflutters
2+
3+
import android.app.Activity
4+
import io.flutter.FlutterInjector
5+
import io.flutter.embedding.engine.FlutterEngine
6+
import io.flutter.embedding.engine.dart.DartExecutor
7+
import io.flutter.plugin.common.MethodChannel
8+
9+
/**
10+
* This interface represents the notifications an EngineBindings may be receiving from the Flutter
11+
* instance.
12+
*
13+
* What methods this interface has depends on the messages that are sent over the EngineBinding's
14+
* channel in `main.dart`. Messages that interact with the DataModel are handled automatically
15+
* by the EngineBindings.
16+
*
17+
* @see main.dart for what messages are getting sent from Flutter.
18+
*/
19+
interface EngineBindingsDelegate {
20+
fun onNext()
21+
}
22+
23+
/**
24+
* This binds a FlutterEngine instance with the DataModel and a channel for communicating with that
25+
* engine.
26+
*
27+
* Messages involving the DataModel are handled by the EngineBindings, other messages are forwarded
28+
* to the EngineBindingsDelegate.
29+
*
30+
* @see main.dart for what messages are getting sent from Flutter.
31+
*/
32+
class EngineBindings(activity: Activity, delegate: EngineBindingsDelegate, entrypoint: String) : DataModelObserver {
33+
val channel: MethodChannel
34+
val engine: FlutterEngine
35+
val delegate: EngineBindingsDelegate
36+
37+
init {
38+
val app = activity.applicationContext as App
39+
// This has to be lazy to avoid creation before the FlutterEngineGroup.
40+
val dartEntrypoint =
41+
DartExecutor.DartEntrypoint(FlutterInjector.instance().flutterLoader().findAppBundlePath(), entrypoint)
42+
engine = app.engines.createAndRunEngine(activity, dartEntrypoint)
43+
this.delegate = delegate
44+
channel = MethodChannel(engine.dartExecutor.binaryMessenger, "multiple-flutters")
45+
}
46+
47+
/**
48+
* This setups the messaging connections on the platform channel and the DataModel.
49+
*/
50+
fun attach() {
51+
DataModel.instance.addObserver(this)
52+
channel.invokeMethod("setCount", DataModel.instance.counter)
53+
channel.setMethodCallHandler { call, result ->
54+
when (call.method) {
55+
"incrementCount" -> {
56+
DataModel.instance.counter = DataModel.instance.counter + 1
57+
result.success(null)
58+
}
59+
"next" -> {
60+
this.delegate.onNext()
61+
result.success(null)
62+
}
63+
else -> {
64+
result.notImplemented()
65+
}
66+
}
67+
}
68+
}
69+
70+
/**
71+
* This tears down the messaging connections on the platform channel and the DataModel.
72+
*/
73+
fun detach() {
74+
DataModel.instance.removeObserver(this)
75+
channel.setMethodCallHandler(null)
76+
}
77+
78+
override fun onCountUpdate(newCount: Int) {
79+
channel.invokeMethod("setCount", newCount)
80+
}
81+
}

0 commit comments

Comments
 (0)