This question already has an answer here:
Calling method on generic type Dart
(1 answer)
Closed 1 year ago.
I want to make APIHelper class.
This class will have method like get, post, put and delete.
And in these method all logic about getting data, decoding, encoding, mapping will be done.
I have base model class like this:
class Model{
Model();
Model.fromJson(Map<String, dynamic> data);
Map<String, dynamic> toJson(){
return {};
}
}
And in API model Event I inherited a class Model:
class EventModel extends Model{
final int desavanjeId;
final String desavanjeName;
EventModel({required this.desavanjeId, required this.desavanjeName});
#override
factory EventModel.fromJson(Map<String, dynamic> data) => EventModel(
desavanjeId: data['desavanjeId'],
desavanjeName: data['desavanjeName'],
);
#override
Map<String, Object> toJson() => {
'desavanjeId': this.desavanjeId,
'desavanjeName': this.desavanjeName,
};
}
And in service I have something like this:
Future<APIResponseModel> get<T>(Uri uri) async{
APIResponseModel apiRespone = APIResponseModel();
try {
Response response = await _client.get(uri);
Map<String, dynamic> data = jsonDecode(response.body);
apiRespone.addData(T.fromJson(data));
} catch (e) {
print(e);
}
return apiRespone;
}
And I am willing to use method get in this way:
get<EventModel>(Uri('...'));
But the problem is that IDE doesn't allow me to use static method fromJson in this way I need.
And I don't want to solve this problem in this way:
switch(Model){
case EventModel:
EventModel.fromJson(data)
}
Is there any other solution for this, but to keep a syntax in this way?
You cannot call a factory constructor or a static method from a generic type in Dart. Your only solution to obtain a similar result would be to use a callback method which will create your object. Here is a possible implementation you could use:
Code Sample
/// By looking at your implementation the Model class should be
/// abstract as it is your base model and should not be able to
/// be instantiated.
abstract class Model {
// fromJson is removed as it will be a static method
Map<String, dynamic> toJson();
}
class EventModel extends Model {
final int desavanjeId;
final String desavanjeName;
EventModel({required this.desavanjeId, required this.desavanjeName});
/// fromJson is now a static method which will return an instance of
/// your constructor so you can still call it like
/// this: EventModel.fromJson()
static EventModel fromJson(Map<String, dynamic> data) => EventModel(
desavanjeId: data['desavanjeId'],
desavanjeName: data['desavanjeName'],
);
#override
Map<String, Object> toJson() => {
'desavanjeId': this.desavanjeId,
'desavanjeName': this.desavanjeName,
};
}
/// Now your method takes a dynamic type which extends your base class Model
/// And you are passing a createCallback parameter which is a Function taking
/// a Map<String, dynamic> as its single parameter and returns an object
/// of type T it will be your method fromJson.
Future<APIResponseModel> get<T extends Model>(
Uri uri, T Function(Map<String, dynamic>) createCallback) async {
APIResponseModel apiRespone = APIResponseModel();
try {
Response response = await _client.get(uri);
final data = jsonDecode(response.body) as Map<String, dynamic>;
apiRespone.addData(createCallback(data));
} catch (e) {
print(e);
}
return apiRespone;
}
Now you should be able to make a call like this:
get<EventModel>(Uri('...'), EventModel.fromJson);
Try the full code on DartPad
Related
I have a class like below & method readJson which returns Future<Map<String, dynamic>> and in constructor TestRemoteConfigManager(){}, I want to assign the returned value to testValues .!
I'm getting issues as calling async method in non async method. Any helps?
class TestRemoteConfigManager {
Map<String, dynamic> testValues = {};
TestRemoteConfigManager() {
readJson().then((value) => testValues = value);
SLogger.i('testValues from contructor-->$testValues');
}
Future<Map<String, dynamic>> readJson() async {
final Map<String, dynamic> data = await json.decode(
await rootBundle.loadString('assets/uat/remote_config_defaults.json'));
SLogger.i('read data: $data');
return data;
}
}
If you care about waiting for the results of asynchronous calls in a constructor, you're better off using a static method to do the asynchronous work that then returns an instance of your object using a private constructor. Something like this:
class TestRemoteConfigManager {
Map<String, dynamic> testValues;
static Future<TestRemoteConfigManager> create() async {
final values = await readJson();
return TestRemoteConfigManager._(values);
}
static Future<Map<String, dynamic>> readJson() async {
// ...
}
// Declaring this private constructor means that this type can only be
// instantiated through TestRemoteConfigManager.create()
TestRemoteConfigManager._(this.testValues);
}
To all Dart gurus: I'm trying to implement a generic networking layer in Dart that converts REST service response to a specified model class:
// The idea is to make a network call and get a deserialized model as a response:
final token =
await _authNetworkService.execute<AccessTokenResponse>(request);
Here is the implementation:
// Model interface
abstract class JsonConvertible {
Map<String, dynamic> toJson();
JsonConvertible.fromJson(Map<String, dynamic> json);
}
// Model
class AccessTokenResponse extends JsonConvertible {
String? accessToken;
#override
Map<String, dynamic> toJson() {
return {};
}
#override
AccessTokenResponse.fromJson(Map<String, dynamic> json)
: super.fromJson(json) {
accessToken = json['access_token'];
}
}
// Network response class
class NetworkResponse<Model> {
Model data;
NetworkResponse.ok(this.data);
}
// Class to create a valid network service request
class NetworkRequest {
}
// Class that performs all network calls
class NetworkService {
Future<NetworkResponse<M>> execute<M extends JsonConvertible>(NetworkRequest request) async {
// For simplicity replaced all DIO calls with static data:
final response = {'data': {'access_token': 'XXX'}};
return NetworkResponse.ok(M.fromJson(response['data'])); //<- Fails here with error: Method 'fromJson' isn't defined for the type 'Type'...
}
}
DartPad: https://dartpad.dev/?id=9a29a7e49a084e69fd1d8078d5f2b977
How can I achieve expected behavior?
one way you can solve this is by passing the fromJson constructor as an argument to the execute function but this will add another step for every time execute is called
// Class that performs all network calls
class NetworkService {
Future<NetworkResponse<M>> execute<M extends JsonConvertible>(NetworkRequest request,M Function(Map<String, dynamic>) parser ) async {
// For simplicity replaced all DIO calls with static data:
final response = {'data': {'access_token': 'XXX'}};
return NetworkResponse.ok( parser(response['data']!)); //<- Fails here with error: Method 'fromJson' isn't defined for the type 'Type'...
}
}
and this is how you would call the execute function
final token =
await _authNetworkService.execute<AccessTokenResponse>(request,AccessTokenResponse.fromJson);
I'm using the freezed package to work with immutable models and make use of the built-in feature for json serialization by the json_serializable package. I have a simple User class/model with different union types (UserLoggedIn, UserGeneral, UserError):
#freezed
class User with _$User {
const factory User(String id, String email, String displayName) =
UserLoggedIn;
const factory User.general(String email, String displayName) = UserGeneral;
const factory User.error(String message) = UserError;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
Since I'm using multiple constructors and don't want my API to include the runtimeType key as suggested by the documentation, I can write a converter (scroll a bit more down, sentence starts with: If you don't control the JSON response, then you can implement a custom converter.).
So based on that I wrote the following converter class:
class UserConverter implements JsonConverter<User, Map<String, dynamic>> {
const UserConverter();
#override
User fromJson(Map<String, dynamic> json) {
if (json['id'] != null) {
return UserLoggedIn.fromJson(json);
} else if (json['error'] != null) {
return UserError.fromJson(json);
} else {
return UserGeneral.fromJson(json);
}
}
#override
Map<String, dynamic> toJson(User data) => data.toJson();
}
The documentation now references another class (a wrapper class) which would now use this converter via annotation, something like this:
#freezed
class UserModel with _$UserModel {
const factory UserModel(#UserConverter() User user) = UserModelData;
factory UserModel.fromJson(Map<String, dynamic> json) =>
_$UserModelFromJson(json);
}
Question: is it possible to make use of this converter without having to use a wrapper class (UserModel)?
Reasoning: this wrapper class is adding another layer of abstraction which is not needed (in my cases). Especially since the wrapper class does not have any other benefit / purpose and it feels like it should be possible to do that without using it.
I have this class
#freezed
abstract class CartEntity with _$CartEntity {
const factory CartEntity.empty(String status, String message) = _Empty;
const factory CartEntity.notEmpty(int x) = _NotEmpty;
factory CartEntity.fromJson(Map<String, dynamic> json) =>
_$CartEntityFromJson(json);
}
And this converter
class CartEntityConverter
implements JsonConverter<CartEntity, Map<String, dynamic>> {
const CartEntityConverter();
#override
CartEntity fromJson(Map<String, dynamic> json) {
//the problem here
print(json);// null
return _Empty.fromJson(json);
}
#override
Map<String, dynamic> toJson(CartEntity object) {
return object.toJson();
}
}
And this wrapper class
#freezed
abstract class CartEntityWrapper with _$CartEntityWrapper {
const factory CartEntityWrapper(#CartEntityConverter() CartEntity cartEntity) =
CartEntityWrapperData;
factory CartEntityWrapper.fromJson(Map<String, dynamic> json) =>
_$CartEntityWrapperFromJson(json);
}
And iam called
final cartEntity = CartEntityWrapperData.fromJson({'x':'y'});
print(cartEntity);
fromJson method which in CartEntityConverter is always receive null json so what's i made wrong ?
Instead of making yet another converter class that you use directly, you could just add .fromJsonA method in the main class.
It will looks like this one:
#freezed
abstract class CartEntity with _$CartEntity {
const factory CartEntity.empty(String status, String message) = _Empty;
const factory CartEntity.notEmpty(int x) = _NotEmpty;
factory CartEntity.fromJson(Map<String, dynamic> json) =>
_$CartEntityFromJson(json);
factory CartEntity.fromJsonA(Map<String, dynamic> json) {
if (/*condition for .empty constructor*/) {
return _Empty.fromJson(json);
} else if (/*condition for .notEmpty constructor*/) {
return _NotEmpty.fromJson(json);
} else {
throw Exception('Could not determine the constructor for mapping from JSON');
}
}
}
solved by using
final cartEntity = CartEntityConverter().fromJson({'x':'y'});
print(cartEntity);
instead of
final cartEntity = CartEntityWrapperData.fromJson({'x':'y'});
print(cartEntity);
documentation have a lack at this point i tried random stuffs to make it work
I want to convert fetched data as below, but I got error and emulator shutdown!
What can I do?
Map<String, dynamic> responseClassMap = {
'$ResponseCompany': ResponseCompany,//ResponseCompany is class
'$ResponseCompanyDetail': ResponseCompanyDetail, //ResponseCompanyDetail is class
};
for (var item in responseClassMap.entries) {
if (className == item.key) {
result = responseData.map((data) => item.value.fromJson(data)).toList();
}
}
Here is class ResponseCompany.dart
#JsonSerializable()
class ResponseCompany {
final num sales, ...;
...
factory ResponseCompany.fromJson(Map<String, dynamic> json) => _$ResponseCompanyFromJson(json);
...
Here is ResponseCompany.g.dart
ResponseCompany _$ResponseCompanyFromJson(Map<String, dynamic> json) {
return ResponseCompany(
);
...
}
IMHO item.value.fromJson will not work. Since fromJson is a factory constructor, and in dart's rule, one cannot call factory constructor for a type stored in a variable. (Indeed, the problem is hidden because you create a Map<string, dynamic> and dart allow everything to be called on dynamic at compile time.)
For your specific case, you can do
Map<String, dynamic> map = {
'$ResponseCompany': (d)=>ResponseCompany.fromJson(d),//ResponseCompany is class
'$ResponseCompanyDetail': (d)=>ResponseCompanyDetail.fromJson(d), //ResponseCompanyDetail is class
};
for (var item in map.entries) {
if (className == item.key) {
result = responseData.map((data) => item.value(data)).toList();
}
}