I am tying retrieve data from an api on Rapid Api using Dart's http package and displaying it using Flutter however the content never loads and the api doesn't return an error.
class APIService {
// API key
static const _api_key = <MYAPIKEY>;
// Base API url
static const String _baseUrl = "covid-19-data.p.rapidapi.com";
// Base headers for Response url
static const Map<String, String> _headers = {
"content-type": "application/json",
"x-rapidapi-host": "covid-19-data.p.rapidapi.com",
"x-rapidapi-key": _api_key,
};
Future<CovidNumbers> fetchData(
{#required String endpoint, #required Map<String, String> query}) async {
Uri uri = Uri.https(_baseUrl, endpoint, query);
final response = await http.get(uri, headers: _headers);
if (response.statusCode == 200) {
return CovidNumbers.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load Data');
}
}
}
The method is then called onInit
Future<CovidNumbers> data;
APIService apiService = APIService();
#override
void initState() {
super.initState();
data = apiService.fetchData(
endpoint: "/country", query: {"format": "json", "name": "malta"});
}
And finally I display it in a FutureBuilder
FutureBuilder<CovidNumbers>(
//future: futureCovidNumbers,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(
"Confirmed Cases: ${snapshot.data.confirmed.toString()}");
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
},
));
The app remains stuck on the CircularProgressIndicator and does not display an error.
you future is empty, for that reason always is returning a CircularProgressIndicator, place your "data" variable inside the future and try again
Related
I want to get an image from an api and I get the error mentioned in the title.
class _ApiState extends State<Api> {
Future<CatData> fetchcat() async {
final response =
await http.get(Uri.parse('https://api.thecatapi.com/v1/images/search'));
// Appropriate action depending upon the
// server response
if (response.statusCode == 200) {
return CatData.fromJson(json.decode(response.body));
//return CatData.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
} else {
throw Exception('Failed to load album');
}
}
late Future<CatData> futureAlbum;
#override
void initState() {
super.initState();
futureAlbum = fetchcat();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<CatData>(
future: fetchcat(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Image.network(snapshot.data!.imagen);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
},
);
}
}
here the class model:
class CatData {
String imagen;
CatData({required this.imagen});
factory CatData.fromJson(Map<String, dynamic> json) {
return CatData(
imagen: json['url'],
);
}
}
If I get an answer please, I would like you to explain to me the reason for the problem. because I always get this kind of errors when I consume API's.
"receives one value but expects another"
https://api.thecatapi.com/v1/images/search
Well, json.decode(response.body) gives you back a List<dynamic>, but you declared the method fromJson to accept one argument of type Map<String, dynamic>, thus the incompatibility.
You can change the signature of the method fromJson and set it to List<dynamic>. Then you could access it with json[0].url, json[0]['url'] or {url} = json[0].
I tested the following code in https://dartpad.dev and works like a charm now.
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<CatData> fetchcat() async {
final response =
await http.get(Uri.parse('https://api.thecatapi.com/v1/images/search'));
// Appropriate action depending upon the
// server response
if (response.statusCode == 200) {
return CatData.fromJson(json.decode(response.body));
//return CatData.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
} else {
throw Exception('Failed to load album');
}
}
class CatData {
String imagen;
CatData({required this.imagen});
factory CatData.fromJson(List<dynamic> json) {
return CatData(
imagen: json[0]['url']
);
}
}
void main() async {
CatData catData = await fetchcat();
print(catData.imagen);
}
You probably making mistake on casting. first make sure what kind of data you are retrieving means is it key-value pair { "url" : "www...." } or List [{"url" :"www...} , { "url": " www..."}]
if its key-value pairs then decode it as follows:
final decoded = json.decode(response.body) as Map<String, dynamic>;
final _catData = CataData.fromJson(decoded);
or if its list of urls then do it as follows:
final _decoded = json.decode(response.body) as List<dynamic>;
final _catsData = _decoded.map((e) => CatData.fromJson(e as Map<String, dynamic>)).toList();
Referring to this article
https://medium.com/solidmvp-africa/making-your-api-calls-in-flutter-the-right-way-f0a03e35b4b1
I was trying to call API from a flutter app. But to make it the right way, I was looking for a complete example and came here. My question is why do I need to create an ApiBaseHelper class then RepositoryClass then all other formalities to call an API. Why can't I use FutureBuilder and a simple async function associated with the API like this:
class Networking {
static const BASE_URL = 'https://example.com';
static Future<dynamic> getProductById({
required String? token,
required String? productId,
}) async {
final url = Uri.parse('$BASE_URL/products/$productId');
final accessToken = 'Bearer $token';
Map<String, String> requestHeaders = {
'Authorization': accessToken,
'Content-Type': 'application/json'
};
try {
final response = await http.get(
url,
headers: requestHeaders,
);
if (response.statusCode != 200) {
throw Exception('Error fetching data.');
}
final responseJSON = json.decode(response.body);
if (responseJSON['error'] != null) {
return throw Exception(responseJSON['error']);
}
final product = Product.fromJson(responseJSON);
return product;
} catch (e) {
throw Exception(e.toString());
}
}
}
And then calling it from a FutureBuilder like this:
FutureBuilder(
future: Networking.getProductById(token, id),
builder: (context, snapshot) {
// rest of the code
}
)
Can anyone tell me what is the most convenient and widely used way to call an API?
I have to check the email and password with the rest API that is going well. The problem is my future is returning a class object that has a token. I need that that for other screen and after login navigate to other screens.
Future<LoginResponse> createLoginState(String email, String password) async {
final http.Response response = await http.post(
'https://www.polestarkw.com/api/login',
headers: <String, String>{
'Accept': 'application/json',
//'content-type' : 'application/json'
},
body: {
"email":email ,
"password":password ,
});
if (response.statusCode == 200) {
// print(response.body);
LoginResponse loginResponse=LoginResponse.fromJson(json.decode(response.body)) ;
return loginResponse;
} else {
throw Exception('Failed to create album.');
}
}
class LoginResponse {
Object _data;
String token_type;
String expires_in;
String access_token;
String refresh_token;
LoginResponse(
{this.token_type, this.expires_in, this.access_token, this.refresh_token});
LoginResponse.fromJson(Map<String, dynamic> json) {
token_type = json['token_type'];
expires_in = json['expires_in'];
access_token = json['access_token'];
refresh_token = json['refresh_token'];
}
}
I need this loginResponse object on my other page. Here is using a future instance.
_futureJwt = createLoginState(emailController.text, pwdController.text);
how to get data from _futureJwt.
The code should go something like this
Future<LoginResponse> createLoginState(String email, String password) async {
final http.Response response = await http.post(
'https://www.polestarkw.com/api/login',
headers: <String, String>{
'Accept': 'application/json',
//'content-type' : 'application/json'
},
body: {
"email":email ,
"password":password ,
});
if (response.statusCode == 200) {
// print(response.body);
LoginResponse loginResponse=fromJson(json.decode(response.body)) ;
return loginResponse;
} else {
throw Exception('Failed to create album.');
}
}
LoginResponse fromJson(Map<String, dynamic> json) {
token_type = json['token_type'];
expires_in = json['expires_in'];
access_token = json['access_token'];
refresh_token = json['refresh_token'];
return LoginResponse(token_type,expires_in,access_token,refresh_token);
}
class LoginResponse {
Object _data;
String token_type;
String expires_in;
String access_token;
String refresh_token;
LoginResponse(
{this.token_type, this.expires_in, this.access_token, this.refresh_token});
}
The above code should work in the way u have written it too but I am not sure since I use this way
Then you can use this like
LoginResponse _futureJwt = await createLoginState(emailController.text, pwdController.text);
var token_type = _futureJwt.token_type;
var expires_in = _futureJwt.expires_in;
var access_token = _futureJwt.access_token;
var refresh_token = _futureJwt.refresh_token;
As simple as that. If you do not want to wait for the Future, you can use .then like this
createLoginState(emailController.text, pwdController.text).then((_futureJwt){
var token_type = _futureJwt.token_type;
var expires_in = _futureJwt.expires_in;
var access_token = _futureJwt.access_token;
var refresh_token = _futureJwt.refresh_token;
});
Use FutureBuilder.
Then you can use AsyncSnapshot to access hasData(), hasError() and get the data like so:
#override
Widget build(BuildContext context) {
Future<String> exampleFuture = Future.delayed(Duration(seconds: 2), "value")
return FutureBuilder(
future: exampleFuture,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasError) {
return Text("error");
} else if (!snapshot.hasData) {
return Text("loading");
} else {
return Text(snapshot.data);
}
});
}
I have to make multiple API calls in order to get the actual data. I have written the below code to make the first API call. It works but I have to use the return value (let'say it returns access token) from the first call, and use this access token as part of the header on the second API call. How can I achieve that?
class Service {
final String url;
Map<String, String> header = new Map();
Map<String, String> body = new Map();
Service(this.url, this.header, this.body);
Future<Data> postCall() async {
final response = await http.post(url, headers: header, body: body);
return Data.fromJson(json.decode(response.body));
}
}
class MyApp extends StatelessWidget {
Service service;
Service serviceTwo;
....
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: FutureBuilder<Data>(
future: service.postCall,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.accessToken);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner.
return CircularProgressIndicator();
},
),
),
),
);}}
There are many ways of achieving that, the simplest one is just using await on your method to append the future calls.
So your method postCall() would be something like this:
Future<Data> postCall() async {
// The first call, suppose you'll get the token
final responseToken = await http.post(url, headers: header, body: body);
// Decode it as you wish
final token = json.decode(responseToken.body);
// The second call to get data with the token
final response = await http.get(
url,
headers: {authorization: "Bearer $token"},
);
// Decode your data and return
return Data.fromJson(json.decode(response.body));
}
If it is a token you'll use many times, I recommend you to store it in flutter_secure_storage and use it as you wish.
how can I read the HTTP post response with the class return type?
my class is
class ContactModal {
String code;
String status;
String name;
ContactModal({this.code, this.status, this.name});
factory ContactModal.fromJson(Map<String, dynamic> json) {
return ContactModal(
code: json['code'],
status: json['status'],
name: json['name']
);
}
}
and my HTTP request is
class ApiClient {
Future<ContactModal> getDetails(String token) async {
print("inside get file "+token);
var response = await http
.get(Uri.parse(this.apiBaseUrl + "/file-list/"), headers: {
"Accept": "application/json",
"Authorization": token
});
if (response.statusCode == 200) {
print("Json response"+response.body);
return ContactModal.fromJson(json.jsonDecode(response.body));
} else {
print("Json exception response");
throw Exception('Failed to fetch access token');
}
}
}
my builder is
body: new Container(
child: new FutureBuilder(
future: apiClient.getFileDetails(this.accessToken),
builder: (context, snapshot) {
print(" prefs.getString('accessTokenValue') "+snapshot.data);
if (snapshot.hasData) {
print("Snap shot data : "+snapshot.data);
new ContactsList(_buildContactList(snapshot));
} else if (snapshot.hasError) {
return new Text("${snapshot.error}");
}
// By default, show a loading spinner
return new CircularProgressIndicator();
},
),
),
my api will return the response List[ContactModal]()
How can i read the all data and am getting everytime snapshot result null.
Your response contains array of contact json. So you might have to build the contact list and return like below.
var jsonArray = json.jsonDecode(response.body);
var contactList = jsonArray.map((json) => ContactModal.fromJson(json)).toList();
Return type of getDetails will be Future<List<ContactModal>>.