-
Notifications
You must be signed in to change notification settings - Fork 6k
Make FlutterAppDelegate respond to life cycle events dynamically
#6086
Conversation
Make `FlutterAppDelegate` respond to various application life cycle events (e.g. `applicationDidEnterBackground:`, `applicationWillEnterForeground:`, etc.) dynamically by forwarding them to the `FlutterPluginAppLifeCycleDelegate` only if a registered plugin wants it. This avoids unnecessary runtime warnings (e.g. issue #9984). `FlutterPluginAppLifeCycleDelegate` now maintains an internal table mapping selectors to corresponding plugins, and it no longer iterates over all plugins for each event (at the cost of using additional space). Note that empirical testing seems to indicate that iOS caches which parts of the `UIApplicationDelegate` protocol the application implements. This means that client applications now must call `-[FlutterAppDelegate addApplicationLifeCycleDelegate]` early in application initialization (e.g. in the `-init` method) for plugins to receive life cycle events. This change also greatly reduces the amount of code that should be copied and pasted from `FlutterAppDelegate` if client applications cannot directly inherit from it. Alternatively, it provides an example for how client applications can use `FlutterAppDelegate` via composition rather than inheritance (issue #20709).
| p.reset([[NSPointerArray weakObjectsPointerArray] retain]); | ||
| } | ||
|
|
||
| [p.get() addPointer:(__bridge void*)delegate]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that I currently don't bother doing anything to compact these NSPointerArrays if plugins are destroyed. I'm not familiar with plugin usage so don't know what to design for. Are plugins expected to be created and destroyed a lot? If so, then I think it'd be better to add a mechanism for plugins to unregister themselves. Of course, if plugins are expected to be created and destroyed during the application's lifetime, then that's probably incompatible with this change's requirement of registering plugins during application startup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The lifespan of Flutter plugins should probably have been tied to that of the FlutterViewController with which they register and set up Dart/iOS communication through platform channels. In general, such Flutter views can be created and destroyed at will during the lifecycle of the app.
However, for Flutter-only apps created using flutter create foo, the life span of the Flutter view is the same as that of the application---which is why we ended up in the current situation where plugins register once and have support for responding to all app delegate calls.
For add2app scenarios in particular, this coupling of the app lifecycle with the Flutter view lifecycle via plugins will have to be broken, I think. A first step was taken in #5173 which made it possible to delay plugin registration until the first Flutter view is created. That doesn't necessarily disqualify the present PR, but indicates that it is a step in a larger refactoring to fix an early design error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, ok. Another possibility would be to add another mechanism to let clients apps specify which lifecycle events they want without having to implement handlers that forward to the FlutterPluginAppLifeCycleDelegate themselves. OTOH, I'm a bit wary of adding multiple ways to do similar things.
And, of course, another possibility is to won't-fix #9984 and continue to live with the runtime warnings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jamesderlin, as you know, I don't have a lot of familiarity with the specifics of iOS, but I definitely agree with the sentiment that plugins should exist within the lifespan of their associated Flutter view or views. It may be worth grabbing a variety of plugin examples and discussing what impact that might have. From an architecture and maintenance perspective, it may very well create issues that Flutter plugins need to be elevated to global app state when Flutter is being used, for example, as merely a list item in a feed.
| didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { | ||
| for (id<FlutterPlugin> plugin in [_pluginDelegates allObjects]) { | ||
| if (!plugin) { | ||
| auto it = _selectorTable.find(_cmd); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] There appears to be two or three minor variations of a common idiom for finding and forwarding to relevant plugins. Any chance one could consolidate that and reduce duplication?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly. I really don't like the code duplication either and have been thinking of ways to reduce it. Definitely should be able to do it with preprocessor macros, but maybe it's also doable with C++ templates or possibly by switching to some form of iterator pattern. However, tackling that as part of this change would make it larger and less readable, so I was planning on attempting that separately.
| p.reset([[NSPointerArray weakObjectsPointerArray] retain]); | ||
| } | ||
|
|
||
| [p.get() addPointer:(__bridge void*)delegate]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The lifespan of Flutter plugins should probably have been tied to that of the FlutterViewController with which they register and set up Dart/iOS communication through platform channels. In general, such Flutter views can be created and destroyed at will during the lifecycle of the app.
However, for Flutter-only apps created using flutter create foo, the life span of the Flutter view is the same as that of the application---which is why we ended up in the current situation where plugins register once and have support for responding to all app delegate calls.
For add2app scenarios in particular, this coupling of the app lifecycle with the Flutter view lifecycle via plugins will have to be broken, I think. A first step was taken in #5173 which made it possible to delay plugin registration until the first Flutter view is created. That doesn't necessarily disqualify the present PR, but indicates that it is a step in a larger refactoring to fix an early design error.
| options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options { | ||
| for (id<FlutterPlugin> plugin in _pluginDelegates) { | ||
| if (!plugin) { | ||
| auto it = _selectorTable.find(_cmd); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps you could extract a helper method along the lines of:
- (NSArray<FlutterObject>)supportsSelector:(SEL)selector {
auto it = _selectorTable.find(_cmd);
if (it == _selectorTable.end()) {
return nil;
}
return [it->second.get() allObjects];
}
| NSPointerArray* _pluginDelegates; | ||
|
|
||
| // Maps selectors to an array of weak references to plugins that respond to those selectors. | ||
| std::map<SEL, fml::scoped_nsobject<NSPointerArray>> _selectorTable; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this name sounds wrong. It talks about selectors not plugins. Not sure I have a better one.
|
Please hold off on merging until @chinmaygarde and I have had a look. We had a couple concerns about a selector table based approach; particularly, we'd like to think through scenarios where the delegate is changed at runtime, etc. |
|
No problem. I was going to wait at least a week before submitting this
anyway since I wanted more time to collect feedback.
…On Wed, Aug 29, 2018 at 11:33 AM Chris Bracken ***@***.***> wrote:
Please hold off on merging until @chinmaygarde
<https://github.com/chinmaygarde> and I have had a look. We had a couple
concerns about a selector table based approach; particularly, we'd like to
think through scenarios where the delegate is changed at runtime, etc.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#6086 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AQlfSqOOMUpDmWQKK2smSLXE_WR6WG0Aks5uVt5egaJpZM4WMK6T>
.
|
|
I think I'm going to retract this (mostly). I don't think I'm comfortable with forcing a change to plugin lifecycles. I think I'd still like to make |
Make
FlutterAppDelegaterespond to various application life cycleevents (e.g.
applicationDidEnterBackground:,applicationWillEnterForeground:, etc.) dynamically by forwardingthem to the
FlutterPluginAppLifeCycleDelegateonly if a registeredplugin wants it. This avoids unnecessary runtime warnings (e.g.
issue #9984).
FlutterPluginAppLifeCycleDelegatenow maintains an internal tablemapping selectors to corresponding plugins, and it no longer iterates
over all plugins for each event (at the cost of using additional
space).
Note that empirical testing seems to indicate that iOS caches which
parts of the
UIApplicationDelegateprotocol the applicationimplements. This means that client applications now must call
-[FlutterAppDelegate addApplicationLifeCycleDelegate]early inapplication initialization (e.g. in the
-initmethod) for pluginsto receive life cycle events.
This change also greatly reduces the amount of code that should be
copied and pasted from
FlutterAppDelegateif client applicationscannot directly inherit from it. Alternatively, it provides an
example for how client applications can use
FlutterAppDelegateviacomposition rather than inheritance (issue #20709).