Skip to content

Instantly share code, notes, and snippets.

@orestesgaolin
Last active April 28, 2024 05:00
Show Gist options
  • Save orestesgaolin/69c112893532e1dd0e8fe01cb07ffc86 to your computer and use it in GitHub Desktop.
Save orestesgaolin/69c112893532e1dd0e8fe01cb07ffc86 to your computer and use it in GitHub Desktop.
How to upload file to S3 with http library and progress updates (Flutter/Dart)
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:meta/meta.dart';
import 'package:mime/mime.dart';
import 'package:path/path.dart';
import 'package:http/http.dart' as http;
abstract class ApiClient {
Future<String> getPresignedUrl(File file, String space);
Future<bool> uploadFile(
File file,
String url,
ProgressUpdate onProgressUpdate,
);
}
class HttpApiClient implements ApiClient {
HttpApiClient({@required this.tokenProvider})
: assert(tokenProvider != null) {
}
final TokenProvider tokenProvider;
static const presignUrl = '';
@override
Future<bool> uploadFile(
File image,
String url,
ProgressUpdate onProgressUpdate,
) async {
try {
final mime = lookupMimeType(image.path);
final uri = Uri.parse(url);
var request = MultipartRequest(
'PUT',
uri,
onProgress: (int bytes, int total) {
onProgressUpdate?.call(bytes / total);
},
);
request.headers.addAll({
'Content-Type': mime,
});
request.files.add(
await http.MultipartFile.fromPath(basename(image.path), image.path),
);
final response = await request.send();
if (response.statusCode >= 200 && response.statusCode < 300) {
return true;
} else {
return false;
}
} on http.ClientException catch (e) {
logger.e('Error while sending file to API, ${e.message}');
throw NetworkException(e.message);
} on SocketException catch (e) {
logger.e('Error while sending file to API', e);
throw NetworkException(e.message);
} catch (e) {
logger.e('Error while sending file to API', e);
return false;
}
}
@override
Future<String> getPresignedUrl(
File image,
String space,
) async {
try {
if (space == null || space.isEmpty) {
throw ArgumentError('Missing space');
}
final token = await tokenProvider.token;
final name = basename(image.path);
final mime = lookupMimeType(image.path);
final uri = Uri.parse(presignUrl);
final request = http.Request('POST', uri);
request.body = jsonEncode({
'contentType': mime,
'filename': '$name',
'space': '$space',
});
request.headers.addAll({
HttpHeaders.authorizationHeader: 'Bearer $token',
'content-type': 'application/json',
});
final presignResult = await request.send();
if (presignResult.statusCode == HttpStatus.accepted) {
final stream = await presignResult.stream.bytesToString();
final data = jsonDecode(stream);
return data['location'];
} else {
logger.e(
'Error while generating presigned url: ${presignResult.reasonPhrase}',
);
return null;
}
} on ArgumentError {
rethrow;
} on SocketException catch (e) {
logger.e('Error while generating presigned url', e);
throw NetworkException(e.message);
} on Exception catch (e) {
logger.e('Error while generating presigned url', e);
return null;
}
}
}
class MultipartRequest extends http.MultipartRequest {
/// Creates a new [MultipartRequest].
MultipartRequest(
String method,
Uri url, {
this.onProgress,
}) : super(method, url);
final void Function(int bytes, int totalBytes) onProgress;
/// Freezes all mutable fields and returns a
/// single-subscription [http.ByteStream]
/// that will emit the request body.
@override
http.ByteStream finalize() {
final byteStream = super.finalize();
if (onProgress == null) return byteStream;
final total = contentLength;
var bytes = 0;
final t = StreamTransformer.fromHandlers(
handleData: (List<int> data, EventSink<List<int>> sink) {
bytes += data.length;
onProgress?.call(bytes, total);
sink.add(data);
},
);
final stream = byteStream.transform(t);
return http.ByteStream(stream);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment