Search code examples
flutterdartasync-awaitsqflite

The await keyword breaks the database function internally in dart


I am writing a flutter app and I am trying to insert some data to a local database and give back the primary key the database creates, but somehow the await keyword breaks the function that handles the database interaction. (Following code is a simplified version of my actual code, but has all the essentials and the same issue)

1.Future<int> insertObjekt(Objekt o) async {
2.  final db = await _database;
3.  return db.insert('objekt', {'name': o.name});
4.}

If I put breakpoints in lines 2 and 3, line 2 stops as expected, but upon resuming the program never stops for line 3 (i let it run for almost 15 minutes to make sure and nothing happened).

1. class TestState extends ChangeNotifier {
2.   var db = tempDB;
3.   Objekt? currentObjekt;
4. 
5.   TestState(){
6.     initialize('doesn\'t work');
7.     /*Objekt o = Objekt.loaded(1, 'in constructor');
8.     db.insertObjekt(o);
9.     debugPrint('${o.id}');
10.    currentObjekt = o;// */
11.    //currentObjekt = Objekt('functions as intended', db);
12.    while(currentObjekt == null){
13.      sleep(const Duration(milliseconds: 10));
14.    }
15.    debugPrint('concluded');
16.    notifyListeners();
17.  }
18.
19.  Future<void> initialize(String name) async {
20.    Objekt o = Objekt.loaded(1, name);
21.    o.id = await db.insertObjekt(o);
22.    debugPrint('${o.id}');
23.    currentObjekt = o;
24.  }
25.}

If I remove the o.id = await in line 21 it works. The problem is, that I need the return value at this spot. Line 7-10 work with inserting and ignoring the return value, but as soon as I try to query (another function same structure as insertObject just with db.query and a cast the the desired class) I need the return value again. Same problem with going through the object itself (although for some reason when going through the object the return value works just fine and I can set the attribute inside the class).

When running the app it just stays in a endless loading screen, despite the while loop concluding and the line 15 printing to the console as it should. I thought this could be because await 'creating' a new thread, but if a new thread means the return value gets lost then I don't see any point in using async-await. Why does the function insertObjekt just stop after one line and how can make sure I get the desired return value so the database can be read?

Edit to add: The requested implementation of _database

1. Future<bool> openDB() async {
2.   _database = openDatabase(
3.     join(await getDatabasesPath(), 'tappDB.db'),
4.     onCreate: (db, version) async {
5.       db.execute('''
6.         CREATE TABLE objekt(
7.           id INTEGER PRIMARY KEY, 
8.           name TEXT 
9.         )''');
10.      debugPrint("DB built");
11.    },
12.    version: 4,
13.  );
14.  debugPrint("DB opened");
15.  return true;
16.}

Solution

  • When running the app it just stays in a endless loading screen, despite the while loop concluding and the line 15 printing to the console as it should.

    12.    while(currentObjekt == null){
    13.      sleep(const Duration(milliseconds: 10));
    14.    }
    15.    debugPrint('concluded');
    

    That is not plausible with this code. sleep blocks, so your loop will never allow asynchronous operations in that isolate to make any progress. Even if the asynchronous operations are running in a different isolate, the looping isolate would never have any opportunity to set currentObjekt. (Also see https://stackoverflow.com/a/56707357).

    I thought this could be because await 'creating' a new thread...

    No, await does not create any new thread. Dart isolates are single-threaded. await simply yields; it's syntactic sugar for returning a Future and registering completion callbacks for you.

    It appears that loop is trying to make an asynchronous operations synchronous; there is no supported way to do that in Dart. Do not perform asynchronous work in constructors. If you need to do asynchronous initialization, replace the constructor with a static factory method:

    class TestState extends ChangeNotifier {
      var db = tempDB;
      Objekt? currentObjekt;
    
      TestState._();
    
      static Future<TestState> createTestState() async {
        var testState = TestState._();
        await testState.initialize('');
        debugPrint('concluded');
        testState.notifyListeners();
        return testState;
      }
    
      Future<void> initialize(String name) async {
        Objekt o = Objekt.loaded(1, name);
        o.id = await db.insertObjekt(o);
        debugPrint('${o.id}');
        currentObjekt = o;
      }
    }
    

    Note that the initialize method is now probably redundant and could be merged into createTestState. Also note that asynchronous operations are inherently contagious, so any functions that needs to wait for a TestState object to be constructed also will need to be asynchronous, and anything that calls those functions will need to be asynchronous, and so on.