synw / sqlcool

Easy and reactive Sqlite for Flutter
MIT License
163 stars 28 forks source link

Unable to get data from SelectBloc #1

Closed KsaRedFx closed 5 years ago

KsaRedFx commented 5 years ago

Following your example for SelectBloc, I'm unable to get data from it. It's just infinitely stuck on the CircularProgressIndicator()

Changefeed doesn't seem to notice changes, and there is data present in the table before and after SelectBloc is initialized, but despite that fact I cannot get it to build my Listview.

Timing everything out, it actually looks like my database is initialized after SelectBloc queries against it. I'm not sure if that effects how it works.

  @override
  void initState() {
    print("Step 1, Init DB");
    String dbpath = "convos.sqlite";
    db.init(path: dbpath, verbose: true).catchError((e) {
      print("Error initializing the database: $e");
    });
    print("Step 2, SelectBloc");
    this.bloc = SelectBloc(table: "convos", orderBy: "time DESC", verbose: true, reactive: true);
    print("Step 3, initState super");
    super.initState();

    List all_data = db.select(table: 'convos');
    print(all_data);
I/flutter (25748): Step 1, Init DB
I/flutter (25748): Step 2, SelectBloc
I/flutter (25748): SELECT * FROM convos ORDER BY time DESC
I/flutter (25748): Step 3, initState super
I/flutter (25748): INITIALIZING DATABASE at /data/user/0/io.dah.sippy2/app_flutter/convos.sqlite
I/flutter (25748): OPENING DATABASE
I/flutter (25748): [{number: XXXXYYY, time: 15503805177.67, message: Hello from SQLITE, to_num: XXXXXXX}, {number: YYYYXXX, time: 15503805177.73, message: Hello from HELL!?, to_num: XXXXXXX}]
KsaRedFx commented 5 years ago

Forcing data into it afterwords does successfully trigger an update, and make it start to build the view... However the view just returns null.

      body: StreamBuilder<List<Map>>(
        stream: bloc.items,
        builder: (BuildContext context, AsyncSnapshot snapshot) {
          if (snapshot.hasData) {
          // the select query has not found anything
            if (snapshot.data.length == 0) {
              return Center(
                child: Text(
                  "No data. Use the + in the appbar to insert an item"),
              );
            }
            var sData = snapshot.data;
            print("SNAPSHOT DATA: $sData");
            // the select query has results
            new ListView.builder(
              itemCount: sData.length,
              itemBuilder: (context, i) => new Text(sData[i])
            );
          } else {
            // the select query is still running
            return CircularProgressIndicator();
          }
        }
      ),
synw commented 5 years ago

Hi, thanks for reporting. The problem here is that the database has not been initialized when trying to build a SelectBloc. My fault: it is supposed to not let you do that in such a case: I added a defensive assertion that should have been there in the first place b47dc1453da8b46c3fdd3d6f6141f40409eac8cd . The program will now crash if you start a select bloc when the database is not ready. Thanks for catching this.

About you code: the init method is aync and the database needs some time to initialize. You are supposed to initialize you database before all. The example does it in a brutal way but you can handle this differently inside you app:

   void main() {
     initDb().then((_) {
       runApp(MyApp());
     });
   }

The second problem in your code is that you initialize an empty database with no create table query. This should only be possible if initializing from an asset database. I'll add a check to prevent this.

I need to work on the error handling for the user to get a more pleasant experience with detailled error messages about what is going wrong. Thinking about custom exceptions.

KsaRedFx commented 5 years ago

In my particular case I am running it without a create because I already know the table exists (And I can actively query it)

I was using table create queries on initialization but I found that your initializer ignores my queries after first creation, and I was trying to drop a table and recreate it on each init for testing purposes.

Obviously it isn't ideal for production but I had to use db.database.rawQuery() just after init to create my tables.

I'll try to shoehorn everything in before the app runs.

I'm not sure why StreamBuilder is returning null when I do get it working properly.

On Sun, Feb 17, 2019, 12:17 PM synw <notifications@github.com wrote:

Hi, thanks for reporting. The problem here is that the database has not been initialized when trying to build a SelectBloc. My fault: it is supposed to not let you do that in such a case: I added a defensive assertion that should have been there in the first place b47dc14 https://github.com/synw/sqlcool/commit/b47dc1453da8b46c3fdd3d6f6141f40409eac8cd . The program will now crash if you start a select bloc when the database is not ready. Thanks for catching this.

About you code: the init method is aync and the database needs some time to initialize. You are supposed to initialize you database before all. The example does it in a brutal way but you can handle this differently inside you app:

void main() { initDb().then((_) { runApp(MyApp()); }); }

The second problem in your code is that you initialize an empty database with no create table query. This should only be possible if initializing from an asset database. I'll add a check to prevent this.

I need to work on the error handling for the user to get a more pleasant experience with detailled error messages about what is going wrong. Thinking about custom exceptions.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/synw/sqlcool/issues/1#issuecomment-464483369, or mute the thread https://github.com/notifications/unsubscribe-auth/ABtSGqFIxPfrPLrajbnuIKXVK1tm3hovks5vOY6mgaJpZM4a_aBB .

synw commented 5 years ago

In my particular case I am running it without a create because I already know the table exists (And I can actively query it)

I see. I should not force at least a query on init then.

I found that your initializer ignores my queries after first creation

Yes the queries only run on database file creation. For running queries after any initialization we may add a afterInitQueries list of queries to run or something like that. Would it satisfy your use case or do you have different ideas? Or we can just wrap db.database.rawQuery() in a db.query method for convenience and let the developer handle this: I tend to prefer this solution but I'm not sure.

I'll try to shoehorn everything in before the app runs.

You don't have to do that. You can start the app, run init async and handle the database state yourself in the app to speed up the things. Like:

   db.init(path: dbpath, verbose: true).then((_) {
      myDbIsReady = true;
    });

For the StreamBuilder not getting any data there might be a problem somewhere: I need more context. Could you please post more code so that we can sort this out once you've done the initialization part right?

KsaRedFx commented 5 years ago

I ended up figuring out what I was missing for StreamBuilder.

I appreciate your help with this, I'm rather new to Dart and it's been... an experience jumping straight into things.

I think a db.query() method would be the easiest for raw queries, In my use case I actually create a table for each set of data that I receive instead of storing everything in a single table for possibly-insane reasons so I have to use db.database.rawQuery() with a CREATE TABLE IF NOT EXISTS since I also can't check if a table exists and is empty.

synw commented 5 years ago

Good that it works for you now. We'll go for the query method, I'll add this.

I'm reexamining things in order to prevent errors and add useful warnings and information for the developer. I started by madding the database parameter required for SelectBloc in order to start getting rid of the global Db instance I provide: it was a mistake and bad practice, the package should not hold any state or data. There will be a 2.0.0 with these breaking changes.

This does not solve the problem of making sure that the database is ready before firing any query. I need to think about this to find what suits best for the job.

Your intervention was useful to me to get a user perspective point of view on the package. Thanks. Do not hesitate to open issues if you find something weird or encounter problems. This package is still very young and is just starting to stabilize and go after maturity

synw commented 5 years ago

I also can't check if a table exists and is empty

You can check if a table exists this way: https://github.com/tekartik/sqflite/blob/master/doc/dev_tips.md#list-existing-tables . Maybe wrapping this is a tables getter would be useful

KsaRedFx commented 5 years ago

Good to know that's a way to do it!

I would suggest you change db.exists() to have where as an optional field. That way it will check if the table exists without a where, and if the row exists with an ID That would feel "natural" to me, at least.

In my case, checking for tables then deciding to create or not was actually more calls than an IF NOT EXISTS so I opted for a NOOP instead of a if for speed reasons.

synw commented 5 years ago

@KsaRedFx : for info the version 2.1.0 is out with the query method and a standard way to initialize the database async using the new onReadycallback ( doc )