Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,24 @@ export 'package:launchdarkly_common_client/launchdarkly_common_client.dart'
PersistenceConfig,
ApplicationInfo,
ConnectionMode,
FDv2ConnectionMode,
FDv2Streaming,
FDv2Polling,
FDv2Offline,
FDv2Background,
ResolvedConnectionMode,
ResolvedStreaming,
ResolvedPolling,
ResolvedBackground,
ResolvedOffline,
OfflineDetail,
OfflineSetOffline,
OfflineNetworkUnavailable,
OfflineBackgroundDisabled,
ModeState,
ModeResolutionEntry,
resolveMode,
flutterDefaultResolutionTable,
Hook,
HookMetadata,
IdentifySeriesContext,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import 'package:launchdarkly_common_client/launchdarkly_common_client.dart';

import 'stub_config.dart'
if (dart.library.io) 'io_config.dart'
if (dart.library.js_interop) 'js_config.dart';

/// Configuration common to web and mobile is contained in this file.
///
/// Configuration specific to either io targets or js targets are in io_config
/// and js_config and then exposed through this file.

final class DefaultApplicationEventsConfig {
final defaultBackgrounding = true;
final defaultNetworkAvailability = true;
}
/// Native IO and web-specific defaults live in `io_config.dart` and
/// `js_config.dart` and are exposed through this file.

