FLUTTER CLEAN ARCHITECTURE (PART 9) – Remote Data Source

Trần Đình Quý

Flutter Clean Architecture

Phần còn lại cuối cùng của data layer mà hiện chúng ta chỉ có hợp đồng là Remote Data Source. Đây là nơi diễn ra tất cả việc giao tiếp với Numbers API, và chúng ta sẽ sử dụng gói http cho việc này.

getConcreteNumberTrivia

Tóm tắt lại 1 chút, chúng ta đang làm việc với Numbers API, cụ thể là endpoint cụ thể cho số 42: http://numbersapi.com/42. Vấn đề là, khi thực hiện yêu cầu GET trên URL đó, chúng ta chỉ nhận được phản hồi văn bản thuần túy chứa chuỗi thông tin. Nhưng chúng ta muốn có được một phản hồi JSON!

Có hai cách để khiến API phản hồi với header là application/json:

  1. Thêm một tham số truy vấn vào URL, làm cho nó giống như sau: http://numbersapi.com/42?jsonhttp://numbersapi.com/42?json
  2. Gửi một tiêu đề Content-Type: application/json cùng với yêu cầu GET.

Chúng ta sẽ chọn phương án thứ hai.

Chúng ta sẽ implement nó như sau:

implementation.dart

@override
Future<NumberTriviaModel> getConcreteNumberTrivia(int number) async {
  final response = await client.get(
    'http://numbersapi.com/$number',
    headers: {'Content-Type': 'application/json'},
  );

  return NumberTriviaModel.fromJson(json.decode(response.body));
}

Đó là trường hợp server trả về thành công, nhưng nhỡ có trường hợp thất bại thì sao? Chúng ta sẽ phải check thêm lúc nào gọi api thành công hay thất bại.

implementation.dart

@override
Future<NumberTriviaModel> getConcreteNumberTrivia(int number) async {
  final response = await client.get(
    'http://numbersapi.com/$number',
    headers: {'Content-Type': 'application/json'},
  );

  if (response.statusCode == 200) {
    return NumberTriviaModel.fromJson(json.decode(response.body));
  } else {
    throw ServerException();
  }
}

Exception ServerException sẽ được xử lý trong repository, tất nhiên rồi.

Chúng ta đã implement thành công getConcreteNumberTrivia

Bước tiếp theo chúng ta sẽ implement getRandomNumberTrivia

getRandomNumberTrivia


Dịch:

Tương tự như khi chúng ta triển khai Repository, sự khác biệt giữa phương thức concrete và random sẽ rất nhỏ. Trên thực tế, tất cả những gì cần thiết để lấy một số ngẫu nhiên là thay đổi URL từ http://numbersapi.com/some_number thành http://numbersapi.com/random.

Do đó chúng ta sẽ copy, paste và chỉnh sửa đôi chút “$number” ở URL thành “random”.

implementation.dart

@override
Future<NumberTriviaModel> getRandomNumberTrivia() async {
  final response = await client.get(
    'http://numbersapi.com/random',
    headers: {'Content-Type': 'application/json'},
  );

  if (response.statusCode == 200) {
    return NumberTriviaModel.fromJson(json.decode(response.body));
  } else {
    throw ServerException();
  }
}

Hãy tạo một helper class! Nó sẽ nhận URL để thực hiện GET request dưới dạng một argument.

implementation.dart

@override
Future<NumberTriviaModel> getConcreteNumberTrivia(int number) =>
    _getTriviaFromUrl('http://numbersapi.com/$number');

@override
Future<NumberTriviaModel> getRandomNumberTrivia() =>
    _getTriviaFromUrl('http://numbersapi.com/random');

Future<NumberTriviaModel> _getTriviaFromUrl(String url) async {
  final response = await client.get(
    url,
    headers: {'Content-Type': 'application/json'},
  );

  if (response.statusCode == 200) {
    return NumberTriviaModel.fromJson(json.decode(response.body));
  } else {
    throw ServerException();
  }
}

Mặc dù rất khó xảy ra, nhưng việc tái cấu trúc này vẫn có thể gây ra lỗi ở đâu đó. Chỉ sau khi kiểm tra lại mã và thấy nó hoạt động thành công, chúng ta mới có thể yên tâm ngủ ngon. Kinh nghiệm đau thương :D.

Tiếp theo

Chúng ta đang tiến triển khá nhanh chóng. Vừa hoàn thành toàn bộ data class và với domain class đã được triển khai, chỉ còn lại presentation layer. Chúng ta sẽ tiếp tục theo truyền thống và bắt đầu từ trung tâm của layer, đó là thành phần nắm giữ logic trình bày. Trong trường hợp của ứng dụng Number Trivia, chúng ta sẽ sử dụng Bloc.

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