Using loosely coupled local data repositories in flutter bloc

1.5k views Asked by At

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.

  1. I have local (installed on the device) and remote (installed on the server) databases.

  2. 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).

  3. 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.

2

There are 2 answers

0
Alexander Kremenchuk On BEST ANSWER

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}]
0
Eng.Ahmad On

You might need to introduce a third class called todo

class Todo {
  final int id;

  final String title;

  Todo(this.id, this.title);

  // implement the required conversion 
  Todo.fromHive(......)
  Todo.fromSqf(.....)
}

Then, use this new model in all repos and interfaces