-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Description
I recently spent some time recreating behaviors that DropdownButton and Autocomplete already implement. I'd like to share this story to explain why these widgets should probably be decomposed, and also to help demonstrate to the Flutter team what happens when generic capabilities are kept private and over-specified.
What I was working on
I'm implementing a number of custom form fields based on a design team's specs. Among these specs was an autocomplete text field, and a single-select dropdown field.
The go-to Flutter widget for autocomplete is Autocomplete, and the go-to widget for a single-select dropdown field is DropdownButton.
Implementing an autocomplete field
First, I worked on the custom autocomplete field. The Autocomplete widget is fairly well decomposed in terms of the properties that it accepts. It allows completely custom widgets for the field and the popup. For some reason, it doesn't take a FocusNode or TextEditingController, which I needed to pass in. For that reason, I had to switch to RawAutocomplete. The RawAutocomplete provided a sufficient level of configuration for my use-cases.
However, to achieve the design spec, I still needed to create the widget that appears as a dropdown list. I didn't notice any obvious existing widgets to serve this purpose. Instead, I ended up copying the implementation for _AutocompleteOptions in material/autocomplete.dart. Most of that widget is just an accumulation of simple layout and visual details, but there are some parts that most people wouldn't think about, including the use of AutocompleteHighlightOption.of() and Scrollable.ensureVisible() in a post-frame callback.
Overall, I achieved my goal by using RawAutocomplete instead of Autocomplete, and by copying the implementation for _AutocompleteOptions. The team might consider providing a public widget that makes it easy to assemble a keyboard navigable, autoscrolling list that can be used as a small popup.
Implementing a dropdown select field
Things got a lot messier when I got to the custom dropdown select field.
Unlike Autocomplete, Flutter's DropdownButton imposes a very specific UI for the field, itself. DropdownButton doesn't accept a builder for the field, but rather accepts properties for things like icon, iconSize, isDense, and underline. But my spec doesn't look anything like this particular visual configuration for a dropdown button, so I couldn't use the DropdownButton widget at all.
My hope was that DropdownButton was a Material Design Component that composed other, generic widgets into a specialized widget. I checked the docs for DropdownButton expecting to find a link to other widgets that would make it easy to build my own, but no such references were found.
I jumped into the DropdownButton source code, hoping to find other widgets that I could compose for my own purposes. What I found was that pretty much everything DropdownButton implemented was custom and private. I couldn't use any other publicly available widgets.
Next, I started copying large chunks of code from DropdownButton into my own file. Pretty soon, I had copied the majority of the DropdownButton implementation because every private class that I copied, referenced another private class that I had to copy over. There were also private constants that I had to copy over. Before I managed to even get the copied code to compile, I noticed that it seemed significantly longer than the code in Autocomplete, and Autocomplete accomplishes pretty much the same thing in terms of a dropdown list.
I deleted all the code that I copied from DropdownButton and instead started copying code from Autocomplete. Eventually, by copying the Autocomplete dropdown implementation, I was able to build my own version of a dropdown button field. It ended up taking me about a day to work through this process with DropdownButton.
Conclusion
As I went through this process, I got the distinct impression that Flutter could have broken out additional, generic widgets and tools that could have saved most/all of this time investment.
There are clearly some concepts involved with Autocomplete and DropdownButton that are completely generic:
- Anchored positioning and sizing
- Keyboard focus traversal and item selection
- List autoscrolling
- Popup routes
Additionally, comparing Autocomplete with DropdownButton, it seems like it would make a lot of sense to have a version of DropdownButton that accepts builders like Autocomplete. Autocomplete was far more useful for me as someone who was implementing a fully custom form field, as opposed to DropdownButton which I completely ruled out immediately.
My guess is that whoever worked on DropdownButton took the Material Design Component spec and focused heavily on directly implementing that spec. What the DropdownButton implementation shows us is that there were clearly some generic behaviors for which Flutter had no public solution. Had development focused on identifying and building those generic tools, first, instead of jumping straight to the MDC implementation, then not only would DropdownButton still exist, but so would a number of tools with utility far beyond this one component, or even Material Design at large.