This is a what is the best practice kind of a question.
I am building an app using flutter and I have the below requirements.
I have local (installed on the device) and remote (installed on the server) databases.
I have to build repositories for the local databases. I have many choices for this (SQLITE, Hive, etc.). I have to keep the choice of the database loosely coupled with the application (Repository pattern).
I have to use the BLOC pattern for the state management.
The point where I am struggling is for each type of database, the entity model (I come from an entity framework background and therefore calling it entity model. I don't know what you call it) is different.
For example,
The model for SQLLite (Moor) looks as below
class ToDosSqlLite extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get title => text().withLength(min: 6, max: 32)();
}
The model for Hive looks as below.
class ToDosHive extends HiveObject {
final int id;
final String title;
Person(this.id, this.title);
}
And for any other choice of database, the model will look different.
And I have repository classes as below.
abstract class LocalToDoRepository{
List<What should be the type here?> getAll();
}
class SqlLiteToDoRepository extends LocalToDoRepository{
///overriding won't work here as Type is different from the base class method
@override
List<ToDosSqlLite> getAll(){///implementation}
}
class HiveToDoRepository extends LocalToDoRepository{
///overriding won't work here as Type is different from the base class method
@override
List<ToDosHive> getAll(){///implementation}
}
In SqlLiteToDoRepository, a getAll()
method returns a List<ToDosSqlLite>
and in HiveToDoRepository, same method returns a List<ToDosHive>
.
And below is my Bloc
class ToDoBloc extends Bloc<ToDoEvent, ToDoState>{
final LocalToDoRepository localToDoRepository;
///localToDoRepository object is dependency injected . If I want to use SQLite, I will inject
///SqlLiteToDoRepository and so on.
ToDoBloc ({@required this.localToDoRepository}): super(ToDoInitialState());
}
How do I do this abstraction in an elegant way? Please suggest if you have any ideas.
Thanks in advance.
You may try something like this:
final _mockedSql = [
ToDoSqlLite(1, 'Implement dependency inversion principle'),
ToDoSqlLite(2, 'Implement repository pattern'),
];
final _mockedHive = [
ToDoHive(1, 'Feed the cat'),
ToDoHive(2, 'Wash dishes'),
];
abstract class ToDoBase<A, B> {
abstract final A id;
abstract final B title;
}
class ToDoBusinessEntity {
final int id;
final String title;
ToDoBusinessEntity(this.id, this.title);
@override
String toString() => 'ToDoBusinessEntity {id: $id, title: $title}';
}
class ToDoSqlLite implements ToDoBase<int, String> {
final int id;
final String title;
ToDoSqlLite(this.id, this.title);
}
class ToDoHive implements ToDoBase<Object, Object> {
final Object id;
final Object title;
ToDoHive(this.id, this.title);
}
abstract class LocalToDoRepository<T extends ToDoBase, Z>{
abstract final DataSource<T> dataSource;
List<Z> getAll();
List<Z> _sourceToEntity(List<T> sourceList);
}
abstract class DataSource<T> {
List<T> getAll();
}
class SqlDataSource implements DataSource<ToDoSqlLite> {
List<ToDoSqlLite> getAll() => _mockedSql;
}
class HiveDataSource implements DataSource<ToDoHive> {
List<ToDoHive> getAll() => _mockedHive;
}
class SqlLiteToDoRepository implements LocalToDoRepository<ToDoSqlLite, ToDoBusinessEntity>{
DataSource<ToDoSqlLite> dataSource;
SqlLiteToDoRepository(this.dataSource);
@override
List<ToDoBusinessEntity> _sourceToEntity(List<ToDoSqlLite> source) => source.map<ToDoBusinessEntity>((e) => ToDoBusinessEntity(e.id, e.title)).toList();
@override
List<ToDoBusinessEntity> getAll() => _sourceToEntity(dataSource.getAll());
}
class HiveToDoRepository implements LocalToDoRepository<ToDoHive, ToDoBusinessEntity>{
DataSource<ToDoHive> dataSource;
HiveToDoRepository(this.dataSource);
@override
List<ToDoBusinessEntity> getAll() => _sourceToEntity(dataSource.getAll());
@override
List<ToDoBusinessEntity> _sourceToEntity(List<ToDoHive> source) => source.map<ToDoBusinessEntity>((e) => ToDoBusinessEntity(int.parse(e.id.toString()), e.title.toString())).toList();
}
class Bloc {
final LocalToDoRepository repository;
Bloc(this.repository) {
print(repository.getAll());
}
}
void main() {
final blocSql = Bloc(SqlLiteToDoRepository(SqlDataSource()));
final blocHive = Bloc(HiveToDoRepository(HiveDataSource()));
}
This outputs
[ToDoBusinessEntity {id: 1, title: Implement dependency inversion principle}, ToDoBusinessEntity {id: 2, title: Implement repository pattern}]
[ToDoBusinessEntity {id: 1, title: Feed the cat}, ToDoBusinessEntity {id: 2, title: Wash dishes}]