ワンクリックで
load-with-freshness
Load data with freshness caching pattern to skip redundant API calls when data is still fresh
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Load data with freshness caching pattern to skip redundant API calls when data is still fresh
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Add custom error handling with catchError to a mix() call for logging, error conversion, or suppression
Add internet connectivity checking before executing a Cubit method with optional retry
Add debounce to delay method execution until after a period of inactivity for search or validation
Add EffectQueue to state for triggering multiple sequential one-time UI effects
Add Effect fields to state class for one-time UI notifications like snackbars, navigation, or form clearing
Add error state handling to widgets using context.isFailed() and context.getException()
| name | load-with-freshness |
| description | Load data with freshness caching pattern to skip redundant API calls when data is still fresh |
This skill loads data with freshness caching to prevent redundant API calls.
Creates a loading pattern that:
Perfect for:
import 'package:bloc_superpowers/bloc_superpowers.dart';
class ProfileCubit extends Cubit<ProfileState> {
ProfileCubit() : super(const ProfileState());
void loadProfile({bool force = false}) => mix(
key: this,
fresh: fresh(
freshFor: 30.sec, // Data stays fresh for 30 seconds
ignoreFresh: force, // Force bypasses freshness
),
() async {
final profile = await api.getProfile();
emit(state.copyWith(profile: profile));
},
);
}
class ProfileScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Load on screen entry - will skip if fresh
useEffect(() {
context.read<ProfileCubit>().loadProfile();
}, []);
return Scaffold(
appBar: AppBar(
title: const Text('Profile'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
// Force refresh bypasses freshness
context.read<ProfileCubit>().loadProfile(force: true);
},
),
],
),
body: ProfileContent(),
);
}
}
User enters screen
↓
loadProfile() called
↓
Is data fresh? (loaded within 30 seconds)
↓
Yes → Skip API call, use existing data
No → Make API call, update data, mark fresh
fresh(freshFor: 5.sec) // Very short - rapidly changing data
fresh(freshFor: 30.sec) // Short - screen data
fresh(freshFor: 5.minutes) // Medium - user profile
fresh(freshFor: 1.hours) // Long - configuration
void loadProduct(String productId) => mix(
key: this,
fresh: fresh(
key: (ProductCubit, productId), // Freshness per product
freshFor: 5.minutes,
),
() async {
final product = await api.getProduct(productId);
emit(state.copyWith(
products: {...state.products, productId: product},
));
},
);
void loadUser(String userId) => mix(
key: UserCubit, // Loading state shows ANY user loading
fresh: fresh(
key: (UserData, userId), // Freshness tracked per user
freshFor: 5.minutes,
),
() async {
final user = await api.getUser(userId);
emit(state.copyWith(users: {...state.users, userId: user}));
},
);
// State
class ProductState {
final Map<String, Product> products;
final List<String> categories;
const ProductState({
this.products = const {},
this.categories = const [],
});
ProductState copyWith({
Map<String, Product>? products,
List<String>? categories,
}) => ProductState(
products: products ?? this.products,
categories: categories ?? this.categories,
);
}
// Cubit
class ProductCubit extends Cubit<ProductState> {
final Api api;
ProductCubit(this.api) : super(const ProductState());
// Categories fresh for 1 hour (rarely change)
void loadCategories({bool force = false}) => mix(
key: LoadCategories,
fresh: fresh(
freshFor: 1.hours,
ignoreFresh: force,
),
() async {
final categories = await api.getCategories();
emit(state.copyWith(categories: categories));
},
);
// Products fresh for 5 minutes
void loadProducts({bool force = false}) => mix(
key: this,
fresh: fresh(
freshFor: 5.minutes,
ignoreFresh: force,
),
retry: retry,
() async {
final products = await api.getProducts();
emit(state.copyWith(
products: {for (var p in products) p.id: p},
));
},
);
// Product details fresh per product
void loadProductDetails(String productId, {bool force = false}) => mix(
key: (ProductDetails, productId),
fresh: fresh(
freshFor: 10.minutes,
ignoreFresh: force,
),
() async {
final product = await api.getProductDetails(productId);
emit(state.copyWith(
products: {...state.products, productId: product},
));
},
);
}
// List Screen
class ProductListScreen extends StatefulWidget {
@override
State<ProductListScreen> createState() => _ProductListScreenState();
}
class _ProductListScreenState extends State<ProductListScreen> {
@override
void initState() {
super.initState();
// Load on screen entry - skips if fresh
context.read<ProductCubit>().loadCategories();
context.read<ProductCubit>().loadProducts();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Products'),
actions: [
IconButton(
icon: context.isWaiting(ProductCubit)
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.refresh),
onPressed: () {
// Force refresh
context.read<ProductCubit>().loadProducts(force: true);
},
),
],
),
body: RefreshIndicator(
onRefresh: () async {
context.read<ProductCubit>().loadProducts(force: true);
},
child: ProductGrid(),
),
);
}
}
// Detail Screen
class ProductDetailScreen extends StatefulWidget {
final String productId;
@override
State<ProductDetailScreen> createState() => _ProductDetailScreenState();
}
class _ProductDetailScreenState extends State<ProductDetailScreen> {
@override
void initState() {
super.initState();
// Load details - skips if this product's data is fresh
context.read<ProductCubit>().loadProductDetails(widget.productId);
}
@override
Widget build(BuildContext context) {
final product = context.watch<ProductCubit>().state.products[widget.productId];
final isLoading = context.isWaiting((ProductDetails, widget.productId));
if (isLoading && product == null) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return Scaffold(
appBar: AppBar(title: Text(product?.name ?? 'Product')),
body: ProductDetailContent(product: product!),
);
}
}
void loadData({bool force = false}) => mix(
key: this,
fresh: fresh(
freshFor: 5.minutes,
ignoreFresh: force,
),
retry: retry,
nonReentrant: nonReentrant,
() async {
final data = await api.getData();
emit(data);
},
);
// Clear freshness for specific key
Superpowers.removeFreshKey(ProductCubit);
Superpowers.removeFreshKey((ProductDetails, productId));
// Clear all freshness
Superpowers.removeAllFreshKeys();
force parameter for manual refresh