diff --git a/lib/api/client.dart b/lib/api/client.dart index f347d38..fca43e5 100644 --- a/lib/api/client.dart +++ b/lib/api/client.dart @@ -53,8 +53,15 @@ class Client { dynamic _handleResponse(http.Response response) { if (response.statusCode < 200 || - response.statusCode > 400 || + response.statusCode >= 400 || json == null) { + if (response.statusCode ~/ 100 == 4) { + Map error = _decoder.convert(response.body); + throw new InvalidRequestApiException( + response.statusCode, + response.request.url.toString(), + error["message"] ?? "Unknown Error"); + } throw new ApiException( response.statusCode, response.request.url.toString()); } @@ -62,6 +69,17 @@ class Client { } } +class InvalidRequestApiException extends ApiException { + final String message; + InvalidRequestApiException(int errorCode, String path, this.message) + : super(errorCode, path); + + @override + String toString() { + return this.message; + } +} + class ApiException implements Exception { final int errorCode; final String path; diff --git a/lib/components/ErrorDialog.dart b/lib/components/ErrorDialog.dart new file mode 100644 index 0000000..640c3c1 --- /dev/null +++ b/lib/components/ErrorDialog.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +class ErrorDialog extends StatelessWidget { + final dynamic error; + + ErrorDialog({this.error}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + content: Text(error.toString()), + actions: [ + FlatButton( + child: Text('Close'), + onPressed: () => Navigator.of(context).maybePop(), + ) + ], + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 5775d04..440a37a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:sentry/sentry.dart'; import 'package:vikunja_app/constants.dart'; @@ -9,6 +10,11 @@ import 'package:vikunja_app/pages/user/login.dart'; import 'package:vikunja_app/theme/theme.dart'; void main() { + if (!kReleaseMode) { + // only log errors in release mode + _startApp(); + return; + } var sentry = new SentryClient(dsn: SENTRY_DSN); FlutterError.onError = (details, {bool forceReport = false}) { @@ -26,9 +32,7 @@ void main() { }; runZoned( - () => runApp(VikunjaGlobal( - child: new VikunjaApp(home: HomePage()), - login: new VikunjaApp(home: LoginPage()))), + _startApp, onError: (Object error, StackTrace stackTrace) { try { sentry.captureException( @@ -44,6 +48,10 @@ void main() { ); } +_startApp() => runApp(VikunjaGlobal( + child: new VikunjaApp(home: HomePage()), + login: new VikunjaApp(home: LoginPage()))); + class VikunjaApp extends StatelessWidget { final Widget home; diff --git a/lib/pages/home.dart b/lib/pages/home.dart index eeeb4b3..0879f16 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:after_layout/after_layout.dart'; import 'package:vikunja_app/components/AddDialog.dart'; +import 'package:vikunja_app/components/ErrorDialog.dart'; import 'package:vikunja_app/pages/namespace/namespace.dart'; import 'package:vikunja_app/pages/namespace/namespace_edit.dart'; import 'package:vikunja_app/pages/placeholder.dart'; @@ -162,7 +163,8 @@ class HomePageState extends State with AfterLayoutMixin { Scaffold.of(context).showSnackBar(SnackBar( content: Text('The namespace was created successfully!'), )); - }); + }).catchError((error) => showDialog( + context: context, builder: (context) => ErrorDialog(error: error))); } Future _loadNamespaces() {