Reference location:
.claude/references/patterns/caching-patterns.md
Optimizes user experience and network efficiency through data caching strategies.
Refer to the /client-cache skill for detailed implementation guide.
Strategy Comparison#
| Strategy | Entry Point | Return Type | Suitable For |
|---|---|---|---|
| SWR | watchStream() |
Stream<Either<Failure, T>> |
GET lists, externally mutable |
| CacheFirst | execute() |
Future<Either<Failure, T>> |
GET single, offline first |
| NetworkFirst | execute() |
Future<Either<Failure, T>> |
Payment/auth, always need latest |
SWR Pattern (Stale-While-Revalidate)#
Returns cached data immediately and refreshes in the background.
Repository Interface#
// domain/repository/i_schedule_repository.dart
abstract interface class IScheduleRepository {
/// Returns Stream using SWR strategy
Stream<Either<Failure, List<ScheduleDayData>>> getScheduleData({
required DateTime startDate,
required DateTime endDate,
String? classId,
});
}
Repository Mixin (Strategy Assembly)#
mixin ScheduleOpenApiMixin implements IScheduleRepository {
OpenApiService get openApiService;
ScheduleItemDao get scheduleItemDao;
@override
Stream<Either<Failure, List<ScheduleDayData>>> getScheduleData({
required DateTime startDate,
required DateTime endDate,
String? classId,
}) =>
SwrStrategyImpl<List<ScheduleDayData>>(
cacheRepository: ScheduleDataCacheRepository(
scheduleItemDao: scheduleItemDao,
),
networkRepository: ScheduleDataNetworkRepository(
openApiService: openApiService,
),
policy: CachePolicies.standard,
).watchStream(
ScheduleDataCacheQuery(
startDate: startDate,
endDate: endDate,
classId: classId,
),
);
}
BLoC Integration: emit.forEach + restartable()#
class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
ScheduleBloc() : super(const ScheduleState()) {
on<_LoadData>(_onLoadData, transformer: restartable());
}
Future<void> _onLoadData(_LoadData event, Emitter<ScheduleState> emit) async {
await emit.forEach(
const GetScheduleDataUseCase().call(
GetScheduleDataParams(
startDate: event.startDate,
endDate: event.endDate,
),
),
onData: (result) => result.fold(
(failure) => state.copyWith(status: Status.failure),
(data) => state.copyWith(status: Status.success, data: data),
),
onError: (_, __) => state.copyWith(status: Status.failure),
);
}
}
How restartable() Works
1. add(LoadData(start: mar1)) โ ์คํธ๋ฆผA ๊ตฌ๋
(์บ์ โ ์๋ฒ ์์ฐจ emit)
2. add(LoadData(start: apr1)) โ restartable()๊ฐ ํธ๋ค๋ฌ1 ์ทจ์ โ ์คํธ๋ฆผA ํด์
โ ํธ๋ค๋ฌ2 ์์ โ ์คํธ๋ฆผB ๊ตฌ๋
emit.forEach๋ ๋ด๋ถ์ ์ผ๋ก stream.listen()์ Callํ๋ฉฐ, restartable()๊ฐ ํธ๋ค๋ฌ๋ฅผ ์ทจ์ํ๋ฉด ๊ตฌ๋
๋ Auto ์ ๋ฆฌ๋ฉ๋๋ค.
close() ์ค๋ฒ๋ผ์ด๋์ StreamSubscription ๊ด๋ฆฌ๊ฐ Not neededํฉ๋๋ค.
CacheFirst Pattern#
Returns immediately without network call if cache is valid. Makes network call if expired or missing.
Repository Mixin#
@override
Future<Either<Failure, ScheduleMemo?>> getMemo({
required DateTime date,
}) =>
CacheFirstStrategyImpl<ScheduleMemo?>(
cacheRepository: ScheduleMemoCacheRepository(
memoCacheDao: memoCacheDao,
),
networkRepository: ScheduleMemoNetworkRepository(
openApiService: openApiService,
),
policy: CachePolicies.standard,
).execute(
ScheduleMemoCacheQuery(date: date),
);
BLoC Integration#
Future<void> _onDetailLoaded(
_DetailLoaded event,
Emitter<ScheduleState> emit,
) async {
emit(state.copyWith(detailStatus: Status.loading));
final result = await const GetMemoUseCase().call(
GetMemoParams(date: event.date),
);
if (isClosed) return; // Required
result.fold(
(failure) => emit(state.copyWith(detailStatus: Status.failure)),
(memo) => emit(state.copyWith(detailStatus: Status.success, memo: memo)),
);
}
NetworkFirst Pattern#
Makes network call first and falls back to cache on failure.
Repository Mixin#
@override
Future<Either<Failure, PaymentStatus>> getPaymentStatus({
required String orderId,
}) =>
NetworkFirstStrategyImpl<PaymentStatus>(
cacheRepository: PaymentStatusCacheRepository(
paymentStatusDao: paymentStatusDao,
),
networkRepository: PaymentStatusNetworkRepository(
openApiService: openApiService,
),
policy: CachePolicies.realtime,
).execute(
PaymentStatusCacheQuery(orderId: orderId),
);
Local Cache Implementation (Drift)#
Tables (Row tables only, JSON blob prohibited)#
@DataClassName('ScheduleItemData')
class ScheduleItems extends Table {
TextColumn get cacheKey => text()(); // Required
DateTimeColumn get date => dateTime()(); // Parent field
TextColumn get id => text()();
TextColumn get title => text()();
DateTimeColumn get cachedAt => dateTime()(); // Required
// No primaryKey (use rowid)
}
DAO (Drift CRUD only, no domain entity dependency)#
// DELETE(cacheKey) + INSERT (upsert prohibited)
Future<void> saveItems(
List<ScheduleItemsCompanion> companions,
String cacheKey,
) async {
await (delete(scheduleItems)
..where((t) => t.cacheKey.equals(cacheKey)))
.go();
await batch((b) => b.insertAll(scheduleItems, companions));
}
// Cache invalidation (prefix matching)
Future<void> deleteByCacheKeyPrefix(String prefix) =>
(delete(scheduleItems)
..where((t) => t.cacheKey.like('$prefix%')))
.go();
Selection Guide#
HTTP Method?
โโโ GET
โ โโโ Response is list โ SWR
โ โโโ Response is single
โ โโโ Offline first โ CacheFirst
โ โโโ Always latest โ NetworkFirst
โโโ POST/PUT/DELETE
โ Direct call + deleteByCacheKeyPrefix()
Suitable Cases by Strategy#
| SWR | CacheFirst | NetworkFirst |
|---|---|---|
| Feed, timeline | User profile | Payment status |
| Notification list | App settings | Auth token |
| Schedule, homework list | Category list | Real-time balance |
Checklist#
- Select caching strategy (SWR / CacheFirst / NetworkFirst)
- Drift table: row table, cacheKey + cachedAt, no primaryKey
- DAO: Drift CRUD only (no domain entity dependency, Companion only)
- CacheQuery: 1:1 mapping with UseCase
-
CacheRepository:
_toEntity,_toCompanionconversion methods - NetworkRepository: Use OpenApiService
-
BLoC SWR:
emit.forEach+restartable()+onError -
BLoC write:
await+isClosed+droppable() -
Invalidate related caches after write with
deleteByCacheKeyPrefix()
Referencing Agents#
/feature:data- Data Layer caching implementation/feature:bloc- Stream-integrated BLoC/client-cache- Detailed implementation guide skill