Created
April 26, 2022 18:33
-
-
Save JasperEssien2/ec11baed03319b44b809fe712693aa1e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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