Flutter dropdown text field
Asked Answered
C

8

55

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.

Corr answered 11/4, 2018 at 17:7 Comment(3)
check this exampleAutography
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.Corr
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!Dissemble
A
79

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!

Arteriosclerosis answered 16/5, 2019 at 14:30 Comment(4)
Please share your _currencies variable as well.Mendicity
Another exception was thrown: There should be exactly one item with [DropdownButton]'s value: .Phobia
it's not clear.Genniegennifer
Don't do this, instead use DropdownButtonFormField. It has everything a Textfield has including validatorHaemophilia
H
39

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),
       );
Homoousian answered 27/2, 2019 at 0:38 Comment(0)
M
14

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,
          )
Mastiff answered 23/1, 2021 at 8:45 Comment(3)
How would you do onValidate?Libbylibeccio
Requires updating to null-safety.Aldose
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;});}According
M
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.

Merciful answered 11/12, 2021 at 13:46 Comment(0)
N
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

Needlecraft answered 19/12, 2022 at 19:41 Comment(0)
C
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

Chavannes answered 7/3, 2023 at 12:23 Comment(3)
Sorry, a null safety fork of the package can be found here: github.com/proismailshah/dropdownfield/tree/master/libChavannes
There is also this one: pub.dev/packages/dropdown_textfieldChavannes
There is a builtin DropdownMenu widget for that sort of thing: api.flutter.dev/flutter/material/DropdownMenu-class.htmlPolyphone
P
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;
            },
          ),
        );
      }
    }
Parrett answered 3/8, 2023 at 6:42 Comment(0)
M
-20

'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

Mawkish answered 2/9, 2018 at 0:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.