flutterdartflutter-blocflutter-moor

Using loosely coupled local data repositories in flutter bloc


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.


Solution

  • 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}]