provider+StateNotifierの状態管理でつまづいた話

## 内容
親画面からモーダルボトムシートを呼び出し後、親画面のState更新処理をすることで子要素であるモーダルボトムシート内も一緒に再描画されると思っていた。


## 修正後
親画面とモーダルボトムシート両方にStateを持たせて更新する必要があった。
サンプルコードだがこれで解決できるらしい。

親画面側
```dart
class ParentScreenState {
  const ParentScreenState({
    required this.checkboxValues,
    required this.selectedItems,
  });

  final List<bool> checkboxValues;
  final List<String> selectedItems;

  ParentScreenState copyWith({
    List<bool>? checkboxValues,
    List<String>? selectedItems,
  }) {
    return ParentScreenState(
      checkboxValues: checkboxValues ?? this.checkboxValues,
      selectedItems: selectedItems ?? this.selectedItems,
    );
  }
}

class ParentScreenVM extends StateNotifier<ParentScreenState> {
  ParentScreenVM()
      : super(
          ParentScreenState(
            checkboxValues: List.generate(4, (_) => false),
            selectedItems: [],
          ),
        );

  void toggleCheckbox(int index) {
    final newCheckboxValues = [...state.checkboxValues];
    newCheckboxValues[index] = !newCheckboxValues[index];

    final newSelectedItems = <String>[];
    for (var i = 0; i < newCheckboxValues.length; i++) {
      if (newCheckboxValues[i]) {
        newSelectedItems.add('チェックボックス ${i + 1}');
      }
    }

    state = state.copyWith(
      checkboxValues: newCheckboxValues,
      selectedItems: newSelectedItems,
    );
  }

  void updateFromBottomSheet(List<bool> newCheckboxValues) {
    final newSelectedItems = <String>[];
    for (var i = 0; i < newCheckboxValues.length; i++) {
      if (newCheckboxValues[i]) {
        newSelectedItems.add('チェックボックス ${i + 1}');
      }
    }

    state = state.copyWith(checkboxValues: newCheckboxValues, selectedItems: newSelectedItems);
  }
}

class ParentScreen extends StatelessWidget {
  const ParentScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return StateNotifierProvider<ParentScreenVM, ParentScreenState>(
      create: (_) => ParentScreenVM(),
      builder: (context, _) {
        return Scaffold(
          appBar: AppBar(title: const Text('親画面')),
          body: _body(context),
        );
      },
    );
  }

  Widget _body(BuildContext ctx) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          _selectedItemsSection(ctx),
          const SizedBox(height: 16),
          _checkboxList(ctx),
          const SizedBox(height: 16),
          _bottomSheetButton(ctx),
        ],
      ),
    );
  }

  Widget _selectedItemsSection(BuildContext ctx) {
    final selectedItems = ctx.select<ParentScreenState, List<String>>(
      (state) => state.selectedItems,
    );

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          '選択された項目：',
          style: TextStyle(
            fontSize: 16,
            fontWeight: FontWeight.bold,
          ),
        ),
        const SizedBox(height: 8),
        if (selectedItems.isEmpty)
          const Text('選択されている項目はありません')
        else
          ...selectedItems.map(
            (item) => Padding(
              padding: const EdgeInsets.only(bottom: 4),
              child: Text(
                '・$item',
                style: const TextStyle(fontSize: 14),
              ),
            ),
          ),
      ],
    );
  }

  Widget _checkboxList(BuildContext ctx) {
    final checkboxValues = ctx.select<ParentScreenState, List<bool>>(
      (state) => state.checkboxValues,
    );

    return Column(
      children: [
        const Divider(),
        const SizedBox(height: 16),
        ...List.generate(
          4,
          (index) => CheckboxListTile(
            value: checkboxValues[index],
            onChanged: (_) => ctx.read<ParentScreenVM>().toggleCheckbox(index),
            title: Text('チェックボックス ${index + 1}'),
          ),
        ),
      ],
    );
  }

  Widget _bottomSheetButton(BuildContext ctx) {
    final checkboxValues = ctx.select<ParentScreenState, List<bool>>(
      (state) => state.checkboxValues,
    );

    return Center(
      child: ElevatedButton(
        onPressed: () {
          showModalBottomSheet(
            context: ctx,
            builder: (context) => CustomModalBottomSheet(
              initialCheckboxValues: checkboxValues,
              onCheckboxesUpdated: (newValues) {
                ctx.read<ParentScreenVM>().updateFromBottomSheet(newValues);
              },
            ),
          );
        },
        child: const Text('ボトムシート表示'),
      ),
    );
  }
}

```

モーダルボトムシート側
```dart
class ModalBottomSheetState {
  const ModalBottomSheetState({
    required this.checkboxValues,
  });

  final List<bool> checkboxValues;

  ModalBottomSheetState copyWith({
    List<bool>? checkboxValues,
  }) {
    return ModalBottomSheetState(
      checkboxValues: checkboxValues ?? this.checkboxValues,
    );
  }
}

class ModalBottomSheetVM extends StateNotifier<ModalBottomSheetState> {
  ModalBottomSheetVM(List<bool> initialValues)
      : super(
          ModalBottomSheetState(
            checkboxValues: initialValues,
          ),
        );

  void toggleCheckbox(int index) {
    final newCheckboxValues = [...state.checkboxValues];
    newCheckboxValues[index] = !newCheckboxValues[index];
    state = state.copyWith(checkboxValues: newCheckboxValues);
  }
}

class CustomModalBottomSheet extends StatelessWidget {
  const CustomModalBottomSheet({
    required this.initialCheckboxValues,
    required this.onCheckboxesUpdated,
    Key? key,
  }) : super(key: key);

  final List<bool> initialCheckboxValues;
  final void Function(List<bool>) onCheckboxesUpdated;

  @override
  Widget build(BuildContext context) {
    return StateNotifierProvider<ModalBottomSheetVM, ModalBottomSheetState>(
      create: (_) => ModalBottomSheetVM(initialCheckboxValues),
      builder: (context, _) {
        return Container(
          padding: const EdgeInsets.all(16),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              const Text('ボトムシート', style: TextStyle(fontSize: 18)),
              _checkboxList(context),
            ],
          ),
        );
      },
    );
  }

  Widget _checkboxList(BuildContext ctx) {
    final checkboxValues = ctx.select<ModalBottomSheetState, List<bool>>(
      (state) => state.checkboxValues,
    );

    return Column(
      children: List.generate(
        4,
        (index) => CheckboxListTile(
          value: checkboxValues[index],
          onChanged: (_) {
            ctx.read<ModalBottomSheetVM>().toggleCheckbox(index);
            onCheckboxesUpdated(ctx.read<ModalBottomSheetState>().checkboxValues);
          },
          title: Text('チェックボックス ${index + 1}'),
        ),
      ),
    );
  }
}

```