diff --git a/example/lib/main.dart b/example/lib/main.dart index 4d42ffe..60ab7b9 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -4,6 +4,7 @@ import './demo_page.dart'; import './widgets/demo_1.dart'; import './widgets/demo_2.dart'; import './widgets/demo_3.dart'; +import './widgets/demo_4.dart'; void main() => runApp(const MyApp()); @@ -38,6 +39,12 @@ class MyApp extends StatelessWidget { }, child: const Text('Demo 3'), ), + ElevatedButton( + onPressed: () { + navigate(context, const Demo4()); + }, + child: const Text('Listener Demo'), + ), ], ); }, diff --git a/example/lib/widgets/demo_4.dart b/example/lib/widgets/demo_4.dart new file mode 100644 index 0000000..4defc14 --- /dev/null +++ b/example/lib/widgets/demo_4.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_offline/flutter_offline.dart'; + +class Demo4 extends StatelessWidget { + const Demo4({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return OfflineListener( + debounceDuration: Duration.zero, + listenWhen: (c) => c == ConnectivityResult.mobile, + listenerBuilder: (context, connectivity) { + // for ex showing a snackbar when using mobile internet if app is downloading something big. + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text( + 'Alert: Switched to mobile internet, consider switching to wifi instead.'))); + }, + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Text( + 'There are no bottons to push :)', + ), + Text( + 'Try switching your internet to wifi and mobile data', + ), + ], + ), + ), + ); + } +} diff --git a/lib/flutter_offline.dart b/lib/flutter_offline.dart index 6029549..be90c19 100644 --- a/lib/flutter_offline.dart +++ b/lib/flutter_offline.dart @@ -1,5 +1,7 @@ library flutter_offline; -export 'package:connectivity_plus/connectivity_plus.dart' show ConnectivityResult; +export 'package:connectivity_plus/connectivity_plus.dart' + show ConnectivityResult; export 'src/main.dart'; +export 'src/offline_listener.dart'; diff --git a/lib/src/offline_listener.dart b/lib/src/offline_listener.dart new file mode 100644 index 0000000..8ba8d41 --- /dev/null +++ b/lib/src/offline_listener.dart @@ -0,0 +1,110 @@ +import 'dart:async'; + +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_offline/src/utils.dart'; +import 'package:network_info_plus/network_info_plus.dart'; + +import 'main.dart'; + +typedef ListenerBuilder = void Function(BuildContext context, T value); +typedef ListenWhen = bool Function(ConnectivityResult); + +class OfflineListener extends StatefulWidget { + factory OfflineListener({ + Key? key, + required Widget child, + ListenWhen? listenWhen, + Duration debounceDuration = kOfflineDebounceDuration, + ListenerBuilder? listenerBuilder, + WidgetBuilder? errorBuilder, + }) { + return OfflineListener.initialize( + key: key, + connectivityService: Connectivity(), + wifiInfo: NetworkInfo(), + debounceDuration: debounceDuration, + errorBuilder: errorBuilder, + listenWhen: listenWhen, + listenerBuilder: listenerBuilder, + child: child, + ); + } + + @visibleForTesting + const OfflineListener.initialize({ + Key? key, + required this.connectivityService, + required this.wifiInfo, + required this.child, + this.listenWhen, + this.listenerBuilder, + this.debounceDuration = kOfflineDebounceDuration, + this.errorBuilder, + }) : super(key: key); + + /// Override connectivity service used for testing + final Connectivity connectivityService; + + /// Specify when to listen + final ListenWhen? listenWhen; + + /// Listen to new updates on connectivity. Only notifies when listenWhen returns true. + /// + /// If listenWhen is null, will listen to all updates. + final ListenerBuilder? listenerBuilder; + + final NetworkInfo wifiInfo; + + /// Debounce duration from epileptic network situations + final Duration debounceDuration; + + /// The widget below this widget in the tree. + final Widget child; + + /// Used for building the error widget incase of any platform errors + final WidgetBuilder? errorBuilder; + + @override + OfflineListenerState createState() => OfflineListenerState(); +} + +class OfflineListenerState extends State { + late Stream _connectivityStream; + + @override + void initState() { + super.initState(); + + _connectivityStream = + Stream.fromFuture(widget.connectivityService.checkConnectivity()) + .asyncExpand((data) => widget + .connectivityService.onConnectivityChanged + .transform(startsWith(data))) + .transform(debounce(widget.debounceDuration)); + + _listenToChanges(); + } + + void _listenToChanges() { + _connectivityStream.listen((data) { + if (widget.listenWhen?.call(data) ?? true) { + widget.listenerBuilder?.call(context, data); + } + }); + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +} + +class OfflineListenerError extends Error { + OfflineListenerError(this.error); + + final Object error; + + @override + String toString() => error.toString(); +}