I have defined a function to get some information from the server
#override
Future<HttpResponse<dynamic>> getAppInfo() async {
const _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _data = <String, dynamic>{};
final _result = await _dio.fetch(_setStreamType<HttpResponse<dynamic>>(
Options(method: 'GET', headers: <String, dynamic>{}, extra: _extra)
.compose(_dio.options, '/user/app-info',
queryParameters: queryParameters, data: _data)
.copyWith(baseUrl: baseUrl ?? _dio.options.baseUrl)));
final value = _result.data;
final httpResponse = HttpResponse(value, _result);
return httpResponse;
}
I am passing this function as an argument at run time to different function. Another function is defined this way
Future<HttpResponse?> handleApi(
Function function, [
List<dynamic>? args,
]) async {
try {
final HttpResponse httpResult = await Function.apply(
function,
args,
) as HttpResponse; // This following bloc of code is not getting called
if (httpResult.response.statusCode == HttpStatus.ok ||
httpResult.response.statusCode == HttpStatus.created) {
return httpResult;
}
} on Exception catch (e) {
return null;
}
}
And i am passing first function to second function as an argument. It was working fine without null safe version of flutter. And after the migration to null safe, the call is not happening on the function at run time.
I am calling handleApi fucntion following way
final httpResultOrError = await _apiCallHandler.handleApi(
getAppInfo,
);
Related
I'm having issues with the return type of this HTTP request, I need to return Future<List> but the map is returning a List<Future>. I don't know how to get the CustomerInfo without it being a future and I don't know how to return it any other way.
if (response.statusCode == 200) {
Iterable l = json.decode(response.body);
final data = List.from(l.map((model) async {
final name = model['UserName'];
final id = model['UserICode'];
return {name, id};
}));
final users = data.map<CustomerInfo>((e) async {return await getFaxinfo(e.id, e.name);});
return users;
} else {
throw 'err';
}
}
Future<CustomerInfo> getFaxinfo(
String id,
String name,
) async {
final baseUrl = 'localhost';
final int port = 3003;
final accountsPath = '/accounts';
final accountsFaxInfoPath = '$accountsPath/fax-info';
final Map<String, dynamic> queryParam = {'id': id};
final uri = Uri(
scheme: 'http',
path: accountsFaxInfoPath,
host: baseUrl,
queryParameters: queryParam);
final response = await http.get(uri);
return CustomerInfo(sent: 200, received: 300, name: 'Test');
}
The problem is that an async function always returns a Future no matter if you call await inside it or not. To fix it a good approach is to use list comprehension. A simple for loop also would do.
Instead of those 2 maps that result in List<Future>:
final data = List.from(l.map((model) async {
final name = model['UserName'];
final id = model['UserICode'];
return {name, id};
}));
final users = data.map<CustomerInfo>((e) async {return await getFaxinfo(e.id, e.name);});
Do the following with list comprehension:
final users = [
for (final model in l)
await getFaxinfo(model['UserICode'], model['UserName'])
];
Now if you want to make the HTTP calls in parallel it's possible to do a Future.wait() in a List<Future> to get the result as List<CustomerInfo>. Something like the following:
final users = await Future.wait(l.map((model) async =>
await getFaxinfo(model['UserICode'], model['UserName'])));
I have a service to call api and some methods expects just Object and the rest expects list of objects. And I'm trying some tricks with generics but then errors in _get method with casting occurs.
// List
Future<List<ToDo>> getToDos() async {
final List<Map<String, dynamic>> response =
await _get<List<Map<String, dynamic>>>(url: "ToDos");
final List<ToDo> result =
response.map((data) => ToDo.fromJson(data)).toList();
return result;
//Object
Future<ToDo> getToDo() async {
final Map<String, dynamic> response =
await _get<Map<String, dynamic>>(url: "ToDo");
final ToDo result = ToDo.fromJson(response);
return result;
}
Future<T> _get<T>({
required String url,
}) async {
final Response response = await get(
Uri.https(_apiURL, url),
);
if (response.statusCode == 200) {
final T jsonResponse = json.decode(response.body) as T;
return jsonResponse;
} else {
throw Exception(response.body);
}
}
Error:
Exception has occurred. _CastError (type 'List<dynamic>' is not a subtype of type 'List<Map<String, dynamic>>' in type cast)
Is it even possible to solve it like I'm trying?
[Update]:
Solution:
Future<T> _get<T, K>({
required String url,
}) async {
final Response response = await get(
Uri.https(_apiURL, url),
);
if (response.statusCode == 200) {
final dynamic jsonResponse = json.decode(response.body);
if (jsonResponse is List) {
return List<K>.from(jsonResponse) as T;
} else {
return jsonResponse as T;
}
} else {
throw Exception(response.body);
}
}
There's a list function called cast. You can cast a dynamic list to List<Map<String, dynamic>> like this:
final aList=[]//your list;
final castedList=aList.cast<Map<String, dynamic>>();
I am working on first my flutter app. App required to call rest api and return back result. I am looking to create generic function to call rest api. I have written below code but I am not understating, how can I decode api response in specific model.
Future<T> apiRequest<T>(
String endPoint,
RequestMethod method, {
String body = '',
String token = '',
}) async {
http.Response resp;
final String url = LocalConstants.apiBaseUrl + endPoint;
final Map<String, String> headers = new Map<String, String>();
headers.putIfAbsent(
HttpHeaders.contentTypeHeader, () => 'application/json');
if (token != null && token.isNotEmpty) {
headers.putIfAbsent(
HttpHeaders.authorizationHeader, () => 'Bearer ' + token);
}
try {
if (method == RequestMethod.get) {
resp = await http.get(
url,
headers: headers,
);
} else if (method == RequestMethod.put) {
resp = await http.put(
url,
headers: headers,
body: body,
);
} else if (method == RequestMethod.post) {
resp = await http.post(
url,
headers: headers,
body: body,
);
} else if (method == RequestMethod.delete) {
resp = await http.delete(
url,
headers: headers,
);
}
if (resp != null && this.validateResponse(resp)) {
return json.decode(resp.body);
}
// else {
// Response resp = new Response();
// resp.respMsg = LocalConstants.genericError;
// resp.respCode = LocalConstants.resp_failure;
// Response.
// }
} on TimeoutException catch (e) {
//handleTimeout();
} on SocketException catch (e) {
print('Socket Error: $e');
//handleTimeout();
} on Error catch (e) {
print('General Error: $e');
//showError();
}
}
Below is code which I can use to call rest api
await ApiService.newInstance(context)
.apiRequest<GenericResp>('/api/people', RequestMethod.get);
Here is my GenericResp class
import 'package:project/models/Response.dart';
class GenericResp extends Response {
int id;
int otherId;
String mappingId;
GenericResp({
this.id,
this.otherId,
this.mappingId,
});
GenericResp.fromJson(Map<String, dynamic> json) {
id = json['id'];
otherId = json['other_id'];
mappingId = json['mapping_id'];
respCode = json['resp_code'];
respMsg = json['resp_msg'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = this.id;
data['other_id'] = this.otherId;
data['mapping_id'] = this.mappingId;
data['resp_code'] = this.respCode;
data['resp_msg'] = this.respMsg;
return data;
}
}
How can I decode body json.decode(resp.body); to GenericResp of type T?
You can add a generic argument that will deserialize your json data to GenericResp. Something like that:
Future<T> apiRequest<T>(
String endPoint,
RequestMethod method, T Function(Object json) fromJson, {
String body = '',
String token = '',
}) async { ... }
And after json decoding you are to use fromJson argument:
if (resp != null && this.validateResponse(resp)) {
return fromJson(json.decode(resp.body));
}
And then a call would look like this:
await ApiService.newInstance(context).apiRequest<GenericResp>('/api/people',
RequestMethod.get, (json) => GenericResp.fromJson(json));
I have a method inside a service class:
#override
Future<String> registerNewVoter(Object deviceAppInfo) async {
Dio dio = new Dio();
final url = API().endpointVoterUri(EndpointVoter.newVoter).toString();
final header = {'Content-type': 'application/json'};
final data = await deviceAppInfo; ///need to call the method getInfo() on the Object class which returns a future
final response =
await dio.post(url, data: data, options: Options(headers: header));
if (response.statusCode == 200) {
Map map = response.data;
final uuid = map['result']['voter_uuid'];
return uuid;
}
print(
'Request $url failed\nResponse: ${response.statusCode} ${response.statusMessage}');
throw response;
}
I'm using type Object deviceAppInfo as a parameter in the method to keep the service as pure as possible(adhering to mvvm principles). The subclass is DeviceAppInfo which has an async method called getInfo()(and where the data comes from) which is supposed to be assigned to data(see the comments in the code). I'm struggling to see how I can keep the class decoupled from DeviceAppInfo class. Any suggestions...? I'm thinking of calling a factory constructor but not sure how to implement it. Here is my DeviceAppInfo class:
class DeviceAppInfo {
DeviceAppInfo({
this.platform,
this.platformVersion,
this.appVersion,
});
final String platform;
final String platformVersion;
final String appVersion;
Map<String, dynamic> toMap() => {
'platform': this.platform,
'platform_version': this.platformVersion,
'app_version': this.appVersion,
};
Future<Map<String, dynamic>> getInfo() async {
final values = await Future.wait([
getPlatform(),
getPlatformVersion(),
getProjectVersion(),
]);
return DeviceAppInfo(
platform: values[0],
platformVersion: values[1],
appVersion: values[2],
).toMap();
}
Future<String> getPlatform() async {
try {
if (Platform.isIOS) {
return 'ios';
}
return 'android';
} on PlatformException catch (e) {
return e.toString();
}
}
Future<String> getPlatformVersion() async {
try {
final platformVersion = await GetVersion.platformVersion;
return platformVersion;
} on PlatformException catch (e) {
return e.toString();
}
}
Future<String> getProjectVersion() async {
try {
final projectVersion = await GetVersion.projectVersion;
return projectVersion;
} on PlatformException catch (e) {
return e.toString();
}
}
}
I believe that DeviceAppInfo is a clear collaborator of your service, and hiding it behind Object is simply bad engineering:
it will make your Api hard to use correctly and easy to use incorrectly
Your api is no longer self-documenting, without reading the docs or code it is impossible to use it correctly.
However, it can be discussed if it should be exposed as a parameter or provided to the constructor of your service.
Having said that, There are at least 3 options that will decouple your service from DeviceAppInfo:
Option 1: Pass in the result of getInfo() to your method
least questionable and a common form of decoupling inbound data
I am a bit sceptical if you use a Map as an input type, it is still easy to provide a map with incorrect keys
Option 2: take a function as an argument
Function a bit harder to use, it is not evident what functions accross the codebase can be used (compared to a class)
Option 3: cast to dynamic
Please dont do that
Most closely matches your goal from question
function is extremely hard to use correctly Without reading docs / code
You change compile-time errors to runtime errors
Is this what you want?
#override
Future<String> registerNewVoter(DeviceAppInfo deviceAppInfo) async {
Dio dio = new Dio();
final url = API().endpointVoterUri(EndpointVoter.newVoter).toString();
final header = {'Content-type': 'application/json'};
final data = await deviceAppInfo.getInfo();
final response =
await dio.post(url, data: data, options: Options(headers: header));
if (response.statusCode == 200) {
Map map = response.data;
final uuid = map['result']['voter_uuid'];
return uuid;
}
print(
'Request $url failed\nResponse: ${response.statusCode} ${response.statusMessage}');
throw response;
}
NOTE: I just changed the type of deviceAppInfo from Object to DeviceAppInfo
I'm working on a project and I'm trying to get information from an API. When I write the link it doesn't detect the character "?" and it substitutes this char for "%3F" so I can't access to the API.
final String _charactersUrl = '/api/character/?page=2';
I get status code 500 from the API:
https://rickandmortyapi.com/api/character/%3Fpage=3
The class that gets information from the API
class Api {
final String _baseUrl = 'rickandmortyapi.com';
final String _charactersUrl = '/api/character/?page=2';
final String _charactersJsonKey = 'results';
final HttpClient _httpClient = HttpClient();
Future<List<Character>> getCharacters() async {
final uri = Uri.https(_baseUrl, _charactersUrl);
final response = await _getJson(uri);
if (response == null || response[_charactersJsonKey] == null) {
print('Api.getCharacters(): Error while retrieving characters');
return null;
}
return _convert(response[_charactersJsonKey]);
}
Future<Map<String, dynamic>> _getJson(Uri uri) async {
try {
final request = await _httpClient.getUrl(uri);
final response = await request.close();
if (response.statusCode != HttpStatus.OK) {
print('Api._getJson($uri) status code is ${response.statusCode}');
return null;
}
final responseBody = await response.transform(utf8.decoder).join();
return json.decode(responseBody);
} on Exception catch (e) {
print('Api._getJson($uri) exception thrown: $e');
return null;
}
}
List<Character> _convert(List charactersJson) {
List<Character> characters = <Character>[];
charactersJson.forEach((character) {
characters.add(Character.fromJson(character));
});
return characters;
}
}
I would be very grateful if someone could help me. Thanks!
The Uri class expects you to use the Uri.https constructor differently.
The third positional parameter is queryParameters, which you should use instead of passing your query parameters to the unencodedPath:
final String _baseUrl = 'rickandmortyapi.com';
final String _charactersPath = '/api/character/';
final Map<String, String> _queryParameters = <String, String>{
'page': '2',
};
Future<List<Character>> getCharacters() async {
final uri = Uri.https(_baseUrl, _charactersPath, _queryParameters);
...