Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/add refresh when network available #24

Merged
merged 5 commits into from
Aug 22, 2024
Merged
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
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 @@ -13,6 +13,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);
});
});
}
Loading