Skip to content

Instantly share code, notes, and snippets.

@JasperEssien2
Created April 26, 2022 18:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JasperEssien2/ec11baed03319b44b809fe712693aa1e to your computer and use it in GitHub Desktop.
Save JasperEssien2/ec11baed03319b44b809fe712693aa1e to your computer and use it in GitHub Desktop.
import 'dart:async';
import 'dart:developer';
class FetchPolicyHandler {
FetchPolicyHandler._();
// ignore: long-parameter-list
static Future<Res> handleFetch<Res>({
FetchPolicy fetchPolicy = FetchPolicy.priorityCache,
required Future<Res> Function() fetchRemote,
required Future<Res> Function() fetchCache,
required Future<void> Function(Res res) cacheResponse,
bool awaitCacheThenRemote = false,
}) async {
switch (fetchPolicy) {
case FetchPolicy.priorityCache:
try {
return fetchCache();
} catch (e) {
return _fetchAndCacheRemote(fetchRemote, cacheResponse);
}
case FetchPolicy.cacheOnly:
return fetchCache();
case FetchPolicy.remoteOnly:
return fetchRemote();
case FetchPolicy.priorityNetwork:
return _fetchAndCacheRemote<Res>(fetchRemote, cacheResponse);
case FetchPolicy.cacheThenCacheRemote:
late final Res cacheData;
try {
cacheData = await fetchCache();
} catch (e) {
return _fetchAndCacheRemote(fetchRemote, cacheResponse);
}
try {
// ignore: unawaited_futures
if (awaitCacheThenRemote) {
await _fetchAndCacheRemote(fetchRemote, cacheResponse);
} else {
unawaited(_fetchAndCacheRemote<Res>(fetchRemote, cacheResponse));
}
} catch (e) {
log('Error in fetching remote');
}
return cacheData;
}
}
static Future<Res> _fetchAndCacheRemote<Res>(
Future<Res> Function() fetchRemote,
Function(Res res) cacheData,
) async {
final remoteData = await fetchRemote();
cacheData(remoteData);
return remoteData;
}
}
enum FetchPolicy {
/// Prioritize result from cache. Only fetch from network if cached result is
/// not available.
priorityCache,
/// Return result from cache if available, fail otherwise.
cacheOnly,
/// Return result from network, fail if network call doesn't succeed,
/// don't save to cache.
remoteOnly,
/// Prioritize result from network, fail if network call doesn't succeed,
/// save to cache.
priorityNetwork,
/// Prioritize result from cache, if cache fails get from remote,
/// Regardless this sync cache data with remote data.
/// One thing to note is that remote failure is handled quietly, provided cac
cacheThenCacheRemote,
}
import 'package:flutter_riverpod_starter_pack/core/http/http_export.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import '../../mocks/usecase_mock.dart';
void main() {
late MockReturnFunction<Future<String>> fetchRemote;
late MockReturnFunction<Future<String>> fetchCache;
late MockParamAndReturnFunction<String, Future<void>> cacheResponse;
setUp(
() {
fetchRemote = MockReturnFunction();
fetchCache = MockReturnFunction();
cacheResponse = MockParamAndReturnFunction();
},
);
group(
'Test FetchPolicy.priorityCache',
() {
test(
'Ensure that fetchCache() is called and data returned, '
'when cache successful',
() async {
_returnSuccess(fetchCache, success: 'Cache success');
expect(
await FetchPolicyHandler.handleFetch<String>(
fetchRemote: fetchRemote,
fetchCache: fetchCache,
cacheResponse: cacheResponse,
),
'Cache success',
);
verify(() => fetchCache());
verifyNever(() => fetchRemote());
verifyNever(() => cacheResponse(any()));
},
);
test(
'Ensure that fetchRemote() and cacheResponse() called when '
'fetchCache() is called but throws an error',
() async {
_doNothing(cacheResponse);
_throwException(fetchCache);
_returnSuccess(fetchRemote, success: 'Remote success');
expect(
await FetchPolicyHandler.handleFetch<String>(
fetchRemote: fetchRemote,
fetchCache: fetchCache,
cacheResponse: cacheResponse,
),
'Remote success',
);
verify(() => fetchCache());
verify(() => fetchRemote());
verify(() => cacheResponse('Remote success'));
},
);
test(
'Ensure that exception is thrown when fetchRemote() throws an exception ',
() async {
_throwException(fetchCache);
_throwException(fetchRemote);
expect(
() async => FetchPolicyHandler.handleFetch<String>(
fetchRemote: fetchRemote,
fetchCache: fetchCache,
cacheResponse: cacheResponse,
),
throwsException,
);
verify(() => fetchCache());
verify(() => fetchRemote());
verifyNever(() => cacheResponse(any()));
},
);
},
);
group(
'Test FetchPolicy.cacheOnly',
() {
test(
'Ensure that fetchCache() is called and fetchRemote() is never called '
'when FetchPolicy.cacheOnly is set',
() async {
_returnSuccess(fetchCache, success: 'Cache success');
expect(
await FetchPolicyHandler.handleFetch<String>(
fetchPolicy: FetchPolicy.cacheOnly,
fetchRemote: fetchRemote,
fetchCache: fetchCache,
cacheResponse: cacheResponse,
),
'Cache success',
);
verify(() => fetchCache());
verifyNever(() => cacheResponse(any()));
verifyNever(() => fetchRemote());
},
);
test(
'Ensure that error is thrown when FetchPolicy.cacheOnly is set '
'and fetchCache() throws error',
() async {
_throwException(fetchCache);
expect(
() async => FetchPolicyHandler.handleFetch<String>(
fetchPolicy: FetchPolicy.cacheOnly,
fetchRemote: fetchRemote,
fetchCache: fetchCache,
cacheResponse: cacheResponse,
),
throwsException,
);
verify(() => fetchCache());
verifyNever(() => cacheResponse(any()));
verifyNever(() => fetchRemote());
},
);
},
);
group(
'Test FetchPolicy.remoteOnly',
() {
test(
'Ensure that fetchRemote() is called and fetchCache() is never called '
'when FetchPolicy.remoteOnly is set',
() async {
_returnSuccess(fetchRemote, success: 'Remote success');
expect(
await FetchPolicyHandler.handleFetch<String>(
fetchPolicy: FetchPolicy.remoteOnly,
fetchRemote: fetchRemote,
fetchCache: fetchCache,
cacheResponse: cacheResponse,
),
'Remote success',
);
verify(() => fetchRemote());
verifyNever(() => fetchCache());
verifyNever(() => cacheResponse(any()));
},
);
test(
'Ensure that error is thrown when FetchPolicy.remoteOnly is set '
'and fetchRemote() throws error',
() async {
_throwException(fetchRemote);
expect(
() async => FetchPolicyHandler.handleFetch<String>(
fetchPolicy: FetchPolicy.remoteOnly,
fetchRemote: fetchRemote,
fetchCache: fetchCache,
cacheResponse: cacheResponse,
),
throwsException,
);
verify(() => fetchRemote());
verifyNever(() => fetchCache());
verifyNever(() => cacheResponse(any()));
},
);
},
);
group(
'Test FetchPolicy.priorityNetwork',
() {
test(
'Ensure that fetchRemote() and cacheResponse() is called and '
'fetchCache() is never called when FetchPolicy.priorityNetwork is set',
() async {
_doNothing(cacheResponse);
_returnSuccess(fetchRemote, success: 'Remote success');
expect(
await FetchPolicyHandler.handleFetch<String>(
fetchPolicy: FetchPolicy.priorityNetwork,
fetchRemote: fetchRemote,
fetchCache: fetchCache,
cacheResponse: cacheResponse,
),
'Remote success',
);
verify(() => fetchRemote());
verify(() => cacheResponse('Remote success'));
verifyNever(() => fetchCache());
},
);
test(
'Ensure that fetchRemote() is called and cacheResponse() and '
'fetchCache() is never called when FetchPolicy.priorityNetwork is set '
'and request fails',
() async {
_throwException(fetchRemote);
expect(
() async => FetchPolicyHandler.handleFetch<String>(
fetchPolicy: FetchPolicy.priorityNetwork,
fetchRemote: fetchRemote,
fetchCache: fetchCache,
cacheResponse: cacheResponse,
),
throwsException,
);
verify(() => fetchRemote());
verifyNever(() => cacheResponse(any()));
verifyNever(() => fetchCache());
},
);
},
);
group(
'Test FetchPolicy.cacheThenCacheRemote',
() {
test(
'Ensure that fetchCache() is called and data returned, '
'when cache successful and awaitCacheThenRemote is true ',
() async {
_doNothing(cacheResponse);
_returnSuccess(fetchCache, success: 'Cache success');
_returnSuccess(fetchRemote, success: 'Remote success');
expect(
await FetchPolicyHandler.handleFetch<String>(
fetchPolicy: FetchPolicy.cacheThenCacheRemote,
fetchRemote: fetchRemote,
fetchCache: fetchCache,
cacheResponse: cacheResponse,
awaitCacheThenRemote: true,
),
'Cache success',
);
verify(() => fetchCache());
verify(() => fetchRemote());
verify(() => cacheResponse('Remote success'));
},
);
test(
'Ensure that fetchRemote() is not waited for '
'when cache successful and awaitCacheThenRemote is false ',
() async {
_doNothing(cacheResponse);
_returnSuccess(fetchCache, success: 'Cache success');
_returnSuccess(fetchRemote, success: 'Remote success');
expect(
await FetchPolicyHandler.handleFetch<String>(
fetchPolicy: FetchPolicy.cacheThenCacheRemote,
fetchRemote: fetchRemote,
fetchCache: fetchCache,
cacheResponse: cacheResponse,
awaitCacheThenRemote: false,
),
'Cache success',
);
verify(() => fetchCache());
verify(() => fetchRemote());
verifyNever(() => cacheResponse('Remote success'));
},
);
test(
'Ensure that fetchRemote() and cacheResponse() called when '
'fetchCache() is called but throws an error',
() async {
_doNothing(cacheResponse);
_throwException(fetchCache);
_returnSuccess(fetchRemote, success: 'Remote success');
expect(
await FetchPolicyHandler.handleFetch<String>(
fetchPolicy: FetchPolicy.cacheThenCacheRemote,
fetchRemote: fetchRemote,
fetchCache: fetchCache,
cacheResponse: cacheResponse,
awaitCacheThenRemote: true,
),
'Remote success',
);
verify(() => fetchCache());
verify(() => fetchRemote());
verify(() => cacheResponse('Remote success'));
},
);
test(
'Ensure that exception is NOT thrown when fetchRemote() '
'throws an exception ',
() async {
_doNothing(cacheResponse);
_returnSuccess(fetchCache, success: 'Cache Success');
_throwException(fetchRemote);
expect(
await FetchPolicyHandler.handleFetch<String>(
fetchPolicy: FetchPolicy.cacheThenCacheRemote,
fetchRemote: fetchRemote,
fetchCache: fetchCache,
cacheResponse: cacheResponse,
awaitCacheThenRemote: true,
),
'Cache Success',
);
verify(() => fetchCache());
verify(() => fetchRemote());
verifyNever(() => cacheResponse(any()));
},
);
},
);
test(
'Ensure that FetchPolicy enum contains required',
() {
expect(
FetchPolicy.values,
[
FetchPolicy.priorityCache,
FetchPolicy.cacheOnly,
FetchPolicy.remoteOnly,
FetchPolicy.priorityNetwork,
FetchPolicy.cacheThenCacheRemote,
],
);
},
);
}
void _throwException(MockReturnFunction func) {
when(() => func()).thenThrow(Exception('An error ocurred'));
}
void _returnSuccess(MockReturnFunction func, {String success = 'Success'}) {
when(() => func()).thenAnswer((_) => Future.value(success));
}
void _doNothing(MockParamAndReturnFunction<String, Future<void>> func) {
when(() => func(any())).thenAnswer((_) => Future.value());
}
class UserRepositoryImpl implements UserRepository {
UserRepositoryImpl({
required UserRemoteDataSource remoteDataSource,
required UserLocalDataSource localDataSource,
}) : _dataSource = remoteDataSource,
_localDataSource = localDataSource;
final UserRemoteDataSource _dataSource;
final UserLocalDataSource _localDataSource;
@override
Future<Either<String, List<UserModel>>> getUsers({
FetchPolicy fetchPolicy = FetchPolicy.priorityCache,
}) async {
try {
final response = await FetchPolicyHandler.handleFetch<List<UserModel>>(
fetchRemote: _dataSource.getUsers,
fetchCache: _localDataSource.getUsers,
cacheResponse: _localDataSource.cacheUser,
fetchPolicy: fetchPolicy,
);
return Right(response);
} on Exception catch (e) {
return Left(e.toString());
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment