Skip to content

Expandable Search - #17629

Merged
goderbauer merged 20 commits into
flutter:masterfrom
goderbauer:search
May 24, 2018
Merged

goderbauer merged 20 commits into
flutter:masterfrom
goderbauer:search

Conversation

@goderbauer

@goderbauer goderbauer commented May 15, 2018 •

Copy link
Copy Markdown
Member

#17119 (comment)

As spec'ed out in https://material.io/design/navigation/search.html#expandable-search.

The search can be triggered by tapping the search icon in the AppBar:

img_0005

Doing so will bring up the search page where the user can type in a search query and see suggested queries:

img_0006

Once the query is submitted, a results page is shown:

img_0007

TODO:

  • i18n for the hint text (will update this PR)
  • tests (will update this PR)
  • change the enter key on the keyboard to the search key (requires Support for customizing return key #17525, will happen in a follow-up PR)

Requires fix from #17624.

@goderbauer
goderbauer requested a review from HansMuller May 15, 2018 20:50
@goderbauer
goderbauer force-pushed the search branch 2 times, most recently from cfb4613 to 5e486fe Compare May 15, 2018 22:04

@HansMuller HansMuller left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still looking at this, here's some feedback before the next update

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To reduce the risk of importing this demo along with all of the others, it's best if this is the only public class in the file.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NICE. You might want to add a comment that indicates that all of the gallery tests depend on this...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be better to put something in this drawer that has to do with the demo. Search limits maybe?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me think about this one...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be OK to put this expression on the previous line

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICT this delegate defines the search experience.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be helpful to mention query and showResultsPage here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a bit more fluttery to define a ResultCard widget instead of this function

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"overlay" might be a little confusing vis the Overlay widget. The docs tend to refer to the result of showSearchOverlay as a page, show maybe showSearchPage etc.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would helpful to also mention [close] here

@goderbauer
goderbauer force-pushed the search branch 2 times, most recently from e0eb89b to 74f68fe Compare May 16, 2018 17:08
@goderbauer

goderbauer commented May 16, 2018 •

Copy link
Copy Markdown
Member Author

@HansMuller Addressed comments and added a way to pipe the search result back to the caller of showSearch. PTAL

@HansMuller HansMuller left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still haven't finished reviewing this, but here are some additional updates

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A slightly simpler way to create a vertical spacer is: const SizedBox(height: 64.0)

@HansMuller HansMuller May 16, 2018 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Developers may want to reuse some of this buildSuggestions method, since showing a list of strings with the query highlighted is common. You could simplify lifting the reusable code by creating a _SuggestionList widget, like:

return new _SuggestionList(
  query: query,
  suggestions: suggestions.map((int i) => '$i'), 
  onSelected: (String suggestion) { // A ValueChanged<String> callback
    query = suggestion;
    showResultsPage(context);    
  },
);

If you passed the search delegate along then an onSelected callback wouldn't be needed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love it!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra blank line here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe: Shows a full screen search experience in terms of a search page and a results page.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overlay => search page

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"search experience" sounds pretty fancy; maybe just "search and results pages"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to the search => to the search page

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the underlying page while the search page fades in or out

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you really want to provide a default value here? Are developers supposed to specify null to get delegate.query?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I did it is that the SearchDelegate is potentially long-lived. If you do two searches back-to-back the expectation is that when the search page is brought up a second time the query field is empty again. That's what's enforced here. If developers want to diverge from the default behavior of resetting the query, they can do so here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs don't appear to cover the return value.

@goderbauer

Copy link
Copy Markdown
Member Author

Addressed comments and restructured code: The search page is now just one PageRoute.

@HansMuller HansMuller left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great, mostly just some doc wordsmithing.

Just needs some tests and it should be ready for its debut!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changes and = changes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since I think we really intend for the suggestions to be based on the query string: The suggestions should be based on the [query] string ... And I think you should what the method is supposed to do if [query] is the empty string, since that's the default.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

content => current value of

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically an [IconButton] configured with a [BackButtonIcon] that exits the search with [close].

One can also use an [AnimatedIcon]...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while the => when the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return by => returned by

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not really clear what this is for. In other words, if I'm building a SearchDelegate subclass what would I typically do with focusNode?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. I'll make it private.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do SearchDelegate subclasses need to see this? If they really wanted to, they could remember if they'd most recently called showSuggestions or showResults

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll make it private as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as earlier: do SearchDelegate subclassers need this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same.

@goderbauer

Copy link
Copy Markdown
Member Author

@HansMuller Comments are addressed and tests have been added. PTAL.

@HansMuller HansMuller left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great. Just one non-nit about Navigator.popping back to the showSearch caller's route


/// The results shown after the user submits a search from the search page.
///
/// The current value of [query] can be used to determine what the users

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

users => user

///
/// Typically an [IconButton] configured with a [BackButtonIcon] that exits
/// the search with [close]. One can also use an [AnimatedIcon] driven by
/// [transitionAnimation], which animated from e.g. a hamburger menu to the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

animated => animates

_textEditingController.text = value;
}

/// Transition to show search results.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be good to link this to buildResults more clearly. Like: Transition from the suggestions returned by [buildSuggestions] to the [query] results returned by [buildResults].

_currentBody = _SearchBody.results;
}

/// Transition to show the search suggestions and places the input focus

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest complicating this this a little (as with showResults) to make the link to buildSuggestions clear.

/// The current value of [query] can be used to determine what the users
/// searched for.
///
/// Typically, this method returns a [ListView] with the search results.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be helpful to include a point about suggestion widget onTap handlers typically calling [close] to return the search result. Just like buildSuggestions mentions what a typical suggestion onTap handler does.

);
break;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra blank line

);
}

TextEditingController get _controller => widget.delegate._textEditingController;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a little clearer if this was called queryTextController or something less generic.

},
decoration: new InputDecoration(
border: InputBorder.none,
hintText: MaterialLocalizations.of(context).searchFieldLabel

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trailing comma


class TestHomePage extends StatelessWidget {
const TestHomePage(
{this.results,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{ goes on the previous line

}

class TestHomePage extends StatelessWidget {
const TestHomePage(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually format this kind of thing like:

  const TestHomePage({
    this.foo,
    this.delegate,
    this.passInInitialQuery: false,
    this.initialQuery,
  });

@HansMuller HansMuller left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

_currentBody = null;
_focusNode.unfocus();
_result.complete(result);
Navigator.of(context)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's possible for the original route to have been removed while the search dialog is up, which would probably trigger a strange failure mode. If developers start running into that, we could make this code more defensive. In a future PR.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.

@goderbauer
goderbauer merged commit d5a1034 into flutter:master May 24, 2018
@goderbauer
goderbauer deleted the search branch May 24, 2018 16:30
@Hixie

Hixie commented Jun 4, 2018

Copy link
Copy Markdown
Contributor

We should update the stocks app to use this, probably.

@ThinkDigitalSoftware

ThinkDigitalSoftware commented Jun 17, 2018 •

Copy link
Copy Markdown
Contributor

Thanks for adding this, @goderbauer. How do we use it? I don't see it anywhere.

@NiklasPor

Copy link
Copy Markdown

@ThinkDigitalRepair I cannot find it either. Maybe take a look at this demo. 🤷‍♂️

@ThinkDigitalSoftware

Copy link
Copy Markdown
Contributor

@NiklasPor, thank you. I ended up repurposing the demo and it works .

@goderbauer goderbauer added framework flutter/packages/flutter repository. See also f: labels. p: material_ui material_ui package in flutter/packages labels Feb 22, 2019
@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Aug 7, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

framework flutter/packages/flutter repository. See also f: labels. p: material_ui material_ui package in flutter/packages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants