Flutter Sqflite multiple table models
I made a few comments advising the contrary, but I just remembered in my last project I did something like that, it was for Firebase Database, but it would quite similar for sqflite.
I created a BaseItem
abstract class, with a key
, and all models would descend from that one.
I also created a BaseProvider
abstract class, which would require a BaseItem and would define the simple methods to access the model.
The upsert
and delete
methods lay in FirebaseBaseProvider
, which extends BaseProvider
.
I'll paste parts of it here (removing quite a lot to make it more understandable):
abstract class BaseItem { const BaseItem({this.key}); final String key;}abstract class BaseProvider<T extends BaseItem> { Future<List<T>> find(); Future<BaseKey> upsert(T item); Future<int> delete(T item);}abstract class FirebaseBaseProvider<T extends BaseItem> { // Abstract methods which need to be implemented T fromMap(BaseKey key, dynamic map); Map<String, dynamic> toJson(BaseKey key, T item); Future<DatabaseReference> getReference(BaseKey base) async { ... } BaseKey compileKey(T item, {String useKey}) { ... } Future<List<T>> find() async { List<T> result = new List(); // my implementation doesnt work like this, // as it's firebase based, but this would // be the place in a Sqflite implementation to use // fromMap and load the items return result; } Future<BaseKey> upsert(T item) async { if (item == null) return null; BaseKey key = compileKey(item); (await getReference(key)).set(toJson(key, item)); return key; } Future<int> delete(T item) async { if (item == null) return null; if (item.key != null && item.key != "") { (await getReference(compileKey(item))).remove(); } return 0; }}
Then, in order to implement a News
model, or any other model, then I would create it by simply defining its contents, like that:
class News extends BaseItem { News({String key, this.creation, this.messageSubject, this.messageBody}) : super(key: key); final DateTime creation; final String messageSubject; final String messageBody; bool operator ==(o) => o is News && (o.key == key); int get hashCode => key.hashCode;}
And it would require its specific provider, that would implement only the toJson
and fromMap
methods, like that:
class NewsProvider extends FirebaseBaseProvider<News> { @override Map<String, dynamic> toJson(BaseKey key, News news) { return { "creation": news.creation, "messageSubject": news.messageSubject, "messageBody": news.messageBody, }; } @override News fromMap(BaseKey key, dynamic map) { DateTime creation = map["creation"] == null ? null : DateTime.tryParse(map["creation"] as String); return new News( key: key.child.key, creation: creation, messageSubject: map["messageSubject"] as String, messageBody: map["messageBody"] as String, ); }}
In the end, NewProvider
is providing the find
, upsert
and delete
methods, but their implementation lay on the abstract class, just one implementation of them for all the models, as you wanted.
Of course, my implementation is quite more complicated than that, both because Firebase requires a different approach to obtain/load the items and also as the find
method ends up having to be different in each model specific provider. But yet, quite a lot can be simplified.
What I was saying in the comment is that this last class, the specific NewsProvider
which have both toJson
and fromMap
specific implementations may also be generalized by using annotations in the News
model class, but that brings quite a lot of problems and obscurity and - in my opinion, of course - it's not worth it.
I had a similar question about creating tables, all the examples just create one table. I found this webpage that created two tables putting an await on each command.
Like this:
Future _onCreate(Database db, int version) async { await db.execute("CREATE TABLE table1 (id INTEGER, valuex TEXT)"); await db.execute("CREATE TABLE table2 (id INTEGER, valuey TEXT)"); await db.execute("CREATE TABLE table3 (id INTEGER, valuez TEXT)"); }