From 0119c7ea0b77f6ef810bdbd7c7210bf3d719b97c Mon Sep 17 00:00:00 2001 From: hamed-deriv <57184669+hamed-deriv@users.noreply.github.com> Date: Tue, 27 Jun 2023 13:25:56 +0800 Subject: [PATCH] hamed/refactor_ws_in_deriv_api hamed/refactor_ws_in_deriv_api --- binary-websocket-api | 2 +- example/ios/Flutter/AppFrameworkInfo.plist | 2 +- example/ios/Flutter/Flutter.podspec | 12 +- example/ios/Podfile | 2 +- example/ios/Podfile.lock | 39 ++--- example/ios/Runner.xcodeproj/project.pbxproj | 40 +++-- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- example/ios/Runner/Info.plist | 4 + .../connection/api_manager/base_api.dart | 7 + .../connection/api_manager/binary_api.dart | 51 +++--- .../connection/api_manager/enums.dart | 7 + .../connection/api_manager/extensions.dart | 23 +++ .../connection/api_manager/mock_api.dart | 8 + lib/state/connection/connection_cubit.dart | 149 ++++++------------ pubspec.yaml | 3 +- .../call_manager/base_call_manager_test.dart | 8 + 16 files changed, 181 insertions(+), 178 deletions(-) create mode 100644 lib/services/connection/api_manager/enums.dart create mode 100644 lib/services/connection/api_manager/extensions.dart diff --git a/binary-websocket-api b/binary-websocket-api index 815af08a17..670da35706 160000 --- a/binary-websocket-api +++ b/binary-websocket-api @@ -1 +1 @@ -Subproject commit 815af08a17a6f3933923fc45d2f0f125721d57ed +Subproject commit 670da35706fb063cddbe2e01ba59d79da6ef6264 diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78a7..4f8d4d2456 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 11.0 diff --git a/example/ios/Flutter/Flutter.podspec b/example/ios/Flutter/Flutter.podspec index 2c4421cfe5..29758b70d5 100644 --- a/example/ios/Flutter/Flutter.podspec +++ b/example/ios/Flutter/Flutter.podspec @@ -1,17 +1,17 @@ # -# NOTE: This podspec is NOT to be published. It is only used as a local source! -# This is a generated file; do not edit or check into version control. +# This podspec is NOT to be published. It is only used as a local source! +# This is a generated file; do not edit or check into version control. # Pod::Spec.new do |s| s.name = 'Flutter' s.version = '1.0.0' - s.summary = 'High-performance, high-fidelity mobile apps.' - s.homepage = 'https://flutter.io' - s.license = { :type => 'MIT' } + s.summary = 'A UI toolkit for beautiful and fast apps.' + s.homepage = 'https://flutter.dev' + s.license = { :type => 'BSD' } s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } - s.ios.deployment_target = '8.0' + s.ios.deployment_target = '11.0' # Framework linking is handled by Flutter tooling, not CocoaPods. # Add a placeholder to satisfy `s.dependency 'Flutter'` plugin podspecs. s.vendored_frameworks = 'path/to/nothing' diff --git a/example/ios/Podfile b/example/ios/Podfile index 1e8c3c90a5..88359b225f 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +# platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 9c4013933b..971f498761 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,47 +1,34 @@ PODS: - - connectivity (0.0.1): - - Flutter - - Reachability - - device_info (0.0.1): + - device_info_plus (0.0.1): - Flutter - Flutter (1.0.0) - flutter_deriv_api (0.0.1): - Flutter - - package_info (0.0.1): + - package_info_plus (0.4.5): - Flutter - - Reachability (3.2) DEPENDENCIES: - - connectivity (from `.symlinks/plugins/connectivity/ios`) - - device_info (from `.symlinks/plugins/device_info/ios`) + - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - Flutter (from `Flutter`) - flutter_deriv_api (from `.symlinks/plugins/flutter_deriv_api/ios`) - - package_info (from `.symlinks/plugins/package_info/ios`) - -SPEC REPOS: - trunk: - - Reachability + - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) EXTERNAL SOURCES: - connectivity: - :path: ".symlinks/plugins/connectivity/ios" - device_info: - :path: ".symlinks/plugins/device_info/ios" + device_info_plus: + :path: ".symlinks/plugins/device_info_plus/ios" Flutter: :path: Flutter flutter_deriv_api: :path: ".symlinks/plugins/flutter_deriv_api/ios" - package_info: - :path: ".symlinks/plugins/package_info/ios" + package_info_plus: + :path: ".symlinks/plugins/package_info_plus/ios" SPEC CHECKSUMS: - connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467 - device_info: d7d233b645a32c40dfdc212de5cf646ca482f175 - Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c + device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 flutter_deriv_api: 9e29abd7cc5091b72303f9c8be549618415f1437 - package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 - Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 + package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e -PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c +PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 -COCOAPODS: 1.10.1 +COCOAPODS: 1.12.1 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 6c53661486..f973131b2d 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -164,7 +164,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -213,19 +213,15 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework", - "${BUILT_PRODUCTS_DIR}/connectivity/connectivity.framework", - "${BUILT_PRODUCTS_DIR}/device_info/device_info.framework", + "${BUILT_PRODUCTS_DIR}/device_info_plus/device_info_plus.framework", "${BUILT_PRODUCTS_DIR}/flutter_deriv_api/flutter_deriv_api.framework", - "${BUILT_PRODUCTS_DIR}/package_info/package_info.framework", + "${BUILT_PRODUCTS_DIR}/package_info_plus/package_info_plus.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/connectivity.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info_plus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_deriv_api.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info_plus.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -234,10 +230,12 @@ }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -248,6 +246,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -357,7 +356,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -379,7 +378,10 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", @@ -439,7 +441,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -488,7 +490,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -511,7 +513,10 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", @@ -538,7 +543,10 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfdb..3db53b6e1f 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/lib/services/connection/api_manager/base_api.dart b/lib/services/connection/api_manager/base_api.dart index 8cc480351a..800e1eec5e 100644 --- a/lib/services/connection/api_manager/base_api.dart +++ b/lib/services/connection/api_manager/base_api.dart @@ -4,6 +4,7 @@ import 'package:flutter_deriv_api/basic_api/generated/forget_receive.dart'; import 'package:flutter_deriv_api/basic_api/request.dart'; import 'package:flutter_deriv_api/basic_api/response.dart'; import 'package:flutter_deriv_api/services/connection/api_manager/connection_information.dart'; +import 'package:flutter_deriv_api/services/connection/api_manager/enums.dart'; import 'package:flutter_deriv_api/services/connection/call_manager/base_call_manager.dart'; /// Callbacks for websocket connection. @@ -50,4 +51,10 @@ abstract class BaseAPI { /// Disconnects from API. Future disconnect(); + + /// Gets current websocket connection status. + APIStatus get currentConnectionStatus; + + /// Gets websocket connection status. + Stream get connectionStatus; } diff --git a/lib/services/connection/api_manager/binary_api.dart b/lib/services/connection/api_manager/binary_api.dart index 896583b8f6..43ac1fe389 100644 --- a/lib/services/connection/api_manager/binary_api.dart +++ b/lib/services/connection/api_manager/binary_api.dart @@ -4,7 +4,8 @@ import 'dart:developer' as dev; import 'dart:io'; import 'package:flutter/widgets.dart'; -import 'package:web_socket_channel/io.dart'; + +import 'package:web_socket_client/web_socket_client.dart' as ws; import 'package:flutter_deriv_api/api/models/enums.dart'; import 'package:flutter_deriv_api/basic_api/generated/forget_all_receive.dart'; @@ -14,6 +15,8 @@ import 'package:flutter_deriv_api/basic_api/response.dart'; import 'package:flutter_deriv_api/helpers/helpers.dart'; import 'package:flutter_deriv_api/services/connection/api_manager/base_api.dart'; import 'package:flutter_deriv_api/services/connection/api_manager/connection_information.dart'; +import 'package:flutter_deriv_api/services/connection/api_manager/enums.dart'; +import 'package:flutter_deriv_api/services/connection/api_manager/extensions.dart'; import 'package:flutter_deriv_api/services/connection/call_manager/base_call_manager.dart'; import 'package:flutter_deriv_api/services/connection/call_manager/call_history.dart'; import 'package:flutter_deriv_api/services/connection/call_manager/call_manager.dart'; @@ -26,13 +29,10 @@ class BinaryAPI extends BaseAPI { BinaryAPI({String? key, bool enableDebug = false}) : super(key: key ?? '${UniqueKey()}', enableDebug: enableDebug); - static const Duration _disconnectTimeOut = Duration(seconds: 5); - static const Duration _websocketConnectTimeOut = Duration(seconds: 10); - /// Represents the active websocket connection. /// /// This is used to send and receive data from the websocket server. - IOWebSocketChannel? _webSocketChannel; + ws.WebSocket? _webSocketChannel; /// Stream subscription to API data. StreamSubscription?>? _webSocketListener; @@ -76,12 +76,16 @@ class BinaryAPI extends BaseAPI { await _setUserAgent(); // Initialize connection to websocket server. - _webSocketChannel = IOWebSocketChannel.connect( - '$uri', - pingInterval: _websocketConnectTimeOut, + _webSocketChannel = ws.WebSocket( + uri, + pingInterval: const Duration(seconds: 1), + backoff: const ws.ConstantBackoff(Duration(seconds: 1)), ); - _webSocketListener = _webSocketChannel?.stream + await connectionStatus + .firstWhere((APIStatus status) => status == APIStatus.connected); + + _webSocketListener = _webSocketChannel?.messages .map?>((Object? result) => jsonDecode('$result')) .listen( (Map? message) { @@ -91,10 +95,12 @@ class BinaryAPI extends BaseAPI { _handleResponse(message, printResponse: printResponse); } }, - onDone: () async { + onDone: () { _logDebugInfo('the websocket is closed.'); onDone?.call(key); + + disconnect(); }, onError: (Object error) { _logDebugInfo( @@ -103,6 +109,8 @@ class BinaryAPI extends BaseAPI { ); onError?.call(key); + + disconnect(); }, ); @@ -117,10 +125,9 @@ class BinaryAPI extends BaseAPI { @override void addToChannel(Map request) { try { - _webSocketChannel?.sink.add(utf8.encode(jsonEncode(request))); - // ignore: avoid_catches_without_on_clauses - } catch (error) { - _logDebugInfo('error while adding to channel.', error: error); + _webSocketChannel?.send(utf8.encode(jsonEncode(request))); + } on Exception catch (error) { + _logDebugInfo('error sending message to websocket.', error: error); } } @@ -167,12 +174,8 @@ class BinaryAPI extends BaseAPI { try { await _webSocketListener?.cancel(); - await _webSocketChannel?.sink.close().timeout( - _disconnectTimeOut, - onTimeout: () => throw TimeoutException('Could not close sink.'), - ); - // ignore: avoid_catches_without_on_clauses - } catch (e) { + _webSocketChannel?.close(); + } on Exception catch (e) { _logDebugInfo('disconnect error.', error: e); } finally { _webSocketListener = null; @@ -180,6 +183,14 @@ class BinaryAPI extends BaseAPI { } } + @override + APIStatus get currentConnectionStatus => + _webSocketChannel!.connection.state.apiStatus; + + @override + Stream get connectionStatus => _webSocketChannel!.connection + .map((ws.ConnectionState state) => state.apiStatus); + /// Handles responses that come from server, by using its reqId, /// and completes caller's Future or add the response to caller's stream if it was a subscription call. void _handleResponse( diff --git a/lib/services/connection/api_manager/enums.dart b/lib/services/connection/api_manager/enums.dart new file mode 100644 index 0000000000..2b5d65ff90 --- /dev/null +++ b/lib/services/connection/api_manager/enums.dart @@ -0,0 +1,7 @@ +// ignore_for_file: public_member_api_docs + +enum APIStatus { + connecting, + connected, + disconnected, +} diff --git a/lib/services/connection/api_manager/extensions.dart b/lib/services/connection/api_manager/extensions.dart new file mode 100644 index 0000000000..e4cabf2c07 --- /dev/null +++ b/lib/services/connection/api_manager/extensions.dart @@ -0,0 +1,23 @@ +import 'package:web_socket_client/web_socket_client.dart'; + +import 'package:flutter_deriv_api/services/connection/api_manager/enums.dart'; + +/// An extension on [ConnectionState]. +extension ConnectionStatusExtension on ConnectionState { + /// Gets the [APIStatus] from [ConnectionState]. + APIStatus get apiStatus { + if (this is Connecting || this is Reconnecting) { + return APIStatus.connecting; + } + + if (this is Connected || this is Reconnected) { + return APIStatus.connected; + } + + if (this is Disconnected || this is Disconnecting) { + return APIStatus.disconnected; + } + + throw Exception('ConnectionStatusExtension unknown connection status.'); + } +} diff --git a/lib/services/connection/api_manager/mock_api.dart b/lib/services/connection/api_manager/mock_api.dart index 8f51940748..3dedc6e313 100644 --- a/lib/services/connection/api_manager/mock_api.dart +++ b/lib/services/connection/api_manager/mock_api.dart @@ -11,6 +11,7 @@ import 'package:flutter_deriv_api/basic_api/request.dart'; import 'package:flutter_deriv_api/basic_api/response.dart'; import 'package:flutter_deriv_api/services/connection/api_manager/base_api.dart'; import 'package:flutter_deriv_api/services/connection/api_manager/connection_information.dart'; +import 'package:flutter_deriv_api/services/connection/api_manager/enums.dart'; import 'package:flutter_deriv_api/services/connection/api_manager/exceptions/api_manager_exception.dart'; import 'package:flutter_deriv_api/services/connection/call_manager/base_call_manager.dart'; import 'package:flutter_deriv_api/services/connection/call_manager/exceptions/call_manager_exception.dart'; @@ -368,4 +369,11 @@ class MockAPI extends BaseAPI { ); } } + + @override + APIStatus get currentConnectionStatus => APIStatus.connected; + + @override + Stream get connectionStatus => + Stream.value(APIStatus.connected); } diff --git a/lib/state/connection/connection_cubit.dart b/lib/state/connection/connection_cubit.dart index b5a6e08019..da55ee907d 100644 --- a/lib/state/connection/connection_cubit.dart +++ b/lib/state/connection/connection_cubit.dart @@ -1,21 +1,20 @@ import 'dart:async'; -import 'dart:developer' as dev; -import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:deriv_dependency_injector/dependency_injector.dart'; + import 'package:flutter_deriv_api/api/api_initializer.dart'; -import 'package:flutter_deriv_api/api/response/ping_response_result.dart'; import 'package:flutter_deriv_api/services/connection/api_manager/base_api.dart'; import 'package:flutter_deriv_api/services/connection/api_manager/binary_api.dart'; import 'package:flutter_deriv_api/services/connection/api_manager/connection_information.dart'; -import 'package:deriv_dependency_injector/dependency_injector.dart'; +import 'package:flutter_deriv_api/services/connection/api_manager/enums.dart'; part 'connection_state.dart'; -/// Bringing [ConnectionCubit] to flutter-deriv-api to simplify the usage of api. +/// [ConnectionCubit] is a [Cubit] which manages connection status of websocket. class ConnectionCubit extends Cubit { /// Initializes [ConnectionCubit]. ConnectionCubit( @@ -24,26 +23,17 @@ class ConnectionCubit extends Cubit { this.enableDebug = false, this.printResponse = false, }) : super(const ConnectionInitialState()) { - APIInitializer().initialize( - api: api ?? BinaryAPI(key: _key, enableDebug: enableDebug), - ); + APIInitializer() + .initialize(api: api ?? BinaryAPI(key: _key, enableDebug: enableDebug)); _api = Injector()(); - _connectionInformation = connectionInformation; - - if (_api is BinaryAPI) { - _setupConnectivityListener(); - } - - _startKeepAliveTimer(); - - _connect(_connectionInformation); + connect(_connectionInformation = connectionInformation); } final String _key = '${UniqueKey()}'; - late final BaseAPI? _api; + late final BaseAPI _api; /// Enables debug mode. /// @@ -55,12 +45,8 @@ class ConnectionCubit extends Cubit { /// Default value is `false`. final bool printResponse; - // In some devices like Samsung J6 or Huawei Y7, the call manager doesn't response to the ping call less than 5 sec. - final Duration _pingTimeout = const Duration(seconds: 5); - - final Duration _connectivityCheckInterval = const Duration(seconds: 5); - - Timer? _connectivityTimer; + /// Connection status of websocket stream subscription. + StreamSubscription? _connectionStatusSubscription; static late ConnectionInformation _connectionInformation; @@ -74,98 +60,53 @@ class ConnectionCubit extends Cubit { /// Gets app id of websocket. static String get appId => _connectionInformation.appId; - /// Reconnect to Websocket. - Future reconnect({ConnectionInformation? connectionInformation}) async { - emit(const ConnectionDisconnectedState()); - - if (connectionInformation != null) { - _connectionInformation = connectionInformation; - } - - await _connect(_connectionInformation); - } + /// Connect to Websocket with new [connectionInformation]. + Future connect([ConnectionInformation? connectionInformation]) async { + try { + if (connectionInformation != null) { + _connectionInformation = connectionInformation; + } - /// Connects to the web socket. - Future _connect(ConnectionInformation connectionInformation) async { - if (state is ConnectionConnectingState) { - return; - } + await _api.disconnect(); - emit(const ConnectionConnectingState()); + await _api.connect( + _connectionInformation, + printResponse: enableDebug && printResponse, + onError: (String error) => emit(ConnectionErrorState(error)), + ); - try { - await _api!.disconnect().timeout(_pingTimeout); + await _handleConnectionStatus(_api); } on Exception catch (e) { - dev.log('$runtimeType disconnect exception: $e', error: e); - - unawaited(reconnect()); - - return; + emit(ConnectionErrorState('$e')); } + } - await _api!.connect( - _connectionInformation, - printResponse: enableDebug && printResponse, - onOpen: (String key) { - if (_key == key) { - emit(const ConnectionConnectedState()); - } - }, - onDone: (String key) async { - if (_key == key) { - await _api!.disconnect(); - - emit(const ConnectionDisconnectedState()); - } - }, - onError: (String key) { - if (_key == key) { - emit(const ConnectionDisconnectedState()); + Future _handleConnectionStatus(BaseAPI api) async { + await _cancelConnectionStatusSubscription(); + + _connectionStatusSubscription = api.connectionStatus.listen( + (APIStatus status) { + switch (status) { + case APIStatus.connecting: + emit(const ConnectionConnectingState()); + break; + case APIStatus.connected: + emit(const ConnectionConnectedState()); + break; + case APIStatus.disconnected: + emit(const ConnectionDisconnectedState()); + break; } }, ); } - void _setupConnectivityListener() => - Connectivity().onConnectivityChanged.listen( - (ConnectivityResult status) async { - final bool isConnectedToNetwork = - status == ConnectivityResult.mobile || - status == ConnectivityResult.wifi; - - if (isConnectedToNetwork) { - final bool isConnected = await _ping(); - - if (!isConnected) { - await reconnect(); - } - } else if (status == ConnectivityResult.none) { - emit(const ConnectionDisconnectedState()); - } - }, - ); - - void _startKeepAliveTimer() { - if (_connectivityTimer == null || !_connectivityTimer!.isActive) { - _connectivityTimer = - Timer.periodic(_connectivityCheckInterval, (Timer timer) => _ping()); - } - } - - Future _ping() async { - try { - final PingResponse response = - await PingResponse.pingMethod().timeout(_pingTimeout); - - return response.ping == PingEnum.pong; - } on Exception catch (_) { - return false; - } - } + Future _cancelConnectionStatusSubscription() async => + _connectionStatusSubscription?.cancel(); @override - Future close() { - _connectivityTimer?.cancel(); + Future close() async { + await _cancelConnectionStatusSubscription(); return super.close(); } diff --git a/pubspec.yaml b/pubspec.yaml index 9612ac8ed5..8a0d345ae2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,10 +29,9 @@ dependencies: meta: ^1.8.0 recase: ^4.0.0 rxdart: ^0.27.7 - web_socket_channel: ^2.3.0 device_info_plus: ^8.1.0 package_info_plus: ^3.0.3 - connectivity_plus: ^3.0.3 + web_socket_client: ^0.1.0-dev.3 dev_dependencies: flutter_test: diff --git a/test/services/call_manager/base_call_manager_test.dart b/test/services/call_manager/base_call_manager_test.dart index 5ba12de77b..2e60eed4e6 100644 --- a/test/services/call_manager/base_call_manager_test.dart +++ b/test/services/call_manager/base_call_manager_test.dart @@ -9,6 +9,7 @@ import 'package:flutter_deriv_api/basic_api/request.dart'; import 'package:flutter_deriv_api/basic_api/response.dart'; import 'package:flutter_deriv_api/services/connection/api_manager/base_api.dart'; import 'package:flutter_deriv_api/services/connection/api_manager/connection_information.dart'; +import 'package:flutter_deriv_api/services/connection/api_manager/enums.dart'; import 'package:flutter_deriv_api/services/connection/call_manager/base_call_manager.dart'; void main() { @@ -95,6 +96,13 @@ class MockAPI implements BaseAPI { @override Future unsubscribeAll({required ForgetStreamType method}) => throw UnimplementedError(); + + @override + APIStatus get currentConnectionStatus => APIStatus.connected; + + @override + Stream get connectionStatus => + Stream.value(APIStatus.connected); } class MockRequest extends Request {}