How to save image correctly that's being uploaded to Flutter Android app? - flutter

First, I created a sample HttpServer console app using shelf-related packages to make sure I have the correct code to handle the image upload URI handler.
The console app was able to receive and save the image correctly then I tried the same code to Flutter Android app with only one minor difference which is the location of the saved image file.
Here's the code:
import 'package:shelf_router/shelf_router.dart' as srouter;
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:mime/mime.dart';
import 'package:path_provider/path_provider.dart' as prod;
var app = srouter.Router();
final directory = await prod.getExternalStorageDirectory();
app.post('/upload', (Request request) async {
String? boundary = request.headers['content-type'];
final payload = await request.read();
final boundWord = 'boundary=';
if (boundary != null) {
var boundIndex = boundary.indexOf(boundWord);
boundary = boundary.substring(boundIndex + boundWord.length);
final transformer = MimeMultipartTransformer(boundary);
final parts = await transformer.bind(payload).toList();
for (var part in parts) {
final content = await part.toList();
await File('${directory?.path}/newImgFile.png').writeAsBytes(content[0]);
}
}
return Response.ok('Upload done');
});
await io.serve(app, '0.0.0.0', 8080);
When I ran this and I send an image file using curl, it seems that it does everything correctly but when I checked the saved newImgFile, it's incorrect.
The file size doesn't match at all. I had my test image is in 900 KB but it was saved with much less than that (I think it was 30 KB).
I did notice one thing that's different on the content after this code line:
final content = await part.toList();
With the same image upload, content is a list of only 1 item for HttpServer console app but for HttpServer Flutter Android app, content is a list with 2 items.
I don't know if that's going to help in solving this issue but that's a difference that I am noticing.

I think the most useful conceptual model of MIME multipart uploads is:
any number of parts, in any order, each comprised of
any number of chunks, in consecutive order
Based on what you've described, you are receiving multiple chunks for at least one of the parts, which you will need to deal with. There is another issue with your code that may crop up, and that is the assumption that the part you care about is last (eg, you may have multiple parts, but you are overwriting the file with the last one anyway).
Putting them both together, you could do something like the following:
import 'package:http_parser/http_parser.dart';
...
app.post('/upload', (Request request) async {
final contentType = request.headers['content-type'];
if (contentType == null) {
return Response(400, body: 'Missing content-type');
}
final mediaType = MediaType.parse(contentType);
if (mediaType.mimeType != 'multipart/form-data') {
return Response(400, body: 'Invalid content-type');
}
final boundary = mediaType.parameters['boundary'];
if (boundary == null) {
return Response(400, body: 'Missing boundary');
}
final payload = request.read();
final parts = await MimeMultipartTransformer(boundary).bind(payload).toList();
for (final part in parts) {
if (part.headers['content-type'] != 'image/png') {
continue;
}
final file = File('${directory?.path}/newImgFile.png');
if (await file.exists()) {
await file.delete();
}
final chunks = await part.toList();
for (final chunk in chunks) {
await file.writeAsBytes(chunk, mode: FileMode.append);
}
return Response.ok('Upload done');
}
return Response(400, body: 'No good parts');
});
which will:
check whether the request Content-Type is valid (using MediaType)
find the first part that has the appropriate image/png content type
delete the destination file if it already exists
append each chunk to the newly created destination file
Going further, you could take advantage of the fact that both MimeMultipartTransformer.bind() and MimeMultipart implement Stream and do something like the following:
app.post('/upload', (Request request) async {
final contentType = request.headers['content-type'];
if (contentType == null) {
return Response(400, body: 'Missing content-type');
}
final mediaType = MediaType.parse(contentType);
if (mediaType.mimeType != 'multipart/form-data') {
return Response(400, body: 'Invalid content-type');
}
final boundary = mediaType.parameters['boundary'];
if (boundary == null) {
return Response(400, body: 'Missing boundary');
}
final payload = request.read();
final parts = MimeMultipartTransformer(boundary).bind(payload).where((part) {
return part.headers['content-type'] == 'image/png';
});
final partsIterator = StreamIterator(parts);
while (await partsIterator.moveNext()) {
final part = partsIterator.current;
final file = File('${directory?.path}/newImgFile.png');
if (await file.exists()) {
await file.delete();
}
final chunksIterator = StreamIterator(part);
while (await chunksIterator.moveNext()) {
final chunk = chunksIterator.current;
await file.writeAsBytes(chunk, mode: FileMode.append);
}
return Response.ok('Upload done');
}
return Response(400, body: 'No good parts');
});
which:
does some filtering upfront to ensure only image/png parts are considered, and
uses StreamIterator to allow iterating over the stream with await

Related

How to pass header to URL in Flutter

I have a question regarding how to view a PDF from URL.
I’m using flutter_pdfview library and I try to get a PDF from an URL and to view it in my Flutter app.
The problem is that my URL can be accessed ONLY with a token (session ID/header), but I don’t know how to pass it because is not working on the way I do it at the moment.
Here is an example of how the owner of the flutter_pdfview library is getting the PDF from an URL (without a Header): https://github.com/endigo/flutter_pdfview/blob/master/example/lib/main.dart#L49
And here is my code where I don’t know how else to pass the header than like this:
Future<File> createFileOfPdfUrl() async {
Completer<File> completer = Completer();
if (kDebugMode) {
print("Start download file from internet!");
}
try {
String url =
"$customURL.pdf";
if (kDebugMode) {
print("url: $url");
}
final filename = url.substring(url.lastIndexOf("/") + 1);
var client = HttpClient();
HttpClientRequest request = await client.getUrl(Uri.parse(url));
request.headers.add(
HttpHeaders.acceptHeader,
HeaderValue(
"text/plain", {'APPAUTH': '${widget.authService.loginToken}'})); // this method doesn't seems to work for me. I'm getting an empty PDF.
var response = await request.close();
var bytes = await consolidateHttpClientResponseBytes(response);
var dir = await getApplicationDocumentsDirectory();
if (kDebugMode) {
print("Download files");
print("${dir.path}/$filename");
}
File file = File("${dir.path}/$filename");
await file.writeAsBytes(bytes, flush: true);
completer.complete(file);
} catch (e) {
throw Exception('Error parsing asset file!');
}
return completer.future;
}
DO NOT do this:
request.headers.add(
HttpHeaders.acceptHeader, // here is the problem
HeaderValue(
"text/plain", {'APPAUTH': '${widget.authService.loginToken}'}));
SOLUTION for me:
request.headers.add("APPAUTH", "12345abcde67890defgh");
For some reason if you provide a HeaderValue you also need to provide a string value before it, which can be HttpHeaders.acceptHeader or HttpHeaders.serverHeader etc. I tried a lot of them from that enum list and none worked for me so I used the above solution where you don't need to pass that HttpHeader value type.

How can I fetch and use data with this design in flutter

