Flutter Clean Architecture (Part 6) – Implement Repository

Trần Đình Quý

Updated on:

Flutter Clean Architecture

Sau phần trước, giờ đây chúng ta đã có tất cả các hợp đồng về các phần phụ thuộc của Repository. Những phần phụ thuộc đó là local và remote data source cũng như class NetworkInfo để tìm hiểu xem người dùng có trực tuyến hay không. Phần này chúng ta sẽ implement repository.

Implement Repository

Hãy kế thừa NumberTriviaRepository như sau:

number_trivia_repository_impl.dart

class NumberTriviaRepositoryImpl implements NumberTriviaRepository {
  NumberTriviaRepositoryImpl({
    required this.remoteDataSource,
    required this.localDataSource,
    required this.networkInfo,
  });

  final NumberTriviaRemoteDataSource remoteDataSource;
  final NumberTriviaLocalDataSource localDataSource;
  final NetworkInfo networkInfo;

  @override
  Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(
      int number) async {
    // TODO: implement getConcreteNumberTrivia
    return null;
  }

  @override
  Future<Either<Failure, NumberTrivia>> getRandomNumberTrivia() async {
    // TODO: implement getRandomNumberTrivia
    return null;
  }
}

Trước tiên hãy bắt đầu với việc triển khai phương thức getConcreteNumberTrivia.

getConcreteNumberTrivia

Công việc của Repository là lấy dữ liệu mới từ API khi có kết nối Internet (và sau đó lưu vào bộ nhớ cache local) hoặc lấy dữ liệu được lưu trong cache khi người dùng ngoại tuyến.

number_trivia_repository_impl.dart

  @override
  Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(
      int number) async {
    networkInfo.isConnected;
    return null;
  }

Sau khi kiểm tra có kết nối mạng hay không, chúng ta hãy thử trả về kết quả.

@override
Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(
  int number,
) async {
  networkInfo.isConnected;
  return Right(await remoteDataSource.getConcreteNumberTrivia(number));
}

Right của Either là “phía thành công” trả về một thực thể NumberTrivia. Việc triển khai có vẻ vẫn chưa nhiều nhưng chúng ta đang đạt được mục tiêu.

Bất cứ khi nào thông tin đố được lấy thành công từ API, chúng ta nên lưu nó vào bộ nhớ cache ở local. Đó là những gì chúng ta sẽ triển khai tiếp theo.

@override
Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(
  int number,
) async {
  networkInfo.isConnected;
  final remoteTrivia = await remoteDataSource.getConcreteNumberTrivia(number);
  localDataSource.cacheNumberTrivia(remoteTrivia);
  return Right(remoteTrivia);
}

Cuối cùng, khi chúng ta trực tuyến và remote data source ném ra ServerException, chúng ta nên chuyển đổi nó thành ServerFailure và trả về nó từ phương thức. Trong trường hợp như vậy, không có gì nên được lưu vào bộ nhớ cache ở local.

@override
Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(
  int number,
) async {
  networkInfo.isConnected;
  try {
    final remoteTrivia =
        await remoteDataSource.getConcreteNumberTrivia(number);
    localDataSource.cacheNumberTrivia(remoteTrivia);
    return Right(remoteTrivia);
  } on ServerException {
    return Left(ServerFailure());
  }
}

bây giờ là lúc thực hiện hành vi ngoại tuyến. Repository sẽ trả về NumberTrivia được lưu trong bộ nhớ cache ở local cuối cùng khi không trực tuyến. Tiếp tục implement repository bằng đoạn code sau:

@override
Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(
  int number,
) async {
  // Finally doing something with the value of isConnected 😉
  if (await networkInfo.isConnected) {
    try {
      final remoteTrivia =
          await remoteDataSource.getConcreteNumberTrivia(number);
      localDataSource.cacheNumberTrivia(remoteTrivia);
      return Right(remoteTrivia);
    } on ServerException {
      return Left(ServerFailure());
    }
  } else {
    final localTrivia = await localDataSource.getLastNumberTrivia();
    return Right(localTrivia);
  }
}

