Edmand Looi
Edmand Looi

Reputation: 3561

Flutter dropdown text field

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.

Upvotes: 57

Views: 176927

Answers (9)

Qwertie
Qwertie

Reputation: 17186

To get a text field with dropdown (combo box) rather than a conventional dropdown-selector, use DropdownMenu using code like this:

  TextEditingController controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return DropdownMenu<String>(
      initialSelection: null,
      controller: controller,
      // By default this is false on mobile and true on desktop. If
      // this is false on mobile, there seems to be no way to edit 
      // the text field, since tapping just opens the dropdown. If 
      // this is true, the dropdown still appears when the text area 
      // is clicked (and doesn't disappear after inputting text, even 
      // if the text matches none of the options).
      requestFocusOnTap: true,
      label: Text(widget.label),
      // This is called when the user chooses an item from the dropdown
      onSelected: (String? val) {
        // TODO: store change with `setState` or whatever
      },
      searchCallback: (list, val) {
        // There's no `onChanged` parameter, but this is called when 
        // text is input. But it is also called just after `build()` 
        // returns. If the handler triggers another `build` at that 
        // point, an exception will occur unless you use 
        // `addPostFrameCallback` like so. Also, the change handler 
        // should check whether `val` differs from the old value to
        // prevent an endless series of re-renders.
        WidgetsBinding.instance.addPostFrameCallback((_) {
          if (_oldValue != val)
            // TODO: store the change
        });
      },
      expandedInsets: EdgeInsets.zero, // expands the width to 100% of parent
      dropdownMenuEntries: ["Option A", "Option B"]
        .map<DropdownMenuEntry<String>>((String option) =>
          DropdownMenuEntry<String>(
            value: option, label: option, enabled: true))
        .toList(),
    );
  }

Upvotes: 0

Abhinav Singh
Abhinav Singh

Reputation: 206

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

Upvotes: 0

Gabriel Beckman
Gabriel Beckman

Reputation: 190

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

Upvotes: 1

Bassirou Diaby
Bassirou Diaby

Reputation: 449

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

Upvotes: 0

Ahmed Elsayed
Ahmed Elsayed

Reputation: 740

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.

Upvotes: 3

Jeff Frazier
Jeff Frazier

Reputation: 579

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

Upvotes: 41

Itope84
Itope84

Reputation: 521

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,
          )

Upvotes: 13

Dharmesh Mansata
Dharmesh Mansata

Reputation: 4728

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!

Upvotes: 79

Taha Ali
Taha Ali

Reputation: 1324

'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

Upvotes: -20

Related Questions