How to return catch exception in flutter - flutter

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.

Related

Flutter Firestore Query snapshot- result is always null

I have a simple flutter code to retrieve some data from Firestore. the data is retireved correctly, however passing the data from the future function making the result always null. can you advise how to adapt the code to return the list?
that is the class where the actual query is happening:
class DatabaseManager {
final CollectionReference BusinessProfilesCollection =
FirebaseFirestore.instance.collection("BusinessProfilesCollection");
Future GetBusinessProfilesCollection() async {
List businessprofileslist = [];
try {
await BusinessProfilesCollection.get().then((QuerySnapshot) {
QuerySnapshot.docs.forEach((element) {
businessprofileslist.add(element.data());
print(businessprofileslist[0]);
});
});
} catch (e) {
print(e.toString());
return null;
}
}
}
here is the page where I am calling the function: (however the result is always null)
class _ProfilesListPageState extends State<ProfilesListPage> {
List businessprofileslist = [];
#override
void initState() {
super.initState();
fetchBusinessProfilesList();
}
fetchBusinessProfilesList() async {
dynamic result = await DatabaseManager().GetBusinessProfilesCollection();
print(result.toString());
if (result == null) {
print('enable to retieve');
} else {
print('success');
setState(() {
businessprofileslist = result;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold();
}
}
You're not returning anything from GetBusinessProfilesCollection but null, so the result seems somewhat expected.
I guess you want to do:
class DatabaseManager {
final CollectionReference BusinessProfilesCollection =
FirebaseFirestore.instance.collection("BusinessProfilesCollection");
Future GetBusinessProfilesCollection() async {
List businessprofileslist = [];
try {
var QuerySnapshot = await BusinessProfilesCollection.get();
querySnapshot.docs.forEach((element) {
businessprofileslist.add(element.data());
});
return businessprofileslist;
} catch (e) {
print(e.toString());
return null;
}
}
}
Btw: returning null when the load fails, is just going to lead to a null pointer exception when you then do print(result.toString());. So I recommend not catching the error and just letting it bubble up. With that your code can be simplified to:
class DatabaseManager {
final CollectionReference BusinessProfilesCollection =
FirebaseFirestore.instance.collection("BusinessProfilesCollection");
Future GetBusinessProfilesCollection() async {
var QuerySnapshot = await BusinessProfilesCollection.get();
return querySnapshot.docs.map((element) => element.data());
}
}
You just need to return the list
return businessprofileslist;
CODE :
class DatabaseManager {
final CollectionReference BusinessProfilesCollection =
FirebaseFirestore.instance.collection("BusinessProfilesCollection");
Future GetBusinessProfilesCollection() async {
List businessprofileslist = [];
try {
await BusinessProfilesCollection.get().then((QuerySnapshot) {
QuerySnapshot.docs.forEach((element) {
businessprofileslist.add(element.data());
print(businessprofileslist[0]);
});
// you just need to return the list here after filling it up
return businessprofileslist;
});
} catch (e) {
print(e.toString());
return null;
}
}
}
Code with a little improvement:
class DatabaseManager {
final CollectionReference BusinessProfilesCollection =
FirebaseFirestore.instance.collection("BusinessProfilesCollection");
Future GetBusinessProfilesCollection() async {
await BusinessProfilesCollection.get().then((QuerySnapshot) {
QuerySnapshot.docs.map((doc) => doc.data()).toList();
});
}
}
Try that with calling the function in feching
fetchBusinessProfilesList()
async {
dynamic result ;
await DatabaseManager().GetBusinessProfilesCollection().then((value){
result=value;
print(result.toString());
if (result == null) {
print('enable to retieve');
} else {
print('success');
setState(() {
businessprofileslist = result;
});
}
});
}

try catch on HttpLinkServerException doesn't catch error

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()
)
)
);

Dart the operator [] isn't defined for 'Future<Map<String,Object>> Function(String)'

I'm trying to set up Auth0 for my Flutter app from this demo, but I ran into this issue. I've only been using Flutter for a little while, and I fixed a few errors, but can't fix this one. I checked for other answers here, and tried the documentation too. The error message is;
The operator [] isn't defined for 'Future<Map<String,Object>> Function(String)'
This is the method with the error;
Future<void> loginAction() async {
setState(() {
isBusy = true;
errorMessage = '';
});
try {
final AuthorizationTokenResponse? result =
await appAuth.authorizeAndExchangeCode(
AuthorizationTokenRequest(
AUTH0_CLIENT_ID,
AUTH0_REDIRECT_URI,
issuer: 'https://$AUTH0_DOMAIN',
scopes: ['openid', 'profile', 'offline_access'],
promptValues: ['login']
),
);
Map<String, Object> parseIdToken(String idToken) {
final parts = idToken.split(r'.');
assert(parts.length == 3);
return jsonDecode(
utf8.decode(base64Url.decode(base64Url.normalize(parts[1]))));
}
Future<Map<String, Object>> getUserDetails(String accessToken) async {
final url = 'https://$AUTH0_DOMAIN/userinfo';
final response = await http.get(Uri.parse(
url,
));
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else {
throw Exception('Failed to get user details');
}
}
await secureStorage.write(
key: 'refresh_token', value: result!.refreshToken);
setState(() {
isBusy = false;
isLoggedIn = true;
name = parseIdToken['name'];
picture = getUserDetails['picture'];
});
} catch (e, s) {
print('login error: $e - stack: $s');
setState(() {
isBusy = false;
isLoggedIn = false;
errorMessage = e.toString();
});
}
}
And the lines that are erroring are;
name = parseIdToken['name'];
picture = getUserDetails['picture'];
Can this be fixed?
getUserDetails is a function, and so it doesn't have a [] operator.
Instead, try: var userDetails = await getUserDetails("token..."); picture = userDetails["picture"];

Display Loading spinner waitint for request to complete while using provider package

I am using a provider package. I want to display a loading spinner while waiting for a request to complete. The pattern below is too verbose. Please help me make it less verbose. Here is my code
class APIService with ChangeNotifier {
// Check for working API backend
bool isWorking = false;
bool isLoading = false;
set _isLoading(bool value) {
isLoading = value; <--
notifyListeners();
}
Future<bool> selectAPI(String input) async {
_isLoading = true; <-- 1
final uri = Uri.tryParse('https://$input$url')!;
final response = await http.get(uri);
if (response.statusCode == 200) {
final body = jsonDecode(response.body) as Map<String, dynamic>;
bool isTrue = body['info']['title'] == 'SamFetch';
_isLoading = false; <-- 2
notifyListeners();
return isWorking = isTrue;
}
_isLoading = false; <-- 3
throw response;
}
}
Here is my UI code
IconButton(
icon: apiService.isLoading
? CircularProgressIndicator()
: Icon(Icons.done),
onPressed: () async {
await addAPI(apiService, cache);
}),
}
Below is addAPI() method
Future<void> addAPI(APIService apiService, Cache cache) async {
if (api != null) {
try {
await apiService.selectAPI(api!);
if (apiService.isWorking) {
await cache.saveAppName(api!);
}
} on SocketException catch (e) {
print(e);
} catch (e) {
await cache.clearCache();
}
}
}
Is setState the final solution?
You can use Future Builder and set your Future Function in future attribute. You can control the visible widget based on the status of your function. So you dont have to use isloading variable.

How do I return error from a Future in dart?

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