Skip to content

Commit

Permalink
Merge pull request #24 from bamlab/feat/add-refresh-when-network-avai…
Browse files Browse the repository at this point in the history
…lable

Feat/add refresh when network available
  • Loading branch information
tanguymossion authored Aug 22, 2024
2 parents da7afbc + 586a1f2 commit fb9f101
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/riverpod_community_extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export 'src/cache_data_for_extension.dart';
export 'src/cache_for_extension.dart';
export 'src/debounce_extension.dart';
export 'src/refresh_extension.dart';
export 'src/refresh_when_network_available_extension.dart';
7 changes: 7 additions & 0 deletions lib/src/connectivity_stream_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:riverpod/riverpod.dart';

/// A stream provider that listens to network connectivity changes.
final connectivityStreamProvider = StreamProvider(
(ref) => Connectivity().onConnectivityChanged.distinct(),
);
60 changes: 60 additions & 0 deletions lib/src/refresh_when_network_available_extension.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_community_extensions/src/connectivity_stream_provider.dart';

/// Adds network-aware refresh functionality to AutoDisposeRef<T> objects.
/// This extension uses the connectivity_plus package to listen for network
/// status changes and refreshes the provider when the network becomes
/// available.
///
/// See [refreshWhenNetworkAvailable]
extension RefreshWhenNetworkAvailableExtension<T> on AutoDisposeRef<T> {
/// Refreshes the provider when the network becomes available after being
/// unavailable.
///
/// This can be useful for scenarios where data needs to be fetched or
/// operations need to be performed only when network connectivity is
/// restored, such as synchronizing local data with a server.
///
/// Example usages:
///
/// without codegen:
/// ```dart
/// final myProvider = Provider.autoDispose<int>((ref) {
/// ref.refreshWhenNetworkAvailable();
/// return fetchData(); // Assume fetchData is a function that fetches data over the network.
/// });
/// ```
///
/// with codegen:
/// ```dart
/// @riverpod
/// int myProvider(AutoDisposeRef ref) {
/// ref.refreshWhenNetworkAvailable();
/// return fetchData();
/// }
/// ```
void refreshWhenNetworkAvailable() {
var isNetworkAvailable = false;
const validResults = [
ConnectivityResult.mobile,
ConnectivityResult.wifi,
ConnectivityResult.ethernet,
ConnectivityResult.vpn,
ConnectivityResult.other,
];

listen(connectivityStreamProvider, (_, connectivityResults) {
connectivityResults.whenData((data) {
final currentlyAvailable =
data.any((result) => validResults.contains(result));
if (currentlyAvailable && !isNetworkAvailable) {
isNetworkAvailable = true;
invalidateSelf();
} else {
isNetworkAvailable = false;
}
});
});
}
}
69 changes: 69 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.18.0"
connectivity_plus:
dependency: "direct main"
description:
name: connectivity_plus
sha256: db7a4e143dc72cc3cb2044ef9b052a7ebfe729513e6a82943bc3526f784365b8
url: "https://pub.dev"
source: hosted
version: "6.0.3"
connectivity_plus_platform_interface:
dependency: transitive
description:
name: connectivity_plus_platform_interface
sha256: b6a56efe1e6675be240de39107281d4034b64ac23438026355b4234042a35adb
url: "https://pub.dev"
source: hosted
version: "2.0.0"
conventional_commit:
dependency: transitive
description:
Expand All @@ -89,6 +105,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.0+1"
dbus:
dependency: transitive
description:
name: dbus
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
url: "https://pub.dev"
source: hosted
version: "0.7.10"
fake_async:
dependency: transitive
description:
Expand All @@ -97,6 +121,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
file:
dependency: transitive
description:
Expand All @@ -115,6 +147,11 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
glob:
dependency: transitive
description:
Expand Down Expand Up @@ -243,6 +280,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
nm:
dependency: transitive
description:
name: nm
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
path:
dependency: transitive
description:
Expand All @@ -251,6 +296,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "6.0.2"
platform:
dependency: transitive
description:
Expand All @@ -259,6 +312,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.5"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
pool:
dependency: transitive
description:
Expand Down Expand Up @@ -432,6 +493,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.5.1"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
yaml:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dev_dependencies:
very_good_analysis: ^5.1.0

dependencies:
connectivity_plus: ^6.0.3
flutter:
sdk: flutter
riverpod: ^2.0.0
18 changes: 18 additions & 0 deletions test/src/connectivity_stream_provider_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_community_extensions/src/connectivity_stream_provider.dart';

void main() {
TestWidgetsFlutterBinding.ensureInitialized();

group('ConnectivityStreamProvider tests', () {
test('connectivityStreamProvider provides an AsyncValue', () {
final container = ProviderContainer();
final providerState = container.read(connectivityStreamProvider);

// Check that the initial state is AsyncLoading.
expect(providerState, isA<AsyncLoading<List<ConnectivityResult>>>());
});
});
}
77 changes: 77 additions & 0 deletions test/src/refresh_when_network_available_extension_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import 'dart:async';

import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_community_extensions/src/connectivity_stream_provider.dart';
import 'package:riverpod_community_extensions/src/refresh_when_network_available_extension.dart';

class MockConnectivity extends Mock implements Connectivity {}

void main() {
TestWidgetsFlutterBinding.ensureInitialized();

group('RefreshWhenNetworkAvailableExtension Tests', () {
late StreamController<List<ConnectivityResult>> connectivityController;
late ProviderContainer container;
late AutoDisposeProvider<int> myProvider;
late Stream<List<ConnectivityResult>> connectivityStream;

var numberOfFetchDataCalls = 0;

int fetchData() {
numberOfFetchDataCalls++;
return 42;
}

setUp(() {
numberOfFetchDataCalls = 0;
connectivityController = StreamController<List<ConnectivityResult>>();
connectivityStream = connectivityController.stream;

myProvider = Provider.autoDispose<int>((ref) {
ref.refreshWhenNetworkAvailable();
return fetchData();
});

container = ProviderContainer(
overrides: [
connectivityStreamProvider.overrideWith(
(ref) => connectivityStream,
),
],
)..listen(myProvider, (_, __) {});
});

tearDown(() {
connectivityController.close();
container.dispose();
});

test('Should not refresh when connectivity does not change to available',
() async {
expect(numberOfFetchDataCalls, 1);

// Simulate network being offline
connectivityController.add([ConnectivityResult.none]);

// Wait for the potential refresh to happen
await Future<void>.delayed(const Duration(seconds: 1));

expect(numberOfFetchDataCalls, 1);
});

test('Should refresh when connectivity changes to available', () async {
expect(numberOfFetchDataCalls, 1);

// Simulate network being online
connectivityController.add([ConnectivityResult.wifi]);

// Wait for the potential refresh to happen
await Future<void>.delayed(const Duration(seconds: 1));

expect(numberOfFetchDataCalls, 2);
});
});
}

0 comments on commit fb9f101

Please sign in to comment.