final class FlutterDefaultConfig {
static final ConnectionManagerConfig connectionManagerConfig =
ConnectionManagerConfig();

static final applicationEventsConfig = DefaultApplicationEventsConfig();
/// Default automatic-resolution background slot.
static FDv2ConnectionMode get defaultBackgroundConnectionMode =>
connectionManagerConfig.defaultBackgroundConnectionMode;

static final ApplicationEventsConfig applicationEventsConfig =
ApplicationEventsConfig();
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
import 'dart:io' show Platform;

import 'package:launchdarkly_common_client/launchdarkly_common_client.dart';

class ConnectionManagerConfig {
bool get runInBackground =>
Platform.isLinux || Platform.isWindows || Platform.isMacOS;

FDv2ConnectionMode get defaultBackgroundConnectionMode =>
Platform.isAndroid || Platform.isIOS || Platform.isFuchsia
? const FDv2Background()
: const FDv2Offline();
}

/// Platform defaults for [ApplicationEvents] on native IO targets.
///
/// Mobile uses application and network signals for automatic connection
/// handling; desktop IO targets do not by default.
final class ApplicationEventsConfig {
bool get _isMobile =>
Platform.isAndroid || Platform.isIOS || Platform.isFuchsia;

bool get defaultBackgrounding => _isMobile;

bool get defaultNetworkAvailability => _isMobile;
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
import 'package:launchdarkly_common_client/launchdarkly_common_client.dart';

class ConnectionManagerConfig {
bool get runInBackground => true;

FDv2ConnectionMode get defaultBackgroundConnectionMode => const FDv2Offline();
}

/// Platform defaults for [ApplicationEvents] on web.
///
/// Web does not use application or network detector signals for automatic
/// connection handling by default.
final class ApplicationEventsConfig {
bool get defaultBackgrounding => false;

bool get defaultNetworkAvailability => false;
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
import 'package:launchdarkly_common_client/launchdarkly_common_client.dart';

class ConnectionManagerConfig {
bool get runInBackground => throw Exception('Stub implementation');

FDv2ConnectionMode get defaultBackgroundConnectionMode => const FDv2Offline();
}

/// Stub defaults for tests and unsupported compilation targets.
final class ApplicationEventsConfig {
bool get defaultBackgrounding => false;

bool get defaultNetworkAvailability => false;
}
10 changes: 6 additions & 4 deletions packages/flutter_client_sdk/lib/src/config/ld_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ final class ApplicationEvents {
bool networkAvailability;

/// Setting [backgrounding] to true allows the SDK to detect and react to
/// the application entering the background or foreground. The default
/// value is `true`.
/// the application entering the background or the foreground. The default
/// depends on the platform (typically enabled on mobile and disabled on
/// desktop and web); see [FlutterDefaultConfig.applicationEventsConfig].
///
/// Setting [networkAvailability] to true allows the SDK to detect and react
/// to network connectivity changes. For instance the SDK may not try to send
/// events if it detects the network is not available. The default value is
/// `true`.
/// events if it detects the network is not available. The default depends on
/// the platform (typically enabled on mobile and disabled on desktop and web);
/// see [FlutterDefaultConfig.applicationEventsConfig].
ApplicationEvents({bool? backgrounding, bool? networkAvailability})
: backgrounding = backgrounding ??
FlutterDefaultConfig.applicationEventsConfig.defaultBackgrounding,
Expand Down
155 changes: 87 additions & 68 deletions packages/flutter_client_sdk/lib/src/connection_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ abstract interface class StateDetector {
/// be tested. The LDCommonClient doesn't implement this, so there is a small
/// private adapter.
abstract interface class ConnectionDestination {
void setMode(ConnectionMode mode);
void setMode(ResolvedConnectionMode mode);

void setNetworkAvailability(bool available);

Expand All @@ -51,8 +51,8 @@ final class DartClientAdapter implements ConnectionDestination {
DartClientAdapter(this._client);

@override
void setMode(ConnectionMode mode) {
_client.setMode(mode);
void setMode(ResolvedConnectionMode mode) {
_client.setResolvedMode(mode);
}

@override
Expand All @@ -72,9 +72,16 @@ final class DartClientAdapter implements ConnectionDestination {
}

final class ConnectionManagerConfig {
/// The initial connection mode the SDK should use.
/// Configured foreground connection mode used as the automatic resolution
/// foreground slot.
final ConnectionMode initialConnectionMode;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is sorta ok if initialConnectionMode is never expected to be background. The asymmetry between initialConnectionMode and backgroundConnectionMode isn't great, but it would require a breaking change to make them symmetric. Thoughts?


/// Configured background connection mode used as the automatic resolution
/// background slot.
///
/// Defaults to [const FDv2Offline()].
final FDv2ConnectionMode backgroundConnectionMode;

/// Some platforms (windows, web, mac, linux) can continue executing code
/// in the background.
final bool runInBackground;
Expand All @@ -88,24 +95,28 @@ final class ConnectionManagerConfig {
final bool disableAutomaticNetworkHandling;

/// Disable handling associated with transitioning between the foreground
/// and background. This means that an application will make no attempt to
/// disconnect when entering background state, and it will not attempt
/// to re-establish a connection entering the foreground, beyond the standard
/// and background. This means that an application will not automatically
/// disconnect when entering background state, and it will not automatically
/// re-establish a connection entering the foreground, beyond the standard
/// retry logic.
///
/// The application will always be treated as in the foreground.
final bool disableAutomaticBackgroundHandling;

ConnectionManagerConfig(
{this.initialConnectionMode = ConnectionMode.streaming,
this.runInBackground = true,
this.disableAutomaticBackgroundHandling = false,
this.disableAutomaticNetworkHandling = false});
ConnectionManagerConfig({
this.initialConnectionMode = ConnectionMode.streaming,
this.backgroundConnectionMode = const FDv2Offline(),
this.runInBackground = true,
this.disableAutomaticBackgroundHandling = false,
this.disableAutomaticNetworkHandling = false,
});
}

/// This class tracks the state of the application, network, configuration,
/// and desired network state. It uses this information to request specific
/// data source configurations.
/// connection modes.
///
/// Automatic resolution uses [resolveMode] with
/// [flutterDefaultResolutionTable] by default, or [resolutionTable] when
/// supplied to the constructor.
///
/// This class does not contain any platform specific code. Instead platform
/// specific code should be implemented in a [StateDetector]. This is primarily
Expand All @@ -115,11 +126,15 @@ final class ConnectionManager {
final ConnectionManagerConfig _config;
final StateDetector _detector;
final ConnectionDestination _destination;
final List<ModeResolutionEntry> _resolutionTable;

StreamSubscription<ApplicationState>? _applicationStateSub;
StreamSubscription<NetworkState>? _networkStateSub;

ConnectionMode _currentConnectionMode;
/// When non-null, [resolveMode] is skipped and this mode is
/// applied regardless of lifecycle/network.
FDv2ConnectionMode? _modeOverride;

ApplicationState _applicationState;
NetworkState _networkState;

Expand All @@ -132,81 +147,78 @@ final class ConnectionManager {
_handleState();
}

ConnectionManager(
{required LDLogger logger,
required ConnectionManagerConfig config,
required ConnectionDestination destination,
required StateDetector detector})
: _logger = logger.subLogger('ConnectionManager'),
ConnectionManager({
required LDLogger logger,
required ConnectionManagerConfig config,
required ConnectionDestination destination,
required StateDetector detector,
List<ModeResolutionEntry>? resolutionTable,
}) : _logger = logger.subLogger('ConnectionManager'),
_config = config,
_destination = destination,
_currentConnectionMode = config.initialConnectionMode,
_resolutionTable = resolutionTable ?? flutterDefaultResolutionTable(),
_applicationState = ApplicationState.foreground,
_networkState = NetworkState.available,
_detector = detector {
if (!_config.disableAutomaticBackgroundHandling) {
_applicationStateSub =
detector.applicationState.listen((applicationState) {
// TODO (SDK-2187): plumb in debouncer here

_applicationState = applicationState;
_handleState();
});
}

if (!_config.disableAutomaticNetworkHandling) {
_networkStateSub = detector.networkState.listen((networkState) {
// TODO (SDK-2187): plumb in debouncer here

_networkState = networkState;
_destination
.setNetworkAvailability(networkState == NetworkState.available);
_handleState();
});
}
}

void _setForegroundAvailableMode() {
if (offline) {
_destination.setMode(ConnectionMode.offline);
_destination.setEventSendingEnabled(false, flush: false);
return;
}

// Currently the foreground mode will always be whatever the last active
// connection mode was.
_destination.setMode(_currentConnectionMode);
_destination.setEventSendingEnabled(true);
}

void _setBackgroundAvailableMode() {
// flush on backgrounding as application may be killed and we don't want to lose events.
_destination.flush();

if (!_config.runInBackground) {
// TODO: Can we support the backgroundDisabled data source status?
// TODO: Is it acceptable for the data source status and `offline` to
// report an `offline` status?
_destination.setMode(ConnectionMode.offline);
void _handleState() {
_logger.debug('Handling state: $_applicationState:$_networkState');

// no need to flush here, we just did up above
_destination.setEventSendingEnabled(false, flush: false);
return;
final networkAvailable = _networkState == NetworkState.available;
final inForeground = _applicationState == ApplicationState.foreground;

final ResolvedConnectionMode resolved;
if (_offline) {
resolved = const ResolvedOffline(OfflineSetOffline());
} else if (_modeOverride case final mode?) {
resolved = switch (mode) {
FDv2Streaming() => const ResolvedStreaming(),
FDv2Polling() => const ResolvedPolling(),
FDv2Background() => const ResolvedBackground(),
FDv2Offline() => const ResolvedOffline(OfflineSetOffline()),
};
} else {
final modeState = ModeState(
networkAvailable: networkAvailable,
inForeground: inForeground,
runInBackground: _config.runInBackground,
foregroundConnectionMode: _fdv2FromFdv1(_config.initialConnectionMode),
backgroundConnectionMode: _config.backgroundConnectionMode,
);
resolved = resolveMode(_resolutionTable, modeState);
}

// If connections in the background are allowed, then use the same mode
// as is configured for the foreground.
_setForegroundAvailableMode();
}
if (!_offline && !inForeground && networkAvailable) {
_destination.flush();
}

void _handleState() {
_logger.debug('Handling state: $_applicationState:$_networkState');
_destination.setMode(resolved);

switch (_networkState) {
case NetworkState.unavailable:
_destination.setNetworkAvailability(false);
case NetworkState.available:
_destination.setNetworkAvailability(true);
switch (_applicationState) {
case ApplicationState.foreground:
_setForegroundAvailableMode();
case ApplicationState.background:
_setBackgroundAvailableMode();
}
if (_offline || (!inForeground && !_config.runInBackground)) {
_destination.setEventSendingEnabled(false, flush: false);
} else {
_destination.setEventSendingEnabled(true);
}
}

Expand All @@ -219,9 +231,16 @@ final class ConnectionManager {
_detector.dispose();
}

/// Set the desired connection mode for the SDK.
void setMode(ConnectionMode mode) {
_currentConnectionMode = mode;
/// Set the desired connection mode for the SDK. Passing null clears the
/// override and resumes automatic mode resolution.
void setMode(FDv2ConnectionMode? mode) {
_modeOverride = mode;
_handleState();
}
}

FDv2ConnectionMode _fdv2FromFdv1(ConnectionMode mode) => switch (mode) {
ConnectionMode.streaming => const FDv2Streaming(),
ConnectionMode.polling => const FDv2Polling(),
ConnectionMode.offline => const FDv2Offline(),
};
Loading
Loading