画面の処理が増え、UIにもロジックを書くことが多くなってきたため、UIと処理の責務を分離しました。
子WidgetはUIの描画とユーザー操作の通知を担当し、実際の処理は親WidgetからVoidCallbackで渡しています。
子(ItemTileなど)
├─ UIの描画
└─ タップなどのイベント通知
例1: ItemTile — タップ処理を親に委譲
親Widgetでは通知を受け取り、Firestoreの更新処理を実行します。
class ItemTile extends StatelessWidget {
final ShoppingItem item;
final VoidCallback onToggle;
const ItemTile({
super.key,
required this.item,
required this.onToggle,
});
@override
Widget build(BuildContext context) {
return ListTile(
onTap: onToggle, // 親から受け取った処理を実行
leading: Checkbox(value: item.isChecked, onChanged: null),
title: Text(item.name),
);
}
}
void _toggleCheck(ShoppingItem item) {
toggleItemCheck(item); // Firestore の update 処理
}
ItemListを経由してItemTileへコールバックを渡しています。
ItemList(
items: foodItems,
onToggle: _toggleCheck,
)
例2: BottomActionBar — ボタン操作を親に委譲
BottomActionBarでもボタンが押された際の処理は持たず、親Widgetから受け取ったコールバックを実行するだけにしています。
class BottomActionBar extends StatelessWidget {
final VoidCallback onAdd;
final VoidCallback? onSort; // null ならボタン無効
const BottomActionBar({
super.key,
required this.onAdd,
required this.onSort,
});
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: FilledButton.icon(
onPressed: onAdd,
icon: const Icon(Icons.add),
label: const Text('追加'),
),
),
Expanded(
child: OutlinedButton.icon(
onPressed: onSort, // null → 自動で disabled
icon: const Icon(Icons.sort),
label: const Text('並び替え'),
),
),
],
);
}
}
例3: FilterBar — 任意のコールバック
任意の引数を受け取るコールバックも同様に親へ処理を委譲できます。
class FilterBar extends StatelessWidget {
final String selected;
final Function(String) onChanged;
final VoidCallback? onClearChecked; // 省略可能
// ...
IconButton(
onPressed: onClearChecked == null
? null
: () => _showConfirmDialog(context),
icon: Icon(Icons.delete_sweep),
)
}
確認ダイアログ後に親の処理を呼ぶ:
if (result == true) {
onClearChecked?.call();
}
まとめ
- 子WidgetはUIの描画とユーザー操作の通知のみを担当する
- 実際の処理は親Widgetが担当し、VoidCallbackや引数付きのコールバックとして子Widgetへ渡す
- VoidCallback?にするとnullを渡せるため、Material系ボタンを簡単に無効化できる
- Firestore通信など状態を変更する処理を親へ集約すると、Widgetごとの責務が明確になり、UIコンポーネントを再利用しやすくなります。