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);
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);
}
I have a repository class (check below) that calls APIs through the HttpService class. I'm receiving historical data that contains 1000s of data objects. To avoid the computation problem, I'm trying to implement the compute() function to move this computation out of the main thread, but Android Studio is giving a compilation error.
class WealthRepo {
Future<Responser<PerformanceHistoryModel>> fetchPortfolioHistory(
String stackId,
String? duration,
) async {
try {
final resp = await _httpService.makePostRequest(<API_EP>, jsonEncode(<REQUEST_OBJ>));
return Responser<PerformanceHistoryModel>(
message: '',
isSuccess: true,
data: compute(parsePerformanceHistory, resp),
);
} catch (e, st) {
return ErrorHandler.error<PerformanceHistoryModel>(
e,
stackTrace: st,
);
}
}
}
/// Outside WealthRepo class
PerformanceHistoryModel parsePerformanceHistory(Map<String, dynamic> response) {
return PerformanceHistoryModel.fromJson(response);
}
lib/repositories/wealth_repo.dart:1431:23: Error: The argument type
'PerformanceHistoryModel Function(Map<String, dynamic>)' can't be
assigned to the parameter type 'FutureOr
Function(dynamic)'.
'PerformanceHistoryModel' is from 'package:aphrodite_v2/data/models/wealth/performance_history_model.dart'
('lib/data/models/wealth/performance_history_model.dart').
'Map' is from 'dart:core'.
data: compute(parsePerformanceHistory, resp),
^
PS - Responser is a custom response class we created. Not sure how to resolve this issue.
class Responser<T> {
final String message;
final bool isSuccess;
T? data;
Responser({
required this.message,
required this.isSuccess,
this.data,
});
#override
String toString() =>
'Responser(message: $message, isSuccess: $isSuccess, data: $data)';
}
You either need to await your resp before passing it into the compute function.
return Responser<PerformanceHistoryModel>(
message: '',
isSuccess: true, \/\/
data: compute(parsePerformanceHistory, await resp),
);
Or you need to update the signature on the function that compute uses so that it takes in a FutureOr<Map<String, dynamic>>.
Future<PerformanceHistoryModel> parsePerformanceHistory(
FutureOr<Map<String, dynamic>> _response) async {
final response = await _response;
return PerformanceHistoryModel.fromJson(response);
}
I have a Model class and a GameModel extends Model
I don't understand why the model can't convert automatically in GameModel
class Php :
abstract class Php
{
final String url;
final Model model;
late final String function;
Php(this.url, this.model);
///Function to get the response from the server
Future<Response> _getResponse()
{
String theUrl = "http://10.0.2.2/TeamMateProject/php/scripts/"+url+"?function="+function;
return get(Uri.parse(theUrl));
}
///Function to return a list of models using GET method
#protected
Future<List<Model>> phpMethodGetList() async {
final response = await _getResponse();
if(response.statusCode==200)
{
print(response.body);
List models = json.decode(response.body);
return models.map((e) => model.fromMap(e)).toList();
}
else
{
throw Exception(response.body);
}
}
}
class PhpGame extends Php
class PhpGame extends Php
{
PhpGame() : super("scriptGame.php", GameModel());
Future<List<GameModel>> getAllGames() async
{
function = "getAllGames";
return phpMethodGetList() as Future<List<GameModel>>;
}
}
ERROR :
type 'Future<List<Model>>' is not a subtype of type 'Future<List<GameModel>>' in type cast
Here : return phpMethodGetList() as Future<List<GameModel>>;
I think you would first have to interpret Model as GameModel before you return it as a Future<List<GameModel>>
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
I'm trying to mock the Firebase Functions package from Flutter Fire, but I keep getting errors.
This is my attempt at creating the Mock. But the call function is giving me errors when I try to override it because the return type is not correct.
library firebase_cloud_functions_mock;
import 'dart:convert';
import 'package:cloud_functions/cloud_functions.dart';
import 'package:mockito/mockito.dart';
class MockFirebaseFunctions extends Mock implements FirebaseFunctions {
final Map<String, String> _jsonStore = <String, String>{};
String _convertMapToJson(Map<String, dynamic> parameters) {
return json.encode(parameters);
}
void mockResult(
{String functionName, String json, Map<String, dynamic> parameters}) {
if (parameters?.isNotEmpty != null) {
// ignore: parameter_assignments
functionName = functionName + _convertMapToJson(parameters);
}
_jsonStore[functionName] = json;
}
String getMockResult(String functionName, Map<String, dynamic> parameters) {
// ignore: parameter_assignments
functionName = parameters == null
? functionName
: (parameters?.isNotEmpty != null
? functionName + _convertMapToJson(parameters)
: functionName);
assert(
_jsonStore[functionName] != null, 'No mock result for $functionName');
return _jsonStore[functionName];
}
#override
HttpsCallable getHttpsCallable({String functionName}) {
return HttpsCallableMock._(this, functionName);
}
}
class HttpsCallableMock extends Mock implements HttpsCallable {
HttpsCallableMock._(this._firebaseFunctions, this._functionName);
final MockFirebaseFunctions _firebaseFunctions;
final String _functionName;
#override
Future<HttpsCallableResult> call([dynamic parameters]) {
final decoded = json.decode(_firebaseFunctions.getMockResult(
_functionName, parameters as Map<String, dynamic>));
return Future.value(HttpsCallableResultMock._(decoded));
}
/// The timeout to use when calling the function. Defaults to 60 seconds.
Duration timeout;
}
class HttpsCallableResultMock extends Mock implements HttpsCallableResult {
HttpsCallableResultMock._(this.data);
/// Returns the data that was returned from the Callable HTTPS trigger.
#override
final dynamic data;
}
Does anyone know how to correctly mock the Firebase Functions package from Flutter Fire?
I was able to make your example work with cloud_function-2.0.0 with three small changes.
Adding the <T> type parameters in HttpsCallableMock.call and
HttpsCallableResultMock.
Replacing the override of
getHttpsCallable with httpsCallable.
Checking parameters?.isNotEmpty == true instead != null. This change might not be needed depending on the behavior you want when empty params are passed.
Here is the updated code.
import 'package:cloud_functions/cloud_functions.dart';
import 'package:mockito/mockito.dart';
class MockFirebaseFunctions extends Mock implements FirebaseFunctions {
final Map<String, String> _jsonStore = <String, String>{};
String _convertMapToJson(Map<String, dynamic> parameters) {
return json.encode(parameters);
}
void mockResult(
{String functionName, String json, Map<String, dynamic> parameters}) {
if (parameters?.isNotEmpty == true) {
functionName = functionName + _convertMapToJson(parameters);
}
_jsonStore[functionName] = json;
}
String getMockResult(String functionName, Map<String, dynamic> parameters) {
// ignore: parameter_assignments
functionName = parameters == null
? functionName
: (parameters?.isNotEmpty == true
? functionName + _convertMapToJson(parameters)
: functionName);
assert(
_jsonStore[functionName] != null, 'No mock result for $functionName. \n Expected one of ${_jsonStore.keys}');
return _jsonStore[functionName];
}
#override
HttpsCallable httpsCallable(String functionName, {HttpsCallableOptions options}) {
return HttpsCallableMock._(this, functionName);
}
}
class HttpsCallableMock extends Mock implements HttpsCallable {
HttpsCallableMock._(this._firebaseFunctions, this._functionName);
final MockFirebaseFunctions _firebaseFunctions;
final String _functionName;
#override
Future<HttpsCallableResult<T>> call<T>([dynamic parameters]) {
final decoded = json.decode(_firebaseFunctions.getMockResult(
_functionName, parameters as Map<String, dynamic>));
return Future.value(HttpsCallableResultMock<T>._(decoded));
}
/// The timeout to use when calling the function. Defaults to 60 seconds.
Duration timeout;
}
class HttpsCallableResultMock<T> extends Mock implements HttpsCallableResult<T> {
HttpsCallableResultMock._(this.data);
/// Returns the data that was returned from the Callable HTTPS trigger.
#override
final T data;
}