FutureBuilder always return Instance of Future - flutter

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

Related

Flutter the getter 'amount' isn't defined for the class

I want to redirect user on details payment screen when payment is initiated and change state automatically when user confirm payment.
Detail screen :
child: FutureBuilder<Deposit?>(
future: AuthService.addDeposit(amount, product, phone, authProvider.token!),
builder: (BuildContext context, AsyncSnapshot<Deposit?> snapshot){
if(snapshot.hasData){
return _snapshotHasData(snapshot.data!);
}else if(snapshot.hasError){
return Text('${snapshot.error}');
}
return const LinearProgressIndicator();
},
)
Service:
static Future<Deposit?> addDeposit(String amount, String product, String phone, String token) async {
assert(token.isNotEmpty);
Response<String> response = await _dio.post(
'url',
data: <String, String>{
'amount': amount,
'product': product,
'phone': phone,
},
);
if(response.statusCode != 200){
throw Exception(response.statusMessage);
}
return Deposit.fromJson(jsonDecode(response.data ?? ''));
}
I put just one parameter for show you the widget
Widget _snapshotHasData(Deposit data){
return Text(data.amount);
}

How to properly make a api request in flutter?

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?

How to emit Bloc State based on retry request response?

I tried to implement a refresh token in my app, so every time I do request, dio will check the response, if the response status code is 401 then it will refresh the access token and retry the request
Future onError(DioError dioError, ErrorInterceptorHandler handler) async {
print(dioError.response!.statusCode);
int? responseCode = dioError.response!.statusCode;
String? oldAccessToken = _sharedPreferencesManager
.getString(SharedPreferencesManager.keyAccessToken);
if (oldAccessToken != null && responseCode == 401) {
_dio.interceptors.responseLock.lock();
_dio.interceptors.responseLock.lock();
String? refreshToken = _sharedPreferencesManager
.getString(SharedPreferencesManager.keyRefreshToken);
Map<String, dynamic> refreshTokenBody = {
'grant_type': 'refresh_token',
'refresh_token': refreshToken
};
ApiRepository apiAuthRepository = ApiRepository();
LoginModel token =
await apiAuthRepository.postRefreshAuth(refreshTokenBody);
if (token.success) {
String newAccessToken = token.data.accessToken;
String newRefreshToken = token.data.refreshToken;
await _sharedPreferencesManager.putString(
SharedPreferencesManager.keyAccessToken, newAccessToken);
await _sharedPreferencesManager.putString(
SharedPreferencesManager.keyRefreshToken, newRefreshToken);
}
RequestOptions options = dioError.response!.requestOptions;
options.headers.addAll({'requirestoken': true});
_dio.interceptors.requestLock.unlock();
_dio.interceptors.responseLock.unlock();
handler.next(dioError);
return _dio.request(options.path,
options: Options(headers: options.headers));
} else {
super.onError(dioError, handler);
}
}
The problem is in my Bloc, if I tried to access the API with the expired access token (If I tried using an active access token there is no problem), the dio works fine by refreshing and retrying the request, but my bloc only emits a Loading/Failure State based on the result of the first response, any idea how to solve it?
Future<void> mapEventToState(
Emitter<DashboardState> emit, DashboardEvent event) async {
emit(DashboardLoading());
DashboardModel getMe = await apiAuthRepository.getMeUser();
if (!getMe.success) {
emit(DashboardFailure(getMe.message));
}
emit(
DashboardSuccess(),
);
}
And here is how I implement the Bloc
BlocProvider<DashboardBloc>(
create: (_) => _dashboardBloc,
child: BlocListener<DashboardBloc, DashboardState>(
listener: (context, state) {
if (state is DashboardFailure)
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(state.error),
));
},
child: BlocBuilder<DashboardBloc, DashboardState>(
builder: (context, state) {
if (state is DashboardSuccess)
return ListView(..);
else if (state is DashboardLoading)
return Center(child: const CircularProgressIndicator());
else
print(state);
return Container();

How can i use the result from the first API call as input for the second API call?

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.

Building widget in Flutter when response statusCode on API call is >400

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.