fixed connection problem

This commit is contained in:
Mehul Ahal 2022-12-22 22:29:37 +01:00 committed by Mguy13
parent 127a09b597
commit 47945dbec7
11 changed files with 218 additions and 196 deletions

View file

@ -1,142 +0,0 @@
import 'dart:async';
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:internet_connection_checker/internet_connection_checker.dart';
import 'package:mc_gallery/features/core/services/logging_service.dart';
import '/locator.dart';
/// Used to observe the current connection type.
class ConnectionService with LoggingService {
ConnectionService({
required Connectivity connectivity,
required InternetConnectionChecker internetConnectionChecker,
bool shouldInitialize = true,
}) : _connectivity = connectivity,
_internetConnectionChecker = internetConnectionChecker {
if (shouldInitialize) initialize();
}
ConnectivityResult? _connectivityResult;
ConnectivityResult? get connectivityResult => _connectivityResult;
final Connectivity _connectivity;
final Map<String, StreamSubscription> _connectivitySubscriptions = {};
final InternetConnectionChecker _internetConnectionChecker;
final Completer _isInitialized = Completer();
Completer<bool>? _hasInternetConnection;
final ValueNotifier<bool> _hasInternetConnectionListenable = ValueNotifier(false);
ValueListenable<bool> get hasInternetConnectionListenable => _hasInternetConnectionListenable;
Future<bool> get hasInternetConnection async {
try {
if (_hasInternetConnection == null) {
_hasInternetConnection = Completer<bool>();
try {
final hasInternetConnection = await _internetConnectionChecker.hasConnection;
_hasInternetConnection!.complete(hasInternetConnection);
return hasInternetConnection;
} catch (error) {
_hasInternetConnection!.complete(false);
return false;
} finally {
_hasInternetConnection = null;
}
} else {
final awaitedHasInternet = await _hasInternetConnection!.future;
return awaitedHasInternet;
}
} on SocketException catch (_, __) {
return false;
}
}
Future<void> initialize() async {
try {
final tag = runtimeType.toString();
if (_connectivitySubscriptions[tag] != null) {
await dispose();
}
_connectivitySubscriptions[tag] = _connectivity.onConnectivityChanged.listen(
_onConnectivityChanged,
cancelOnError: false,
onError: (error, stack) {},
);
await _onConnectivityChanged(await _connectivity.checkConnectivity());
_internetConnectionChecker.onStatusChange.listen((final event) {
switch (event) {
case InternetConnectionStatus.connected:
_hasInternetConnectionListenable.value = true;
break;
case InternetConnectionStatus.disconnected:
_hasInternetConnectionListenable.value = false;
break;
}
});
_isInitialized.complete();
} catch (error, stackTrace) {
handle(error, stackTrace);
}
}
Future<void> dispose() async {
for (final subscription in _connectivitySubscriptions.values) {
await subscription.cancel();
}
_connectivitySubscriptions.clear();
_connectivityResult = null;
}
Future<void> addListener({
required String tag,
required Future<void> Function({
required ConnectivityResult connectivityResult,
required bool hasInternet,
})
listener,
bool tryCallListenerOnAdd = true,
}) async {
try {
if (_connectivityResult != null && tryCallListenerOnAdd)
await listener(
connectivityResult: _connectivityResult!, hasInternet: await hasInternetConnection);
_connectivitySubscriptions[tag] =
_connectivity.onConnectivityChanged.listen((connectivityResult) async {
await listener(
connectivityResult: connectivityResult, hasInternet: await hasInternetConnection);
});
} catch (error, stackTrace) {
handle(error, stackTrace);
}
}
Future<void> removeListener({required String tag}) async {
try {
final subscription = _connectivitySubscriptions[tag];
if (subscription != null) {
await subscription.cancel();
} else {}
} catch (error, stackTrace) {
handle(error, stackTrace);
}
}
Future<void> _onConnectivityChanged(ConnectivityResult connectivityResult) async {
try {
_connectivityResult = connectivityResult;
} catch (error, stackTrace) {
handle(error, stackTrace);
}
}
static ConnectionService get locate => Locator.locate();
}
class NoInternetException implements Exception {}

View file

