So I'm trying to call the REST API for the login here. This is in my api_services.dart where I am calling all the APIs for the application.
api_services.dart
Future<User> loginUser(String email, String password)
async {
final response = await http.post(serverOauthUrl+'/token',
headers: {
HttpHeaders.AUTHORIZATION: "xxxx"
},
body: {
"email":"$email",
"password":"$password",
}
);
print(response.statusCode);
final responseJson = json.decode(response.body);
return new User.fromJson(responseJson);
}
And there are two ways I can call this loginUser() method in my UI files and get the response. One that uses the then() method and the other uses FutureBuilder. However, in none of the method, can I get the status code. My use case is that when the status code is >400, I will build a widget that shows the error message.
login_screen.dart
then() method code:
_callLoginAPI(String email, String password){
loginUser(userName, password, "password").then((response) {
response.data.token;
// want my status code here as well along with response data
}
else
{
//todo show something on error
}
}, onError: (error) {
debugPrint(error.toString());
});
}
Or using FutureBuilder :
return new FutureBuilder<User>(
future: loginUser(email, password),
builder: (context, snapshot) {
if (snapshot.hasData) {
print(snapshot.data.token);
} else if (snapshot.hasError) {
print(snapshot.error);
return new Text("${snapshot.error}");
}
return new CircularProgressIndicator();
},
);
What I want to do is something like this
if(response.statusCode > 400)
return new Text("Error"):</code>
Thanks to #Thomas, this issue is resolved. Was an easy solution actually.
Adding the changes in the code for other beginners to follow :
api_services.dart
Future<http.Response> loginUser(String email, String password) async {
final response = await http.post(serverOauthUrl+
'/token',
headers: {
HttpHeaders.AUTHORIZATION: "Basic xxx"
},
body: {
"email":"$email",
"password":"$password",
}
);
return response;
}
So instead of the User, I'm returning the http.Response object and now I can retrieve all the required info from the UI files.
Like this:
final responseJson = json.decode(response.body);
User user = User.fromJson(responseJson);
print(user.userName);
Hope it helps somebody
Why aren't you return an Api Result object instead of a user that contains the error code and the user?
Then you can build different widgets on your FutureBuilder depending on the status code.
Related
I been reading multiple threads about passing data between futures, but for some reason I can get this to work.:(
I have two futures, where the first future is doing post request to retrieve a token
Future<AuthStart> fetchAuthStart() async {
final response = await http.post(
Uri.parse('http://ipsum.com/auth/start'),
body: jsonEncode(<String, String>{
'IdNumber': '198805172387',
}));
if (response.statusCode == 200) {
final responseJson = jsonDecode(response.body);
return AuthStart.fromJson(responseJson);
} else {
throw Exception("Failed");
}
}
this request will generate an GUID that I need in my next future
Future<AuthStatus> fetchAuthStatus(String token) async {
final response = await http.post(Uri.parse('http://ipsum/auth/status'),
body: jsonEncode(<String, String>{
'Token':token,
}),
);
if (response.statusCode == 200) {
return AuthStatus.fromJson(jsonDecode(response.body));
} else {
throw Exception("Failed");
}
}
I have tried multiple ways, one that worked was to pass the variable in my FutureBuilder like this
FutureBuilder(
future: futureAuthStart,
builder: (context, snapshot) {
if (snapshot.hasData) {
fetchAuthStatus(snapshot.data!.Token);
return Text(snapshot.data!.Token);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return const CircularProgressIndicator();
},
),
But this does not make sense?
I would rather pass it in my InitState something like this.
void initState() {
super.initState();
futureAuthStart = fetchAuthStart();
futureAuthStatus = fetchAuthStatus(token);
}
Am I totally on the wrong path? Any tutorials out there that can give better info :)?
Thanks in advance.
Create a void function and pass it the functions and one at a time it will be executed with async/await. You should take a look at Asynchronous programming: futures, async, await.
void initState() {
super.initState();
_onInit();
}
void _onInit() async {
try {
var futureAuthStart = await fetchAuthStart();
var futureAuthStatus = await fetchAuthStatus(futureAuthStart.data.token);
}catch(e, stackTrace){
//handler error
print(e);
print(stackTrace);
}
}
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 used get method to retrieve user details and have got 200 status as well. I am having confusion how to show the details in UI. In my homepage I have a floating action button which leads to the profile page. Any help would be much appreciated Thank you.
Future getProfile() async {
String? token = await getToken();
final response = await http.get(Uri.parse('$API_URL/user'), headers: {
'Accept': 'application/json',
'Authorization': 'Bearer $token'
});
print(response.statusCode);
if (response.statusCode == 200) {
if (response.body != "") {
var results = json.decode(response.body);
var resultData = results['data']['name'];
print(resultData);
}
}
}
you can use a FutureBuilder like this:
FutureBuilder<dynamic>(
future: getProfile,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting: return Text('Loading....');
default:
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
else
return Text('Result: ${snapshot.data}');
}
},
);
...
Future getProfile() async {
String? token = await getToken();
final response = await http.get(Uri.parse('$API_URL/user'), headers: {
'Accept': 'application/json',
'Authorization': 'Bearer $token'
});
dynamic resultData;
if (response.statusCode == 200) {
if (response.body != "") {
var results = json.decode(response.body);
resultData = results['data']['name'];
print(resultData);
}
}
return resultData;
}
When you are working with network data (i.e. API responses), the best practice states that you should convert the received data into Dart objects. You will then be able to easily access your data.
Quick and easy approach (not recommended)
For a quick and dirty approach, you could do the following:
1- create a model for your user. Create new file and name it user_model.dart
class User{
String id;
String name;
// Add whatever other properties you need to pull from the server here
User({
this.id,
this.name,
});
// This function will help you convert the deata you receive from the server
// into an instance of User
factory User.fromJson(Map<String, dynamic> json) => User({
id: json['id'],
namne: json['name']
})
}
2- Instanciate a new user in your getProfile() function
Future<User?> getProfile() async { // you want to get a Future<User>
String? token = await getToken();
final response = await http.get(Uri.parse('$API_URL/user'), headers: {
'Accept': 'application/json',
'Authorization': 'Bearer $token'
});
print(response.statusCode);
if (response.statusCode == 200) {
if (response.body != "") {
var result = json.decode(response.body)['data']; // select the data you need here
final user = User.fromJson(result) // create a new User instance
return user // return it
}
}
// in case something went wrong you want to return null
// you can always check the nullity of your instance later in your code
return null;
}
3- In your UI, you can consume the newly created instance like so. I am assuming you are inside a build() function of any widget!
//...
#override
Widget build(BuildContext context) {
return FutureBuilder<dynamic>(
future: getProfile,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if(snapshot.hasData){
final user = snapshot;
// now you can access your user's data as you wish
print(user.id);
print(user.name);
}
);
}
//...
Better Approach (recommended)
The above approach, although seems to work, won't be ideal for a more complex project. For that, you want to follow a road map that could look like the following:
Automate object serialization/deserialization using packages like freezed. This will offload you from any unwanted error injection by building toJson and fromJson methods, among others, for you ;). Check their documentation for more details.
Manage data streams using a state management library like bloc. You can access your state, in your case the user's profile data, from anywhere in the widget tree without having to use FutureBuilder everywhere. It will also help you keep in sync with your data. Check their well-written documentation for more details on how to use it.
I have mentioned these two libraries here because they are the ones I work with all the time and that I am familiar with. They might be others out there that do more or less the same. It's up to you to pick whichever you feel comfortable with ;)
Once you get familiar with a state management library you could architect your app as follow:
/...
-lib
|- model #build your data instance and return object
|- repository #call API methods and convert received data to model instance
|- api #make HTTP calls
|- ui #build UI elements
|- bloc #receive events from UI and call repository functions then return datastreams to UI
I try to display a custom message when user logs or when log fail.
My post api call returns nothing, so I wanted to base on response status code to know if log went well or not.
I did something like in my api :
Future<Response> login(String email, String password) async {
final http.Response response = await http.post(
baseUrl + 'auth/login',
headers: headers,
body: jsonEncode(<String, dynamic> {
'email': email,
'password': password
}),
);
return response.statusCode == 200 ? Response(statusCode: 200) : Response(message: "Failed to login");}
class Response {
final String message;
final int statusCode;
Response({this.message, this.statusCode});
factory Response.fromJson(Map<String, dynamic> json) {
return Response(
message: json["message"],
);}}
And I call this method inside a FutureBuilder to display the message:
FutureBuilder(
future: lap.login(emailController.text, passwordController.text),
builder: (BuildContext context, AsyncSnapshot<Response> snapshot) {
if(snapshot.hasData)
print(snapshot.data.statusCode);
return CircularProgressIndicator();
},);
In my print method, I print nothing I don't understand why it doesn't display status code I return in my api method.
Could someone know why ?
Well I finally did the job with a .then().
Still don't understand why the first way didn't do it but after all, it works.
onPressed: () {
if(_formKey.currentState.validate()) {
lap.login(emailController.text, passwordController.text)
.then((responseMessage) => Scaffold
.of(context)
.showSnackBar(SnackBar(content: Text(responseMessage.message))));
}
},
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.