I have a flutter application that uses graphql: ^5.0.0 to perform mutations and queries on my database and I'm trying to handle invalid token exceptions I get. When I get an invalid token error from my server, an error is thrown here.
Here is the error making its way up into my code
here is my code:
try {
final QueryResult result = await client.query(options);
List<dynamic> taskList = result.data!['userTasksConnections']['tasks'];
List<Task> tasks = [];
for(int i = 0; i < taskList.length; i++) {
tasks.add(Task.fromJson(taskList[i]));
}
return tasks;
} on HttpLinkServerException catch(e) {
if(e.parsedResponse?.errors?[0] == 'Invalid Token'){
await UserRepo().getAccessToken();
return getTasks(page: page, keyword: keyword);
}
else{
return [];
}
}
since the error is clearly of type HttpLinkServerException I have an on HttpLinkServerException catch(). However, when the code runs the exception is not caught in the catch block and the code continues after the result await as if nothing happened, causing a null data exception on this line
List<dynamic> taskList = result.data!['userTasksConnections']['tasks'];
You need to write your own parser to fix this issue. You do so with something like this
import 'package:graphql/client.dart';
class CustomResponseParser extends ResponseParser {
#override
Response parseResponse(Map<String, dynamic> body) {
Map<String, String> errors = new Map();
if(body["errors"] != null) {
errors['message'] = body["errors"][0];
}
Response res = Response(
errors: (body["errors"] as List?)
?.map(
(dynamic error) => parseError(errors),
)
.toList(),
data: body["data"] as Map<String, dynamic>?,
context: Context().withEntry(
ResponseExtensions(
body["extensions"],
),
),
);
return res;
}
#override
GraphQLError parseError(Map<String, dynamic> error) {
return GraphQLError(
message: error['message'],
);
}
}
And then you use it when initializing your graphqlClient like this
final GraphQLClient client = GraphQLClient(
cache: GraphQLCache(),
link: AuthLink(getToken: () {
if (store.state.auth.accessToken == '') {
return "";
} else {
return "Bearer ${store.state.auth.accessToken}";
}
}).concat(
HttpLink(
Environment().config.apiHost,
parser: CustomResponseParser()
)
)
);
Related
In the above error, I tried to get the parsedResponse and add handling for the statusCode, but I couldn't catch the error.
The linkException doesn't seem to recognize the parsedResponse. If you know a solution, please let me know.
thank you
Below is the graphql client I made and use using graphql_flutter
import 'package:firebase_auth/firebase_auth.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
// ignore: implementation_imports
import 'package:gql/src/ast/ast.dart';
import 'package:noling_app/config/parser.dart';
class MyGraphQLClient {
late GraphQLClient _client;
GraphQLClient get client => _client;
GraphQLClient setClient(String idToken) {
HttpLink _httpLink = HttpLink(
'http://localhost:5005/noling-develop/asia-northeast3/graphql',
parser: CustomResponseParser(),
defaultHeaders: {
'X-USER-TOKEN': idToken,
},
);
Link _link;
if (idToken != '' && idToken.isNotEmpty) {
final AuthLink authLink = AuthLink(
getToken: () => idToken,
headerKey: 'X-USER-TOKEN',
);
_link = authLink.concat(_httpLink);
} else {
_link = _httpLink;
}
_client = GraphQLClient(
cache: GraphQLCache(),
link: _link,
);
return _client;
}
Future<dynamic> query(
DocumentNode document, {
Map<String, dynamic>? data,
}) async {
try {
QueryResult result = await _client.query(QueryOptions(
document: document,
variables: data ?? {},
));
if (result.hasException) {
print(result);
var message = result.exception!.graphqlErrors.first.message;
throw Exception(message);
}
return result.data;
} catch (e) {
print("error catch ?");
rethrow;
}
}
Future<dynamic> mutate(
DocumentNode document, {
Map<String, dynamic>? data,
}) async {
var result = await _client.mutate(MutationOptions(
document: document,
variables: data ?? {},
));
if (result.hasException) {
var message = result.exception!.graphqlErrors.first.message;
throw GraphQLError(message: message);
}
return result.data;
}
}
MyGraphQLClient graphQLClient = MyGraphQLClient();
In the MyGraphQLClient class, I created and used a CustomResponseParser like the one I saw in another issue post, but to no avail.
class CustomResponseParser extends ResponseParser {
#override
Response parseResponse(Map<String, dynamic> body) {
Map<String, String> errors = new Map();
if (body["errors"] != null) {
errors['message'] = body["errors"][0];
}
Response res = Response(
response: body,
errors: (body["errors"] as List?)
?.map(
(dynamic error) => parseError(errors),
)
.toList(),
data: body["data"] as Map<String, dynamic>?,
context: const Context().withEntry(
ResponseExtensions(
body["extensions"],
),
),
);
print(res);
return res;
}
#override
GraphQLError parseError(Map<String, dynamic> error) {
return GraphQLError(
message: error['message'],
);
}
}
I'm implementing a flutter project using Getx library.
My current project has a format in which the response from the backend is recombined at the front end to the ui model and show on the screen. (Because the project's d-day is very close, so I could not change backend. I should just use a existing backend response).
Meanwhile, I was writing a code that some api call futures must keep the order.
To put it briefly, I receive a survey's result list and make a string list with survey's id (it is in the survey's result object).
After that, I receive a survey list and compare it to survey's result id list.
Through these courses, I will be able to know whether the survey has been completed or not from the survey list.
To implement this action, I declared two future, and I thought that the future was guaranteed the order by executing it with wait.
But nothing is changed in my view...
Below things are my codes.
class ExampleController extends GetxController {
final PreferenceManager _preferenceManager =
Get.find(tag: (PreferenceManager).toString());
/// ------------> related to survey values
final RxList<SurveyListUiModel> _rxSurveyListUiModelList = RxList.empty();
List<SurveyListUiModel> get surveyListUiModelList =>
_rxSurveyListUiModelList.toList();
final List<String> _surveyResultIdList = [];
void getSurveyConfigListWithSurveyResult() async{
Future<String> surveyResultListFuture =
_preferenceManager.getString('survey_result_list');
callDataService(
surveyResultListFuture,
onSuccess: _handleSurveyResultListResponseSuccess,
);
Future<String> surveyListFuture =
_preferenceManager.getString('survey_list');
await callDataService(
surveyConfigListFuture,
onSuccess: _handleSurveyListResponseSuccess,
);
setListLoading(false);
}
void _handleSurveyResultListResponseSuccess(String response) {
List<dynamic> list = jsonDecode(response) as List<dynamic>;
for (var element in list) {
SurveyConfigResponse surveyConfigResponse = SurveyConfigResponse.fromJson(
element['survey_config'] as Map<String, dynamic>);
_surveyResultIdList.add(surveyConfigResponse.id);
}
}
void _handleSurveyListResponseSuccess(String response) {
List<dynamic> list = jsonDecode(response) as List<dynamic>;
for (var element in list) {
SurveyConfigResponse surveyConfigResponse =
SurveyConfigResponse.fromJson(element as Map<String, dynamic>);
surveyListUiModelList.add(SurveyListUiModel(
surveyConfigId: surveyConfigResponse.id,
surveyConfigTitle: surveyConfigResponse.title,
isDiagnosed: _surveyResultIdList.contains(surveyConfigResponse.id),
));
_rxSurveyListUiModelList.refresh();
}
}
/// ------------> related to survey values
#override
void onInit() {
getSurveyConfigListWithSurveyResult();
super.onInit();
}
}
// callDataService method
dynamic callDataService<T>(
Future<T> future, {
Function(Exception exception)? onError,
Function(T response)? onSuccess,
Function? onStart,
Function? onComplete,
}) async {
Exception? _exception;
onStart == null ? showLoading() : onStart();
try {
final T response = await future;
if (onSuccess != null) onSuccess(response);
onComplete == null ? hideLoading() : onComplete();
return response;
} on ServiceUnavailableException catch (exception) {
_exception = exception;
showErrorMessage(exception.message);
} on UnauthorizedException catch (exception) {
_exception = exception;
showErrorMessage(exception.message);
} on TimeoutException catch (exception) {
_exception = exception;
showErrorMessage(exception.message);
} on NetworkException catch (exception) {
_exception = exception;
showErrorMessage(exception.message);
} on JsonFormatException catch (exception) {
_exception = exception;
showErrorMessage(exception.message);
} on NotFoundException catch (exception) {
_exception = exception;
showErrorMessage(exception.message);
} on ApiException catch (exception) {
_exception = exception;
} on AppException catch (exception) {
_exception = exception;
showErrorMessage(exception.message);
} catch (error) {
_exception = AppException(message: "$error");
logger.e("Controller>>>>>> error $error");
}
if (onError != null) onError(_exception);
onComplete == null ? hideLoading() : onComplete();
}
// example view
class ExampleView extends GetView<ExampleController> {
#override
Widget body(BuildContext context) {
return Obx(() => Text(controller.surveyListUiModelList.length.toString()));
}
}
What is the missing point in my codes..?
Edit 1
With obove code, the lists in the _handle~~~Success method has a right value.
A bit tricky to follow your code, but I think you should be awaiting the first call to callDataService(...)
I.e. this:
callDataService(
surveyLifeResultListFuture,
onSuccess: _handleSurveyResultListResponseSuccess,
);
should be:
await callDataService(
surveyLifeResultListFuture,
onSuccess: _handleSurveyResultListResponseSuccess,
);
Oh It's my mistake. The method callDataService is an already synchronized method...
So I did not have to consider asynchronization.
The order in which the code was written was applied in the order of execution.
A real problem was RxList.
In my question code, I writed a getter method of RxList like this.
List<SurveyListUiModel> get surveyListUiModelList => _rxSurveyListUiModelList.toList();
The toList() method is just extracting a Growable List from RxList.
it's code is here.
List<E> toList({bool growable = true}) {
if (this.isEmpty) return List<E>.empty(growable: growable);
var first = this[0];
var result = List<E>.filled(this.length, first, growable: growable);
for (int i = 1; i < this.length; i++) {
result[i] = this[i];
}
return result;
}
And, with that extracted list(copy the past state of RxList), I tried adding new items...
So, the obx widget in exampleView that is observing RxList did not response the past extracted list.
To solve this question, I changed my add items code with RxList keeping others in same.
before
void _handleSurveyConfigListResponseSuccess(String response) {
List<dynamic> list = jsonDecode(response) as List<dynamic>;
for (var element in list) {
SurveyConfigResponse surveyConfigResponse =
SurveyConfigResponse.fromJson(element as Map<String, dynamic>);
surveyListUiModelList.add(SurveyListUiModel(
surveyConfigId: surveyConfigResponse.id,
surveyConfigTitle: surveyConfigResponse.title,
isDiagnosed: _surveyResultIdList.contains(surveyConfigResponse.id),
));
}
_rxSurveyListUiModelList.refresh();
}
after
void _handleSurveyConfigListResponseSuccess(String response) {
List<dynamic> list = jsonDecode(response) as List<dynamic>;
for (var element in list) {
SurveyConfigResponse surveyConfigResponse =
SurveyConfigResponse.fromJson(element as Map<String, dynamic>);
_rxSurveyListUiModelList.add(SurveyListUiModel(
surveyConfigId: surveyConfigResponse.id,
surveyConfigTitle: surveyConfigResponse.title,
isDiagnosed: _surveyResultIdList.contains(surveyConfigResponse.id),
));
}
_rxSurveyListUiModelList.refresh();
}
I working on error handling of api's. i want if api is crashed then it display a message of "Server is down" something like this, in UI.
I created a class where i'm creating methods of api, here in getBooks method if i modify the api url then it is printing this Exception, and i want it in UI. The problem is getBooks return type is List<Book>> so we can't return this Exception, any solution how to do this?
Exception
E/flutter (12924): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: Exception
here is my api code
class BooksApi {
static Future<List<Book>> getBooks(String query) async {
try {
final url = Uri.parse(
'https://gist.githubusercontent.com/JohannesMilke/d53fbbe9a1b7e7ca2645db13b995dc6f/raw/eace0e20f86cdde3352b2d92f699b6e9dedd8c70/books.json');
final response = await http.get(url);
if (response.statusCode == 200) {
final List books = json.decode(response.body);
return books.map((json) => Book.fromJson(json)).where((book) {
final titleLower = book.title.toLowerCase();
final authorLower = book.author.toLowerCase();
final searchLower = query.toLowerCase();
return titleLower.contains(searchLower) ||
authorLower.contains(searchLower);
}).toList();
} else {
throw Exception;
}
} catch (e) {
print("e");
print(e);
}
throw Exception;
}
}
and calling it like
Future init() async {
setState(() {
isLoading = true;
});
var books = await BooksApi.getBooks(query); //this
var response = await obj.getProduct();
print(response);
setState(() => this.books = books);
setState(() {
isLoading = false;
});
}
You could handle errors with then and onError :
await BooksApi.getBooks(query).then((books) async {
setState(() => {
this.books = books;
this.isLoading = false;
})
}, onError: (error) {
// do something with error
});
or a simple try-catch (you can write try-catch clauses the same way you would in synchronous code).
See handling errors.
You can also use catchError id you don't use async/await :
BooksApi.getBooks(query).then((books) {
setState(() => {
this.books = books;
this.isLoading = false;
})
}).catchError((error, stackTrace) {
print("error is: $error");
});
See futures error handling.
Try to wrap 'var books = await BooksApi.getBooks(query)' with try and catch.
...
try {
var books = await BooksApi.getBooks(query);
} catch (e) {
// To do for UI
}
...
For api, you need to make something like this:
APIModel{
final int code;
// or a success flag
// final bool success;
final String message;
final List<Book> data;
APIModel({this.code,this.message,this.data});
}
It means, every api have its own code,message,and data filed.
When you request, you can check your code or success:
var response = await request(params);
isLoading = false;
if(response.code == 0){}
// or
if(response.success){
// do what you want
}
else {
Toast.show(response.message);
}
You can use build_runner and json_serializable.
In my flutter app, I have a future that handles http requests and returns the decoded data. But I want to be able to send an error if the status code != 200 that can be gotten with the .catchError() handler.
Heres the future:
Future<List> getEvents(String customerID) async {
var response = await http.get(
Uri.encodeFull(...)
);
if (response.statusCode == 200){
return jsonDecode(response.body);
}else{
// I want to return error here
}
}
and when I call this function, I want to be able to get the error like:
getEvents(customerID)
.then(
...
).catchError(
(error) => print(error)
);
Throwing an error/exception:
You can use either return or throw to throw an error or an exception.
Using return:
Future<void> foo() async {
if (someCondition) {
return Future.error('FooError');
}
}
Using throw:
Future<void> bar() async {
if (someCondition) {
throw Exception('BarException');
}
}
Catching the error/exception:
You can use either catchError or try-catch block to catch the error or the exception.
Using catchError:
foo().catchError(print);
Using try-catch:
try {
await bar();
} catch (e) {
print(e);
}
You can use throw :
Future<List> getEvents(String customerID) async {
var response = await http.get(
Uri.encodeFull(...)
);
if (response.statusCode == 200){
return jsonDecode(response.body);
}else{
// I want to return error here
throw("some arbitrary error"); // error thrown
}
}
Another way to solve this is by using the dartz package.
An example of how to use it would look something similar like this
import 'package:dartz/dartz.dart';
abstract class Failure {}
class ServerFailure extends Failure {}
class ResultFailure extends Failure {
final int statusCode;
const ResultFailure({required this.statusCode});
}
FutureOr<Either<Failure, List>> getEvents(String customerID) async {
try {
final response = await http.get(
Uri.encodeFull(...)
);
if (response.statusCode == 200) {
return Right(jsonDecode(response.body));
} else {
return Left(ResultFailure(statusCode: response.statusCode));
}
}
catch (e) {
return Left(ServerFailure());
}
}
main() async {
final result = await getEvents('customerId');
result.fold(
(l) => print('Some failure occurred'),
(r) => print('Success')
);
}
You can return the error data like this if you want to read the error object:
response = await dio.post(endPoint, data: data).catchError((error) {
return error.response;
});
return response;
//POST
Future<String> post_firebase_async({String? path , required Product product}) async {
final Uri _url = path == null ? currentUrl: Uri.https(_baseUrl, '/$path');
print('Sending a POST request at $_url');
final response = await http.post(_url, body: jsonEncode(product.toJson()));
if(response.statusCode == 200){
final result = jsonDecode(response.body) as Map<String,dynamic>;
return result['name'];
}
else{
//throw HttpException(message: 'Failed with ${response.statusCode}');
return Future.error("This is the error", StackTrace.fromString("This is its trace"));
}
}
Here is how to call:
final result = await _firebase.post_firebase_async(product: dummyProduct).
catchError((err){
print('huhu $err');
});
This is my exception class. Exception class has been implemented by the abstract exception class of flutter. Am I missing something?
class FetchDataException implements Exception {
final _message;
FetchDataException([this._message]);
String toString() {
if (_message == null) return "Exception";
return "Exception: $_message";
}
}
void loginUser(String email, String password) {
_data
.userLogin(email, password)
.then((user) => _view.onLoginComplete(user))
.catchError((onError) => {
print('error caught');
_view.onLoginError();
});
}
Future < User > userLogin(email, password) async {
Map body = {
'username': email,
'password': password
};
http.Response response = await http.post(apiUrl, body: body);
final responseBody = json.decode(response.body);
final statusCode = response.statusCode;
if (statusCode != HTTP_200_OK || responseBody == null) {
throw new FetchDataException(
"An error occured : [Status Code : $statusCode]");
}
return new User.fromMap(responseBody);
}
CatchError doesn't catch the error when the status is not 200. In short error caught is not printed.
Try
void loginUser(String email, String password) async {
try {
var user = await _data
.userLogin(email, password);
_view.onLoginComplete(user);
});
} on FetchDataException catch(e) {
print('error caught: $e');
_view.onLoginError();
}
}
catchError is sometimes a bit tricky to get right.
With async/await you can use try/catch like with sync code and it is usually much easier to get right.
Let's say this is your function which throws an exception:
Future<void> foo() async {
throw Exception('FooException');
}
You can either use try-catch block or catchError on the Future since both do the same thing.
Using try-catch
try {
await foo();
} on Exception catch (e) {
print(e); // Only catches an exception of type `Exception`.
} catch (e) {
print(e); // Catches all types of `Exception` and `Error`.
}
Use catchError
await foo().catchError(print);
I was trying to find this answer when got to this page, hope it helps: https://stackoverflow.com/a/57736915/12647239
Basicly i was just trying to catch an error message from a method, but i was calling
throw Exception("message")
And in "catchError" i was getting "Exception: message" instead of "message".
catchError(
(error) => print(error)
);
fixed with the return in the above reference
Future < User > userLogin(email, password) async { try {
Map body = {
'username': email,
'password': password
};
http.Response response = await http.post(apiUrl, body: body);
final responseBody = json.decode(response.body);
final statusCode = response.statusCode;
if (statusCode != HTTP_200_OK || responseBody == null) {
throw new FetchDataException(
"An error occured : [Status Code : $statusCode]");
}
return new User.fromMap(responseBody); }
catch (e){
print(e.toString());
}