Reputation: 1967
I am building my first app in Flutter, this app is using the sqlite database
. So I have models and repositories.
The code layout:
I have 2 models (will have more in the finished app) UserModel
, TimesheetModel
, these both extend a BaseModel
I have 2 repositories (will have more in the finished app) UserRepository
, TimesheetRepository
, these both extend BaseRepository
What I am trying to do:
I would like to have the reusable code such as: getAll()
, countAll()
etc in the BaseRepository
that way all the repositories that extend the base repository have this functionality and all I should need to do is set the table name and set the returned Model
.
The Error:
As you can see from my code because the BaseRepository
is returning a BaseModel
type, when I call the all()
function on timesheet object, I get the following error : type 'List' is not a subtype of type 'List'
I am not sure how to fix this, any suggestions please?
BaseRepository
abstract class BaseRepository {
final String table;
final model;
BaseRepository({this.table, this.model});
// Retrieve all the items
Future<List<BaseModel>> all() async {
final sql = '''SELECT * FROM $table''';
final data = await db.rawQuery(sql);
List<BaseModel> forms = List();
for (final node in data) {
final form = model.fromJson(jsonData: node);
forms.add(form);
}
return forms;
}
// Find an item by its ID
Future findById(int id) async {
final sql = '''SELECT * FROM $table
WHERE id = ?''';
List<dynamic> params = [id];
final data = await db.rawQuery(sql, params);
final form = model.fromJson(jsonData: data.first);
return form;
}
// Count all the items
Future<int> count() async {
final data = await db.rawQuery('''SELECT COUNT(*) FROM $table''');
int count = data[0].values.elementAt(0);
int idForNewItem = count++;
return idForNewItem;
}
// clear the table
Future<void> delete() async {
// truncate current database table
await db.rawQuery('''DELETE FROM $table''');
}
}
TimesheetRepository
class TimesheetRepository extends BaseRepository {
String table = 'timesheets';
TimesheetModel model = new TimesheetModel();
// Search for a item by its name
Future<List<TimesheetModel>> findByDate(DateTime dateTime) async {
final String date = DateFormat("yyyy-MM-dd").format(dateTime);
final sql = '''SELECT * FROM $table WHERE timesheet_date = ?''';
List<dynamic> params = [date];
final data = await db.rawQuery(sql, params);
List<TimesheetModel> forms = List();
for (final node in data) {
final form = TimesheetModel.fromJson(jsonData: node);
forms.add(form);
}
return forms;
}
// Add a new item
Future<void> store(TimesheetModel timesheet) async {
final sql = '''INSERT INTO $table
(
user_id,
timesheet_date,
start_time,
end_time,
json,
is_uploaded
)
VALUES (?,?,?,?,?,?)''';
List<dynamic> params = [
timesheet.userId,
DateFormat("yyyy-MM-dd").format(timesheet.timesheetDate),
timesheet.startTime,
timesheet.endTime,
convert.json.encode(timesheet.json),
timesheet.is_uploaded,
];
final result = await db.rawInsert(sql, params);
DatabaseCreator.databaseLog('Add form', sql, null, result, params);
}
}
When calling all on Timesheet
TimesheetRepository timesheet = TimesheetRepository();
timesheet.all();
Base Model
abstract class BaseModel {
fromJson();
}
Timesheet Model
class TimesheetModel extends BaseModel {
int id;
int userId;
DateTime timesheetDate;
String startTime;
String endTime;
Map json = {
"task": "",
"detail": "",
"notes": "",
};
bool is_uploaded;
TimesheetModel({
this.id,
this.userId,
this.timesheetDate,
this.startTime,
this.endTime,
this.json,
this.is_uploaded,
});
fromJson({Map<String, dynamic> jsonData}) {
return TimesheetModel(
id: jsonData['id'] as int,
userId: jsonData['user_id'] as int,
timesheetDate: timesheetDate,
startTime: jsonData['start_time'],
endTime: jsonData['end_time'],
is_uploaded: hasUploaded,
);
}
}
Upvotes: 13
Views: 8645
Reputation: 1
My reply might be late, but in this method:
Future<List<T>> all() async {
final sql = '''SELECT * FROM $table''';
final data = await db.rawQuery(sql);
return data.map((node) {
return model.fromJson(jsonData: node);
}).toList();
}
You need to add: return (model.fromJson(jsonData: node) as T)
to avoid the error: List<BaseModel> is not a subtype of List<TimesheetModel>
Upvotes: 0
Reputation: 945
Not exactly like you code but get rid of the error: 'List' is not a subtype of type 'List'. This is useful when you have multiple tables with multiple models which inherit from a super class.
Lets say MySubClassA
and MySubClassB
inherit from MySuperClass
:
abstract class MySuperClass {
int id;
MySuperClass({this.id});
MySuperClass fromMap(Map<String, dynamic> map);
Map<String, dynamic> toMap();
}
class MySubClassA extends MySuperClass {
String name;
MySubClassA({int id, this.name}) : super(id: id);
@override
MySubClassA fromMap(Map<String, dynamic> map) {
return MySubClassA(
id: map['id'],
name: map['name'],
);
}
@override
Map<String, dynamic> toMap() {
Map<String, dynamic> myMap = Map<String, dynamic>();
myMap['id'] = id;
myMap['name'] = name;
return myMap;
}
}
class MySubClassB extends MySuperClass {
int postcode;
MySubClassB({int id, this.postcode}) : super(id: id);
@override
MySubClassB fromMap(Map<String, dynamic> map) {
return MySubClassB(
id: map['id'],
postcode: map['postcode'],
);
}
@override
Map<String, dynamic> toMap() {
Map<String, dynamic> myMap = Map<String, dynamic>();
myMap['id'] = id;
myMap['postcode'] = postcode;
return myMap;
}
}
And an enum with extension to return table name:
enum MyEnum { classA, classB }
extension MyEnumExtension on MyEnum {
static final tableNames = {
MyEnum.classA: 'table_a',
MyEnum.classB: 'table_b',
};
String get tableName => tableNames[this];
}
In your databasehelper.dart to get the list. This is not full code, add your own singleton, initDb, createDb:
Future<List<Map<String, dynamic>>> _getItemMapList(MyEnum myEnum) async {
Database db = await this.db;
var result = await db.query(myEnum.tableName);
return result;
}
Future<List<MySuperClass>> getItemList(MyEnum myEnum) async {
var itemMapList = await _getItemMapList(myEnum);
List<MySuperClass> itemList;
switch (myEnum) {
case MyEnum.classA:
itemList= List<MySubClassA>();
break;
case MyEnum.classB:
itemList= List<MySubClassB>();
break;
}
for (int i = 0; i < itemMapList.length; i++) {
switch (myEnum) {
case MyEnum.classA:
itemList.add(MySubClassA().fromMap(itemMapList[i]));
break;
case MyEnum.classB:
itemList.add(MySubClassB().fromMap(itemMapList[i]));
break;
}
}
return itemList;
}
Upvotes: 1
Reputation: 2283
I wouldn't do the parse fromJson the way you're doing since you need to pass an empty instance of the model to be able to create a valid instance of the same object. But in order to have your architecture working you need to do some corrections:
1 - make usage of generics.
BaseRepository
abstract class BaseRepository<T extends BaseModel> {
BaseRepository({this.table, this.model});
final String table;
final T model;
// Retrieve all the items
Future<List<T>> all() async {
final sql = '''SELECT * FROM $table''';
final data = await db.rawQuery(sql);
return data.map((node) {
return model.fromJson(jsonData: node);
}).toList();
}
Future<T> findById(int id) async {
final sql = '''SELECT * FROM $table
WHERE id = ?''';
final data = await db.rawQuery(sql, [id]);
return model.fromJson(jsonData: data.first);
}
// Count all the items
Future<int> count() async {
final data = await db.rawQuery('''SELECT COUNT(*) FROM $table''');
int count = data[0].values.elementAt(0);
int idForNewItem = count++;
return idForNewItem;
}
// clear the table
Future<void> delete() async {
// truncate current database table
await db.rawQuery('''DELETE FROM $table''');
}
}
2 - correctly call the super constructor
TimesheetRepository
class TimesheetRepository extends BaseRepository<TimesheetModel> {
///IMHO you should not pass TimesheetModel instance here, it is really redundant
///you can create a parse class that receives the type and a json and does the
///trick
TimesheetRepository() : super(table: 'timesheets', model: TimesheetModel());
}
3 - add the correct return to your fromJson
method
abstract class BaseModel {
BaseModel fromJson({Map<String, dynamic> jsonData});
}
I could not test it integrated with the database, so let me know if that works.
Upvotes: 5
Reputation: 2448
If you create the all method like this? I think dart need this type of info to infer the type. Not quite sure though
Future<List<T extends BaseModel>> all() async {
Upvotes: 0