Search code examples
flutterdartstatedropdown

How to build dependent dropdown menus in Flutter?


I have three dropdown-menus, and I want a fourth one to be displayed ONLY if certain conditions are met (Favourite Animal = Cat). I also want that fourth dropdown-menu to disappear whenever the conditions are no longer true. Right now the changes are displayed if I manually save the file and execute hot reload, so I'm guessing it has something to do with setState(), which I have not included in the filter.dart-file.

File 1 - filter.dart:

import 'package:flutter/material.dart';
import './dropdown.dart';

class Filter extends StatefulWidget {
  @override
  _FilterState createState() => _FilterState();
}

class _FilterState extends State<Filter> {  
  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 10,
      child: Container(
        height: ((MediaQuery.of(context).size.height) / 2),
        padding: EdgeInsets.all(20),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: <Widget>[
            Column(
              children: <Widget>[
                Dropdown('Gender', ['Female', 'Male']),
                Dropdown('Age', ['<15', '15-20', '>20']),
                Dropdown('Favourite Animal', ['Cat', 'Dog', 'Hamster']),
                (cat) ? Dropdown('Favourite cat-toy', ['Toy-mouse', 'Ribbon', 'Ball']) : Text('')
              ],
            ),
            RaisedButton(
              child: Text('Submit'),
              onPressed: () {},
            ),
          ],
        ),
      ),
    );
  }
}

File 2 - dropdown.dart:

import 'package:flutter/material.dart';

class Dropdown extends StatefulWidget {
  final String _key;
  final List<String> _values;

  Dropdown(this._key, this._values);

  @override
  _DropdownState createState() => _DropdownState();
}

class _DropdownState extends State<Dropdown> {
  var _chosenValue;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
      child: DropdownButton<String> (
        hint: Text(widget._key),
        value: _chosenValue,
        icon: Icon(Icons.arrow_drop_down),
        iconSize: 24,
        isExpanded: true,
        items: widget._values.map<DropdownMenuItem<String>>((String value) {
          return DropdownMenuItem<String>(
            value: value,
            child: Text(value),
          );
        }).toList(),
        onChanged: (String value) {
          _chosenValue = value;
          (widget._key == 'Favourite Animal' && _chosenValue == 'Cat') ? cat = true : cat = false;
          setState(() {

          });
        },
      ),
    );
  }
}

bool cat = false; //Feels wrong to have this bool out in the open

The filter.dart-file is called from main with Filter().

Bonus question: I also want to extract the chosen values and return them to the filter.dart-file and use them in the onPressed-function in the RaisedButton, not quite sure how to do that.


Solution

  • Fix 6 places. How about this?

    class _FilterState extends State<Filter> {
      bool cat = false; // Move here.
    
      @override
      Widget build(BuildContext context) {
        return Card(
          elevation: 10,
          child: Container(
            height: ((MediaQuery.of(context).size.height) / 2),
            padding: EdgeInsets.all(20),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: <Widget>[
                Column(
                  children: <Widget>[
                    Dropdown('Gender', ['Female', 'Male']),
                    Dropdown('Age', ['<15', '15-20', '>20']),
                    Dropdown('Favourite Animal', ['Cat', 'Dog', 'Hamster'], update: _update), // Fix this line.
                    (cat)
                        ? Dropdown(
                            'Favourite cat-toy', ['Toy-mouse', 'Ribbon', 'Ball'])
                        : Text('')
                  ],
                ),
                RaisedButton(
                  child: Text('Submit'),
                  onPressed: () {},
                ),
              ],
            ),
          ),
        );
      }
    
      // Add this function.
      void _update(bool b) {
        setState(() {
          cat = b;
        });
      }
    
    }
    


    class Dropdown extends StatefulWidget {
      final String _key;
      final List<String> _values;
      final Function update; // Add this line.
    
      Dropdown(this._key, this._values, {this.update = null}); // Fix this line.
    
      @override
      _DropdownState createState() => _DropdownState();
    }
    
    class _DropdownState extends State<Dropdown> {
      var _chosenValue;
    
      @override
      Widget build(BuildContext context) {
        return Padding(
          padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
          child: DropdownButton<String>(
            hint: Text(widget._key),
            value: _chosenValue,
            icon: Icon(Icons.arrow_drop_down),
            iconSize: 24,
            isExpanded: true,
            items: widget._values.map<DropdownMenuItem<String>>((String value) {
              return DropdownMenuItem<String>(
                value: value,
                child: Text(value),
              );
            }).toList(),
            onChanged: (String value) {
              // Fix start.
              setState(() {
                _chosenValue = value;
              });
              if (widget.update != null) {
                (widget._key == 'Favourite Animal' && _chosenValue == 'Cat')
                    ? widget.update(true)
                    : widget.update(false);
              }
              // Fix end.
            },
          ),
        );
      }
    }