flutter error 403 in post request using dio - flutter

i have a problem with hate. I'm trying to login using dio, the login method works perfectly, but when I put invalid credentials dio gives me this error:
DioError
Error in execution
I created a boolean function that would return true or false if the statuscode was 200 it would return true and if not it would return false, but when logging in with the right credentials everything is ok, everything happens as it should, but when logging in with invalid credentials this error above causes it. I'm using shared preferences to store the tolken in the app, and the logic would be simple, if it was 200 I would log into the app, otherwise it would show me a snackbar I made in another file, this is my code:
loginFinal() async {
if (formKey.currentState!.validate()) {
bool loginIsOk = await loginConect();
if (loginIsOk) {
Get.offAllNamed("/home");
await Future.delayed(const Duration(seconds: 1));
message(MessageModel.info(
title: "Sucesso",
message: "Seja bem vindo(a) influenciador(a)",
));
} else {
loaderRx(false); //LOADER
message(MessageModel.error(
title: "Erro",
message: "Erro ao realizar login",
));
}
}
}
//LOGICA DE ENTRAR NO APP
Future<bool> loginConect() async {
final dio = Dio();
String baseUrl = "https://soller-api-staging.herokuapp.com";
loaderRx(true); //LOADER
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
final response = await dio.post(
baseUrl + "/auth",
data: jsonEncode(
{
"login": emailController.text,
"senha": passWordController.text,
},
),
options: Options(
headers: {'Content-Type': 'application/json; charset=UTF-8'},
method: "post",
),
);
if (response.statusCode == 200) {
await sharedPreferences.setString(
"token",
"string: ${response.data["string"]}",
);
print("Resposta: ${response.data["string"]}");
loaderRx(false);
return true;
} else {
print("RESPOSTA: ${response.data}");
return false;
}
}
}

Dio always throw an exception if the status code in the header is not 200,
you will need to catch the exception using try catch.
In the catch method, you can check if the type of the error is DioError and then handle that exception,
Here is a code snippet of a login process that I use in my code to handle this behavior.
Future<SignInApiResponse> signInUser(String _email,String _password) async {
try {
final dio = Dio(ApiConstants.headers());
final Response response = await dio.post(
ApiConstants.baseUrl + ApiConstants.signInUrl,
data: {"email": _email,
"password": _password,
},
);
if (response.statusCode == 200) {
return SignInApiResponse.fromJson(response.data);
} else {
return SignInApiResponse(message: response.toString());
}
} catch (e) {
if (e is DioError) {
if (e.response?.data == null) {
return SignInApiResponse(message: Messages.loginFailed);
}
return SignInApiResponse.fromJson(e.response?.data);
} else {
return SignInApiResponse(message: e.toString());
}
}
}
hopefully, this will help
if not you can always use http package that does not throw an exception in similer case

Related

how to redirect the user to the login page if the token has expired

hello I have a case where when the user token expires the user does not switch to the loginPage page, even though I have set it here.
how do i solve this problem thanks.
i set it on splashscreen if token is not null then go to main page and if token is null then go to login page.
but when the token expires it still remains on the main page
Future<void> toLogin() async {
Timer(
const Duration(seconds: 3),
() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String? token = prefs.getString(Constant.token);
Navigator.pushReplacementNamed(
context,
token != null ? AppRoute.mainRoute : AppRoute.loginRoute,
arguments: token,
);
},
);
}
and function when user login
CustomButtonFilled(
title: 'Login',
onPressed: () async {
final prefs =
await SharedPreferences.getInstance();
prefs.setString(Constant.token, '');
if (nimController.text.isEmpty ||
passwordController.text.isEmpty) {
showError('NIM/Password harus diisi');
} else {
setState(() {
isLoading = true;
});
User? user = await userProvider.login(
nimController.text,
passwordController.text);
setState(() {
isLoading = false;
});
if (user == null) {
showError('NIM/Password tidak sesuai!');
} else {
userProvider.user = user;
Navigator.pushNamedAndRemoveUntil(
context,
'/main',
(route) => false,
);
}
}
},
),
and this call api
Future<User?> login(String nim, String password) async {
String url = Constant.baseURL;
try {
var body = {
'username': nim,
'password': password,
};
var response = await http.post(
Uri.parse(
'$url/login_mhs',
),
body: body,
);
if (response.statusCode == 200) {
final token = jsonDecode(response.body)['data']['access_token'];
//Ini mulai nyimpen token
await UtilSharedPreferences.setToken(token);
print(token);
// print(await UtilSharedPreferences.getToken());
return User.fromJson(jsonDecode(response.body));
} else {
return null;
}
} catch (e) {
print(e);
throw Exception();
}
}
you can just make your own HTTP client using Dio and add Interceptor to automatically regenerate idToken if expired using the refreshToken given.
Http client gives an error if the refreshToken also gets expired.
In that case, just navigate to the login screen.
Full code for adding interceptor and making own HTTP client is given below
import 'package:dio/dio.dart';
import '../utils/shared_preference.dart';
class Api {
static Dio? _client;
static Dio clientInstance() {
if (_client == null) {
_client = Dio();
_client!.interceptors
.add(InterceptorsWrapper(onRequest: (options, handler) async {
if (!options.path.contains('http')) {
options.path = 'your-server' + options.path;
}
options.headers['Authorization'] =
'Bearer ${PreferenceUtils.getString('IdToken')}';
return handler.next(options);
}, onError: (DioError error, handler) async {
if ((error.response?.statusCode == 401 &&
error.response?.data['message'] == "Invalid JWT")) {
if (PreferenceUtils.exists('refreshToken')) {
await _refreshToken();
return handler.resolve(await _retry(error.requestOptions));
}
}
return handler.next(error);
}));
}
return _client!;
}
static Future<void> _refreshToken() async {
final refreshToken = PreferenceUtils.getString('refreshToken');
final response = await _client!
.post('/auth/refresh', data: {'refreshToken': refreshToken});
if (response.statusCode == 201) {
// successfully got the new access token
PreferenceUtils.setString('accessToken', response.data);
} else {
// refresh token is wrong so log out user.
PreferenceUtils.deleteAll();
}
}
static Future<Response<dynamic>> _retry(RequestOptions requestOptions) async {
final options = Options(
method: requestOptions.method,
headers: requestOptions.headers,
);
return _client!.request<dynamic>(requestOptions.path,
data: requestOptions.data,
queryParameters: requestOptions.queryParameters,
options: options);
}
}
Dio client = Api.clientInstance();
var resposne = (hit any request);
if(error in response is 401){
//it is sure that 401 is because of expired refresh token as we
//already handled idTokoen expiry case in 401 error while
//adding interceptor.
navigate to login screen for logging in again.
}
Please accept the solution if it solves your problem.
If your session expire feature has some predefine interval or logic than you have to implement it in splash screen and based on that you can navigate user further. Otherwise you want to handle it in API response only you have add condition for statusCode 401.
checkSessionExpire(BuildContext context)
if (response.statusCode == 200) {
//SuccessWork
} else if (response.statusCode == 401) {
//SessionExpire
} else {
return null
}
}

flutter Unhandled Exception: DioError [DioErrorType.response]: Http status error [422]

I have an API which sends status 201 in case of a success and if there's any error with the submitted data it sends status 422 (Unprocessable Entity) with a JSON response.
{
"message": "The given data was invalid.",
"errors": {
"mobile": [
"The selected mobile is invalid."
]
}}
I am using Dio to post user credentials (mobile, password) if I enter the correct user credential I can fetch data from it but when I enter the wrong credential gives me this error:
Unhandled Exception: DioError [DioErrorType.response]: Http status error [422]
Dio code
userLogin(
String password,
String mobile,
) async {
try {
String url = "url";
Dio dio = Dio();
dio.options.headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
};
var response = await dio.post(url, queryParameters: {
"password": password,
"mobile": mobile,
});
if (response.statusCode == 200) {
return response.data;
} catch (e) {
return e.toString();
}}
How I cloud handle error response and success?
If some of Http status codes in responses are approved then you could use validateStatus function in BaseOptions to make them valid for all dio requests.
Dio dio = Dio(
BaseOptions(
headers: {...},
validateStatus: (statusCode){
if(statusCode == null){
return false;
}
if(statusCode == 422){ // your http status code
return true;
}else{
return statusCode >= 200 && statusCode < 300;
}
},
)
);
or validateStatus function in Options of concrete request
var response = await dio.post(url,
queryParameters: {
"password": password,
"mobile": mobile,
},
options: Options(
responseType: ResponseType.json,
validateStatus: (statusCode){
if(statusCode == null){
return false;
}
if(statusCode == 422){ // your http status code
return true;
}else{
return statusCode >= 200 && statusCode < 300;
}
},
),
);
The catch method has to be added to the try. In your case it was added to if(response.statuscode ==200)
userLogin(
String password,
String mobile,
) async {
try {
String url = "url";
Dio dio = Dio();
dio.options.headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
};
var response = await dio.post(url, queryParameters: json.encode({
"password": password??"",
"mobile": mobile??"",
}));
if (response.statusCode == 200) {
return response.data;
}
else{
print(response.data);
return "request failed";
}
}catch (e) {
return e.toString();
}
My Api response was this enter image description here
I have dealt with this method by allowing it BaseOptions
here is my post API code and bingo got the solution to problem
Future postApiResponse(
String url, dynamic data, bool tokentrue, String? token) async {
dynamic responceJson;
try {
// print('here 222');
if (kDebugMode) {
print('In Dio in try');
print(url);
print(data.toString());
print(tokentrue.toString());
print(token.toString());
print('In Dio in try');
}
Dio dio = Dio(BaseOptions(validateStatus: (statusCode) {
if (statusCode == 422) {
return true;
}
if (statusCode == 200) {
return true;
}
return false;
}));
if (tokentrue == true) {
// dio.options.headers['content-Type'] = 'application/json';
dio.options.headers['Accept'] = 'application/json';
dio.options.headers["authorization"] = "Bearer $token";
} else {
dio.options.headers['Accept'] = 'application/json';
}
// print('responceJson.toString()');
Response responce = await dio
.post(
url,
data: data,
)
.timeout(const Duration(seconds: 20));
debugPrint('.toString()');
responceJson = returnResponce(responce);
debugPrint(responce.toString());
} on DioError catch (e) {
returnExceptionError(e);
}
return responceJson;
}
DioError [DioErrorType.response]: Http status error [422]
The Solution :)
Dio dio = Dio(
BaseOptions(
headers: {...},
validateStatus: (statusCode){
if(statusCode == null){
return false;
}
if(statusCode == 422){ // your http status code
return true;
}else{
return statusCode >= 200 && statusCode < 300;
}
},
)
);

How to show error if server is unreachable flutter

Am still pretty new to flutter. I have a network call to be executed. But before doing that I need to check whether the device have internet connectivity and that the server is api server is reachable. I have managed to check if the internet connectivity is available, but cant show an when server is not reachable
This is what i have done so far:
login(username, password) async {
final String url = "http://10.0.2.2:8080/api/auth/signin"; // iOS
var responseJson;
try {
final response= await http.post(
url,
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'username': username,
'password': password,
}),
);
responseJson = _response(response);
} on SocketException {
throw FetchDataException('No Internet connection');
}
print(responseJson);
SharedPreferences prefs = await SharedPreferences.getInstance();
var parse = jsonDecode(responseJson.body);
await prefs.setString('username', parse["username"]);
await prefs.setString('message', parse["message"]);
await prefs.setString('accessToken', parse["accessToken"]);
return responseJson;
}
dynamic _response(http.Response response) {
switch (response.statusCode) {
case 200:
var responseJson = json.decode(response.body.toString());
print(responseJson);
return responseJson;
case 400:
throw BadRequestException(response.body.toString());
case 401:
case 403:
throw UnauthorisedException(response.body.toString());
case 500:
throw FetchDataException(
'Error occured while Communication with Server with StatusCode : ${response
.statusCode}');
default:
throw FetchDataException(
'Error occured while Communication with Server with StatusCode : ${response
.statusCode}');
}
}
My login button function
RoundedButton(
text: "LOGIN",
press: () async {
if (_formKey.currentState.validate()) {
progressDialog.show();
await login(
username,
password,
);
SharedPreferences prefs =
await SharedPreferences.getInstance();
String token = prefs.getString("accessToken");
print(token);
if (token == null) {
progressDialog.hide();
showAlertsDialog(context);
} else {
showAlertzDialog(context);
}
}
},
)
Whenever I switch of the server and click on login, the app is stuck a progress bar showing signing in. How can I display an alert that there is no connection to the server?
This is how you can manage your API call.
Future<dynamic> requestGET({String url}) async {
try {
final response = await http.get(Uri.parse(url));
switch (response.statusCode) {
case 200:
case 201:
final result = jsonDecode(response.body);
final jsonResponse = {'success': true, 'response': result};
return jsonResponse;
case 400:
final result = jsonDecode(response.body);
final jsonResponse = {'success': false, 'response': result};
return jsonResponse;
case 401:
final jsonResponse = {
'success': false,
'response': ConstantUtil.UNAUTHORIZED
};
return jsonResponse;
case 500:
case 501:
case 502:
final jsonResponse = {
'success': false,
'response': ConstantUtil.SOMETHING_WRONG
};
return jsonResponse;
default:
final jsonResponse = {
'success': false,
'response': ConstantUtil.SOMETHING_WRONG
};
return jsonResponse;
}
} on SocketException {
final jsonResponse = {
'success': false,
'response': ConstantUtil.NO_INTERNET
};
return jsonResponse;
} on FormatException {
final jsonResponse = {
'success': false,
'response': ConstantUtil.BAD_RESPONSE
};
return jsonResponse;
} on HttpException {
final jsonResponse = {
'success': false,
'response': ConstantUtil.SOMETHING_WRONG //Server not responding
};
return jsonResponse;
}
}
Call this function and use response I'm calling it in init method of statefulWidget.
#override
void initState() {
// TODO: implement initState
super.initState();
final result = await requestGET('google.com');
if (result['success'] == false) {
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Error"),
content: Text(result['response']),
actions: [
FlatButton(
child: Text("OK"),
onPressed: () {
Navigator.pop(context);
},
),
],
);
;
},
);
}
}
I think you can check the response code from the api call using http code request from this link http status code
as you can check the response from json like this:
Future<String> checkServerResponse() await
{
http.Response response =
await http.get('server_link'):
print(response.statusCode);
}
now as you can see the response code of the server based on http status code.

sign in The method '_mulFromInteger' was called on null

I've got sign in methode in my provider.
Future<void> signIn(
String email, String password, BuildContext context) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
final clientID = "com.super.app";
final body = "username=$email&password=$password&grant_type=password";
final String clientCredentials =
const Base64Encoder().convert("$clientID:".codeUnits);
try {
final http.Response response =
await http.post("http://localhost:8888/auth",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": "Basic $clientCredentials"
},
body: body);
final jsonResponse = json.decode(response.body);
// if (jsonResponse["error"] != null) {
// throw HttpException(jsonResponse["error"]);
// }
_userId = 1;
_token = jsonResponse['access_token'];
_expiryDate = DateTime.now().add(
Duration(
seconds: jsonResponse['expires_in'],
),
);
_autoLogout();
notifyListeners();
final userData = json.encode(
{
'userId': 1,
'email': email,
'token': _token,
'expiryDate': _expiryDate.toIso8601String(),
},
);
sharedPreferences.setString('userData', userData);
} catch (error) {
print(error.toString()); //<-- misleading error
}
}
All works fine but when incorrect login credential are passed then I get misleading error
flutter: NoSuchMethodError: The method '_mulFromInteger' was called on null.
Receiver: null
Tried calling: _mulFromInteger(1000000)
The back end passing error code 400 and body {"error": "invalid client"} but I get that strange error as output. So what does that error means and why do I get that instead of body
As from #Suragch comments I had few problem in my code. First I thought that when server return 400 code then it will automatically throw an error and skip rest of the lines.. I was wrong so basically I had to uncomment my code for http exceptions and in my button catch the error
try {
await Provider.of<Auth>(context, listen: false).signIn(
emailController.text, passwordController.text);
} on HttpException catch (error) {
print(error.toString());
} catch (error) {
print(error);
}

