MultiSearch in flutter

Viewed 55

The goal of this project is to use Multisearch to search local JSON data based on an int number inside the string. The search for string content has shown the result, now I'm trying to add a search for number entered by the user that searches by int inside string content, but it returns an error.

null isn't subtype of string.

enter image description here

There is a problem with this search code since it works for both string content and integer number.

import 'dart:convert';
import 'package:ebook_flutter_app/constant.dart';
import 'package:ebook_flutter_app/screens/show_item.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:persistent_bottom_nav_bar/persistent-tab-view.dart';

import '../widgets/showImage.dart';

class SearchScreen extends StatefulWidget {
  const SearchScreen({Key? key}) : super(key: key);

  @override
  SearchScreenState createState() => SearchScreenState();
}

class SearchScreenState extends State<SearchScreen> {
  List textValues = [];
  List original = [];
  static TextEditingController txtQuery = TextEditingController();

  void loadData() async {
    String jsonStr = await rootBundle.loadString('assets/db/text_value.json');
    var json = jsonDecode(jsonStr);
    textValues = json;
    original = json;
    setState(() {});
  }

  void loadImage() async {
    String jsonStr = await rootBundle.loadString('assets/db/image_db.json');
    var json = jsonDecode(jsonStr);
    textValues = json;
    original = json;
    print('load Image is running....');
    setState(() {});
  }

  void search(String query) {
    if (query.isEmpty) {
      textValues = original;
      setState(() {});
      return;
    }

    query = query.toLowerCase();
    print(query);
    List result = [];
    textValues.forEach((element) {
      var name = element["name"].toString().toLowerCase();
      var description = element["description"].toString().toLowerCase();
      if (name.contains(query) || description.contains(query)) {
        result.add(element);
      }
    });

    textValues = result;
    setState(() {});
  }

  void searchById(String query1) {
    if (query1.isEmpty) {
      textValues = original;
      print('query1 is .....$query1');
      setState(() {});
      return;
    }

    print('query1 is $query1');
    List result = [];
    textValues.forEach((element) {
      var id_num = element["id_num"].toString();
      var id_img = element["id_img"].toString();
      if (id_num.contains(query1) ||
          id_img.contains(query1)) {
        result.add(element);
        print('result is......$result');    
      }
    });

    textValues = result;
    print('textValues is .....$textValues');
    setState(() {});
  }

  @override
  void initState() {
    super.initState();
    txtQuery.text.contains(r'^[0-3]+$') ? loadImage() : loadData();

    print('Init State is Loading....');
  }

  String StrOrInt(String text) {
    if (text.toString().startsWith('1') || text.toString().startsWith('2')) {
      return 'int';
    } else {
      return 'str';
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: CustomAppBar(2),
      body: Column(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Container(
              margin: const EdgeInsets.all(10),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.start,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  TextFormField(
                    controller: txtQuery,
                    onChanged: search,
                    textDirection: TextDirection.rtl,
                    decoration: InputDecoration(
                      hintText: "جست وجو...",
                      hintTextDirection: TextDirection.rtl,
                      hintStyle: TextStyle(
                        color: Colors.black,
                        fontSize: 18,
                        fontFamily: 'iran-sans-ds',
                        decoration: TextDecoration.none,
                        fontStyle: FontStyle.italic,
                      ),
                      border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(4.0)),
                      focusedBorder: const OutlineInputBorder(
                          borderSide: BorderSide(color: Colors.black)),
                      prefixIcon: const Icon(Icons.search),
                      suffixIcon: IconButton(
                        icon: const Icon(Icons.clear),
                        onPressed: () {
                          txtQuery.text = '';
                          txtQuery.text.contains(r'^[0-3]+$') ? searchById(txtQuery.text) : search(txtQuery.text);
                        },
                      ),
                    ),
                    keyboardType: TextInputType.text,
                  ),
                ],
              ),
            ),
            txtQuery.text.isEmpty ? Container() : _listView(textValues)
          ]),
    );
  }
}

Widget _listView(text_value) {
  return Expanded(
    child: ListView.builder(
        itemCount: text_value.length,
        itemBuilder: (context, index) {
          var textVal = text_value[index];
          String description = textVal['description'];

          String id_num = textVal['id_num'];
          String id_img = textVal['id_img'];


          print('id_num is ....$id_num' ' id_img is.....$id_img');

          return Card(
            margin: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 4.0),
            shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(15.0)),
            color: Colors.blue[50],
            child: Theme(
              data:
                  Theme.of(context).copyWith(dividerColor: Colors.transparent),
              child: InkWell(
                onTap: (() => pushNewScreen(
                      context,
                      screen: !textVal['id_num'].toString().contains(r'^[0-3]+$')
                          ? ShowItem(
                              name: textVal['name'],
                              description: textVal['description'],
                            )
                          : ShowImage(
                              title: textVal['id_num'],
                              image: Myasset(textVal['id_img']),
                            ),
                      withNavBar: true, // OPTIONAL VALUE. True by default.
                      pageTransitionAnimation:
                          PageTransitionAnimation.slideRight,
                    )),
                child: ExpansionTile(
                  title: Text(
                    !textVal['id_num'].toString().contains(r'^[0-3]+$')
                        ? textVal['name']
                        : textVal['id_num'],
                    textDirection: TextDirection.rtl,
                    style:
                        const TextStyle(fontSize: 20.0, color: Colors.black54),
                  ),
                  childrenPadding: const EdgeInsets.only(
                      bottom: 20.0, right: 20.0, left: 20.0, top: 5.0),
                  children: [
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceAround,
                      children: !textVal['id_num'].toString().contains(r'^[0-3]+$')
                          ? [
                              const Text(
                                'بیشتر',
                                textDirection: TextDirection.rtl,
                                textAlign: TextAlign.justify,
                                style: TextStyle(
                                    color: Colors.blue,
                                    fontWeight: FontWeight.bold),
                              ),
                              Text(
                                '${description.substring(0, 39)} ...',
                                textDirection: TextDirection.rtl,
                                textAlign: TextAlign.justify,
                                style: TextStyle(color: Colors.black),
                              ),
                            ]
                          : [Image.asset(Myasset(textVal['id_img']))],
                    )
                  ],
                ),
              ),
            ),
          );
        }),
  );
}