I have this design, I created it and put the data manually, the question is how can I get the data in the image and it is from this website (https://jsonplaceholder.typicode.com/posts) and display it in the same design
var url = Uri.parse("https://jsonplaceholder.typicode.com/posts");
var response = await http.get(url);
if (response.statusCode == 200) {
var responseJson = jsonDecode(response.body);
responseJson as List;
return responseJson.map((e) => YourModel.fromJson(e)).toList();
}
Firstly, you can paste your JSON in the link below, click convert and get your Dart classes for free.
Secondly, you can copy the result which is named JsonPlaceHolderResponse and create a new file in your project, and paste the result there.
Finally, you can use this code to get your data from defined API:
import 'package:http/http.dart';
Future<JsonPlaceHolderResponse?> getData(String url) async {
final _response = await Client().get(
url
);
if (_response.successResponse) {
final _json = jsonDecode(_response.body);
return JsonPlaceHolderResponse.fromJson(_json);
} else {
return null;
}
return null;
}
extension ResponseExtension on Response {
bool get hasWrongCredentials => statusCode == 422;
bool get tooManyRequests => statusCode == 429;
bool get successResponse => statusCode >= 200 && statusCode < 300;
}

Writing a query parameter with an array of values in Flutter

I'm using the Http package on flutter. I have a query request that should take a large list of values
localhost/accounts/fax-info?ids=(66, 97) this works in post man however. In flutter I tried this exact thing and it just gives me a general error that doesn't tell me anything.
Future<List<CustomerInfo>> getFaxinfo(
List<UnfinishedAccount> accounts,
) async {
final baseUrl = 'localhost';
final int port = 3003;
final accountsPath = '/accounts';
final accountsFaxInfoPath = '$accountsPath/fax-info';
try {
final uri = Uri.parse('http://localhost:3003/accounts/fax-info?ids=(66, 97)');
final response = await http.get(uri, headers: headers);
if (response.statusCode == 200) {
print(jsonDecode(response.body));
}
return [CustomerInfo(sent: 200, received: 300, name: 'Test')];
} catch (err) {
print(err);
rethrow;
}
I tried mapping the values of accounts.id then converting that to a list, I'm not sure if that's the correct way to put it in the query as a list of values because it looks like (66,97) not [66, 97].

Image is null after its picked by using image_picker package and saved on local memory (flutter)

Description:
I'm using (image_picker: ^0.8.5+3) package to pick the images from gallery and camera and then upload them on backend server (django by using django rest framework)
Issue:
Image is getting null outside picker function, even though state has been set by using setSatet(() {}) method
Failed Solutions:
I tried to save the image locally before uploading the image on the backend, But image still shown null once its used or referenced outside picker function
Note: backend API's works fine and i can upload images by using postman
picker function:
// Get image from gallery and store it locally
Future<File?> _getFromGallery() async {
PickedFile? image = await ImagePicker.platform.pickImage(source: ImageSource.gallery);
if (image == null) {
return null;
}
final File file = File(image.path);
final Directory directory = await getApplicationDocumentsDirectory();
final imagepath = directory.path;
final String fileName = path.basename(image.path);
File newImage = await file.copy('$imagepath/$fileName');
setState(() {
_imagelocal = newImage;
print('image.path1');
print(_imagelocal!.path);
});
}
Uploading image function:
_uploadimage() async {
Map<String,String> header = {
"Content-Type":"application/octet-stream)"
};
print("uploaded image invoked");
var request = http.MultipartRequest('POST', Uri.parse('http://192.168.1.15:8000/api/uploadimage'));
// request.files.add(await http.MultipartFile('image',http.ByteStream(image.openRead()).cast(),await image.length(),filename:image.name, contentType: MediaType('image', 'jpg')));
request.files.add(await http.MultipartFile('LoadModelImage',http.ByteStream(_imagelocal!.openRead()).cast(),await _imagelocal!.length(),));
request.headers.addAll(header);
//for completeing the request
var response =await request.send();
//for getting and decoding the response into json format
var responsed = await http.Response.fromStream(response);
final responseData = json.decode(responsed.body);
if (response.statusCode==201) {
print("SUCCESS");
print(responseData);
}
else {
print("ERROR");
}
}

Sending base64 image from flutter

I'm facing a strange issue lately. I have to send a base64 encoded image from flutter to a remote API. The problem is that I convert the image using below code:
Future getProfileImage() async {
final _pickedFile =
await _imagePicker.getImage(source: ImageSource.gallery);
_profileImage = await File(_pickedFile!.path);
print("${_pickedFile.toString()}");
//print("${_pickedFile.path}");
List<int> _profileImageBytes = await _profileImage!.readAsBytesSync();
_profileImageBase64 = await base64Encode(_profileImageBytes);
print("$_profileImageBase64");
}
But when I try to send using following code:
Future updateProfile() async {
print("$_profileImageBase64");
String pre = "data:image/jpg;base64,";
String imageString = '';
print("$imageString");
if (_profileImageBase64 == null) {
imageString = '';
} else {
imageString = "$pre$_profileImageBase64";
}
String apiUrl = "http://162.0.236.163:3000/api/users/profile-update";
Map<String, String> header = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': "Bearer ${widget.user.token}"
};
print(
"Bearer ${widget.user.token}, ${_firstName!.controller!.text}, ${_lastName!.controller!.text}");
//print("$imageString");
//log(imageString);
Map<String, String> body = {
'firstName': _firstName!.controller!.text,
'lastName': _lastName!.controller!.text,
'image': imageString
};
print("${body["image"]}");
http.Response reponse =
await http.post(Uri.parse(apiUrl), body: body, headers: header);
//print("${jsonDecode(reponse.body)}");
var data = jsonDecode(reponse.body) as Map;
print("$data");
if (data["status"] == 1) {
widget.user.first = _firstName!.controller!.text;
widget.user.last = _lastName!.controller!.text;
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('firstName', widget.user.first.toString());
prefs.setString('lastName', widget.user.last.toString());
}
setState(() {});
}
it fails. Strange thing is that when I print the string at the start of above method, it shows different value, but when I print body["image"] it is slightly different string. Moreover, what is surprising is that when I copy any of these Strings from console, and hardcode them for image String, the code is successful. I can't figure out why I cannot successfully send the image using a String variable, which effectively has same code. Can anyone help please?
Edit: I have just realized that the string may not be printed in console completely. When I check the length of the String it is almost 314000 characters for base64 (220kb file). But in console few thousands appear. The one from console can be successfully
sent, but full string fails. Can this be due to limitation on server end
Well, if there isn't a problem with authorization, and shorter strings are successfully saved, then it sounds like some form of data validation is performed on the server-side (API) that is limiting either that field or the entire message body.
If the data is being stored in a database, make sure the image field is large enough to hold that many characters.
One suggestion to try is to reduce the size of the image before you convert it to base64. There are a few optional arguments on the getImage method in the 'image_picker' package that allows you to specify a maxHeight (width would be proportional) and image quality.
import 'dart:async';
import 'dart:io' as Io;
import 'dart:convert';
import 'package:image_picker/image_picker.dart';
final pickedFile = await picker.getImage(
source: ImageSource.gallery,
maxHeight: 150,
imageQuality: 90,
);
final bytes = await pickedFile.readAsBytes();
final b64img = base64.encode(bytes);