Unverified Commit 49667ba2 authored by Gary Qian's avatar Gary Qian Committed by GitHub

DeferredComponent utility class for manual handling of Deferred Components (#72895)

parent 388dcd24
...@@ -15,6 +15,7 @@ export 'src/services/autofill.dart'; ...@@ -15,6 +15,7 @@ export 'src/services/autofill.dart';
export 'src/services/binary_messenger.dart'; export 'src/services/binary_messenger.dart';
export 'src/services/binding.dart'; export 'src/services/binding.dart';
export 'src/services/clipboard.dart'; export 'src/services/clipboard.dart';
export 'src/services/deferred_component.dart';
export 'src/services/font_loader.dart'; export 'src/services/font_loader.dart';
export 'src/services/haptic_feedback.dart'; export 'src/services/haptic_feedback.dart';
export 'src/services/keyboard_key.dart'; export 'src/services/keyboard_key.dart';
......
// 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 'dart:async';
import 'system_channels.dart';
/// Manages the installation and loading of deferred component modules.
///
/// Deferred components allow Flutter applications to download precompiled AOT
/// dart code and assets at runtime, reducing the install size of apps and
/// avoiding installing unnessecary code/assets on end user devices. Common
/// use cases include deferring installation of advanced or infrequently
/// used features and limiting locale specific features to users of matching
/// locales. Deferred components can only deliver split off parts of the same
/// app that was built and installed on the device. It cannot load new code
/// written after the app is distributed.
///
/// Deferred components are currently an Android-only feature. The methods in
/// this class are a no-op and all assets and dart code are already available
/// without installation if called on other platforms.
class DeferredComponent {
// This class is not meant to be instantiated or extended; this constructor
// prevents instantiation and extension.
DeferredComponent._();
// TODO(garyq): We should eventually expand this to install modules by loadingUnitId
// as well as moduleName, but currently, loadingUnitId is opaque to the dart code
// so this is not possible. The API has been left flexible to allow adding
// loadingUnitId as a parameter.
/// Requests that an assets-only deferred component identified by the [moduleName]
/// be downloaded and installed.
///
/// This method returns a Future<void> that will complete when the feature is
/// installed and any assets are ready to be used. When an error occurs, the
/// future will complete with an error.
///
/// This method should be used for asset-only deferred components or loading just
/// the assets from a component with both dart code and assets. Deferred components
/// containing dart code should call `loadLibrary()` on a deferred imported
/// library's prefix to ensure that the dart code is properly loaded as
/// `loadLibrary()` will provide the loading unit id needed for the dart
/// library loading process. For example:
///
/// ```dart
/// import 'split_module.dart' deferred as SplitModule;
/// ...
/// SplitModule.loadLibrary();
/// ```
///
/// This method will not load associated dart libraries contained in the dynamic
/// feature module, though it will download the files necessary and subsequent
/// calls to `loadLibrary()` to load will complete faster.
///
/// Assets installed by this method may be accessed in the same way as any other
/// local asset by providing a string path to the asset.
///
/// See also:
///
/// * [uninstallDeferredComponent], a method to request the uninstall of a component.
/// * [loadLibrary](https://api.dart.dev/dart-mirrors/LibraryDependencyMirror/loadLibrary.html),
/// the dart method to trigger the installation of the corresponding deferred component that
/// contains the dart library.
static Future<void> installDeferredComponent({required String moduleName}) async {
await SystemChannels.deferredComponent.invokeMethod<void>(
'installDeferredComponent',
<String, dynamic>{ 'loadingUnitId': -1, 'moduleName': moduleName },
);
}
/// Requests that a deferred component identified by the [moduleName] be
/// uninstalled.
///
/// Since uninstallation typically requires significant disk i/o, this method only
/// signals the intent to uninstall. Completion of the returned future indicates
/// that the request to uninstall has been registered. Actual uninstallation (e.g.,
/// removal of assets and files) may occur at a later time. However, once uninstallation
/// is requested, the deferred component should not be used anymore until
/// [installDeferredComponent] or `loadLibrary()` is called again.
///
/// It is safe to request an uninstall when dart code from the component is in use,
/// but assets from the component should not be used once the component uninstall is
/// requested. The dart code will remain usable in the app's current session but
/// is not guaranteed to work in future sessions.
///
/// See also:
///
/// * [installDeferredComponent], a method to install asset-only components.
/// * [loadLibrary](https://api.dart.dev/dart-mirrors/LibraryDependencyMirror/loadLibrary.html),
/// the dart method to trigger the installation of the corresponding deferred component that
/// contains the dart library.
static Future<void> uninstallDeferredComponent({required String moduleName}) async {
await SystemChannels.deferredComponent.invokeMethod<void>(
'uninstallDeferredComponent',
<String, dynamic>{ 'loadingUnitId': -1, 'moduleName': moduleName },
);
}
}
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:ui'; import 'dart:ui';
import 'message_codecs.dart'; import 'message_codecs.dart';
...@@ -319,29 +318,30 @@ class SystemChannels { ...@@ -319,29 +318,30 @@ class SystemChannels {
StandardMethodCodec(), StandardMethodCodec(),
); );
/// A [MethodChannel] for installing and managing dynamic features. /// A [MethodChannel] for installing and managing deferred components.
/// ///
/// The following outgoing methods are defined for this channel (invoked using /// The following outgoing methods are defined for this channel (invoked using
/// [OptionalMethodChannel.invokeMethod]): /// [OptionalMethodChannel.invokeMethod]):
/// ///
/// * `installDynamicFeature`: Requests that a dynamic feature identified by /// * `installDeferredComponent`: Requests that a deferred component identified by
/// the provided loadingUnitId or moduleName be downloaded and installed. /// the provided loadingUnitId or moduleName be downloaded and installed.
/// Providing a loadingUnitId with null moduleName will install a dynamic /// Providing a loadingUnitId with null moduleName will install a dynamic
/// feature module that includes the desired loading unit. If a moduleName /// feature module that includes the desired loading unit. If a moduleName
/// is provided, then the dynamic feature with the moduleName will be installed. /// is provided, then the deferred component with the moduleName will be installed.
/// This method returns a future that will not be completed until the /// This method returns a future that will not be completed until the
/// feature is fully installed and ready to use. When an error occurs, the /// feature is fully installed and ready to use. When an error occurs, the
/// future will complete an error. Calling `loadLibrary()` on a deferred /// future will complete an error. Calling `loadLibrary()` on a deferred
/// imported library is equivalent to calling this method with a /// imported library is equivalent to calling this method with a
/// loadingUnitId and null moduleName. /// loadingUnitId and null moduleName.
/// * `getDynamicFeatureInstallState`: Gets the current installation state of /// * `uninstallDeferredComponent`: Requests that a deferred component identified by
/// the dynamic feature identified by the loadingUnitId or moduleName. /// the provided loadingUnitId or moduleName be uninstalled. Since
/// This method returns a string that represents the state. Depending on /// uninstallation typically requires significant disk i/o, this method only
/// the implementation, this string may vary, but the default Google Play /// signals the intent to uninstall. Actual uninstallation (eg, removal of
/// Store implementation beings in the "Requested" state before transitioning /// assets and files) may occur at a later time. However, once uninstallation
/// into the "Downloading" and finally the "Installed" state. /// is requested, the deferred component should not be used anymore until
static const MethodChannel dynamicfeature = OptionalMethodChannel( /// `installDeferredComponent` or `loadLibrary` is called again.
'flutter/dynamicfeature', static const MethodChannel deferredComponent = OptionalMethodChannel(
'flutter/deferredcomponent',
StandardMethodCodec(), StandardMethodCodec(),
); );
} }
// 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/services.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
test('installDeferredComponent test', () async {
final List<MethodCall> log = <MethodCall>[];
SystemChannels.deferredComponent.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(methodCall);
});
await DeferredComponent.installDeferredComponent(moduleName: 'testModuleName');
expect(log, hasLength(1));
expect(log.single, isMethodCall(
'installDeferredComponent',
arguments: <String, dynamic>{'loadingUnitId': -1, 'moduleName': 'testModuleName'},
));
});
test('uninstallDeferredComponent test', () async {
final List<MethodCall> log = <MethodCall>[];
SystemChannels.deferredComponent.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(methodCall);
});
await DeferredComponent.uninstallDeferredComponent(moduleName: 'testModuleName');
expect(log, hasLength(1));
expect(log.single, isMethodCall(
'uninstallDeferredComponent',
arguments: <String, dynamic>{'loadingUnitId': -1, 'moduleName': 'testModuleName'},
));
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment