Axel
Axel

Reputation: 1475

How to use async functions to connect to database in Flutter?

I am trying to connect to a static database as it is explained in this answer. I therefore created an asynchronous function that looks like this:

Future<void> loadDataBase() async {
  // Construct a file path to copy database to
  Directory documentsDirectory = await getApplicationDocumentsDirectory();
  String path = join(documentsDirectory.path, "asset_worldcities.db");

  // Only copy if the database doesn't exist
  if (FileSystemEntity.typeSync(path) == FileSystemEntityType.notFound) {
    // Load database from asset and copy
    ByteData data = await rootBundle.load(join('assets', 'worldcities.db'));
    List<int> bytes = data.buffer.asUint8List(
        data.offsetInBytes, data.lengthInBytes);

    // Save copied asset to documents
    await new File(path).writeAsBytes(bytes);
  }
}

Now I thought I could access my database inside my main widget by using this function and then call

Directory appDocDir = await getApplicationDocumentsDirectory();
String databasePath = join(appDocDir.path, 'asset_database.db');
this.db = await openDatabase(databasePath);
initialized = true;
Future<List<Page>> search(String word, int parentId) async {
    if (!initialized) await this._initialize();
    String query = '''
      SELECT * FROM users
      LIMIT 25''';
    return await this.db.rawQuery(query);
}

but this way I am not allowed to use this.db and also not await as I am not inside an async function. Where do I need to put this database request so that it works?

Upvotes: 0

Views: 852

Answers (1)

rmtmckenzie
rmtmckenzie

Reputation: 40443

Depending whether you need to do this every time and the database could grow, or whether it's a one-time operation (which it seems like it might be?) and the database is small enough that it's not going to take long to query it, there are different approaches I'd take.

If it's a one-time per install sort of thing and the database will always be small, making the user wait while it copies the file across probably isn't a huge deal. In that case I'd do something like this:

main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  if (needToLoadDatabase()) {
    await loadDatabase();
  }

  let users = await queryUsers();

  runApp(MainWidget(users: users));
}

However, if you're reading from the database and it's something that could take any significant amount of time, I'd recommend initiating the load and then passing the future into your main widget, where it could use a FutureBuilder to build an intermediate UI.

That'd look something like this:

main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  let loadUsers = () async {
    if (needToLoadDatabase()) {
      await loadDatabase();
    }

    return await queryUsers();
  }();


  runApp(MainWidget(loadUsers: loadUsers));
}

class MainApp extends StatelessWidget {

  final Future<Users> loadUsers;

  MainApp({@required this.loadUsers, Key key}): super(key: key);

  Widget build(BuildContext context) {
    return FutureBuilder(
      builder: (ctx, snapshot) {
        if (snapshot.hasData) {
          // build your UI with data
        } else {
          // build your UI without data
        }
      }
    );
  }
}

Also note that there's no reason you have to do the loading in the main function - you could make your widget stateful and kick that off in the initState, or any number of places like directly where you use the list. You could also look at the FutureProvider from the Provider package.

Upvotes: 1

Related Questions