/
FlutterViewEngine.kt
243 lines (219 loc) · 9.44 KB
/
FlutterViewEngine.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
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!!;
}
}