2

I've been trying to implement a search bar into my app for bringing selected listView items to the top of a list. The list contains quite a few items, around approximately 1700 so the addition of a search bar is essential. I'd like the listView search box to appear from a search icon on the right hand side of the top appBar. Below is a picture of the current view for reference.

When you click the search iconButton a search field should replace the title in the appBar. It's going to be evident to the user that this is for the crypto listView as I'll add a hint in the search view identifying this.

current crypto app view

I'm not including all my code as this would be cumbersome for a stack question, but below is my home_page.dart file, where as the rest of my classes for the bottom crypto listView can be found at this GitHub repo.

This is what my 'home_page.dart` looks like;

import 'package:cryptick/cryptoData/crypto_data.dart';
import 'package:cryptick/cryptoData/trending_data.dart';
import 'package:cryptick/modules/crypto_presenter.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'background.dart';

//FOLLOWING DART CODE COPYRIGHT OF 2017 - 2018 SQUARED SOFTWARE LONDON

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => new _HomePageState();
}

class ServerStatusScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        iconTheme: new IconThemeData(color: Colors.white),
        centerTitle: true,
        backgroundColor: Colors.black,
        title: new Text(
          'API Server Status',
          textAlign: TextAlign.center,
          style: new TextStyle(
              color: Colors.white, fontSize: 27.5, fontFamily: 'Kanit'),
        ),
      ),
      body: new Center(
        child: new Column(
          children: [
            new Divider(color: Colors.white),
            new Text(
              'News Feed: ',
              textAlign: TextAlign.center,
              style: new TextStyle(
                color: Colors.black,
                fontSize: 27.5,
                fontFamily: 'Kanit',
              ),
            ),
            new Divider(),
            new Text(
              'Crypto Feed: ',
              textAlign: TextAlign.center,
              style: new TextStyle(
                color: Colors.black,
                fontSize: 27.5,
                fontFamily: 'Kanit',
              ),
            ),
            new Divider(),
            new Wrap(
              alignment: WrapAlignment.center,
              children: <Widget>[
                new Chip(
                  backgroundColor: Colors.black,
                  label: new Text(
                    '© 2017-2018 Squared Software',
                    style: new TextStyle(
                      fontSize: 15.0,
                      fontFamily: 'Poppins',
                      color: Colors.white,
                    ),
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

class MoreInfoScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final ThemeData themeData = Theme.of(context);
    final TextStyle aboutTextStyle = themeData.textTheme.body2;
    final TextStyle linkStyle =
        themeData.textTheme.body2.copyWith(color: themeData.accentColor);
    return new Scaffold(
      appBar: new AppBar(
        iconTheme: new IconThemeData(color: Colors.white),
        centerTitle: true,
        backgroundColor: Colors.black,
        title: new Text(
          'More Info',
          textAlign: TextAlign.center,
          style: new TextStyle(
              color: Colors.white, fontSize: 27.5, fontFamily: 'Kanit'),
        ),
      ),
      body: new Center(
        child: new Column(
          children: [
            new Divider(color: Colors.white),
            new ListTile(
                title: new Text('Squared Software',
                    style: new TextStyle(
                      fontWeight: FontWeight.w500,
                      fontFamily: 'Poppins',
                    )
                  ),
                leading: new CircleAvatar(
                    radius: 30.0,
                    backgroundImage: new AssetImage(
                        'images/sqinterlock.png'
                        )
                      )
                    ),
            new Divider(),
            new Text('Where do we get our information?',
                style: new TextStyle(
                  color: Colors.black,
                  fontFamily: 'Poppins',
                  fontSize: 16.5,
                )
              ),
            new Divider(color: Colors.white),
            new Text(
              "News Feed: bit.ly/2MFpzHX",
              style: new TextStyle(
                fontFamily: 'Poppins',
                fontSize: 16.5,
              ),
            ),
            new Divider(color: Colors.white),
            new Text(
              "Crypto Feed: bit.ly/2iIdJht",
              style: new TextStyle(
                fontFamily: 'Poppins',
                fontSize: 16.5,
              ),
            ),
            new Divider(color: Colors.white),
            new Wrap(
              alignment: WrapAlignment.center,
              children: <Widget>[
                new Chip(
                  backgroundColor: Colors.black,
                  label: new Text(
                    '© 2017-2018 Squared Software',
                    style: new TextStyle(
                      fontSize: 15.0,
                      fontFamily: 'Poppins',
                      color: Colors.white,
                    ),
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

class _HomePageState extends State<HomePage> implements CryptoListViewContract {
  CryptoListPresenter _presenter;
  List<Crypto> _currencies;
  bool _isLoading;
  final List<MaterialColor> _colors = [Colors.blue, Colors.indigo, Colors.red];

  _HomePageState() {
    _presenter = new CryptoListPresenter(this);
  }

  @override
  void onLoadTrendingComplete(Trending trending) {
    // TODO:
    articlesMap = trending.articles;

    for (Map articleMap in articlesMap) {
      articles.add(Articles.fromMap(articleMap));
    }

    if (mounted) setState(() {});
  }

  @override
  void onLoadTrendingError() {
    // TODO:
  }

  List articlesMap = [];
  List<Articles> articles = [];

  @override
  void initState() {
    super.initState();
    _isLoading = true;
    _presenter.loadCurrencies();
    _presenter.loadTrending();
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
          title: new Text(
            "Cryp - Tick Exchange",
            style: new TextStyle(
              color: Colors.white,
              fontFamily: 'Poppins',
              fontSize: 22.5,
            ),
          ),
          iconTheme: new IconThemeData(color: Colors.white),
          backgroundColor: const Color(0xFF273A48),
          elevation: 0.0,
          centerTitle: true,
        ),
        drawer: new Drawer(
          child: new ListView(padding: EdgeInsets.zero, children: <Widget>[
            new DrawerHeader(
              child: new CircleAvatar(
                child: new Image.asset('images/ctavatar.png'),
              ),
              decoration: new BoxDecoration(
                color: Colors.black,
              ),
            ),
            new MaterialButton(
                child: new Text(
                  'Server Status',
                  textAlign: TextAlign.center,
                  style: new TextStyle(fontSize: 27.5, fontFamily: 'Kanit'),
                ),
                onPressed: () {
                  Navigator.push(
                    context,
                    MaterialPageRoute(
                        builder: (context) => ServerStatusScreen()),
                  );
                }),
            new Divider(),
            new MaterialButton(
                child: new Text(
                  'More Info',
                  textAlign: TextAlign.center,
                  style: new TextStyle(fontSize: 27.5, fontFamily: 'Kanit'),
                ),
                onPressed: () {
                  Navigator.push(
                    context,
                    MaterialPageRoute(builder: (context) => MoreInfoScreen()),
                  );
                }),
            new Divider(),
            new Wrap(
              alignment: WrapAlignment.center,
              children: <Widget>[
                new Chip(
                  backgroundColor: Colors.black,
                  label: new Text(
                    'v0.0.1',
                    style: new TextStyle(
                      fontSize: 15.0,
                      fontFamily: 'Poppins',
                      color: Colors.white,
                    ),
                  ),
                ),
              ],
            ),
          ]),
        ),
        body: _isLoading
            ? new Center(child: new CupertinoActivityIndicator(radius: 15.0))
            : _allWidget());
  }


  Widget _allWidget() {
    final _width = MediaQuery.of(context).size.width;
    final _height = MediaQuery.of(context).size.height;
//CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED
    final headerList = new ListView.builder(
      itemBuilder: (context, index) {
        EdgeInsets padding = index == 0
            ? const EdgeInsets.only(
                left: 20.0, right: 10.0, top: 4.0, bottom: 30.0)
            : const EdgeInsets.only(
                left: 10.0, right: 10.0, top: 4.0, bottom: 30.0);
        return new Padding(
          padding: padding,
          child: new InkWell(
            onTap: () {
              print('@url');
            },
            child: new Container(
              decoration: new BoxDecoration(
                borderRadius: new BorderRadius.circular(10.0),
                color: const Color(0xFF273A48),
                boxShadow: [
                  new BoxShadow(
                    color: Colors.black.withAlpha(70),
                    offset: const Offset(3.0, 10.0),
                      blurRadius: 15.0)
                ],
                image: new DecorationImage(
                  image: new NetworkImage(articles[index].urlToImage),
                  fit: BoxFit.fitHeight,
                ),
              ),
              height: 200.0,
              width: 275.0,
              child: new Stack(
                children: <Widget>[
                  new Align(
                    alignment: Alignment.bottomCenter,
                    child: new Container(
                      padding: new EdgeInsets.only(left: 10.0),
                        decoration: new BoxDecoration(
                          color: const Color(0xFF273A48),
                          borderRadius: new BorderRadius.only(
                              bottomLeft: new Radius.circular(10.0),
                              bottomRight: new Radius.circular(10.0)),
                        ),
                        height: 50.0,
                        child: new Row(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: <Widget>[
                            new Expanded(child: new Text(
                              articles[index].title,
                              overflow: TextOverflow.ellipsis,
                              maxLines: 2,
                              style: new TextStyle(
                                color: Colors.white,
                                fontFamily: 'Poppins',
                              ),
                            ),
                          ),
                        ],
                      )
                    ),
                  )
                ],
              ),
            ),
          ),
        );
      },
      scrollDirection: Axis.horizontal,
      itemCount: articles.length,
    );

    final body = new Scaffold(
      backgroundColor: Colors.transparent,
      body: new Container(
        child: new Stack(
          children: <Widget>[
            new Padding(
              padding: new EdgeInsets.only(top: 10.0),
              child: new Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisSize: MainAxisSize.max,
                mainAxisAlignment: MainAxisAlignment.start,
                children: <Widget>[
                  new Align(
                    alignment: Alignment.centerLeft,
                    child: new Padding(
                        padding: new EdgeInsets.only(
                          left: 10.0,
                        ),
                        child: new Text(
                          "Trending News",
                          style: new TextStyle(
                            letterSpacing: 0.8,
                            fontFamily: 'Kanit',
                            fontSize: 17.5,
                            color: Colors.white,
                          ),
                        )),
                  ),
                  new Container(
                      height: 300.0, width: _width, child: headerList),
                  new Expanded(child: ListView.builder(
                      itemBuilder: (BuildContext context, int index) {
                    final int i = index;
                    final Crypto currency = _currencies[i];
                    final MaterialColor color = _colors[i % _colors.length];
                    return new ListTile(
                      title: new Column(
                        children: <Widget>[
                          new Row(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: <Widget>[
                              new Container(
                                height: 72.0,
                                width: 72.0,
                                decoration: new BoxDecoration(
                                    color: Colors.white,
                                    boxShadow: [
                                      new BoxShadow(
                                          color: Colors.black.withAlpha(80),
                                          offset: const Offset(2.0, 2.0),
                                          blurRadius: 15.0)
                                    ],
                                    borderRadius: new BorderRadius.all(
                                        new Radius.circular(35.0)),
                                    image: new DecorationImage(
                                      image: new ExactAssetImage(
                                        "cryptoiconsBlack/" +
                                            currency.symbol.toLowerCase() +
                                            "@2x.png",
                                      ),
                                      fit: BoxFit.cover,
                                    )),
                              ),
                              new SizedBox(
                                width: 8.0,
                              ),
                              new Expanded(
                                  child: new Column(
                                mainAxisAlignment: MainAxisAlignment.start,
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: <Widget>[
                                  new Text(
                                    currency.name,
                                    style: new TextStyle(
                                        fontSize: 15.0,
                                        fontFamily: 'Poppins',
                                        color: Colors.black87,
                                        fontWeight: FontWeight.bold),
                                  ),
                                  _getSubtitleText(currency.price_usd,
                                      currency.percent_change_1h),
                                ],
                              )),
                            ],
                          ),
                          new Divider(),
                        ],
                      ),
                    );
                  }))
                ],
              ),
            ),
          ],
        ),
      ),
    );

    return new Container(
      decoration: new BoxDecoration(
        color: const Color(0xFF273A48),
      ),
      child: new Stack(
        children: <Widget>[
          new CustomPaint(
            size: new Size(_width, _height),
            painter: new Background(),
          ),
          body,
        ],
      ),
    );
  }

// CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED

  Widget _getSubtitleText(String priceUSD, String percentageChange) {
    TextSpan priceTextWidget = new TextSpan(
        text: "\$$priceUSD\n",
        style: new TextStyle(
          color: Colors.black,
          fontSize: 14.0,
        ));
    String percentageChangeText = "1 hour: $percentageChange%";
    TextSpan percentageChangeTextWidget;

    if (double.parse(percentageChange) > 0) {
      percentageChangeTextWidget = new TextSpan(
          text: percentageChangeText,
          style: new TextStyle(
            color: Colors.green,
            fontFamily: 'PoppinsMediumItalic',
          ));
    } else {
      percentageChangeTextWidget = new TextSpan(
          text: percentageChangeText,
          style: new TextStyle(
            color: Colors.red,
            fontFamily: 'PoppinsMediumItalic',
          ));
    }

    return new RichText(
        text: new TextSpan(
            children: [priceTextWidget, percentageChangeTextWidget]));
  }

  //Works with cryptoListViewContract implimentation in _MyHomePageState
  @override
  void onLoadCryptoComplete(List<Crypto> items) {
    // TODO: implement onLoadCryptoComplete

    setState(() {
      _currencies = items;
      _isLoading = false;
    });
  }

  @override
  void onLoadCryptoError() {
    // TODO: implement onLoadCryptoError
  }
}

Thanks for the help, Jake

Jake
  • 1,906
  • 9
  • 32
  • 60
  • Have you consider [AppBar.actions](https://docs.flutter.io/flutter/material/AppBar/actions.html)? If so, what is the desired affect? That is, do you want a have a dialog pop up to search? Would be cool to have an TextField instead of an Icon and you just expand that our while hiding the other parts of the AppBar. – Ashton Thomas Sep 08 '18 at 16:06
  • Yeah I'll edit the question after this to reflect this comment but is there anyway when you click the search `iconButton` a search field could replace the title in the `appBar`?. It's going to be evident to the user that this is for the crypto `listView` as I'll add a hint in the search view identifying this. Thanks – Jake Sep 08 '18 at 16:10

2 Answers2

5

There are probably many ways to implement this based on the resulting experience you want. A simple solution is to create activeSearch state that toggles a 'search app bar' and a 'normal app bar'

Here's the normal app bar:

return AppBar(
  title: Text("My App"),
  actions: <Widget>[
    IconButton(
      icon: Icon(Icons.search),
      onPressed: () => setState(() => activeSearch = true),
    ),
  ],
);

And here's the search app bar:

return AppBar(
  leading: Icon(Icons.search),
  title: TextField(
    decoration: InputDecoration(
      hintText: "here's a hint",
    ),
  ),
  actions: <Widget>[
    IconButton(
      icon: Icon(Icons.close),
      onPressed: () => setState(() => activeSearch = false),
    )
  ],
);

Note: if you don't want to have a leading icon when search is active you may want to disable the default behavior for a drawer and back button icon with:

automaticallyImplyLeading: false

Full example:

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  bool activeSearch;

  @override
  void initState() {
    super.initState();
    activeSearch = false;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: _appBar(),
      drawer: _drawer(),
    );
  }

  PreferredSizeWidget _appBar() {
    if (activeSearch) {
      return AppBar(
        leading: Icon(Icons.search),
        title: TextField(
          decoration: InputDecoration(
            hintText: "here's a hint",
          ),
        ),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.close),
            onPressed: () => setState(() => activeSearch = false),
          )
        ],
      );
    } else {
      return AppBar(
        title: Text("My App"),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.search),
            onPressed: () => setState(() => activeSearch = true),
          ),
        ],
      );
    }
  }

  Widget _drawer() {
    return Container();
  }
}

enter image description here

enter image description here

UPDATE: Here's a hint at handling results

return AppBar(
  ...
  title: TextField(
    onChanged: _search,
  ),
);

And what _search could look like:

  List<MyResultObject> _results;

  void _search(String queryString) {
    // do some searching and sorting
    // then call setState() with the results
    // and then in your ListView you can read from results
    // (handle empty, default case as well in view)
    setState(() {
      _results = ...
    });
  }

  List<Widget> _resultWidgets() {
    if (_results.isEmpty) return _defaultWidgets();
    _results.map((r) => _buildRowWidget(s)).toList();
  }
Ashton Thomas
  • 16,659
  • 5
  • 31
  • 25
  • This looks great, just implementing it now – Jake Sep 08 '18 at 16:49
  • Is there anyway you could provide or hint at the code I would need to actually sort the items? The search bar is really good thanks for what you've done so far. – Jake Sep 08 '18 at 16:59
  • @Jake, I added quick update at the end that may help – Ashton Thomas Sep 08 '18 at 17:09
  • Brilliant, thanks for the help. I'll take a look at the question @Dinesh Balasubramanian provided and reference it soon. – Jake Sep 08 '18 at 17:15
  • Hey @Ashton, just noticed that when the on screen iOS keyboard pops up when I type a value to the search field, I get a severe pixel overflow. Anything I could do about this? Thanks – Jake Sep 09 '18 at 19:22
  • You need to wrap the body of your scaffold in a listview to creat a scrollable area – Ashton Thomas Sep 09 '18 at 21:08
1

Can u refer a simple search view in this answer. In that example, as the user types, the list will get filtered.

Dinesh Balasubramanian
  • 20,532
  • 7
  • 64
  • 57
  • I'll implement the question from Ashton first, then I'll add that after. Thanks for the additional info :) – Jake Sep 08 '18 at 16:50