Skip to content

Data Layer

The core business entities and their relationships are defined, which is the heart of the application. It's also the layer that defines the use cases of the application.

To follow this step you have to understand :

Structure

  • config handle environment flavor
  • exception class to handle internet exception
  • failure class to handle failure
  • usecase blue print of usecases
  • values type data hard code
  • repositories after repo domain created then extends the repositories in here.
  • datasource design pattern that is used to abstract the data access logic from the rest of the application.
  • models used to represent the data that is used by the application.

1. Create Remote Datasource

abstract class UserRemoteDatasource {
  Future<List<UserEntity>> getUserData(UserParamsEntity params);
}

class UserRemoteDatasourceImpl implements UserRemoteDatasource {
  final BaseApi baseApi;

  UserRemoteDatasourceImpl(this.baseApi);

  @override
  Future<List<UserEntity>> getUserData(UserParamsEntity params) async {
    final data = await baseApi.get(USERS, queryParameters: {'_start': params.start, '_limit': params.limit});
    return (data as Iterable).map<UserModel>((value) => UserModel.fromJson(value)).toList();
  }
}

Info

After class BaseApi register on injection.dart

2. Crate Local Datasource

import 'package:hive/hive.dart';
import 'package:solid_data/solid_data.dart';

abstract class UserLocalDatasource {
  Future<List<UserModel>> getUsersFromCache();
  Future<void> usersToCache(List<UserModel> params);
  Future<void> usersDeleteCache();
}

class UserLocalDatasourceImpl implements UserLocalDatasource {
  Future<Box> _getBox() async {
    return await Hive.openBox(BOX_KEY_USERS).onError((e, s) => throw CacheException());
  }

  @override
  Future<List<UserModel>> getUsersFromCache() async {
    Box box = await _getBox();
    try {
      if (box.containsKey(BOX_KEY_USERS)) {
        List<dynamic> data = await box.get(BOX_KEY_USERS);
        return data.map((e) => e as UserModel).toList();
      } else {
        throw CacheException(message: EXCEPTION_UNKNOWN);
      }
    } catch (e) {
      throw CacheException();
    }
  }

  @override
  Future<void> usersToCache(List<UserModel> params) async {
    Box box = await _getBox();

    List<UserModel> local = await box.get(BOX_KEY_USERS) ?? [];

    local.addAll(params);

    box.put(BOX_KEY_USERS, local).onError((e, s) => throw CacheException());
  }

  @override
  Future<void> usersDeleteCache() async {
    Box box = await _getBox();
    print('Users cache has been deleted');

    box.delete(BOX_KEY_USERS).onError((e, s) => throw CacheException());
  }
}

3. Implement repositories

class UserRepositoryImpl extends UserRepository {
  final UserRemoteDatasource remoteDatasource;
  final UserLocalDatasource localDatasource;
  final NetworkInfo networkInfo;
  UserRepositoryImpl({
    required this.remoteDatasource,
    required this.localDatasource,
    required this.networkInfo,
  });

  @override
  Future<Either<Failure, List<UserEntity>>> getUserData(UserParamsEntity params) async {
    return _checkUserCache(
      () {
        try {
          return remoteDatasource.getUserData(params);
        } catch (e) {
          if (e is ServerException) {
            return Left(ServerFailure(message: e.message));
          } else {
            return Left(UnknownFailure(message: FAILURE_UNKNOWN));
          }
        }
      },
      isInit: params.isInit,
    );
  }

  Future<Either<Failure, List<UserModel>>> _checkUserCache(Function() getUsers, {required bool isInit}) async {
    try {
      if (await networkInfo.isConnected) {
        if (isInit) {
          localDatasource.usersDeleteCache();
        }

        final getUsersRemote = await getUsers();
        localDatasource.usersToCache(getUsersRemote);

        return Right(getUsersRemote);
      } else {
        List<UserModel> localData = await localDatasource.getUsersFromCache();

        return Right(localData);
      }
    } catch (e) {
      if (e is CacheException) {
        return Left(CacheFailure(message: e.message));
      } else {
        return Left(UnknownFailure(message: FAILURE_UNKNOWN));
      }
    }
  }
}

4. Register to Dependancy Injection

After class UserRepositoryImpl make sure UserRemoteDatasource, UserLocalDatasource, NetworkInfo register on injection.dart in Feature Layer.

 // Datasource
 sl.registerLazySingleton<UserRemoteDatasource>(() => UserRemoteDatasourceImpl(sl()));
 sl.registerLazySingleton<UserLocalDatasource>(() => UserLocalDatasourceImpl());

 // RepositoryImpl
 sl.registerLazySingleton<UserRepository>(
    () => UserRepositoryImpl(
      remoteDatasource: sl(),
      localDatasource: sl(),
      networkInfo: sl(),
    ),
  );

For more information injectable.

Authors: Nanang Prasetya