Skip to content

Commit 14cde75

Browse files
committed
chore: add new cache manager
1 parent 052ba73 commit 14cde75

7 files changed

Lines changed: 192 additions & 0 deletions

File tree

lib/core/app/res/const/app_constant.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ class AppConstant {
44
static const String appName = 'Flutter Template';
55
static const String baseUrl = '';
66
static const String getRefreshToken = '';
7+
static const String userLocalStorageKey = 'USER_LOCAL_STORAGE_KEY';
78
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import 'package:dartz/dartz.dart';
2+
import 'package:flutter_template/core/shared/data/data_source/error/failure.dart';
3+
import 'package:flutter_template/core/shared/data/model/user_model.dart';
4+
5+
abstract class IUserCacheService {
6+
String get storageKey;
7+
8+
Future<Either<Failure, User>> fetchUser();
9+
Future<bool> saveUser({required User user});
10+
Future<bool> deleteUser();
11+
Future<bool> hasUser();
12+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import 'dart:convert';
2+
3+
import 'package:dartz/dartz.dart';
4+
import 'package:flutter_template/core/app/res/const/app_constant.dart';
5+
import 'package:flutter_template/core/app/service/interface/i_user_cache_service.dart';
6+
import 'package:flutter_template/core/shared/data/data_source/error/failure.dart';
7+
import 'package:flutter_template/core/shared/data/data_source/local/interface/storage_service.dart';
8+
import 'package:flutter_template/core/shared/data/model/user_model.dart';
9+
10+
class UserCacheService implements IUserCacheService {
11+
UserCacheService(this.storageService);
12+
13+
final StorageService storageService;
14+
15+
@override
16+
String get storageKey => AppConstant.userLocalStorageKey;
17+
18+
@override
19+
Future<Either<Failure, User>> fetchUser() async {
20+
final data = await storageService.get(storageKey);
21+
if (data == null) {
22+
return const Left(CacheFailure());
23+
}
24+
final userJson = jsonDecode(data.toString());
25+
26+
return Right(User.fromJson(userJson));
27+
}
28+
29+
@override
30+
Future<bool> saveUser({required User user}) async {
31+
return await storageService.set(storageKey, jsonEncode(user.toJson()));
32+
}
33+
34+
@override
35+
Future<bool> deleteUser() async {
36+
return await storageService.remove(storageKey);
37+
}
38+
39+
@override
40+
Future<bool> hasUser() async {
41+
return await storageService.has(storageKey);
42+
}
43+
}

lib/core/shared/data/data_source/error/failure.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ class Failure extends Equatable {
1616
/// Overrides [Equatable]'s props to include the message and metadata for comparison.
1717
@override
1818
List<Object> get props => [message, metadata!]; // Forces metadata to be non-null.
19+
20+
/// Overrides [Equatable]'s toString method to return the message.
21+
/// This is useful for debugging purposes.
22+
@override
23+
String toString() => 'Failure: $message\nMetadata: $metadata';
1924
}
2025

2126
/// A subclass of [Failure] representing a default failure case.
@@ -31,3 +36,10 @@ class CancelTokenFailure extends Failure {
3136

3237
const CancelTokenFailure(super.errorMessage, this.statusCode);
3338
}
39+
40+
/// A subclass of [Failure] representing a cache failure case.
41+
/// Uses a cache error message from [ResponseMessage].
42+
/// This is used when a cache operation fails.
43+
class CacheFailure extends Failure {
44+
const CacheFailure() : super(ResponseMessage.cacheError);
45+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/// Storage service interface
2+
abstract class StorageService {
3+
void init();
4+
5+
bool get hasInitialized;
6+
7+
Future<bool> remove(String key);
8+
9+
Future<Object?> get(String key);
10+
11+
Future<bool> set(String key, String data);
12+
13+
Future<bool> has(String key);
14+
15+
Future<void> clear();
16+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import 'dart:async';
2+
3+
import 'package:flutter_template/core/shared/data/data_source/local/interface/storage_service.dart';
4+
import 'package:shared_preferences/shared_preferences.dart';
5+
6+
class SharedPrefsStorageService implements StorageService {
7+
SharedPreferences? _sharedPreferences;
8+
9+
final Completer<SharedPreferences> initCompleter = Completer<SharedPreferences>();
10+
11+
@override
12+
void init() {
13+
initCompleter.complete(SharedPreferences.getInstance());
14+
}
15+
16+
@override
17+
bool get hasInitialized => _sharedPreferences != null;
18+
19+
@override
20+
Future<Object?> get(String key) async {
21+
_sharedPreferences = await initCompleter.future;
22+
return _sharedPreferences!.get(key);
23+
}
24+
25+
@override
26+
Future<void> clear() async {
27+
_sharedPreferences = await initCompleter.future;
28+
await _sharedPreferences!.clear();
29+
}
30+
31+
@override
32+
Future<bool> has(String key) async {
33+
_sharedPreferences = await initCompleter.future;
34+
return _sharedPreferences?.containsKey(key) ?? false;
35+
}
36+
37+
@override
38+
Future<bool> remove(String key) async {
39+
_sharedPreferences = await initCompleter.future;
40+
return await _sharedPreferences!.remove(key);
41+
}
42+
43+
@override
44+
Future<bool> set(String key, data) async {
45+
_sharedPreferences = await initCompleter.future;
46+
return await _sharedPreferences!.setString(key, data);
47+
}
48+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// ignore_for_file: public_member_api_docs, sort_constructors_first
2+
import 'dart:convert';
3+
4+
import 'package:equatable/equatable.dart';
5+
6+
class User extends Equatable {
7+
final String id;
8+
final String name;
9+
final String email;
10+
final String? imageUrl;
11+
12+
const User({
13+
required this.id,
14+
required this.name,
15+
required this.email,
16+
this.imageUrl,
17+
});
18+
19+
@override
20+
List<Object> get props => [id, name, email, imageUrl ?? ''];
21+
22+
User copyWith({
23+
String? id,
24+
String? name,
25+
String? email,
26+
String? imageUrl,
27+
}) {
28+
return User(
29+
id: id ?? this.id,
30+
name: name ?? this.name,
31+
email: email ?? this.email,
32+
imageUrl: imageUrl ?? this.imageUrl,
33+
);
34+
}
35+
36+
Map<String, dynamic> toMap() {
37+
return <String, dynamic>{
38+
'id': id,
39+
'name': name,
40+
'email': email,
41+
'image_url': imageUrl,
42+
};
43+
}
44+
45+
factory User.fromMap(Map<String, dynamic> map) {
46+
return User(
47+
id: map['id'] as String,
48+
name: map['name'] as String,
49+
email: map['email'] as String,
50+
imageUrl: map['image_url'] != null ? map['image_url'] as String : null,
51+
);
52+
}
53+
54+
String toJson() => json.encode(toMap());
55+
56+
factory User.fromJson(String source) => User.fromMap(json.decode(source) as Map<String, dynamic>);
57+
58+
@override
59+
bool get stringify => true;
60+
}

0 commit comments

Comments
 (0)