Question mark converted to %3F in URI - flutter

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);
...

Related

Issues with flutter futures - map results in List<Future>

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'])));

How to cast dynamic to T or List<T>

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>>();

Flutter mockito post always return null

I'm trying to test my code that makes a post to login from an API using a mocked http client, but instead of returning what I asked for, it returns null, I did the same test but changing the endpoint and method to GET and it worked perfectly. I'm currently using flutter's http to make the requests, but I've already tested it with Dio and the result was the same, below is my code
Future<String> signIn(String email, String password) async {
final Map<String, dynamic> body = {"email": email, "password": password};
final String url = url_base + Urls.auth_login;
final Map<String, String> customHeader = {
"Content-type": "application/json",
};
String returnCode;
try {
var x = jsonEncode(body);
http.Response response = await client.post(Uri.parse(url), body: x, headers: customHeader);
var parsedJson = json.decode(response.data);
if (parsedJson.containsKey("token")) {
returnCode = parsedJson["token"];
} else {
returnCode = parsedJson["non_field_errors"][0];
}
}catch (e) {
throw ServerException();
}
if (returnCode == null) {
throw ServerException();
} else {
return returnCode;
}
}
and the test case:
class ClientMock extends Mock implements http.Client {}
void main() {
RemoteData remoteData;
group('Test signIn', () {
test('Login with email and wrong password', () async {
final clientMock = ClientMock();
remoteData = RemoteData(client: clientMock);
String jsonMockResponse =
'{non_field_errors: [Unable to log in with provided credentials.]}';
when(clientMock.post(any))
.thenAnswer((_) async => http.Response(jsonMockResponse, 400));
String loginReturn =
await remoteData.signIn('test#email.com', 'password123');
expect(loginReturn,throwsA(const TypeMatcher<ServerException>()));
});
}
I've already tested some things like changing 'any' for exactly the same thing the real function gets and it didn't work either.
The actual test return 'Instance of 'ServerException'', an in debug mode i could see that the return is null, and the last if is the one who throws this exception.

Flutter generic REST API call function

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));

Register to aqueduct backend from Flutter frontend

I'm having a bit of difficulty with registering to aqueduct backend from my Flutter frontend
Here is my code in my frontend:
Future<void> signUp(String email, String password) async {
final body = "username:$email,password:$password"; //<- return request entity could not be decoded
//final body = {"username": email, "password": password}; //<- return bad state: Cannot set the body fields of Request with content-type "application/json"
try {
final http.Response response = await http.post(
"http://localhost:8888/register",
headers: {"Content-Type": "application/json"},
body: body);
final jsonResponse = json.decode(response.body);
if (jsonResponse["error"] != null) {
throw HttpException(jsonResponse["error"]);
}
} catch (error) {
throw error;
}
}
There must be some silly mistake. I believe it is with formatting body so I tried 2 options and both throw different http exception (as in comment).
Here is an example of connecting to an Aqueduct server from a Flutter client. (This isn't really a server question, though, since the client and server are independent of each other.)
Here is an example of registering:
void _register(String email, String password) async {
Map<String, String> headers = {"Content-type": "application/json"};
final jsonString = '{"username":"$email", "password":"$password"}';
Response response = await post(YOUR_URL_HERE, headers: headers, body: jsonString);
print('${response.statusCode} ${response.body}');
}
In your example you aren't encoding the JSON correctly.
And here is another example of signing in. The class is a view model architecture that I talk about here.
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
class LoginViewModel extends ChangeNotifier {
String _token = '';
bool _isLoggedIn = false;
bool get isLoggedIn => _isLoggedIn;
String get token => _token;
Future onLoginPressed(String username, String password) async {
if (username.isEmpty || password.isEmpty) {
return;
}
_isLoggedIn = await _login(username, password);
notifyListeners();
}
Future<bool> _login(String username, String password) async {
var clientID = 'com.example.app';
var clientSecret = '';
var body = 'username=$username&password=$password&grant_type=password';
var clientCredentials = Base64Encoder().convert('$clientID:$clientSecret'.codeUnits);
Map<String, String> headers = {
'Content-type': 'application/x-www-form-urlencoded',
'authorization': 'Basic $clientCredentials'
};
var response = await http.post(YOUR_URL_HERE, headers: headers, body: body);
final responseBody = response.body;
if (response.statusCode != 200) {
return false;
}
final map = json.decode(responseBody);
_token = map['access_token'];
return true;
}
}