HttpException occure when i post a request to a server as below

This method is to post an order to a server and it's in a Provider class :
Future<void> addOrder(OrderRequest orderRequest) async {
final prefs = await SharedPreferences.getInstance();
String accessToken = prefs.getString(Constants.prefsUserAccessTokenKey);
String url = Urls.addOrderUrl;
try {
var bodyParams = json.encode({
"Branch": {"Id": orderRequest.branchId},
"DeliveryAddress":
orderRequest.addressId == 0 ? {} : {"Id": orderRequest.addressId},
"InBranch": orderRequest.inBranch,
"TableNumber": orderRequest.tableNumber.toString(),
"OrderItems": orderRequest.items,
"PromoCode": orderRequest.promoCodeId == 0
? {}
: {"Id": orderRequest.promoCodeId}
});
print("Url: " + url);
print("Token: " + accessToken);
print("Params: " + bodyParams);
final response = await retry(
() => http
.post(url,
headers: {
"content-type": "application/json",
"Accept": "application/json",
"Authorization": "Bearer " + accessToken
},
body: json.encode(bodyParams))
.timeout(Duration(seconds: 5)),
retryIf: (e) => e is SocketException || e is TimeoutException);
final responseData = json.decode(response.body);
print(responseData);
if (response.statusCode == 200) {
} else if (response.statusCode == 401) {
throw AuthException("401", responseData['Message']);
} else {
throw HttpException(responseData['Message']);
}
} catch (error) {
print(error);
throw error;
}
}
and in my screen class i create a method to upload my data to the server which i use it when i press a button which handle the post request :
Future<void> _addOrder() async {
OrderRequest request = OrderRequest();
request.addressId = _selectedAddress.id;
request.branchId = int.parse(_selectedBranchId);
request.inBranch = _selectedAddress.id == 0;
request.items = _cartItemsList;
request.promoCodeId = _promoCodeId;
request.tableNumber = _tableNumber;
try {
setState(() {
_isLoading = true;
});
await Provider.of<OrderProvider>(context).addOrder(request);
Provider.of<CartProvider>(context).emptyCart();
_showDialog("Order Sent", "Your order is sent to restaurant.");
} on HttpException catch (error) {
_showDialog("Error adding order", error.message);
} on SocketException catch (_) {
_showDialog("Error adding order",
"Please check your internet connection and try again");
} on TimeoutException catch (_) {
_showDialog("Error adding order",
"Please check your internet connection and try again");
} on AuthException catch (_) {
_refreshToken();
} catch (error) {
print(error);
_showDialog("Error adding address", "Something went wrong");
}
}
but when i press a Order button t to send a post request to a server i got this error:
I/flutter (12421): {Message: Error:Object reference not set to an instance of an object.}
I/flutter (12421): HttpException: Error:Object reference not set to an instance of an object.
this is the model class that i use
class OrderRequest{
int branchId;
int addressId;
bool inBranch;
int promoCodeId;
int tableNumber;
List<CartItem> items;
OrderRequest(
{
this.branchId,
this.addressId,
this.inBranch,
this.promoCodeId,
this.tableNumber,
this.items});
}