Home > Software design >  How to search/filter from a list from an API?
How to search/filter from a list from an API?

Time:10-03

I have managed to load a list of cryptocurrencies from an API. This is done via the ListView.builder.

Subsequently, how does one perform a search/filter in order to select an item from the list?

By scrolling towards the end to see the last code, I have shown the code that I presumed would be able to do the job of 'search'. But I am unsure where to place this code.

Image below shows current crypto list loaded from API:

enter image description here

The code for the above screen is as follows:

class AddCryptoAssetScreen extends StatefulWidget {
  @override
  _AddCryptoAssetScreenState createState() => _AddCryptoAssetScreenState();
}

class _AddCryptoAssetScreenState extends State<AddCryptoAssetScreen> {
  Future<List<Asset>> fetchCoin() async {
    assetList = [];
    final response = await http.get(Uri.parse(
        'https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=100&page=1&sparkline=false'));

    if (response.statusCode == 200) {
      List<dynamic> values = [];
      values = json.decode(response.body);
      if (values.length > 0) {
        for (int i = 0; i < values.length; i  ) {
          if (values[i] != null) {
            Map<String, dynamic> map = values[i];
            assetList.add(Asset.fromJson(map));
          }
        }
        setState(() {
          assetList;
        });
      }
      return assetList;
    } else {
      throw Exception('Failed to load coins');
    }
  }

  @override
  void initState() {
    fetchCoin();
    Timer.periodic(Duration(seconds: 1), (timer) => fetchCoin());
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      behavior: HitTestBehavior.opaque,
      onTap: () => Navigator.pop(context),
      child: DraggableScrollableSheet(
        builder: (_, controller) => Container(
          decoration: BoxDecoration(),
          clipBehavior: Clip.antiAlias,
          child: Scaffold(
            appBar: AppBar(),
            body: Column(
              children: [
                Container(
                  margin: const EdgeInsets.fromLTRB(),
                  child: TextField(
                    keyboardType: TextInputType.text,
                    textAlign: TextAlign.start,
                    decoration: InputDecoration(
                        prefixIcon: const Icon(Icons.search),
                        hintText: 'Search',
                        border: OutlineInputBorder(),
                        contentPadding: EdgeInsets.only()),
                  ),
                ),
                Expanded(
                  child: ListView.builder(
                    scrollDirection: Axis.vertical,
                    itemCount: assetList.length,
                    itemBuilder: (context, index) {
                      return AssetCryptoCard(
                        name: assetList[index].name,
                        image: assetList[index].image,
                      );
                    },
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

The Asset class is as follows. To derive assetList.

class Asset {
  String name;
  String image;
  num currentPrice;
  num priceChange24h;
  num priceChangePercentage24h;
  String symbol;

  

  Asset({
    required this.name,
    required this.image,
    required this.currentPrice,
    required this.priceChange24h,
    required this.priceChangePercentage24h,
    required this.symbol,
  });

  factory Asset.fromJson(Map<String, dynamic> json) {
    return Asset(
      name: json['name'],
      symbol: json['symbol'],
      image: json['image'],
      currentPrice: json['current_price'],
      priceChange24h: json['price_change_24h'],
      priceChangePercentage24h: json['price_change_percentage_24h'],
    );
  }
}

List<Asset> assetList = [];

The AssetCryptoCard class is as follows.

class AssetCryptoCard extends StatelessWidget {
  AssetCryptoCard({
    required this.name,
    required this.image,
  });

  final String name;
  final String image;

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: () {
        showModalBottomSheet(
          context: context,
          builder: (context) => EditAssetScreen(),
        );
      },
      child: Container(
        padding: EdgeInsets.only(),
        child: Column(
          children: [
            Row(
              children: [
                Container(child: Image.network(image)),
                SizedBox(),
                Text(name),
                Spacer(),
                Icon(Icons.arrow_forward_ios_rounded),
              ],
            ),
            Container(),
          ],
        ),
      ),
    );
  }
}

I have written the code below but unsure where is the right place to put it. Also, presumably this is the right code to do a search/filter on the list.

  List<Map<String, dynamic>> foundAssetList = [];
  @override
  initState() {
    foundAssetList = assetList;
    super.initState();
  }

  void _runFilter(String enteredKeyword) {
    List<Map<String, dynamic>> results = [];
    if (enteredKeyword.isEmpty) {
      results = assetList;
    } else {
      results = assetList
          .where((user) =>
          user["name"].toLowerCase().contains(
              enteredKeyword.toLowerCase().toList();
          }
              setState(() {
      foundAssetList = results;
      });
    }

Any help would be much appreciated.

CodePudding user response:

Use the textfoemfield onChanged property

TextFormField(
 OnChanged: (value){
  _runFilter(value);
 }
);

Or

For API request use future feature in textfield_search package

please review this package can provide solution with different approach

I hope it works for you.

CodePudding user response:

You're missing a few things:

  1. You need a state that keeps track of the text in the TextField - you can do this by adding a state variable and use onChanged of the TextField to update the variable
  2. Write a function that returns a list of the cryptocurrencies based on the keyword: You can create a function that returns the list of all cryptocurrencies if the keyword is empty, else return a filtered list
  3. Replace the assetList variable in the ListView.builder with the filteredList (should be the value of the function in step 2)

The result code should look like this (put this in _AddCryptoAssetScreenState:

 String _keyword = "";
  
  List<Map<String, dynamic>> _getFilteredList() {
    if (_keyword.isEmpty) {
      return assetList;
    }
    return assetList
          .where((user) =>
          user["name"].toLowerCase().contains(
              _keyword.toLowerCase())).toList();
  }

 @override
 Widget build(BuildContext context) {
   final filteredList = _getFilteredList();
    return GestureDetector(
      behavior: HitTestBehavior.opaque,
      onTap: () => Navigator.pop(context),
      child: DraggableScrollableSheet(
        builder: (_, controller) => Container(
          decoration: BoxDecoration(),
          clipBehavior: Clip.antiAlias,
          child: Scaffold(
            appBar: AppBar(),
            body: Column(
              children: [
                Container(
                  margin: const EdgeInsets.fromLTRB(),
                  child: TextField(
                    keyboardType: TextInputType.text,
                    textAlign: TextAlign.start,
                    decoration: InputDecoration(
                        prefixIcon: const Icon(Icons.search),
                        hintText: 'Search',
                        border: OutlineInputBorder(),
                        contentPadding: EdgeInsets.only()),
                    onChanged: (text) {
                      setState(() {
                        _keyword = text;
                      });
                    },
                  ),
                ),
                Expanded(
                  child: ListView.builder(
                    scrollDirection: Axis.vertical,
                    itemCount: filteredList.length,
                    itemBuilder: (context, index) {
                      return AssetCryptoCard(
                        name: filteredList[index].name,
                        image: filteredList[index].image,
                      );
                    },
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

Let me know if this works.

  • Related