How to have a abstract master class with factory method

77 Views Asked by At

I have a model which extends a master model and need to pass the ExtendedModel to the parse method which accepts a generic class type so that I can pass any model which extends MasterModel and can call T.fromJson(); but I get an error as factory method can not be used as override.


class MasterModel {
  MasterModel();

  factory MasterModel.fromJson(Map<String, dynamic> json){
    return MasterModel();
  }
}

class ExtendedModel extends MasterModel {
  int id;
  String name;

  ExtendedModel(this.id, this.name);

  factory ExtendedModel.fromJson(Map<String, dynamic> json) {
    return ExtendedModel(json['id'], json['name']);
  }
}

T parse<T extends MasterModel>(Map<String, dynamic> json) {
  return T.fromJson(json) as T; // Error : The method 'fromJson' isn't defined for the type 'Type'
}

I have also tried to use the normal method like

abstract class MasterModel {
   MasterModel fromJson(Map<String, dynamic> json);
} 

But here while calling the fromJson in parse method we need a object of class but we are creating a object in parse method so can not use this as well.

Another way is to have if conditions in the parse method like

T parse<T extends MasterModel>(Map<String, dynamic> json) {
  if(T is ExtendedModel){
    return ExtendedModel.fromJson(
        json) as T;
  }else {
    // handle for other models
  }
}

but this method has to be a generic method and I don't want to include the if condition to check types every time.

1

There are 1 best solutions below

1
Hakkı Alkan On

I am using this way, maybe it will be useful for you.

Base model;

base_model.dart

abstract class BaseModel<T> {
  Map<String, dynamic> toJson();
  T fromJson(Map<String, dynamic> json);
}

My user model;

user_model.dart

class UserModel extends BaseModel<UserModel> {
  UserModel({this.mail, this.fullName});

  String? mail;
  String? fullName;

  factory UserModel.fromJson(Map<String, dynamic> json) => UserModel(
        mail: json["mail"],
        fullName: json["fullName"],
      );

  @override
  Map<String, dynamic> toJson() => {
        "mail": mail,
        "fullName": fullName,
      };

  @override
  UserModel fromJson(Map<String, dynamic> json) => UserModel.fromJson(json);
}

This is the method I'm requesting;

  Future<ResponseModel<T>> callPostApi<T extends BaseModel>(
    String path, {
    required T parseModel,
    formData,
    Map<String, dynamic>? queryParameters,
    Map<String, dynamic>? headers,
    required StackTrace stackTrace,
  }) async {
    try {
      // Dio Certificate Callback
      (_client!.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (HttpClient client) {
        client.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
        return client;
      };

      bool connectivityResult = await appService.isRunApi();
      if (connectivityResult) {
        Response response = await _client!.post(path, options: Options(headers: headers, validateStatus: (status) => true), data: formData, queryParameters: queryParameters);
        return _checkResponseStatus<T>(response, parseModel);
      } else {
        debugPrint('İnternet Yok');
        return ResponseModel<T>(error: ErrorModel(message: 'İnternet Bulunmamaktadır!'));
      }
    } on DioError catch (e) {
      // connectTimeout, receiveTimeout, isSocketException, isHttpException dışındaki hataları mobil loga kaydetmek için if eklendi
      if (e.type != DioErrorType.connectTimeout && e.type != DioErrorType.receiveTimeout && !(e.isSocketException) && !(e.isHttpException)) logService.sendLog(stackTrace, e.message);

      return ResponseModel<T>(error: ErrorModel(message: e.message, statusCode: e.response?.statusCode));
    } on Exception catch (e) {
      logService.sendLog(stackTrace, e.toString());
      return ResponseModel<T>(error: ErrorModel(message: e.toString()));
    }
  }
  

  _checkResponseStatus<T extends BaseModel>(Response response, T parseModel) {
    T? model;
    switch (response.statusCode) {
      case 200:
        model = _parseBody<T>(parseModel, response.data);
        return ResponseModel<T>(data: model);
      case 400:
      case 401:
      case 403:
      case 404:
      case 500:
        return ResponseModel<T>(error: ErrorModel(statusCode: response.statusCode, message: response.statusMessage));
      default:
        return ResponseModel<T>(error: ErrorModel(statusCode: response.statusCode));
    }
  }

  _parseBody<T>(BaseModel parseModel, dynamic responseBody) {
    if (responseBody is List) {
      return responseBody.map((data) => parseModel.fromJson(data)).cast<T>().toList();
    } else if (responseBody is Map<String, dynamic>) {
      return parseModel.fromJson(responseBody);
    }
  }

Since I know the return type when sending a request to the API, I specify it so that it knows what to parse ( T )

  Future<UserModel?> getUserData() async {
    var response = await networkService.manager.callPostApi<UserModel>(
      'network_url.....',
      parseModel: UserModel(),
      stackTrace: StackTrace.current,
      queryParameters: {'some_parameters': 'paramaters'},
      headers: {'some_parameters': 'paramaters'},
    );
    super.showMessage(response.error); // Having the error message (Toast) here is actually not a correct method.
    return response.data;
  }