handle exceptions by throwing, not with results
This commit is contained in:
87
lib/api.dart
87
lib/api.dart
@@ -5,7 +5,6 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path_provider/path_provider.dart'; // For cache storage
|
||||
import 'package:sheetless/utility.dart';
|
||||
|
||||
import 'sheet.dart';
|
||||
|
||||
@@ -16,9 +15,8 @@ class ApiClient {
|
||||
|
||||
ApiClient({required this.baseUrl, this.token});
|
||||
|
||||
Future<Result<void>> login(String username, String password) async {
|
||||
Future<void> login(String username, String password) async {
|
||||
log.info("Logging in...");
|
||||
try {
|
||||
final url = '$baseUrl/login';
|
||||
final response = await http.post(
|
||||
Uri.parse(url),
|
||||
@@ -29,19 +27,11 @@ class ApiClient {
|
||||
if (response.statusCode == 200) {
|
||||
token = jsonDecode(response.body);
|
||||
log.info('Login successful');
|
||||
return Result.ok(null);
|
||||
} else {
|
||||
log.warning('Login failed: ${response.statusCode}, ${response.body}');
|
||||
return Result.error(
|
||||
Exception(
|
||||
"Response code ${response.statusCode}\nResponse: ${response.body}",
|
||||
),
|
||||
throw Exception(
|
||||
"Failed logging in: Response code ${response.statusCode}\nResponse: ${response.body}",
|
||||
);
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
log.warning('Error during login', e);
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
void logout() {
|
||||
@@ -49,8 +39,11 @@ class ApiClient {
|
||||
log.info('Logged out successfully.');
|
||||
}
|
||||
|
||||
Future<http.Response?> get(String endpoint, {bool isBinary = false}) async {
|
||||
try {
|
||||
Future<http.Response> get(
|
||||
String endpoint, {
|
||||
bool isBinary = false,
|
||||
bool throwExceptionIfStatusCodeNot200 = false,
|
||||
}) async {
|
||||
final url = '$baseUrl$endpoint';
|
||||
final headers = {
|
||||
'Authorization': 'Bearer $token',
|
||||
@@ -59,17 +52,13 @@ class ApiClient {
|
||||
|
||||
final response = await http.get(Uri.parse(url), headers: headers);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
if (!throwExceptionIfStatusCodeNot200 || response.statusCode == 200) {
|
||||
return response;
|
||||
} else {
|
||||
log.warning(
|
||||
throw Exception(
|
||||
'GET request failed: ${response.statusCode} ${response.body}',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
log.warning('Error during GET request', e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<http.Response?> post(
|
||||
@@ -130,52 +119,29 @@ class ApiClient {
|
||||
}
|
||||
|
||||
Future<List<Sheet>> fetchSheets() async {
|
||||
try {
|
||||
final response = await get("/list/sheets");
|
||||
final response = await get(
|
||||
"/list/sheets",
|
||||
throwExceptionIfStatusCodeNot200: true,
|
||||
);
|
||||
|
||||
if (response == null) {
|
||||
return List.empty();
|
||||
}
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body);
|
||||
return (data as List<dynamic>)
|
||||
.map((sheet) => Sheet.fromJson(sheet as Map<String, dynamic>))
|
||||
.toList();
|
||||
} else {
|
||||
log.warning(
|
||||
'Failed to fetch sheets with status: ${response.statusCode}',
|
||||
}
|
||||
|
||||
Future<Uint8List> fetchPdfFileData(String sheetUuid) async {
|
||||
final response = await get(
|
||||
'/sheet/pdf/$sheetUuid',
|
||||
isBinary: true,
|
||||
throwExceptionIfStatusCodeNot200: true,
|
||||
);
|
||||
log.info('Response: ${response.body}');
|
||||
}
|
||||
} catch (e) {
|
||||
log.warning('Error during fetching sheets', e);
|
||||
}
|
||||
|
||||
return List.empty();
|
||||
}
|
||||
|
||||
Future<Uint8List?> fetchPdfFileData(String sheetUuid) async {
|
||||
try {
|
||||
final response = await get('/sheet/pdf/$sheetUuid', isBinary: true);
|
||||
|
||||
if (response != null && response.statusCode == 200) {
|
||||
log.info("PDF downloaded");
|
||||
return response.bodyBytes;
|
||||
} else {
|
||||
log.warning(
|
||||
"Failed to fetch PDF from API. Status: ${response?.statusCode}",
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
log.warning("Error fetching PDF", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<File?> getPdfFileCached(String sheetUuid) async {
|
||||
try {
|
||||
Future<File> getPdfFileCached(String sheetUuid) async {
|
||||
final cacheDir = await getTemporaryDirectory();
|
||||
final cachedPdfPath = '${cacheDir.path}/$sheetUuid.pdf';
|
||||
|
||||
@@ -186,15 +152,8 @@ class ApiClient {
|
||||
}
|
||||
|
||||
final pdfFileData = await fetchPdfFileData(sheetUuid);
|
||||
await cachedFile.writeAsBytes(
|
||||
pdfFileData!,
|
||||
); // TODO: proper error handling
|
||||
await cachedFile.writeAsBytes(pdfFileData);
|
||||
log.info("PDF cached at: $cachedPdfPath");
|
||||
return cachedFile;
|
||||
} catch (e) {
|
||||
log.warning("Error fetching PDF", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
},
|
||||
);
|
||||
} else if (snapshot.hasError) {
|
||||
log.warning("Error loading sheets:", snapshot.error);
|
||||
return Center(
|
||||
child: Text(
|
||||
style: Theme.of(
|
||||
|
||||
@@ -63,15 +63,16 @@ class _LoginPageState extends State<LoginPage> {
|
||||
_error = null;
|
||||
});
|
||||
final apiClient = ApiClient(baseUrl: "$serverUrl/api");
|
||||
final loginResult = await apiClient.login(email, password);
|
||||
if (loginResult.isOk()) {
|
||||
try {
|
||||
await apiClient.login(email, password);
|
||||
|
||||
await _storageHelper.writeSecure(SecureStorageKey.url, serverUrl);
|
||||
await _storageHelper.writeSecure(SecureStorageKey.jwt, apiClient.token!);
|
||||
await _storageHelper.writeSecure(SecureStorageKey.email, email);
|
||||
await _navigateToMainPage();
|
||||
} else {
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_error = "Login failed.\n${loginResult.error()}";
|
||||
_error = "Login failed.\n$e";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,17 +60,11 @@ class _SheetViewerPageState extends State<SheetViewerPage>
|
||||
|
||||
Future<bool> loadPdf() async {
|
||||
if (kIsWeb) {
|
||||
var data = await widget.apiClient.fetchPdfFileData(widget.sheet.uuid);
|
||||
if (data == null) {
|
||||
throw Exception("Failed fetching pdf file");
|
||||
}
|
||||
final data = await widget.apiClient.fetchPdfFileData(widget.sheet.uuid);
|
||||
|
||||
document = await PdfDocument.openData(data);
|
||||
} else {
|
||||
var file = await widget.apiClient.getPdfFileCached(widget.sheet.uuid);
|
||||
if (file == null) {
|
||||
throw Exception("Failed fetching pdf file");
|
||||
}
|
||||
final file = await widget.apiClient.getPdfFileCached(widget.sheet.uuid);
|
||||
|
||||
document = await PdfDocument.openFile(file.path);
|
||||
}
|
||||
@@ -181,6 +175,7 @@ class _SheetViewerPageState extends State<SheetViewerPage>
|
||||
],
|
||||
);
|
||||
} else if (snapshot.hasError) {
|
||||
log.warning("Error loading pdf:", snapshot.error);
|
||||
return Center(
|
||||
child: Text(
|
||||
style: Theme.of(
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
/// Utility class that simplifies handling errors.
|
||||
///
|
||||
/// Return a [Result] from a function to indicate success or failure.
|
||||
///
|
||||
/// A [Result] is either an [Ok] with a value of type [T]
|
||||
/// or an [Err] with an [Exception].
|
||||
///
|
||||
/// Use [Result.ok] to create a successful result with a value of type [T].
|
||||
/// Use [Result.error] to create an error result with an [Exception].
|
||||
///
|
||||
/// Evaluate the result using a switch statement:
|
||||
/// ```dart
|
||||
/// switch (result) {
|
||||
/// case Ok(): {
|
||||
/// print(result.value);
|
||||
/// }
|
||||
/// case Error(): {
|
||||
/// print(result.error);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
sealed class Result<T> {
|
||||
const Result();
|
||||
|
||||
/// Creates a successful [Result], completed with the specified [value].
|
||||
const factory Result.ok(T value) = Ok._;
|
||||
|
||||
/// Creates an error [Result], completed with the specified [error].
|
||||
const factory Result.error(Exception error) = Err._;
|
||||
|
||||
bool isOk() {
|
||||
return this is Ok;
|
||||
}
|
||||
|
||||
T value() {
|
||||
Ok<T> ok = this as Ok<T>;
|
||||
return ok._value;
|
||||
}
|
||||
|
||||
bool isErr() {
|
||||
return this is Err;
|
||||
}
|
||||
|
||||
Exception error() {
|
||||
Err<T> err = this as Err<T>;
|
||||
return err._error;
|
||||
}
|
||||
}
|
||||
|
||||
/// A successful [Result] with a returned [value].
|
||||
final class Ok<T> extends Result<T> {
|
||||
const Ok._(this._value);
|
||||
|
||||
/// The returned value of this result.
|
||||
final T _value;
|
||||
|
||||
@override
|
||||
String toString() => 'Result<$T>.ok($_value)';
|
||||
}
|
||||
|
||||
/// An error [Result] with a resulting [error].
|
||||
final class Err<T> extends Result<T> {
|
||||
const Err._(this._error);
|
||||
|
||||
/// The resulting error of this result.
|
||||
final Exception _error;
|
||||
|
||||
@override
|
||||
String toString() => 'Result<$T>.error($_error)';
|
||||
}
|
||||
Reference in New Issue
Block a user