@ -0,0 +1,95 @@
import 'dart:async';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:internet_connection_checker/internet_connection_checker.dart';
import '/features/core/services/logging_service.dart';
import '/locator.dart';
/// Used to observe the current connection type.
class ConnectionsService {
ConnectionsService({
required Connectivity connectivity,
required InternetConnectionChecker internetConnectionChecker,
required LoggingService loggingService,
}) : _internetConnectionChecker = internetConnectionChecker,
_connectivity = connectivity,
_loggingService = loggingService {
_init();
}
final InternetConnectionChecker _internetConnectionChecker;
final Connectivity _connectivity;
final LoggingService _loggingService;
late final ValueNotifier<InternetConnectionStatus> _internetConnectionStatusNotifier;
ValueListenable<InternetConnectionStatus> get internetConnectionStatusListenable =>
_internetConnectionStatusNotifier;
late final ValueNotifier<ConnectivityResult> _connectivityResultNotifier;
ValueListenable<ConnectivityResult> get connectivityResultListenable =>
_connectivityResultNotifier;
Future<void> _init() async {
// Initialize notifiers
_internetConnectionStatusNotifier =
ValueNotifier(await _internetConnectionChecker.connectionStatus);
_connectivityResultNotifier = ValueNotifier(await _connectivity.checkConnectivity());
_loggingService
.info('Initial internet status: ${internetConnectionStatusListenable.value.nameWithIcon}');
_loggingService
.info('Initial connectivity result: ${connectivityResultListenable.value.nameWithIcon}');
// Attach converters by listening to stream
_internetConnectionChecker.onStatusChange
.listen((final InternetConnectionStatus internetConnectionStatus) {
_loggingService.info(
'Internet status changed to: ${internetConnectionStatus.nameWithIcon}. Notifying...');
_internetConnectionStatusNotifier.value = internetConnectionStatus;
});
_connectivity.onConnectivityChanged.listen((final connectivityResult) {
_loggingService
.info('Connectivity result changed to: ${connectivityResult.nameWithIcon}. Notifying...');
_connectivityResultNotifier.value = connectivityResult;
});
Locator.instance().signalReady(this);
}
Future<void> dispose() async {
_internetConnectionStatusNotifier.dispose();
_connectivityResultNotifier.dispose();
}
static ConnectionsService get locate => Locator.locate();
}
extension _connectionStatusEmojiExtension on InternetConnectionStatus {
String get nameWithIcon {
switch (this) {
case InternetConnectionStatus.connected:
return '$name (🌐✅)';
case InternetConnectionStatus.disconnected:
return '$name (🔌)';
}
}
}
extension _connectivityEmojiExtension on ConnectivityResult {
String get nameWithIcon {
switch (this) {
case ConnectivityResult.bluetooth:
return '$name (ᛒ🟦)';
case ConnectivityResult.wifi:
return '$name (◲)';
case ConnectivityResult.ethernet:
return '$name (🌐)';
case ConnectivityResult.mobile:
return '$name (📶)';
case ConnectivityResult.none:
return '$name (🔌)';
case ConnectivityResult.vpn:
return '$name (🔒)';
}
}
}

View file

@ -13,7 +13,9 @@ class LoggingService {
settings: TalkerSettings(
useHistory: false,
),
logger: TalkerLogger(formater: const ColoredLoggerFormatter()),
logger: TalkerLogger(
formater:
!Platform.isIOS ? const ColoredLoggerFormatter() : const ExtendedLoggerFormatter()),
loggerSettings: TalkerLoggerSettings(
enableColors: !Platform.isIOS,
),
@ -44,7 +46,10 @@ class LoggingService {
dio.interceptors.add(
TalkerDioLogger(
talker: Talker(
logger: TalkerLogger(formater: const ColoredLoggerFormatter()),
logger: TalkerLogger(
formater: !Platform.isIOS
? const ColoredLoggerFormatter()
: const ExtendedLoggerFormatter()),
settings: TalkerSettings(
useConsoleLogs: true,
useHistory: false,

View file

@ -16,18 +16,30 @@ class OverlayService {
required String tag,
required OverlayEntry overlayEntry,
}) {
_overlayEntryMap.addEntries([MapEntry(tag.hashCode, overlayEntry)]);
Overlay.of(context, rootOverlay: true)?.insert(overlayEntry);
_loggingService.info('Overlay inserted with tag: $tag');
if (!_overlayEntryMap.containsKey(tag.hashCode) && !overlayEntry.mounted) {
_overlayEntryMap.addEntries([MapEntry(tag.hashCode, overlayEntry)]);
try {
Overlay.of(context, rootOverlay: true)?.insert(overlayEntry);
//todo(mehul): Fix and not ignore Overlay building while Widget building error.
} on FlutterError catch (_) {}
_loggingService.info('Overlay inserted with tag: $tag');
} else
_loggingService.info('Overlay with tag: $tag, NOT inserted');
}
void removeOverlayEntry({
required String tag,
}) {
_overlayEntryMap[tag.hashCode]?.remove();
_overlayEntryMap.remove(tag.hashCode);
_loggingService.info('Overlay removed with tag: $tag');
if (_overlayEntryMap.containsKey(tag.hashCode)) {
final _overlayEntry = _overlayEntryMap[tag.hashCode];
if (_overlayEntry?.mounted ?? false) {
_overlayEntryMap[tag.hashCode]?.remove();
_overlayEntryMap.remove(tag.hashCode);
_loggingService.info('Overlay removed with tag: $tag');
} else
_loggingService.info('Overlay with tag: $tag already mounted OR not found. Skipped');
} else
_loggingService.info('Overlay with tag: $tag already exists. Skipped');
}
void dispose() {

View file

@ -1,12 +1,15 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:internet_connection_checker/internet_connection_checker.dart';
import 'package:mc_gallery/features/core/data/constants/const_colors.dart';
import 'package:mc_gallery/features/core/services/logging_service.dart';
import 'package:mc_gallery/l10n/generated/l10n.dart';
import '../data/constants/const_durations.dart';
import '../services/connection_service.dart';
import '/features/core/services/connections_service.dart';
import '/features/core/services/overlay_service.dart';
class McgScaffold extends StatelessWidget {
const McgScaffold({
class McgScaffold extends StatelessWidget with LoggingService {
McgScaffold({
this.appBar,
this.bodyBuilderWaiter,
this.body,
@ -28,36 +31,59 @@ class McgScaffold extends StatelessWidget {
/// Enabling listing to [ConnectionState], showing a small text at the top, when connectivity is lost.
final bool forceInternetCheck;
final ConnectionsService _connectionsService = ConnectionsService.locate;
final OverlayService _overlayService = OverlayService.locate;
@override
Widget build(BuildContext context) {
final Widget loginOptionsBody = bodyBuilderWaiter != null
? ValueListenableBuilder<bool>(
valueListenable: bodyBuilderWaiter!,
builder: (context, final isReady, child) => !isReady
? Center(child: waitingWidget ?? const CircularProgressIndicator())
: body ?? const SizedBox.shrink(),
)
: body ?? const SizedBox.shrink();
if (forceInternetCheck) {
_connectionsService.internetConnectionStatusListenable.addListener(
() => _handleOverlayDisplay(context: context),
);
if (_connectionsService.internetConnectionStatusListenable.value ==
InternetConnectionStatus.disconnected) _handleOverlayDisplay(context: context);
}
return Scaffold(
appBar: appBar,
body: forceInternetCheck
? SingleChildScrollView(
child: ValueListenableBuilder<bool>(
valueListenable: ConnectionService.locate.hasInternetConnectionListenable,
builder: (context, hasInternetConnection, _) => Column(
children: [
AnimatedSwitcher(
duration: ConstDurations.defaultAnimationDuration,
child: !hasInternetConnection ? Text('No internet') : const SizedBox.shrink(),
),
loginOptionsBody,
],
),
),
body: bodyBuilderWaiter != null
? ValueListenableBuilder<bool>(
valueListenable: bodyBuilderWaiter!,
builder: (context, final isReady, child) => !isReady
? Center(child: waitingWidget ?? const CircularProgressIndicator())
: body ?? const SizedBox.shrink(),
)
: loginOptionsBody,
: body ?? const SizedBox.shrink(),
);
}
void _handleOverlayDisplay({required BuildContext context}) {
switch (_connectionsService.internetConnectionStatusListenable.value) {
case InternetConnectionStatus.disconnected:
_overlayService.insertOverlayEntry(
context,
tag: runtimeType.toString(),
overlayEntry: OverlayEntry(
opaque: false,
builder: (_) => Align(
alignment: Alignment.topCenter,
child: Card(
elevation: 16,
surfaceTintColor: ConstColours.transparent,
color: ConstColours.transparent,
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
Strings.current.noInternetMessage,
),
),
),
),
),
);
break;
case InternetConnectionStatus.connected:
_overlayService.removeOverlayEntry(tag: runtimeType.toString());
}
}
}

View file

@ -1,5 +1,5 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import '/features/core/abstracts/base_view_model.dart';

View file

@ -32,6 +32,7 @@ class ImageCarouselView extends StatelessWidget {
argumentBuilder: () => imageCarouselViewArguments,
builder: (context, final model) => McgScaffold(
bodyBuilderWaiter: model.isInitialised,
forceInternetCheck: true,
appBar: AppBar(
title: Text(model.strings.imageCarousel),
),