Billy Mahmood
Billy Mahmood

Reputation: 1967

Flutter Class inheritance

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

Answers (4)

Lucas Longarini
Lucas Longarini

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

Tuss
Tuss

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

haroldolivieri
haroldolivieri

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

Rodrigo Bastos
Rodrigo Bastos

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

Related Questions