Skip to content

Conversation

kinyoklion
Copy link
Member

@kinyoklion kinyoklion commented Sep 8, 2025

The flutter SDK is arranged into a common-client package that is independent of the flutter package to allow for eventually supporting non-flutter usages such as dart command line applications. This means, similar to JS, that plugins are a concern of the leaf-node of the SDK because plugins depend on types that are not common to the leaf nodes.

A plugin for the flutter client SDK is registered with an instance of the flutter client SDK and not an instance of the common client SDK. (In flutter the common client is actually composed into the leaf-node and not a base class, so this difference is even more meaningful.)

The work-around to this is to make common types for all the meta-data, and then a generic base class to be extended by the leaf-node implementations. That extended version makes the client concrete instead of generic. The base operations that need to be done on plugins, such as getting a list of hooks from them, and registering them, are handled via generic methods in the common client package.

  graph TD
      subgraph "common_client package"
          A[PluginBase<TClient>]
      end

      subgraph "flutter_client_sdk package"
          B[Plugin]
          B --> |extends| A
      end

      subgraph "User Application"
          C[MyCustomPlugin]
          C --> |extends| B
      end

      A --> |Provides| D[register method<br/>hooks property<br/>abstract metadata property]
      B --> |Specializes for| E[LDClient type<br/>Flutter SDK context]
      C --> |Implements| F[Custom behavior via override of register<br/>Custom hooks via override of hooks property<br/>Concrete metadata property]

Loading

@kinyoklion kinyoklion force-pushed the rlamb/o11y-449/add-plugin-support branch from 727ac25 to ade623a Compare September 8, 2025 22:01
/// Implementation note: SDK packages must export a specialized version of this
/// for their specific TClient type. This class cannot provide a type, because
/// it would limit the API to methods available in the base client.
abstract base class PluginBase<TClient> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generic base class that is then specialized in the leaf-node SDK.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exposed the credential type here on the common-client. It isn't exposed on the SDK client, but exposing it would probably be fine if we choose to in the future.

/// @override
/// PluginMetadata get metadata => _metadata;
/// ```
PluginMetadata get metadata;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No body for the getter makes this abstract.

import 'ld_client.dart';

/// Base class from which all plugins must derive.
abstract base class Plugin extends PluginBase<LDClient> {}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what finally gets exported from the SDK package and makes the plugins non-generic.

import 'hook.dart';

List<Hook>? combineHooks(List<Hook>? baseHooks, List<Hook>? extendedHooks) {
if (baseHooks == null) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If both are null, that is fine as well, we just end up returning null, which the calling code will then pass to an optional constructor argument.


export 'src/plugins/operations.dart' show safeGetHooks, safeRegisterPlugins;

export 'src/config/defaults/credential_type.dart' show CredentialType;
Copy link
Member Author

@kinyoklion kinyoklion Sep 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is exported now so that it can be a component of the meta-data.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file isn't strictly part of the plugins support. We have not released hooks yet and this should be a readonly list.

allAttributesPrivate ?? DefaultConfig.allAttributesPrivate,
globalPrivateAttributes = globalPrivateAttributes ?? [];
globalPrivateAttributes = globalPrivateAttributes ?? [],
hooks = hooks != null ? UnmodifiableListView(List.from(hooks)) : null;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make an unmodifiable list from a copy of the input list. If a copy isn't made, then the original list could be mutated and the view would reflect those mutations.

}
}

final class _WifiConnected extends ConnectivityPlatform {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally speaking we do almost all testing at the dart level instead of the flutter level, but the plugins need logic at the flutter level, so we need to do testing in flutter.

When running flutter unit tests you have to mock the rest of the environment dependencies. We depend on the widget tree, connectivity, and persistent storage. So all of those need to be mocked. If we add more flutter level tests, then we should probably move these to some common test file.

final LDContext testContext =
LDContextBuilder().kind('user', 'test-user-key').build();

final config = LDConfig(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As inert a client as possible.

@kinyoklion kinyoklion marked this pull request as ready for review September 8, 2025 22:43
@kinyoklion kinyoklion requested a review from a team as a code owner September 8, 2025 22:43
cursor[bot]

This comment was marked as outdated.

@kinyoklion kinyoklion merged commit 5bd9ce7 into main Sep 9, 2025
5 checks passed
@kinyoklion kinyoklion deleted the rlamb/o11y-449/add-plugin-support branch September 9, 2025 22:16
kinyoklion pushed a commit that referenced this pull request Sep 12, 2025
🤖 I have created a release *beep* *boop*
---


##
[1.7.0](launchdarkly_common_client-v1.6.2...launchdarkly_common_client-v1.7.0)
(2025-09-12)


### Features

* Add experimental plugin support.
([#225](#225))
([5bd9ce7](5bd9ce7))
* Add support for hooks.
([#220](#220))
([6e7a26d](6e7a26d))
* Internal environment ID support.
([#217](#217))
([71b522b](71b522b))


### Bug Fixes

* Change hook data values to `dynamic` from `LDValue`.
([d7720f3](d7720f3))
* Export required plugin meta-data types.
([d7720f3](d7720f3))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
kinyoklion added a commit that referenced this pull request Sep 15, 2025
🤖 I have created a release *beep* *boop*
---


##
[4.12.0](4.11.2...4.12.0)
(2025-09-15)


### Features

* Add experimental plugin support.
([#225](#225))
([5bd9ce7](5bd9ce7))
* Add hook support and experimental plugin support.
([#228](#228))
([b698905](b698905))
* Add support for hooks.
([#220](#220))
([6e7a26d](6e7a26d))
* Update version constraints for device_info_plus to allow using 12.x.
([9b5f530](9b5f530))
* Update version constraints for package_info_plus to allow using 9.x.
([9b5f530](9b5f530))


### Bug Fixes

* Change hook data values to `dynamic` from `LDValue`.
([d7720f3](d7720f3))
* Export required plugin meta-data types.
([d7720f3](d7720f3))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: Ryan Lamb <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants