Dart Flutter Generic Api Response Class Dynamic Class Data Type
Solution 1
You can't call methods on types on Dart because static methods must be resolved at compile time and types do not have a value until runtime.
You can, however, pass a parser callback to your constructor and use an interface(eg. Serializable
) that every model may implement. Then, by updating your ApiResponse
to ApiResponse<T extends Serializable>
it will know that every type T
will have a toJson()
method.
Here's the full example updated.
class MyModel implements Serializable {
String id;
String title;
MyModel({this.id, this.title});
factory MyModel.fromJson(Map<String, dynamic> json) {
return MyModel(
id: json["id"],
title: json["title"],
);
}
@override
Map<String, dynamic> toJson() => {
"id": this.id,
"title": this.title,
};
}
class ApiResponse<T extends Serializable> {
bool status;
String message;
T data;
ApiResponse({this.status, this.message, this.data});
factory ApiResponse.fromJson(Map<String, dynamic> json, Function(Map<String, dynamic>) create) {
return ApiResponse<T>(
status: json["status"],
message: json["message"],
data: create(json["data"]),
);
}
Map<String, dynamic> toJson() => {
"status": this.status,
"message": this.message,
"data": this.data.toJson(),
};
}
abstract class Serializable {
Map<String, dynamic> toJson();
}
class Test {
test() {
ApiResponse apiResponse = ApiResponse<MyModel>();
var json = apiResponse.toJson();
var response = ApiResponse<MyModel>.fromJson(json, (data) => MyModel.fromJson(data));
}
}
Solution 2
base_response.dart
class BaseResponse {
dynamic message;
bool success;
BaseResponse(
{this.message, this.success});
factory BaseResponse.fromJson(Map<String, dynamic> json) {
return BaseResponse(
success: json["success"],
message: json["message"]);
}
}
list_response.dart
server response for list
{
"data": []
"message": null,
"success": true,
}
@JsonSerializable(genericArgumentFactories: true)
class ListResponse<T> extends BaseResponse {
List<T> data;
ListResponse({
String message,
bool success,
this.data,
}) : super(message: message, success: success);
factory ListResponse.fromJson(Map<String, dynamic> json, Function(Map<String, dynamic>) create) {
var data = List<T>();
json['data'].forEach((v) {
data.add(create(v));
});
return ListResponse<T>(
success: json["success"],
message: json["message"],
data: data);
}
}
single_response.dart
server response for single object
{
"data": {}
"message": null,
"success": true,
}
@JsonSerializable(genericArgumentFactories: true)
class SingleResponse<T> extends BaseResponse {
T data;
SingleResponse({
String message,
bool success,
this.data,
}) : super(message: message, success: success);
factory SingleResponse.fromJson(Map<String, dynamic> json, Function(Map<String, dynamic>) create) {
return SingleResponse<T>(
success: json["success"],
message: json["message"],
data: create(json["data"]));
}
}
data_response.dart
class DataResponse<T> {
Status status;
T res; //dynamic
String loadingMessage;
GeneralError error;
DataResponse.init() : status = Status.Init;
DataResponse.loading({this.loadingMessage}) : status = Status.Loading;
DataResponse.success(this.res) : status = Status.Success;
DataResponse.error(this.error) : status = Status.Error;
@override
String toString() {
return "Status : $status \n Message : $loadingMessage \n Data : $res";
}
}
enum Status {
Init,
Loading,
Success,
Error,
}
or if using freeezed then data_response can be
@freezed
abstract class DataResponse<T> with _$DataResponse<T> {
const factory DataResponse.init() = Init;
const factory DataResponse.loading(loadingMessage) = Loading;
const factory DataResponse.success(T res) = Success<T>;
const factory DataResponse.error(GeneralError error) = Error;
}
Usage: (part of retrofit library and retrofit_generator auto generated code)
const _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _data = <String, dynamic>{};
final _result = await _dio.request<Map<String, dynamic>>('$commentID',
queryParameters: queryParameters,
options: RequestOptions(
method: 'GET',
headers: <String, dynamic>{},
extra: _extra,
baseUrl: baseUrl),
data: _data);
final value = SingleResponse<Comment>.fromJson(
_result.data,
(json) => Comment.fromJson(json),
);
Solution 3
You can try my approach to apply generic response: APIResponse<MyModel>
by implements a custom Decodable abstract class, response from http requests will return as MyModel
object.
Future<User> fetchUser() async {
final client = APIClient();
final result = await client.request<APIResponse<User>>(
manager: APIRoute(APIType.getUser),
create: () => APIResponse<User>(create: () => User())
);
final user = result.response.data; // reponse.data will map with User
if (user != null) {
return user;
}
throw ErrorResponse(message: 'User not found');
}
Here is my source code: https://github.com/katafo/flutter-generic-api-response
Alberto Acuña
Updated on December 25, 2022Comments
-
Alberto Acuña over 1 year
My app currently is working with a custom classes for each api responses as models. But I'm trying to change it, to optimize some little things, so I'm trying to implement a Class wrapper, called ApiResponse for example. But its not working fine the static call and methods, for make fromJson and toJson.
I will show what I'm trying, as example. MyModel -> class response. ApiResponse -> main class that contains any model class inside, and must be call child methods as itselfs 'fromjson/tojson'. Test -> class for test purpose, errors comments on classes.
class MyModel { String id; String title; MyModel({this.id, this.title}); factory MyModel.fromJson(Map<String, dynamic> json) { return MyModel( id: json["id"], title: json["title"], ); } Map<String, dynamic> toJson() => { "id": this.id, "title": this.title, }; } class ApiResponse<T> { bool status; String message; T data; ApiResponse({this.status, this.message, this.data}); factory ApiResponse.fromJson(Map<String, dynamic> json) { return ApiResponse<T>( status: json["status"], message: json["message"], data: (T).fromJson(json["data"])); // The method 'fromJson' isn't defined for the type 'Type'. // Try correcting the name to the name of an existing method, or defining a method named 'fromJson'. } Map<String, dynamic> toJson() => { "status": this.status, "message": this.message, "data": this.data.toJson(), // The method 'toJson' isn't defined for the type 'Object'. // Try correcting the name to the name of an existing method, or defining a method named 'toJson' }; } class Test { test() { ApiResponse apiResponse = ApiResponse<MyModel>(); var json = apiResponse.toJson(); var response = ApiResponse<MyModel>.fromJson(json); } }