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
Related
I am creating an ecommerce Android flutter application, and I am new to this dart language. I need to get data from one table and post it to another table, where the API is built in .NET Core using a SQL Server database.
This is my code:
httpService.getPosts().then((value) {
if (value != null) {
value.forEach((element) {
httpServices.addPosts(
0,
element.cartProductID, element.productBrandId,
element.cartUserID, element.item,
element.quantity, element.price,
element.totalPrice,
element.discount,
// element.isOrdered,
element.paymentID,
element.paymentMode,
element.date,
);
});
My get method
class GetOrderHttpService with ChangeNotifier {
Future<List<OrderTotal>> getPosts() async {
Response res =
await http.get(Uri.https('********'));
if (res.statusCode == 200) {
List<dynamic> body = jsonDecode(res.body);
List<OrderTotal> posts = body
.map(
(dynamic dynamic) => OrderTotal.fromJson(dynamic),
)
.toList();
notifyListeners();
return posts;
} else {
throw "Unable to retrieve posts.";
}
}
}
Future<bool> addPosts(
int orderID,
int orderProductID,
int productBrandId,
int orderUserID,
String item,
int quantity,
double price,
double totalPrice,
double discount,
int paymentID,
String? paymentMode,
DateTime date,
) async {
var response = await http.post(
Uri.https('************'),
body: jsonEncode({
'orderID': orderID,
'orderProductID': orderProductID,
'productBrandId': productBrandId,
'orderUserID': orderUserID,
'item': item,
'quantity': quantity,
'price': price,
'totalPrice': totalPrice,
'discount': discount,
'paymentID': paymentID,
'paymentMode': paymentMode,
'date': date
}),
headers: {
"Accept": "application/json",
"content-type": "application/json"
});
var data = response.body;
if (response.statusCode == 200) {
return true;
} else
throw Exception();
}
}
It successfully retrieves the data and passes it on to the future post method, but the database is not updated. When the breakpoint hits the post method, it doesn't go through the code and doesn't get any status code. Thank you
Notice you are using Future in both your get() and post() methods, but, when calling these methods you are not using the "await" keyword. You should use it every time you call a Future function assuring you are waiting that method to complete and retrieve data successfully. It might work without it (as you say your get method works) but, in more complex situations this might not be the case due to asynchronous nature of these type of functions.
Your code should look like this:
await httpService.getPosts().then((value) async {
if (value != null) {
value.forEach((element) {
await httpServices.addPosts(
0,
element.cartProductID, element.productBrandId,
element.cartUserID, element.item,
element.quantity, element.price,
element.totalPrice,
element.discount,
// element.isOrdered,
element.paymentID,
element.paymentMode,
element.date,
);
});
Hope this works. Have a nice day!
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've fetched a json object and deserialized it and then returned it too.
I want to use this in another file.
I'm unable to assign the values that I'm getting in the first step.
Here are all the codes...
Service
Future getGeoPoints(String accessToken, String tripId) async {
String requestUrl;
var response = await get(
Uri.parse(requestUrl),
headers: {
'Authorization': "Bearer $accessToken",
},
);
if (response.statusCode == 200) {
Map<String, dynamic> responseBody = json.decode(response.body);
GetGeoPoints geoPoints = GetGeoPoints.fromJson(responseBody);
List listOfGeoPoints = [];
for (var geoPoint in geoPoints.geoPoints) {
listOfGeoPoints.add(
{
'latitude': geoPoint.latitude,
'longitude': geoPoint.longitude,
'timestamp': geoPoint.timeStamp,
},
);
}
// print('List of geo points: ' + '$listOfGeoPoints');
return listOfGeoPoints;
} else {
throw Exception('Failed to load data from server');
}
}
File where I need the above values
List routeCoordinates;
Future<void> getValues() async {
getGeoPoints(widget.accessToken, widget.tripId)
.then((value) => routeCoordinates = value);
}
When I run the app, routeCoordinates is null but when I hotreload, it contains the value.
I want to have the values as soon as the screen starts. What is the right way to assign the values here?
I've also tried this:
routeCoordinates = getGeoPoints...
It throws error..
Please help.. Thanks..
The function getGeoPoints() is an asynchronous one. But on the other file, you are not using the await keyword, instead you are using then(). So your code is not waiting for that function to return value.
Try using below code,
List routeCoordinates;
Future<void> getValues() async {
routeCoordinates = await getGeoPoints(widget.accessToken, widget.tripId);
}
Let us know how it went.
You need to use a FutureBuilder to define a behaviour depending on the state of the request. You'll be able to tell the widget what to return while your app is waiting for the response to your request. You can also return a specific widget if you get an error(if your user is offline, for example).
Edit: I've linked the official docs but give this article a read if it's not clear enough.
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.
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.