Below is the last source code which works with string content and returns the result.

import 'dart:convert';
import 'package:ebook_flutter_app/constant.dart';
import 'package:ebook_flutter_app/screens/show_item.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:persistent_bottom_nav_bar/persistent-tab-view.dart';

class SearchScreen extends StatefulWidget {
  const SearchScreen({Key? key}) : super(key: key);

  @override
  SearchScreenState createState() => SearchScreenState();
}

class SearchScreenState extends State<SearchScreen> {
  List textValues = [];
  List original = [];
  TextEditingController txtQuery = TextEditingController();

  void loadData() async {
    String jsonStr = await rootBundle.loadString('assets/db/text_value.json');
    var json = jsonDecode(jsonStr);
    textValues = json;
    original = json;
    setState(() {});
  }

  void search(String query) {
    if (query.isEmpty) {
      textValues = original;
      setState(() {});
      return;
    }

    query = query.toLowerCase();
    print(query);
    List result = [];
    textValues.forEach((element) {
      var name = element["name"].toString().toLowerCase();
      var description = element["description"].toString().toLowerCase();
      if (name.contains(query) || description.contains(query)) {
        result.add(element);
      }
    });

    textValues = result;
    setState(() {});
  }

  @override
  void initState() {
    super.initState();
    loadData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: CustomAppBar(2),
      body: Column(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Container(
              margin: const EdgeInsets.all(10),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.start,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  TextFormField(
                    controller: txtQuery,
                    onChanged: search,
                    textDirection: TextDirection.rtl,
                    decoration: InputDecoration(
                      hintText: "جست وجو...",
                      hintTextDirection: TextDirection.rtl,
                      hintStyle: TextStyle(
                        color: Colors.black,
                        fontSize: 18,
                        fontFamily: 'iran-sans-ds',
                        decoration: TextDecoration.none,
                        fontStyle: FontStyle.italic,
                      ),
                      border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(4.0)),
                      focusedBorder: const OutlineInputBorder(
                          borderSide: BorderSide(color: Colors.black)),
                      prefixIcon: const Icon(Icons.search),
                      suffixIcon: IconButton(
                        icon: const Icon(Icons.clear),
                        onPressed: () {
                          txtQuery.text = '';
                          search(txtQuery.text);
                        },
                      ),
                    ),
                    keyboardType: TextInputType.text,
                  ),
                ],
              ),
            ),
            txtQuery.text.isEmpty ? Container() : _listView(textValues)
          ]),
    );
  }
}

Widget _listView(text_value) {
  return Expanded(
    child: ListView.builder(
        itemCount: text_value.length,
        itemBuilder: (context, index) {
          var textVal = text_value[index];
          String description = textVal['description'];

          return Card(
            margin: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 4.0),
            shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(15.0)),
            color: Colors.blue[50],
            child: Theme(
              data:
                  Theme.of(context).copyWith(dividerColor: Colors.transparent),
              child: InkWell(
                onTap: (() => pushNewScreen(
                      context,
                      screen: ShowItem(
                        name: textVal['name'],
                        description: textVal['description'],
                      ),
                      withNavBar: true, // OPTIONAL VALUE. True by default.
                      pageTransitionAnimation:
                          PageTransitionAnimation.slideRight,
                    )),
                child: ExpansionTile(
                  title: Text(
                    textVal['name'],
                    textDirection: TextDirection.rtl,
                    style:
                        const TextStyle(fontSize: 20.0, color: Colors.black54),
                  ),
                  childrenPadding: const EdgeInsets.only(
                      bottom: 20.0, right: 20.0, left: 20.0, top: 5.0),
                  children: [
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceAround,
                      children: [
                        const Text(
                          'بیشتر',
                          textDirection: TextDirection.rtl,
                          textAlign: TextAlign.justify,
                          style: TextStyle(
                              color: Colors.blue, fontWeight: FontWeight.bold),
                        ),
                        Text(
                          '${description.substring(0, 39)} ...',
                          textDirection: TextDirection.rtl,
                          textAlign: TextAlign.justify,
                          style: TextStyle(color: Colors.black),
                        ),
                      ],
                    )
                  ],
                ),
              ),
            ),
          );
        }),
  );
}
1 Answers

The problem is in the ListView.itemBuilder on the function _listView. The code below assumes textVal contain both image and data and there should be only one type.

itemBuilder: (context, index) {
  var textVal = text_value[index];
  String description = textVal['description'];

  String id_num = textVal['id_num'];
  String id_img = textVal['id_img'];
  ...

To fix it just default the value to something else if null. It's easy with the null-coalescing operator ??. Take a look:

itemBuilder: (context, index) {
  var textVal = text_value[index];
  String description = textVal['description'] ?? '';

  String id_num = textVal['id_num'] ?? '';
  String id_img = textVal['id_img'] ?? '';
  ...
Related