52

I am still new to Flutter. Is there an example of a material dropdown list text field? I saw the example on Material Text Field but I didn't find anywhere in the documentation on how to implement this. Thanks for your help on this.

Edmand Looi
  • 3,331
  • 6
  • 19
  • 21
  • check this [example](https://www.youtube.com/watch?v=v6SvFQnP2K8) – Raouf Rahiche Apr 11 '18 at 17:12
  • Raouf, thanks for the video links, however it is not what I'm looking for. I am trying to create a drop down with a look a feel like all the other material design input. For example, it has a label and when the control gets focus the label will shrink to the top and expand when lost focus. Thanks again for helping on this. – Edmand Looi Apr 11 '18 at 19:46
  • Please be specific and clear on what you need, you need a drop down or a mere text field that has the hint text above? you gave an example that has nothing to do with a drop down! – Manoranjan Feb 22 '19 at 07:22

8 Answers8

77

UPDATED :

Text form field with a dropdown

var _currencies = [
    "Food",
    "Transport",
    "Personal",
    "Shopping",
    "Medical",
    "Rent",
    "Movie",
    "Salary"
  ];

 FormField<String>(
          builder: (FormFieldState<String> state) {
            return InputDecorator(
              decoration: InputDecoration(
                  labelStyle: textStyle,
                  errorStyle: TextStyle(color: Colors.redAccent, fontSize: 16.0),
                  hintText: 'Please select expense',
                  border: OutlineInputBorder(borderRadius: BorderRadius.circular(5.0))),
              isEmpty: _currentSelectedValue == '',
              child: DropdownButtonHideUnderline(
                child: DropdownButton<String>(
                  value: _currentSelectedValue,
                  isDense: true,
                  onChanged: (String newValue) {
                    setState(() {
                      _currentSelectedValue = newValue;
                      state.didChange(newValue);
                    });
                  },
                  items: _currencies.map((String value) {
                    return DropdownMenuItem<String>(
                      value: value,
                      child: Text(value),
                    );
                  }).toList(),
                ),
              ),
            );
          },
        )

enter image description here

Hope this helps!

Community
  • 1
  • 1
Dharmesh Mansata
  • 4,422
  • 1
  • 27
  • 33
35

You want the DropdownButton or DropdownButtonFormField https://api.flutter.dev/flutter/material/DropdownButton-class.html

and the DropdownMenuItem https://api.flutter.dev/flutter/material/DropdownMenuItem-class.html

return DropdownButtonFormField(
  items: categories.map((String category) {
    return new DropdownMenuItem(
      value: category,
      child: Row(
        children: <Widget>[
          Icon(Icons.star),
          Text(category),
        ],
       )
      );
     }).toList(),
     onChanged: (newValue) {
       // do other stuff with _category
       setState(() => _category = newValue);
     },
     value: _category,
     decoration: InputDecoration(
       contentPadding: EdgeInsets.fromLTRB(10, 20, 10, 20),
         filled: true,
         fillColor: Colors.grey[200],
         hintText: Localization.of(context).category, 
         errorText: errorSnapshot.data == 0 ? Localization.of(context).categoryEmpty : null),
       );
DarkMikey
  • 383
  • 1
  • 4
  • 24
Jeff Frazier
  • 519
  • 4
  • 9
13

Other answers have fully described what you need, but here is an example that puts it all together, this is a reusable dropdown textfield widget that allows you to specify a list of options of any type (without losing dart's beautiful type system).

class AppDropdownInput<T> extends StatelessWidget {
  final String hintText;
  final List<T> options;
  final T value;
  final String Function(T) getLabel;
  final void Function(T) onChanged;

  AppDropdownInput({
    this.hintText = 'Please select an Option',
    this.options = const [],
    this.getLabel,
    this.value,
    this.onChanged,
  });

  @override
  Widget build(BuildContext context) {
    return FormField<T>(
      builder: (FormFieldState<T> state) {
        return InputDecorator(
          decoration: InputDecoration(
            contentPadding: EdgeInsets.symmetric(
                horizontal: 20.0, vertical: 15.0),
            labelText: hintText,
            border:
                OutlineInputBorder(borderRadius: BorderRadius.circular(5.0)),
          ),
          isEmpty: value == null || value == '',
          child: DropdownButtonHideUnderline(
            child: DropdownButton<T>(
              value: value,
              isDense: true,
              onChanged: onChanged,
              items: options.map((T value) {
                return DropdownMenuItem<T>(
                  value: value,
                  child: Text(getLabel(value)),
                );
              }).toList(),
            ),
          ),
        );
      },
    );
  }
}

And you may use it like this:

AppDropdownInput(
            hintText: "Gender",
            options: ["Male", "Female"],
            value: gender,
            onChanged: (String value) {
              setState(() {
                gender = value;
                // state.didChange(newValue);
              });
            },
            getLabel: (String value) => value,
          )
Itope84
  • 471
  • 5
  • 7
  • How would you do onValidate? – user2570135 Jan 23 '21 at 19:36
  • 1
    Requires updating to null-safety. – Jahn E. Dec 12 '21 at 18:46
  • I like this solution, but it cannot be used from Stateless widget (e.g. with `TextEditingController`). That's why I'd recommend to re-write the widget to "Stateful", and update its internal value in `onChanged()` property: `onChanged: (t) {widget.onChanged(t); setState(() {value = t;});}` – Mitrakov Artem Jan 01 '23 at 16:05
3

Following Jeff Frazier's answer, You can have more customization by using DropdownButton2 or DropdownButtonFormField2 from DropdownButton2 package. It's based on Flutter's core DropdownButton with more options you can customize to your needs.

Ahmed Elsayed
  • 538
  • 1
  • 4
  • 11
0

This answer provide a example using a DropdownButtonFormField a convenience widget that wraps a DropdownButton widget in a FormField.

Ideal if you are using a Material FormField

Bassirou Diaby
  • 279
  • 4
  • 7
0

I suppose you want a dropdown button wich is also a text field. So I found this video. It explains how to implement this kind of field using this package.

enter image description here

Gabriel Beckman
  • 130
  • 1
  • 1
  • 8
0

I guess what you are asking as I was searching for the same and came up with this solution.

Take a look at screenshots.

Signup Page

Text Field when focused dropdown appears

Selected item updated in TextField

Here I'm attaching my code:

    import 'package:flutter/material.dart';
    import 'package:google_fonts/google_fonts.dart';
    import 'package:paperbook/Constants/size_config.dart';
    import 'package:paperbook/Constants/size_constant.dart';
    import '../../../Constants/text_constants.dart';

    class CourseDropdownTextField extends StatefulWidget {
      const CourseDropdownTextField({super.key, required this.courseController});

      final TextEditingController courseController;

      @override
      State<CourseDropdownTextField> createState() => CourseDropdownTextFieldState();
    }

    class CourseDropdownTextFieldState extends State<CourseDropdownTextField> {

      late TextEditingController courseName;

      List<String> dropdownList = <String>[
        'One', 
        'Two', 
        'Three',
        'Four',
        'Five',
        'Six',
        'Seven',
        'Eight',
        'Nine',
        'Ten'
      ];

      final FocusNode _focusNode = FocusNode();
      bool _isFocused = false;

        void _onFocusChange() {
        setState(() {
          _isFocused = _focusNode.hasFocus;
        });

        // Perform your function when the TextField gains focus
        if (_isFocused) {
          showOverlay();
        } else {
          hideOverlay();
        }
      }

      OverlayEntry? entry;
      final layerLink = LayerLink();

      void showOverlay() {
        final overlay = Overlay.of(context);
        final renderBox = context.findRenderObject() as RenderBox;
        final size = renderBox.size;

        entry = OverlayEntry(
          builder: (context) => Positioned(
            width: size.width ,
            child: CompositedTransformFollower(
              link: layerLink,
              showWhenUnlinked: false,
              offset: Offset(0, size.height + 10),
              child: buildOverlay())
          ),
        );
        overlay.insert(entry!);
      }

      void hideOverlay() {
        entry?.remove();
        entry = null;
      }

      Widget buildOverlay() => NotificationListener<OverscrollIndicatorNotification>(
        onNotification: (OverscrollIndicatorNotification? notification){
          notification!.disallowIndicator();
          return true;
        },
        child: Container(
          clipBehavior: Clip.hardEdge,
          height: SizeConfig.safeBlockVertical!* 42,
          decoration: BoxDecoration(
            color: Theme.of(context).colorScheme.secondary,
            borderRadius: BorderRadius.circular(15),
          ),
          child: ListView.separated(
            padding: const EdgeInsets.all(0),
            itemBuilder: (context, index){
              return GestureDetector(
                onTap: () {
                  courseName.text = dropdownList[index];
                  hideOverlay();
                  _focusNode.unfocus();
                },
                child: Container(
                  padding: EdgeInsets.all(mainpadding),
                  child: DefaultTextStyle(
                    style: const TextStyle(),
                    child: Text(
                      dropdownList[index],
                      style: GoogleFonts.robotoCondensed(
                        textStyle: const TextStyle(letterSpacing: 0.4),
                        fontSize: SizeConfig.safeBlockVertical! * 2.2,
                        fontWeight: FontWeight.w500,
                        fontStyle: FontStyle.normal,
                        color: Colors.grey
                      ),
                    ),
                  )
                ),
              );
            }, 
            separatorBuilder: (context, index) {
              return const Divider(
                height: 0,
                thickness: 3,
              );
            }, 
            itemCount: dropdownList.length)
        ),
      );

      @override
      void initState(){
        super.initState();
        courseName = TextEditingController();
        _focusNode.addListener(_onFocusChange);
      }

      @override
      void dispose(){
        super.dispose();
        courseName.dispose();
        _focusNode.removeListener(_onFocusChange);
        _focusNode.dispose();
      }

      @override
      Widget build(BuildContext context) {
        return CompositedTransformTarget(
          link: layerLink,
          child: TextFormField(
            keyboardType: TextInputType.none,
            readOnly: true,
            textInputAction: TextInputAction.next,
            decoration: const InputDecoration(
              prefixIcon: Icon(Icons.menu_book_rounded),
              suffixIcon: Icon(Icons.arrow_drop_down_rounded),
              labelText: courseText,
              hintText: courseText,
              border: OutlineInputBorder(
                borderRadius: BorderRadius.all(Radius.circular(15))
              )
            ),
            controller: courseName,
            focusNode: _focusNode,
            onChanged: (value) {
              widget.courseController.text = value;
            },
          ),
        );
      }
    }
-18

'Dropdown' may not be the correct word that you are using to describe the design of text field referred in your material design example.

Here is how to implement it in Flutter:

import 'package:flutter/material.dart';

void main() {
  runApp(TextFieldExample());
}

class TextFieldExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Text Field Example',
      home: HomePage(),
      theme: ThemeData(
        primaryColor: Colors.deepPurple,
        accentColor: Colors.white,
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Text Field Example'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: <Widget>[
            //Material example
            TextField(
              decoration: InputDecoration(
                  filled: true,
                  hintText: 'Enter text',
                  labelText: 'Default text field'),
              controller: new TextEditingController(),
            ),
            SizedBox(
              height: 16.0,
            ),
            //Alternate
            TextField(
              decoration: InputDecoration(
                  border: OutlineInputBorder(),
                  hintText: 'Enter text',
                  labelText: 'Text field alternate'),
              controller: new TextEditingController(),
            ),
          ],
        ),
      ),
    );
  }
}

This sample app contains two different examples of text field design that shrink and expand the associated label.

enter image description here

Gif of sample app - click here

Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135
Taha Ali
  • 1,150
  • 9
  • 14