openplanning

Bài thực hành Dart http CRUD

  1. Cài đặt thư viện
  2. CRUD Demo Server
  3. Model
  4. Api Model
  5. POST Utils
  6. POST Example
  7. PUT Utils
  8. PUT Example
  9. DELETE Utils
  10. DELETE Example
Trong bài thực hành này chúng ta sẽ sử dụng gói http để gửi các yêu cầu tạo, cập nhập và xoá các bản ghi tới máy chủ. Và sử dụng gói dart:convert để chuyển đổi JSON thành đối tượng Dart và ngược lại.

1. Cài đặt thư viện

pubspec.yaml
dependencies: 
  http:

2. CRUD Demo Server

Trước hết hãy đảm bảo rằng bạn đã triển khai CrudDemoServer, một máy chủ REST miễn phí được sử dụng trong các ví dụ của bài thực hành này.

3. Model

Các lớp Dart tham gia trong các ví dụ của bài học này.
model.dart
import 'dart:convert';

class ProductData {
  int id;
  String name;
  bool active;
  double price;
  double estimatedInputPrice;
  double taxRate;
  ImageData? image;
  CategoryData category;

  ProductData({
    required this.id,
    required this.name,
    required this.active,
    required this.price,
    required this.estimatedInputPrice,
    required this.taxRate,
    required this.image,
    required this.category,
  });

  static ProductData fromMap(Map<String, dynamic> map) {
    Map<String, dynamic> imgMap = map['image'];
    return ProductData(
      id: map['id'],
      name: map['name'],
      active: map['active'],
      price: toDouble(map['price']),
      estimatedInputPrice: toDouble(map['estimatedInputPrice']),
      taxRate: toDouble(map['taxRate']),
      image: imgMap == null ? null : ImageData.fromMap(imgMap),
      category: CategoryData.fromMap(map['category']),
    );
  }

  static ProductData fromJsonString(String jsonString) {
    Map<String, dynamic> map = jsonDecode(jsonString);
    return fromMap(map);
  }
}

class CategoryData {
  int id;
  String name;

  CategoryData({required this.id, required this.name});

  static CategoryData fromMap(Map<String, dynamic> map) {
    return CategoryData(id: map['id'], name: map['name']);
  }
}

class ImageData {
  int id;
  String ext;
  int width;
  int height;
  int size;

  ImageData({
    required this.id,
    required this.ext,
    required this.width,
    required this.height,
    required this.size,
  });

  static ImageData fromMap(Map<String, dynamic> map) {
    return ImageData(
      id: map['id'],
      ext: map['ext'],
      width: map['width'],
      height: map['height'],
      size: map['size'],
    );
  }
}

double toDouble(dynamic v) {
  if (v is int) {
    return v.toDouble();
  } else if (v is double) {
    return v;
  }
  throw Exception('Can not convert $v to double');
}

4. Api Model

api_model.dart
import 'dart:convert';

typedef Converter<D> = D Function(String jsonString);
typedef ErrorConverter = String Function(String jsonString);

class Result<D> {
  D? data;
  String? errorMessage;
  Result({this.data, this.errorMessage});
}

/// json : '{"errorMessage":"Some Error!"}';
String defaultErrorConverter(String json)  {
  try {
    Map<String,dynamic> map = jsonDecode(json);
    var errorMessage = map['errorMessage'];
    return errorMessage??json;
  } catch(e)  {
    return json;
  }
}
Lớp Result<D> mô phỏng một kết quả được từ việc gửi yêu cầu tới máy chủ.
D data
Đây là một đối tượng Dart đại diện cho dữ liệu mà bạn nhận được từ máy chủ. Trong thực tế máy chủ trả về cho bạn một văn bản JSON và bạn cần cung cấp một hàm để chuyển đổi JSON thành đối tượng này.
errorMessage
Một văn bản mô tả nguyên nhân lỗi. Nếu thuộc tính này có giá trị null có nghĩa là không có lỗi gì xẩy ra.

5. POST Utils

Một hàm tiện ích gửi một yêu cầu tới máy chủ để tạo một tài nguyên.
post_utils.dart
import 'package:http/http.dart' as http;

import 'api_model.dart';

Future<Result<D>> postData<D>({
  required String apiUrl,
  Object? body,
  Map<String, String>? headers,
  required Converter<D> converter,
  ErrorConverter? errorConverter,
  bool showDebug = false,
}) async {
  D? data;
  String? errorMessage;
  try {
    Uri url = Uri.parse(apiUrl);
    final response = await http.post(
      url,
      headers: headers,
      body: body,
    );

    String jsonString = response.body;
    if (showDebug) {
      print("Request Status Code: ${response.statusCode}");
      print("JSON String: $jsonString");
    }
    if (response.statusCode == 201 || response.statusCode == 200) {
      data = converter(jsonString);
    } else {
      if (errorConverter != null) {
        errorMessage = errorConverter(jsonString);
      } else {
        errorMessage = jsonString;
      }
    }
  } catch (e) {
    errorMessage = e.toString();
  }
  return Result(errorMessage: errorMessage, data: data);
}
body
Dữ liệu được đính kèm trong yêu cầu để gửi tới máy chủ.
headers
Thông tin headers, được gửi kèm trong yêu cầu tới máy chủ. Nó có thể bao gồm thông tin uỷ quyền (authorization) để đảm bảo người dùng có quyền hành động với các tài nguyên máy chủ.
{
 "Content-type": "application/json", 
 "Accept": "application/json",
 "Authorization": "Some token"
}
converter
Trong trường hợp không có lỗi xẩy ra, máy chủ trả về một văn bản JSON. Hàm này được sử dụng để chuyển đổi văn bản JSON này thành đối tượng Dart <D>.
errorConverter
Trong trường hợp có lỗi xẩy ra tại, máy chủ trả về một văn bản mang thông tin lỗi, nó có thể là một văn bản JSON. Hàm này chuyển đổi văn bản trên thành một thông điệp lỗi thân thiện với người dùng.
var json = '{"errorMessage": "Internal error!"}';
// ==> "Internal error!"
var errorMessage = errorConverter(json) 

6. POST Example

Trong ví dụ này chúng ta sẽ gửi một yêu cầu tới máy chủ để tạo một Product.
main_post_demo.dart
import 'dart:convert';

import 'api_model.dart';
import 'model.dart';
import 'post_utils.dart';

Future<void> main() async {
  // Public API (No login or Authentication Token required).
  String apiUrl = "http://localhost:8080/public/rest/product/create";

  // body must by Map<String,String>, not Map<String,dynamic>.
  Map<String, String> body = {
    "name": "Test Product",
    "active": true.toString(),
    "price": 20.toString(),
    "estimatedInputPrice": 18.toString(),
    "taxRate": 0.1.toString(),
    "categoryId": 1.toString(),
  };

  // Authentication Token required if API is not public.
  // var headers = <String, String>{
  //   'Authorization': 'Basic Your-Basic-Token',
  // };

  var headers = <String, String>{};

  Result<ProductData> result = await postData(
    apiUrl: apiUrl,
    headers: headers,
    body: body,
    converter: ProductData.fromJsonString,
    errorConverter: defaultErrorConverter,
    showDebug: true,
  );

  if (result.errorMessage != null) {
    print(result.errorMessage);
  } else {
    ProductData product = result.data!;
    print('Product: ${product.id}');
  }
}
Sau khi chạy ví dụ bạn sẽ thấy một bản ghi được tạo ra trên bảng PRODUCT.

7. PUT Utils

Một hàm tiện ích để gửi một yêu cầu cập nhập một tài nguyên trên máy chủ.
put_utils.dart
import 'package:http/http.dart' as http;

import 'api_model.dart';

Future<Result<D>> putData<D>({
  required String apiUrl,
  Object? body,
  Map<String, String>? headers,
  required Converter<D> converter,
  ErrorConverter? errorConverter,
  bool showDebug = false,
}) async {
  D? data;
  String? errorMessage;
  try {
    Uri url = Uri.parse(apiUrl);
    final response = await http.put(
      url,
      headers: headers,
      body: body,
    );

    String jsonString = response.body;
    if (showDebug) {
      print("Request Status Code: ${response.statusCode}");
      print("JSON String: $jsonString");
    }
    if (response.statusCode == 200) {
      data = converter(jsonString);
    } else {
      if (errorConverter != null) {
        errorMessage = errorConverter(jsonString);
      } else {
        errorMessage = jsonString;
      }
    }
  } catch (e) {
    errorMessage = e.toString();
  }
  return Result(errorMessage: errorMessage, data: data);
}

8. PUT Example

Trong ví dụ này chúng ta sẽ gửi một yêu cầu tới máy chủ để cập nhập một PRODUCT.
main_put_demo.dart
import 'api_model.dart';
import 'model.dart';
import 'put_utils.dart';

Future<void> main() async {
  // Public API (No login or Authentication Token required).
  String apiUrl = "http://localhost:8080/public/rest/product/update";

  // body must by Map<String,String>, not Map<String,dynamic>.
  Map<String, String> body = {
    "id": 1.toString(),
    "name": "333 Beer (Update)", // <== Update Name.
    "active": true.toString(),
    "price": 1.toString(),
    "estimatedInputPrice": 0.8.toString(),
    "taxRate": 0.1.toString(),
    "categoryId": 1.toString(),
  };

  // Authentication Token required if API is not public.
  // var headers = <String, String>{
  //   'Authorization': 'Basic Your-Basic-Token',
  // };

  var headers = <String, String>{};

  Result<ProductData> result = await putData(
    apiUrl: apiUrl,
    headers: headers,
    body: body,
    converter: ProductData.fromJsonString,
    errorConverter: defaultErrorConverter,
    showDebug: true,
  );

  if (result.errorMessage != null) {
    print(result.errorMessage);
  } else {
    ProductData product = result.data!;
    print('Product: ${product.id}');
  }
}

9. DELETE Utils

Một hàm tiện ích để gửi yêu cầu xoá một tài nguyên trên máy chủ.
delete_utils.dart
import 'package:http/http.dart' as http;

import 'api_model.dart';

Future<Result<void>> deleteData<D>({
  required String apiUrl,
  Object? body,
  Map<String, String>? headers,
  ErrorConverter? errorConverter,
  bool showDebug = false,
}) async {
  String? errorMessage;
  try {
    Uri url = Uri.parse(apiUrl);
    final response = await http.delete(
      url,
      headers: headers,
      body: body,
    );

    String jsonString = response.body;
    if (showDebug) {
      print("Request Status Code: ${response.statusCode}");
      print("JSON String: $jsonString");
    }
    if (response.statusCode == 200) {
      // Nothing to do.
    } else {
      if (errorConverter != null) {
        errorMessage = errorConverter(jsonString);
      } else {
        errorMessage = jsonString;
      }
    }
  } catch (e) {
    errorMessage = e.toString();
  }
  return Result(errorMessage: errorMessage, data: null);
}

10. DELETE Example

Trong ví dụ này chúng ta sẽ tạo một yêu cầu xoá một PRODUCT.
main_delete_demo.dart
import 'api_model.dart';
import 'delete_utils.dart';

Future<void> main() async {
  // Public API (No login or Authentication Token required).
  String apiUrl = "http://localhost:8080/public/rest/product/delete/19";

  // body must by Map<String,String>, not Map<String,dynamic>.
  Map<String, String> body = {};

  // Authentication Token required if API is not public.
  // var headers = <String, String>{
  //   'Authorization': 'Basic Your-Basic-Token',
  // };

  var headers = <String, String>{};

  Result<void> result = await deleteData(
    apiUrl: apiUrl,
    headers: headers,
    body: body,
    errorConverter: defaultErrorConverter,
    showDebug: true,
  );

  if (result.errorMessage != null) {
    print(result.errorMessage);
  } else {
    print('Successful!');
  }
}