Skip to content

Commit 5d0ee4e

Browse files
committed
feat: add custom cache and fig performance
1 parent 1bf9d65 commit 5d0ee4e

13 files changed

+365
-331
lines changed

.flutter-plugins-dependencies

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"path_provider_foundation","path":"/Users/redev.rx/.pub-cache/hosted/pub.dev/path_provider_foundation-2.3.1/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"android":[{"name":"path_provider_android","path":"/Users/redev.rx/.pub-cache/hosted/pub.dev/path_provider_android-2.2.2/","native_build":true,"dependencies":[]}],"macos":[{"name":"path_provider_foundation","path":"/Users/redev.rx/.pub-cache/hosted/pub.dev/path_provider_foundation-2.3.1/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/redev.rx/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/redev.rx/.pub-cache/hosted/pub.dev/path_provider_windows-2.2.1/","native_build":false,"dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]}],"date_created":"2024-01-11 15:51:06.729086","version":"3.16.5"}
1+
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"path_provider_foundation","path":"/Users/redev.rx/.pub-cache/hosted/pub.dev/path_provider_foundation-2.3.1/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"android":[{"name":"path_provider_android","path":"/Users/redev.rx/.pub-cache/hosted/pub.dev/path_provider_android-2.2.2/","native_build":true,"dependencies":[]}],"macos":[{"name":"path_provider_foundation","path":"/Users/redev.rx/.pub-cache/hosted/pub.dev/path_provider_foundation-2.3.1/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/redev.rx/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/redev.rx/.pub-cache/hosted/pub.dev/path_provider_windows-2.2.1/","native_build":false,"dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]}],"date_created":"2024-01-11 18:39:24.736531","version":"3.16.5"}

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## 1.0.1
2+
- Add Custom Cache Manager
3+
- Fix Performance
14
## 1.0.0
25
- add Fade in image
36
## 0.0.6

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ and the Flutter guide for
2020
## Getting started
2121

2222
```dart
23-
rxcache_network_image: 1.0.0
23+
rxcache_network_image: 1.0.1
2424
```
2525

