1

I am using the following code to copy database from asset. Most of time time, this code work fine. However, after my app was published to app store, I received complain from some users whereby the database failed to load. Then I tried on my own iPhone, uninstall and download the app again from app store. The weird thing is out of approximately 10 times installation, there is 1 time the database failed to load. This is my database helper:

class DatabaseHelper {
  static const NEW_DB_VERSION = 6;

  static final DatabaseHelper _instance = DatabaseHelper.internal();

  factory DatabaseHelper() => _instance;

  DatabaseHelper.internal();

  Database _db;

  Future<Database> get db async {
    if (_db != null) {
      return _db;
    } else {
      _db = await initDb();
      return _db;
    }
  }

  Future<Database> initDb() async {

    final databasesPath = await getDatabasesPath();
    final path = join(databasesPath, "dictionary");

    var db = await openDatabase(path);

    //if database does not exist yet it will return version 0
    if (await db.getVersion() < NEW_DB_VERSION) {
      db.close();

      try {
        await Directory(dirname(path)).create(recursive: true);
      } catch (_) {}

      //copy db from assets to database folder
      ByteData data = await rootBundle.load("assets/dictionary");
      List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
      await File(path).writeAsBytes(bytes, flush: true);

      //open the newly created db
      db = await openDatabase(path);

      //set the new version to the copied db so you do not need to do it manually on your bundled database.db
      db.setVersion(NEW_DB_VERSION);

    }

    return db;
  }
}

I initialize the database in my main activity by using

DatabaseHelper db = DatabaseHelper();

Why this weird problem happened?

1 Answer 1

1

There are some race conditions that could occur if db/initDb is called multiple times (and it can happen in your code) that could give some unpredictable results.

I'd rather make your initDb private and making sure it is not called multiple times with something like:

import 'package:sqflite_common/sqlite_api.dart';
import 'package:synchronized/synchronized.dart';

class OpenHelper {
  Database _db;

  final _dbLock = Lock();

  Future<Database> get db async {
    if (_db != null) {
      return _db;
    } else {
      // Safe guard _initDb so that it is ran only once
      await _dbLock.synchronized(() async {
        // Don't call it if _db is defined.
        _db ??= await _initDb();
      });
      return _db;
    }
  }
}

or even simpler (but not storing the database reference, only a future):

import 'package:sqflite_common/sqlite_api.dart';

class OpenHelper {
  Future<Database> _db;

  Future<Database> get db {
    _db ??= _initDb();
    return _db;
}

And see if that fixes the issue.

Sign up to request clarification or add additional context in comments.

5 Comments

Thank you. Mean I can totally delete the code DatabaseHelper.internal(), factory DatabaseHelper() => _instance?
@alexk, wish to get your kind explanation. Why DatabaseHelper.internal() and factory DatabaseHelper() => _instance can be removed?
I found this tutorial citrusleaf.in/blog/sqflite-flutter-tutorial-introduction which create an Injection class to make sure there is only one instance of the database available throughout the app’s lifecycle. That should fix my problem also right?
Yes you need to ensure to have a single instance of your open helper (what you have, injection, global). What you have is fine.
I use the dbLock.synchronized but still getting complain from users that the database is unable to load.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.