Flutter base model approach

For fetch data from the internet

https://flutter.dev/docs/cookbook/networking/fetch-data

In the official docs, they guide to use factory fromJson but when use in a large project, I realize that way is not good for extending. I propose another way and I think it’s more flexible with the response like

{
    "error": true,
    "data": null,
    "errors": [
        {
            "code": 1029,
            "message": "User not found!."
        }
    ]
}

First, create base_response.dart

/*
 * Developed by Nhan Cao on 10/25/19 5:09 PM.
 * Last modified 10/25/19 5:09 PM.
 * Copyright (c) 2019 Beesight Soft. All rights reserved.
 */

/*
{
    "error": true,
    "data": null,
    "errors": [
        {
            "code": 1029,
            "message": "User not found!."
        }
    ]
}
 */
import 'dart:core';

abstract class BaseResponse<T> {
  bool error;
  T data;
  List<BaseError> errors;

  BaseResponse(Map<String, dynamic> fullJson) {
    parsing(fullJson);
  }

  /// @nhancv 12/6/2019: Abstract json to data
  T jsonToData(Map<String, dynamic> fullJson);
  /// @nhancv 12/6/2019: Abstract data to json
  dynamic dataToJson(T data);

  /// @nhancv 12/6/2019: Parsing data to object
  parsing(Map<String, dynamic> fullJson) {
    error = fullJson["error"] ?? false;
    data = jsonToData(fullJson);
    errors = parseErrorList(fullJson);
  }

  /// @nhancv 12/6/2019: Parse error list from server
  List<BaseError> parseErrorList(Map<String, dynamic> fullJson) {
    List errors = fullJson["errors"];
    return errors != null
        ? List<BaseError>.from(errors.map((x) => BaseError.fromJson(x)))
        : <BaseError>[];
  }

  /// @nhancv 12/6/2019: Data to json
  Map<String, dynamic> toJson() => {
        "error": error,
        "data": dataToJson(data),
        "errors": List<dynamic>.from(errors.map((x) => x.toJson())),
      };
}

class BaseError {
  int code;
  String message;

  BaseError({
    this.code,
    this.message,
  });

  factory BaseError.fromJson(Map<String, dynamic> json) => BaseError(
        code: json["code"],
        message: json["message"],
      );

  Map<String, dynamic> toJson() => {
        "code": code,
        "message": message,
      };
}

Ok, to deal with data response for Brand

{
    "error": true,
    "data": [{"name": name, "id": id}],
    "errors": [
        {
            "code": 1029,
            "message": "User not found!."
        }
    ]
}

Create brand_response.dart

/*
 * Developed by Nhan Cao on 12/6/19 2:52 PM.
 * Last modified 12/6/19 11:51 AM.
 * Copyright (c) 2019 Beesight Soft. All rights reserved.
 */

import 'base_response.dart';

class BrandModel {
  String name;
  int id;

  BrandModel({this.name, this.id});

  Map<String, dynamic> toJson() {
    return {"name": name, "id": id};
  }
}

class BrandResponse extends BaseResponse<List<BrandModel>> {
  BrandResponse(Map<String, dynamic> fullJson) : super(fullJson);

  @override
  dynamic dataToJson(data) {
    return List<dynamic>.from(data.map((x) => x.toJson()));
  }

  @override
  List<BrandModel> jsonToData(Map<String, dynamic> fullJson) {
    List dataList = fullJson["data"];
    return dataList != null
        ? List<BrandModel>.from(dataList.map((x) => BrandModel(name: x["name"] ?? "", id: x["id"] ?? 0)))
        : <BrandModel>[];
  }
}

How to use the model?

# Short guide
final brandResponse = BrandResponse(json.decode(data.body));

# Full example with bflutter
final okTrigger = Bloc<bool, List<BrandModel>>();

okTrigger.logic = (input) => input.asyncMap((d) async {
          mainBloc.appLoading.push(true);
          return authApi.getBrands().timeout(Duration(seconds: 30));
        }).asyncMap((data) async {
          if (data == null) return <BrandModel>[];
          mainBloc.appLoading.push(false);
          if (data.statusCode != 500) {
            try {
              final brandResponse = BrandResponse(json.decode(data.body));
              if (brandResponse.data != null) {
                return brandResponse.data;
              } else {
                // @nhancv 10/25/2019: Parse error
                if (brandResponse.error) {
                  final error = brandResponse.errors.first;
                  throw Exception(error != null
                      ? 'Code ${error.code ?? -1} - ${error.message ?? 'Empty'}'
                      : 'Unknow error.');
                } else {
                  throw Exception(data.reasonPhrase);
                }
              }
            } catch (e) {
              throw e;
            }
          } else {
            throw Exception(data.reasonPhrase);
          }
        }).handleError((error) {
          mainBloc.appLoading.push(false);
          mainBloc.showAlertDialog(error.toString());
        });

Bonus. To deal with a flexible response like

Error
{
    "error": true,
    "data": null,
    "errors": [
        {
            "code": 1029,
            "message": "User not found!."
        }
    ]
}

Successful
{
    "token_type": "Bearer",
    "expires_in": 1295998,
    "access_token": "nhancv_dep_trai",
    "refresh_token": "call_nhancv_dep_trai"
}

Create a log_in_response.dart


import 'base_response.dart';

class LoginResponse extends BaseResponse {
  String tokenType;
  int expiresIn;
  String accessToken;
  String refreshToken;

  LoginResponse(Map<String, dynamic> fullJson) : super(fullJson) {
    tokenType= fullJson["token_type"];
    expiresIn= fullJson["expires_in"];
    accessToken= fullJson["access_token"];
    refreshToken= fullJson["refresh_token"];
  }

  @override
  Map<String, dynamic> toJson() {
    return {
      "token_type": tokenType,
      "expires_in": expiresIn,
      "access_token": accessToken,
      "refresh_token": refreshToken,
      ... super.toJson()
    };
  }

  @override
  Map<String, dynamic> dataToJson(data) {
    return null;
  }

  @override
  jsonToData(Map<String, dynamic> fullJson) {
    return null;
  }
}

1 thought on “Flutter base model approach

Leave a Reply

Your email address will not be published.Required fields are marked *