Skip to content

Commit

Permalink
chore: add new cache manager
Browse files Browse the repository at this point in the history
  • Loading branch information
mobinbyn committed Jan 5, 2025
1 parent 052ba73 commit 14cde75
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/core/app/res/const/app_constant.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ class AppConstant {
static const String appName = 'Flutter Template';
static const String baseUrl = '';
static const String getRefreshToken = '';
static const String userLocalStorageKey = 'USER_LOCAL_STORAGE_KEY';
}
12 changes: 12 additions & 0 deletions lib/core/app/service/interface/i_user_cache_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'package:dartz/dartz.dart';
import 'package:flutter_template/core/shared/data/data_source/error/failure.dart';
import 'package:flutter_template/core/shared/data/model/user_model.dart';

abstract class IUserCacheService {
String get storageKey;

Future<Either<Failure, User>> fetchUser();
Future<bool> saveUser({required User user});
Future<bool> deleteUser();
Future<bool> hasUser();
}
43 changes: 43 additions & 0 deletions lib/core/app/service/user_cache_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import 'dart:convert';

import 'package:dartz/dartz.dart';
import 'package:flutter_template/core/app/res/const/app_constant.dart';
import 'package:flutter_template/core/app/service/interface/i_user_cache_service.dart';
import 'package:flutter_template/core/shared/data/data_source/error/failure.dart';
import 'package:flutter_template/core/shared/data/data_source/local/interface/storage_service.dart';
import 'package:flutter_template/core/shared/data/model/user_model.dart';

class UserCacheService implements IUserCacheService {
UserCacheService(this.storageService);

final StorageService storageService;

@override
String get storageKey => AppConstant.userLocalStorageKey;

@override
Future<Either<Failure, User>> fetchUser() async {
final data = await storageService.get(storageKey);
if (data == null) {
return const Left(CacheFailure());
}
final userJson = jsonDecode(data.toString());

return Right(User.fromJson(userJson));
}

@override
Future<bool> saveUser({required User user}) async {
return await storageService.set(storageKey, jsonEncode(user.toJson()));
}

@override
Future<bool> deleteUser() async {
return await storageService.remove(storageKey);
}

@override
Future<bool> hasUser() async {
return await storageService.has(storageKey);
}
}
12 changes: 12 additions & 0 deletions lib/core/shared/data/data_source/error/failure.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ class Failure extends Equatable {
/// Overrides [Equatable]'s props to include the message and metadata for comparison.
@override
List<Object> get props => [message, metadata!]; // Forces metadata to be non-null.

/// Overrides [Equatable]'s toString method to return the message.
/// This is useful for debugging purposes.
@override
String toString() => 'Failure: $message\nMetadata: $metadata';
}

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

const CancelTokenFailure(super.errorMessage, this.statusCode);
}

/// A subclass of [Failure] representing a cache failure case.
/// Uses a cache error message from [ResponseMessage].
/// This is used when a cache operation fails.
class CacheFailure extends Failure {
const CacheFailure() : super(ResponseMessage.cacheError);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/// Storage service interface
abstract class StorageService {
void init();

bool get hasInitialized;

Future<bool> remove(String key);

Future<Object?> get(String key);

Future<bool> set(String key, String data);

Future<bool> has(String key);

Future<void> clear();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import 'dart:async';

import 'package:flutter_template/core/shared/data/data_source/local/interface/storage_service.dart';
import 'package:shared_preferences/shared_preferences.dart';

class SharedPrefsStorageService implements StorageService {
SharedPreferences? _sharedPreferences;

final Completer<SharedPreferences> initCompleter = Completer<SharedPreferences>();

@override
void init() {
initCompleter.complete(SharedPreferences.getInstance());
}

@override
bool get hasInitialized => _sharedPreferences != null;

@override
Future<Object?> get(String key) async {
_sharedPreferences = await initCompleter.future;
return _sharedPreferences!.get(key);
}

@override
Future<void> clear() async {
_sharedPreferences = await initCompleter.future;
await _sharedPreferences!.clear();
}

@override
Future<bool> has(String key) async {
_sharedPreferences = await initCompleter.future;
return _sharedPreferences?.containsKey(key) ?? false;
}

@override
Future<bool> remove(String key) async {
_sharedPreferences = await initCompleter.future;
return await _sharedPreferences!.remove(key);
}

@override
Future<bool> set(String key, data) async {
_sharedPreferences = await initCompleter.future;
return await _sharedPreferences!.setString(key, data);
}
}
60 changes: 60 additions & 0 deletions lib/core/shared/data/model/user_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:convert';

import 'package:equatable/equatable.dart';

class User extends Equatable {
final String id;
final String name;
final String email;
final String? imageUrl;

const User({
required this.id,
required this.name,
required this.email,
this.imageUrl,
});

@override
List<Object> get props => [id, name, email, imageUrl ?? ''];

User copyWith({
String? id,
String? name,
String? email,
String? imageUrl,
}) {
return User(
id: id ?? this.id,
name: name ?? this.name,
email: email ?? this.email,
imageUrl: imageUrl ?? this.imageUrl,
);
}

Map<String, dynamic> toMap() {
return <String, dynamic>{
'id': id,
'name': name,
'email': email,
'image_url': imageUrl,
};
}

factory User.fromMap(Map<String, dynamic> map) {
return User(
id: map['id'] as String,
name: map['name'] as String,
email: map['email'] as String,
imageUrl: map['image_url'] != null ? map['image_url'] as String : null,
);
}

String toJson() => json.encode(toMap());

factory User.fromJson(String source) => User.fromMap(json.decode(source) as Map<String, dynamic>);

@override
bool get stringify => true;
}

0 comments on commit 14cde75

Please sign in to comment.