ワンクリックで
add-loading-indicator
Add loading state tracking to widgets using context.isWaiting() for spinners and skeletons
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Add loading state tracking to widgets using context.isWaiting() for spinners and skeletons
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 | add-loading-indicator |
| description | Add loading state tracking to widgets using context.isWaiting() for spinners and skeletons |
This skill adds loading state tracking to widgets using context.isWaiting().
Shows loading indicators in widgets by:
context.isWaiting(key) to check if an operation is in progressThe Cubit method must use mix() with a key:
class UserCubit extends Cubit<User> {
void loadUser() => mix(
key: this, // Key used for tracking
() async {
final user = await api.getUser();
emit(user);
},
);
}
Check context.isWaiting(key) in the widget's build method:
class UserScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Check if loading
if (context.isWaiting(UserCubit)) {
return const Center(
child: CircularProgressIndicator(),
);
}
// Show data
final user = context.watch<UserCubit>().state;
return Text('Hello, ${user.name}');
}
}
The key in isWaiting() must match the key in mix():
// In Cubit
mix(key: this, ...) // → context.isWaiting(UserCubit)
mix(key: UserCubit, ...) // → context.isWaiting(UserCubit)
mix(key: 'loadUser', ...) // → context.isWaiting('loadUser')
mix(key: LoadAction.user, ...)// → context.isWaiting(LoadAction.user)
mix(key: (UserCubit, id), ...)// → context.isWaiting((UserCubit, id))
Widget build(BuildContext context) {
if (context.isWaiting(UserCubit)) {
return const Center(child: CircularProgressIndicator());
}
return UserProfile();
}
Widget build(BuildContext context) {
return Column(
children: [
if (context.isWaiting(UserCubit))
const LinearProgressIndicator(),
UserProfile(),
],
);
}
Widget build(BuildContext context) {
return Stack(
children: [
UserProfile(),
if (context.isWaiting(UserCubit))
Container(
color: Colors.black26,
child: const Center(child: CircularProgressIndicator()),
),
],
);
}
Widget build(BuildContext context) {
final isLoading = context.isWaiting(SaveUser);
return ElevatedButton(
onPressed: isLoading ? null : () => cubit.saveUser(user),
child: isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Save'),
);
}
Widget build(BuildContext context) {
if (context.isWaiting(ProductCubit)) {
return ListView.builder(
itemCount: 5,
itemBuilder: (_, __) => const ProductSkeleton(),
);
}
final products = context.watch<ProductCubit>().state.products;
return ListView.builder(
itemCount: products.length,
itemBuilder: (_, i) => ProductCard(product: products[i]),
);
}
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
// Check loading for this specific item
if (context.isWaiting((DeleteItem, item.id))) {
return const ListTile(
title: Text('Deleting...'),
trailing: CircularProgressIndicator(),
);
}
return ListTile(
title: Text(item.name),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => cubit.deleteItem(item.id),
),
);
},
);
}
Widget build(BuildContext context) {
final isLoading = context.isWaiting(LoadUsers) ||
context.isWaiting(LoadPosts);
if (isLoading) {
return const CircularProgressIndicator();
}
// ...
}
Widget build(BuildContext context) {
return Column(
children: [
// Header with user loading
if (context.isWaiting(LoadUser))
const LinearProgressIndicator()
else
UserHeader(),
// Posts section with its own loading
if (context.isWaiting(LoadPosts))
const PostsSkeleton()
else
PostsList(),
],
);
}
// Cubit with multiple operations
class ProfileCubit extends Cubit<ProfileState> {
ProfileCubit() : super(const ProfileState());
void loadProfile() => mix(
key: LoadProfile,
() async {
final profile = await api.getProfile();
emit(state.copyWith(profile: profile));
},
);
void updateAvatar(File image) => mix(
key: UpdateAvatar,
() async {
final url = await api.uploadAvatar(image);
emit(state.copyWith(avatarUrl: url));
},
);
void saveSettings(Settings settings) => mix(
key: SaveSettings,
() async {
await api.saveSettings(settings);
emit(state.copyWith(settings: settings));
},
);
}
// Widget with loading states
class ProfileScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Full screen loading for initial load
if (context.isWaiting(LoadProfile)) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
final state = context.watch<ProfileCubit>().state;
return Scaffold(
body: Column(
children: [
// Avatar with overlay loading
Stack(
children: [
CircleAvatar(backgroundImage: NetworkImage(state.avatarUrl)),
if (context.isWaiting(UpdateAvatar))
const Positioned.fill(
child: CircularProgressIndicator(),
),
],
),
// Settings section
SettingsForm(
settings: state.settings,
onSave: (s) => context.read<ProfileCubit>().saveSettings(s),
),
// Save button with loading
ElevatedButton(
onPressed: context.isWaiting(SaveSettings)
? null
: () => context.read<ProfileCubit>().saveSettings(state.settings),
child: context.isWaiting(SaveSettings)
? const CircularProgressIndicator()
: const Text('Save'),
),
],
),
);
}
}
Ask the user: