1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

内容

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

修正後

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

親画面側

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('ボトムシート表示'),
      ),
    );
  }
}

モーダルボトムシート側

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}'),
        ),
      ),
    );
  }
}

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?