Importing an SQFlite database from Flutter app's assets and using rawQuery to display specific rows

Viewed 38

I've built an app using Flutter. Part of its functionality is that users can search through data which is in the assets area of the app. This data was originally in JSON format, although I have converted it into an SQLite database to save storage space. That has actually helped me to save around 90%, which is great. The problem is, the search delegate no longer works. It simply returns an empty list, although no errors are produced in the console.

I have created a model class to help read the data from the SQLite database table, which looks like this:

/// Class to handle the country data in the database
class CountriesDB {
  /// Defining the variables to be pulled from the json file
  late int id;
  late String continent;
  late String continentISO;
  late String country;
  late String countryISO;
  late String flagIconLocation;

  CountriesDB({
    required this.id,
    required this.continent,
    required this.continentISO,
    required this.country,
    required this.countryISO,
    required this.flagIconLocation,
  });

  CountriesDB.fromMap(dynamic obj) {
    this.id = obj[id];
    this.continent = obj[continent];
    this.continentISO = obj[continentISO];
    this.country = obj[country];
    this.countryISO = obj[countryISO];
    this.flagIconLocation = obj[flagIconLocation];
  }

  Map<String, dynamic> toMap() {
    var map = <String, dynamic>{
      'id': id,
      'continent': continent,
      'continentISO': continentISO,
      'country': country,
      'countryISO': countryISO,
      'flagIconLocation': flagIconLocation,
    };
    return map;
  }
}

As far as I am aware, to read data in a database that is stored within the assets folder of the app, I need to programatically convert it into a working database. I have written the following code, to sort that:

  /// Creating the database values
  static final DatabaseClientData instance = DatabaseClientData._init();
  static Database? _database;
  DatabaseClientData._init();

  /// Calling the database
  Future<Database> get database async {
    if (_database != null) return _database!;
    _database = await _initDB('databaseWorking.db');
    return _database!;
  }

  /// Future function to open the database
  Future<Database> _initDB(String filePath) async {
    /// Getting the data from the database in 'assets'
    var databasesPath = await getDatabasesPath();
    var path = join(databasesPath, filePath);

    /// Check if the database exists
    var exists = await databaseExists(path);

    if (!exists) {
      /// Should happen only the first time the application is launched
      print('Creating new copy from asset');

      /// Make sure the parent directory exists
      try {
        await Directory(dirname(path)).create(recursive: true);
      } catch (_) {}

      /// Copy from the asset
      ByteData data =
          await rootBundle.load('assets/data/database.db');
      List<int> bytes =
          data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);

      /// Write and flush the bytes written
      await File(path).writeAsBytes(bytes, flush: true);
    } else {
      print('Opening existing database');
    }
    return await openDatabase(path, readOnly: true);
  }

The next thing I have done is to create a Future function that searches the database using a rawQuery. The code for this is:

  /// Functions to search for specific database entries
  /// Countries
  static Future<List<CountriesDB>> searchCountries(String keyword) async {
    final db = await instance.database;
    List<Map<String, dynamic>> allCountries = await db.rawQuery(
        'SELECT * FROM availableISOCountries WHERE continent=? OR continentISO=? OR country=? OR countryISO=?',
        ['%keyword%']);
    List<CountriesDB> countries =
        allCountries.map((country) => CountriesDB.fromMap(country)).toList();
    return countries;
  }

Finally, I am using the Flutter Search Delegate class to allow the user to interact with the database and search for specific rows. This is the widget I have built for that:

  /// Checks to see if suggestions can be made and returns error if not
  Widget buildSuggestions(BuildContext context) => Container(
        color: Color(0xFFF7F7F7),
        child: FutureBuilder<List<CountriesDB>>(
          future: DatabaseClientData.searchCountries(query),
          builder: (context, snapshot) {
            switch (snapshot.connectionState) {
              case ConnectionState.waiting:
                return Center(
                    child: PlatformCircularProgressIndicator(
                  material: (_, __) => MaterialProgressIndicatorData(
                    color: Color(0xFF287AD3),
                  ),
                  cupertino: (_, __) => CupertinoProgressIndicatorData(),
                ));
              default:
                if (query.isEmpty) {
                  return buildAllSuggestionsNoSearch(snapshot.data!);
                } else if (snapshot.hasError || snapshot.data!.isEmpty) {
                  return buildNoSuggestionsError(context);
                } else {
                  return buildSuggestionsSuccess(snapshot.data!);
                }
            }
          },
        ),
      );

The idea is that the functionality I have built will return the whole list before a user searches and once a users starts typing, they will only be shown any rows that match their search query. This worked fine when I was using JSON data but it is returning an empty list, yet there are no errors printed in the console, at all. That makes it quite hard to know where my code is going wrong.

Where have I gone wrong with my code, such that it is not returning any data? How can I correct this? Thanks!

0 Answers
Related