Motivation
Imagine a user launching the app for the second time and seeing a skeletal loading screen or progress bar, especially in parts of the UI where the data doesn't change often. This is not a good UX.
To fix this, we can load the data from the cache in the first step, and then update the data in the second step.
We don't aim to minimize requests to the server, but to minimize the time it takes to display the data to the user.
Using
import 'package:http/http.dart' as http;
import 'package:json_fetcher/json_fetcher.dart';
import 'package:path_provider/path_provider.dart';
Future<String> get cachePath => getApplicationCacheDirectory().then((dir) => dir.path);
final client = JsonHttpClient(http.Client(), createCache(cachePath));
final postsStream = JsonFetcher<Model>>(
client,
(json) => Model.fromJson(json),
).fetch('https://example.com/get-json');
Tip
Examples can be found in the example directory:
Configuration
At the first step you should create JsonHttpClient:
final jsonClient = JsonHttpClient(httpClient, cache);
HttpClient
This package uses standard Dart http package.
Basicaly, is enough to use Client() for creating raw client:
final jsonClient = JsonHttpClient(Client(), cache);
So, you can configure client more precisely before creating JsonHttpClient itself.
Client httpClient() {
if (Platform.isAndroid) {
final engine = CronetEngine.build(
cacheMode: CacheMode.memory,
cacheMaxSize: 1000000);
return CronetClient.fromCronetEngine(engine);
}
if (Platform.isIOS || Platform.isMacOS) {
final config = URLSessionConfiguration.ephemeralSessionConfiguration()
..cache = URLCache.withCapacity(memoryCapacity: 1000000);
return CupertinoClient.fromSessionConfiguration(config);
}
return IOClient();
}
final jsonClient = JsonHttpClient(httpClient(), cache);
Tip
The package contains convenitent client-wrapper with logging capabitility LoggableHttpClient, see Flutter example as use case.
Cache
For creating cache
just use convenient function createCache(path)
.
Note
path
can be String
of Future<String>
Future<String>
variant is very useful for creating a client somewhere in DI
at app startup, to prevent unneded waiting.
In Flutter Android/iOS app you can create client that caches data in standard cache directory with following snippet:
import 'package:path_provider/path_provider.dart';
Future<String> get path => getApplicationCacheDirectory().then((value) => value.path);
final jsonClient = JsonHttpClient(httpClient(), createCache(path));
Tip
Package exposes interface HttpCache, that can be implemented to use your own cache.
How it works
If the data has changed (new data has arrived from the network), it will be updated in the second step.
That's why we use Stream<T>
instead of Future<T>
. The stream's subscriber will get two snapshots.
If the data hasn't changed, the stream's subscriber will get one snapshot
If there is no cached copy, the stream's subscriber will get one snapshot.
Cache
The cache data is managed by implementations of HttpCache.
dart:io (mobile/desktop)
HttpFilesCache stores data in files. Performance improved by using concurrent IO with synchronization by keys.
Web
HttpWebCache stores data in IndexedDB. It uses package:web from Dart team.