2626
## Usage
@@ -41,6 +41,20 @@ cacheManager.download()
4141
cacheManager.getFile()
4242
```
4343

44+
## Create Custom CacheManager
45+
```dart
46+
class CustomCacheManager extends BaseRxCacheManager {
47+
static CustomCacheManager? _instance;
48+
49+
CustomCacheManager._({String folder = "rx_image_cache"}) : super(folder: folder);
50+
51+
factory CustomCacheManager({String folder = "rx_image_cache"}) {
52+
_instance ??= CustomCacheManager._(folder: folder);
53+
return _instance!;
54+
}
55+
}
56+
```
57+
4458
## Example
4559
```dart
4660
class _MyHomePageState extends State<MyHomePage> {

example/pubspec.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ packages:
265265
path: ".."
266266
relative: true
267267
source: path
268-
version: "1.0.0"
268+
version: "1.0.1"
269269
rxdart:
270270
dependency: transitive
271271
description:

lib/rxcache_network_image.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export 'src/cache_manager/rxcache_manager.dart';
2-
export 'src/rx_image.dart';
3-
export 'src/rxcache_image_provider.dart';
2+
export 'src/cache_manager/base_rx_cache_manager.dart';
3+
export 'src/image/rx_image.dart';
4+
export 'src/provider/rxcache_image_provider.dart';
Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
import 'dart:async';
2+
import 'dart:io';
3+
4+
import 'package:flutter/cupertino.dart';
5+
import 'package:flutter/foundation.dart';
6+
import 'package:path_provider/path_provider.dart';
7+
import 'package:rxcache_network_image/src/cache_manager/rx_cache_manager_mixing.dart';
8+
9+
class BaseRxCacheManager implements RxCacheManagerMixing {
10+
final String _folder;
11+
BaseRxCacheManager({required String folder}) : _folder = folder {
12+
_createCacheFolder();
13+
}
14+
15+
// Do not access this field directly; use [_httpClient] instead.
16+
// We set `autoUncompress` to false to ensure that we can trust the value of
17+
// the `Content-Length` HTTP header. We automatically uncompress the content
18+
// in our call to [consolidateHttpClientResponseBytes].
19+
static final HttpClient _sharedHttpClient = HttpClient()
20+
..autoUncompress = false
21+
..connectionTimeout = const Duration(seconds: 20)
22+
..idleTimeout = const Duration(seconds: 20);
23+
24+
static HttpClient get _httpClient {
25+
HttpClient? client;
26+
assert(() {
27+
if (debugNetworkImageHttpClientProvider != null) {
28+
client = debugNetworkImageHttpClientProvider!();
29+
}
30+
return true;
31+
}());
32+
return client ?? _sharedHttpClient;
33+
}
34+
35+
final Map<String, Uint8List> _cacheImages = {};
36+
final Map<String, StreamController<bool>> _loadImageTask = {};
37+
38+
String? _cacheFolder;
39+
int _maxMemoryCache = 16;
40+
41+
@override
42+
void clearCache() async {
43+
final cache = await getCache();
44+
try {
45+
final folder = Directory(cache);
46+
if (folder.existsSync()) {
47+
await for (final file in folder.list()) {
48+
if (file is File) {
49+
await file.delete();
50+
}
51+
}
52+
}
53+
} catch (_) {}
54+
}
55+
56+
///[download]
57+
///download file from url and check exits in disk
58+
@override
59+
Future<void> download({
60+
String? url,
61+
Map<String, String>? headers,
62+
String? key,
63+
}) async {
64+
if (_cacheFolder == '' || _cacheFolder == null) {
65+
await getCache();
66+
}
67+
if (url == null || url.isEmpty == true) return;
68+
final fileName = key ?? Uri.parse(url).pathSegments.lastOrNull;
69+
70+
///check waiting download
71+
if (!_loadImageTask.containsKey(fileName)) {
72+
_loadImageTask[fileName ?? ''] = StreamController();
73+
_queueLoad(fileName ?? '', url, headers, key);
74+
}
75+
}
76+
77+
void _queueLoad(
78+
String fileName,
79+
String url,
80+
Map<String, String>? headers,
81+
String? key,
82+
) async {
83+
if (_loadImageTask.isNotEmpty) {
84+
await _download(fileName, url, headers, key);
85+
}
86+
}
87+
88+
Future<void> _download(
89+
String fileName,
90+
String url,
91+
Map<String, String>? headers,
92+
String? key,
93+
) async {
94+
try {
95+
if (fileName.isEmpty) return;
96+
final mFile = File("$cacheFolder/$fileName");
97+
98+
///exists file in disk
99+
if (await mFile.exists()) {
100+
await _loadImageTask[fileName]?.close();
101+
_loadImageTask.remove(fileName);
102+
return;
103+
}
104+
105+
final Uri resolved = Uri.base.resolve(url);
106+
final HttpClientRequest request = await _httpClient.getUrl(resolved);
107+
headers?.forEach((String name, String value) {
108+
request.headers.add(name, value);
109+
});
110+
111+
final HttpClientResponse response = await request.close();
112+
if (response.statusCode != HttpStatus.ok) {
113+
await response.drain<List<int>>(<int>[]);
114+
throw NetworkImageLoadException(
115+
statusCode: response.statusCode, uri: resolved);
116+
}
117+
final Uint8List bytes = await consolidateHttpClientResponseBytes(
118+
response,
119+
onBytesReceived: (_, __) {},
120+
);
121+
122+
setImageCache(fileName, bytes);
123+
124+
///save file to dis
125+
resizeAndSave(fileName, mFile, bytes);
126+
} catch (_) {
127+
await _loadImageTask[fileName]?.close();
128+
_loadImageTask.remove(fileName);
129+
}
130+
}
131+
132+
Future<void> _createCacheFolder() async {
133+
final path = await getApplicationCacheDirectory();
134+
final mFile = File("${path.path}/$_folder");
135+
Directory(mFile.path).createSync(recursive: true);
136+
_cacheFolder = mFile.path;
137+
}
138+
139+
@override
140+
Future<File?> getFile({String? url, String? key}) async {
141+
File? mFile;
142+
try {
143+
if (cacheFolder.isEmpty) {
144+
await _createCacheFolder();
145+
}
146+
final mKey = url == null ? key : Uri.parse(url).pathSegments.last;
147+
mFile = File("$_cacheFolder/$mKey");
148+
149+
///check exit in memory
150+
if (_cacheImages.containsKey(mKey)) {
151+
mFile = File.fromRawPath(_cacheImages[mKey]!);
152+
} else {
153+
if (!await mFile.exists()) {
154+
mFile = null;
155+
} else {
156+
final bytes = await mFile.readAsBytes();
157+
setImageCache(mKey ?? '', bytes);
158+
}
159+
}
160+
} catch (_, __) {
161+
mFile = null;
162+
}
163+
164+
return mFile;
165+
}
166+
167+
@override
168+
String get cacheFolder => _cacheFolder ?? '';
169+
170+
@override
171+
Future<String> getCache() async {
172+
if (_cacheFolder == null || _cacheFolder?.isEmpty == true) {
173+
await _createCacheFolder();
174+
}
175+
176+
return cacheFolder;
177+
}
178+
179+
///[downloadStream]
180+
///download file from url no check exits in disk
181+
///and cache to memory
182+
@override
183+
Future<Uint8List?> downloadStream({
184+
String? url,
185+
Map<String, String>? headers,
186+
String? key,
187+
void Function(int, int?)? onBytesReceived,
188+
}) async {
189+
final Uri resolved = Uri.base.resolve(url ?? '');
190+
final fileName = key ?? resolved.pathSegments.lastOrNull;
191+
final mFile = File("$cacheFolder/$fileName");
192+
if (url == null) return null;
193+
194+
try {
195+
///
196+
if (_loadImageTask.containsKey(fileName)) {
197+
///wait for downloading
198+
await for (final _ in _loadImageTask[fileName]!.stream) {}
199+
final fileBytes = getFormMemoryCache(fileName ?? '');
200+
if (fileBytes != null) {
201+
return fileBytes;
202+
}
203+
}
204+
205+
if (await mFile.exists()) {
206+
final fileBytes = getFormMemoryCache(fileName ?? '');
207+
if (fileBytes != null) {
208+
return fileBytes;
209+
} else {
210+
final bytes = await mFile.readAsBytes();
211+
return bytes;
212+
}
213+
}
214+
215+
_loadImageTask[fileName ?? ''] = StreamController();
216+
final HttpClientRequest request = await _httpClient.getUrl(resolved);
217+
headers?.forEach((String name, String value) {
218+
request.headers.add(name, value);
219+
});
220+
221+
final HttpClientResponse response = await request.close();
222+
if (response.statusCode != HttpStatus.ok) {
223+
await response.drain<List<int>>(<int>[]);
224+
throw NetworkImageLoadException(
225+
statusCode: response.statusCode, uri: resolved);
226+
}
227+
228+
final Uint8List bytes = await consolidateHttpClientResponseBytes(
229+
response,
230+
onBytesReceived: onBytesReceived,
231+
);
232+
233+
///set to memory cache
234+
setImageCache(fileName ?? '', bytes);
235+
236+
///save file to disk
237+
resizeAndSave(fileName ?? '', mFile, bytes);
238+
239+
return bytes;
240+
} catch (_) {
241+
await _loadImageTask[fileName]?.close();
242+
_loadImageTask.remove(fileName);
243+
final bytes = getFormMemoryCache(fileName ?? '');
244+
245+
return bytes;
246+
}
247+
}
248+
249+
void resizeAndSave(String fileName, File filePath, Uint8List bytes) async {
250+
await filePath.writeAsBytes(bytes);
251+
_loadImageTask[fileName]
252+
?..sink
253+
..add(
254+
true,
255+
);
256+
await _loadImageTask[fileName]?.close();
257+
_loadImageTask.remove(fileName);
258+
}
259+
260+
@override
261+
void setMemoryCache(int size) {
262+
if (size <= 1) return;
263+
_maxMemoryCache = size;
264+
}
265+
266+
@override
267+
String get memorySize => '$_maxMemoryCache';
268+
269+
@override
270+
void setImageCache(String key, Uint8List bytes) {
271+
if (_cacheImages.length >= _maxMemoryCache) {
272+
_cacheImages.clear();
273+
}
274+
275+
if (!_cacheImages.containsKey(key)) {
276+
_cacheImages[key] = bytes;
277+
}
278+
}
279+
280+
@override
281+
void clearMemoryCache() {
282+
_cacheImages.clear();
283+
}
284+
285+
@override
286+
Uint8List? getFormMemoryCache(String key) {
287+
if (_cacheImages.containsKey(key)) {
288+
return _cacheImages[key];
289+
}
290+
291+
return null;
292+
}
293+
294+
@override
295+
int currentMemoryCacheSize() => _cacheImages.length;
296+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import 'dart:io';
2+
import 'dart:typed_data';
3+
4+
mixin RxCacheManagerMixing {
5+
String get cacheFolder;
6+
String get memorySize;
7+
8+
void clearCache();
9+
void clearMemoryCache();
10+
Future<void> download({
11+
String? url,
12+
Map<String, String>? headers,
13+
String? key,
14+
});
15+
16+
Future<Uint8List?> downloadStream({
17+
String? url,
18+
Map<String, String>? headers,
19+
String? key,
20+
void Function(int, int?)? onBytesReceived,
21+
});
22+
23+
Future<File?> getFile({String? url, String? key});
24+
Future<String> getCache();
25+
void setMemoryCache(int size);
26+
void setImageCache(String key, Uint8List bytes);
27+
Uint8List? getFormMemoryCache(String key);
28+
int currentMemoryCacheSize();
29+
}

0 commit comments

Comments
 (0)