samedi 28 octobre 2023

How to select all checkboxes in a Flutter PopupMenu when selecting one checkbox?

A similar question has been asked before for Flutter see question. However no valid answer was given, so it may be worth reopening.

Here is a complete code example.

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

const cities = <String>{
  'Atlanta',
  'Baltimore',
  'Boston',
  'Chicago',
  'Denver',
  'Houston',
  'Los Angeles',
  'Philadelphia',
  'San Francisco',
  'Washington, DC',
};

enum SelectionState {
  all('(All)'),
  none('(None)'),
  some('(Some)');

  const SelectionState(this._value);
  final String _value;

  @override
  String toString() => _value;
}

class SelectionModel {
  SelectionModel({required this.selection, required this.choices});

  late final Set<String> selection;
  final Set<String> choices;

  SelectionState get selectionState {
    if (selection.isEmpty) return SelectionState.none;
    if (choices.difference(selection).isNotEmpty) {
      return SelectionState.some;
    } else {
      return SelectionState.all;
    }
  }

  SelectionModel add(String value) {
    if (value == '(All)') {
      return SelectionModel(selection: {...choices}, choices: choices);
    } else {
      return SelectionModel(selection: selection..add(value), choices: choices);
    }
  }

  SelectionModel remove(String value) {
    if (value == '(All)') {
      return SelectionModel(selection: <String>{}, choices: choices);
    } else {
      selection.remove(value);
      return SelectionModel(selection: selection, choices: choices);
    }
  }
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Dropdown with Select (All)',
      theme: ThemeData(
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  SelectionModel model =
      SelectionModel(selection: {...cities}, choices: {...cities});

  List<PopupMenuItem<String>> getCheckboxList() {
    var out = <PopupMenuItem<String>>[];
    out.add(PopupMenuItem<String>(
        padding: EdgeInsets.zero,
        value: '(All)',
        child: StatefulBuilder(builder: (context, setState) {
          return CheckboxListTile(
            value: model.selectionState == SelectionState.all,
            controlAffinity: ListTileControlAffinity.leading,
            title: const Text('(All)'),
            onChanged: (bool? checked) {
              setState(() {
                if (checked!) {
                  model = model.add('(All)');
                } else {
                  model = model.remove('(All)');
                }
              });
            },
          );
        })));

    for (final value in model.choices) {
      out.add(PopupMenuItem<String>(
          padding: EdgeInsets.zero,
          value: value,
          child: StatefulBuilder(builder: (context, setState) {
            return CheckboxListTile(
              value: model.selection.contains(value),
              controlAffinity: ListTileControlAffinity.leading,
              title: Text(value),
              onChanged: (bool? checked) {
                setState(() {
                  if (checked!) {
                    model = model.add(value);
                  } else {
                    model = model.remove(value);
                  }
                });
              },
            );
          })));
    }
    return out;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(20.0),
              child: Container(
                width: 250,
                color: Colors.orangeAccent,
                child: PopupMenuButton<String>(
                  constraints: const BoxConstraints(maxHeight: 400),
                  position: PopupMenuPosition.under,
                  child: Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: Row(
                      children: [
                        Text(model.selectionState.toString()),
                        const Spacer(),
                        const Icon(Icons.keyboard_arrow_down_outlined),
                      ],
                    ),
                  ),
                  itemBuilder: (context) {
                    return getCheckboxList();
                  },
                  onCanceled: () {
                    setState(() {
                      model = SelectionModel(
                          selection: model.selection, choices: model.choices);
                    });
                  },
                ),
              ),
            ),
            const Spacer(),
            Text('Selected cities: ${model.selection.join(', ')}'),
          ],
        ),
      ),
    );
  }
}

See screeenshot. base1

If I click individual cities, everything is fine. If I click the (All) checkbox, I would like all the checkboxes to turn false (which does not happen unless I close the menu.)

How can I do this? If I just have a list of CheckboxListTiles in the main app, the logic works fine, and the checkboxes update as I want. However, once they are part of the menu, it doesn't work properly anymore.

Thank you for any help with this! Tony




Aucun commentaire:

Enregistrer un commentaire