Chúng tôi cũng phải xử lý trường hợp khi local data source ném ra CacheException bằng cách trả về CacheFailure thông qua phía “lỗi” bên trái (Left) của Either. Như được ghi trong tài liệu của phương thức getLastNumberTrivia, CacheException sẽ xảy ra bất cứ khi nào không có gì bên trong bộ đệm.

@override
Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(
  int number,
) async {
  if (await networkInfo.isConnected) {
    try {
      final remoteTrivia =
          await remoteDataSource.getConcreteNumberTrivia(number);
      localDataSource.cacheNumberTrivia(remoteTrivia);
      return Right(remoteTrivia);
    } on ServerException {
      return Left(ServerFailure());
    }
  } else {
    try {
      final localTrivia = await localDataSource.getLastNumberTrivia();
      return Right(localTrivia);
    } on CacheException {
      return Left(CacheFailure());
    }
  }
}

getRandomNumberTrivia

Cách chúng ta xây dựng getRandomNumberTrivia sẽ gần giống với getConcreteNumberTrivia.

Trước tiên chúng ta hãy triển khai code đại cái đã, mặc dù chúng ta đã biết rằng nó sẽ có sự lặp đi lặp lại ở đây.

@override
Future<Either<Failure, NumberTrivia>> getRandomNumberTrivia() async {
  if (await networkInfo.isConnected) {
    try {
      final remoteTrivia = await remoteDataSource.getRandomNumberTrivia();
      localDataSource.cacheNumberTrivia(remoteTrivia);
      return Right(remoteTrivia);
    } on ServerException {
      return Left(ServerFailure());
    }
  } else {
    try {
      final localTrivia = await localDataSource.getLastNumberTrivia();
      return Right(localTrivia);
    } on CacheException {
      return Left(CacheFailure());
    }
  }
}

Sự khác biệt duy nhất theo giữa concreterandom chỉ là yêu cầu tái cấu trúc code này. Hầu hết logic có thể được chia sẻ giữa các phương thức cụ thể và ngẫu nhiên, đồng thời chúng ta sẽ xử lý lệnh gọi khác nhau tới local data resource bằng function higher-order. Cuối cùng của NumberTriviaRepositoryImpl sẽ có dạng như sau:

typedef _ConcreteOrRandomChooser = Future<NumberTrivia> Function();

class NumberTriviaRepositoryImpl implements NumberTriviaRepository {
  NumberTriviaRepositoryImpl({
    required this.remoteDataSource,
    required this.localDataSource,
    required this.networkInfo,
  });

  final NumberTriviaRemoteDataSource remoteDataSource;
  final NumberTriviaLocalDataSource localDataSource;
  final NetworkInfo networkInfo;

  @override
  Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(
      int number) async {
    return _getTrivia(() {
      return remoteDataSource.getConcreteNumberTrivia(number);
    });
  }

  @override
  Future<Either<Failure, NumberTrivia>> getRandomNumberTrivia() async {
    return _getTrivia(() {
      return remoteDataSource.getRandomNumberTrivia();
    });
  }

  Future<Either<Failure, NumberTrivia>> _getTrivia(
    _ConcreteOrRandomChooser getConcreteOrRandom,
  ) async {
    if (await networkInfo.isConnected) {
      try {
        final NumberTriviaModel remoteTrivia =
            await getConcreteOrRandom() as NumberTriviaModel;
        localDataSource.cacheNumberTrivia(remoteTrivia);
        return Right<Failure, NumberTrivia>(remoteTrivia);
      } on ServerException {
        return const Left<Failure, NumberTrivia>(Failure.serverFailure());
      }
    } else {
      try {
        final NumberTriviaModel localTrivia =
            await localDataSource.getLastNumberTrivia();
        return Right<Failure, NumberTrivia>(localTrivia);
      } on CacheException {
        return const Left<Failure, NumberTrivia>(Failure.cacheFailure());
      }
    }
  }
}

Tiếp theo

Bây giờ chúng ta đã implement repository một cách đầy đủ, chúng ta sẽ bắt đầu làm việc trên các phần cấp thấp của data layer bằng cách triển khai data resource và networkInfo.

Phần trước: https://dev.tora-tech.com/flutter-ca-contracts-trong-data-sources/

Phần tiếp theo: https://dev.tora-tech.com/flutter-clean-architecture-part-7-network-info/

Viết một bình luận