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);
Related
I want to send a GET http request with parameters, my problem is that when I add the parameters in the request URL manually it works fine, but when I pass them as parameters it returns an exception without any explanation and somehow the execution stops after Uri.https
here is the code that I want to achieve
Future<List<LawFirm>> getLawFirms () async {
Map<String, dynamic> parameters = {
'total': true
};
final uri =
Uri.http('www.vision.thefuturevision.com:5000',
'/api/law-firm', parameters);
final response = await http.get(uri);
var dynamicResponse = jsonDecode(response.body);
totaLawFirms = await dynamicResponse['total'];
var lawFirms = await dynamicResponse['data'];
List<LawFirm> list = List<LawFirm>.from(lawFirms.map((x) => LawFirm.fromJson(x)));
print(list);
notifyListeners();
return list;
}
and here is the manual way which shouldn't be applied
final response = await get(Uri.parse('$baseURL/law-firm?total=true'));
I have also tried the dio library from pub.dev but also wasn't helpful.
And finally thanks in advance to everyone
You may try this
Map<String, dynamic> parameters = {
'total': true
};
var uri = Uri(
scheme: 'http',
host: 'www.vision.thefuturevision.com:5000',
path: '/law-firm',
queryParameters: parameters,
);
final response = await http.get(uri);
import 'package:http/http.dart' as http;
final response =
await http.get(Uri.parse("${Constants.baseUrl}endpoint/param1/param2"));
Just modify your GET request like this.
Try this
import 'package:http/http.dart' as http;
callAPI() async {
String login = "sunrule";
String pwd = "api";
Uri url = Uri.parse(
"http://vijayhomeservices.in/app/api/index.php?apicall=login&login=$login&password=$pwd");
final response = await http.get(url);
if (response.statusCode == 200) {
final body = json.decode(response.body);
print(body.toString());
} else {
throw Exception("Server Error !");
}
}
Query parameters don't support bool type. Use String instead: 'true'.
A value in the map must be either a string, or an Iterable of strings, where the latter corresponds to multiple values for the same key.
Map<String, dynamic> parameters = {'total': 'true'};
final uri = Uri.http(
'www.vision.thefuturevision.com:5000', '/api/law-firm', parameters);
print(uri); // http://www.vision.thefuturevision.com:5000/api/law-firm?total=true
See Uri constructor for details.
I am sending an image as base64 string to API through post function and getting errors like;
The requested URL is over the maximum size allowed
statusCode: 414
414 Request-URI Too Large
The function I am using
getAndUploadProfilePicture() async {
final _picker = ImagePicker();
final XFile? pickedImage = await _picker.pickImage(
source: ImageSource.gallery,
imageQuality: 20,
);
if (pickedImage != null) {
file = File(pickedImage.path);
final bytes = File(pickedImage.path).readAsBytesSync();
String img64 = base64Encode(bytes);
try {
Dio dio = ApiServices().launch();
final response = await dio.post(EndPoints.uploadUserProfilePicture,
queryParameters: {"id": '24', "picture": img64, "type": 'guide'});
if (response.statusCode == 200) {
print('picture upload successfullyy');
print(response.statusMessage);
}
} catch (e, s) {
// ignore: avoid_print
print("DatabaseService getAndUploadProfilePicture() Exception: $e");
// ignore: avoid_print
print(s);
}
} else {}
}
The API call work fine when i pass a small dummy string instead of base64 image
You cannot decrease the length of a base64-encoded string for an image without first resizing or recompressing the image to be smaller or without corrupting the image by truncating its data. However, that's the wrong question to ask to fix your problem.
If you're making a request via an http POST operation, you shouldn't be passing the image data through queryParameters (which are encoded into the URL itself, hence the error about the URI being too long). You should be supplying it through dio.post's data argument instead. package:dio's README.md shows an example for using dio.post:
Performing a POST request:
response = await dio.post('/test', data: {'id': 12, 'name': 'wendu'});
I want to take picture, which should contain just a few letters, with my phone and then send it to a server where it will convert the picture to a text string.
My imported packages:
import 'dart:io';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:image_picker/image_picker.dart';
I currently have this camera function:
// Camera implementation
File? _image;
final ImagePicker _picker = ImagePicker();
Future getImage() async {
final image = await _picker.pickImage(source: ImageSource.camera);
setState(() {
_image = File(image!.path);
});
}
And I use it in this button:
// Camera button
ElevatedButton.icon(
onPressed: getImage,
icon: const Icon(Icons.camera_alt_rounded),
label: const Text('Scan'),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.green[500]),
textStyle: MaterialStateProperty.all(const TextStyle(fontSize: 26)),
)
)
I have tested to just send some data to jsonplaceholder and it works, but I can't get understand how to implement it to a picture that should be sent to my server.
// Send Data to the Server (TEST VERSION)
postDataTest() async{
try{
var response = await http.post(Uri.parse("https://jsonplaceholder.typicode.com/posts"),
body: {
"id": 1.toString(),
"name": "Hax",
}
);
print(response.body);
} catch(e){
print(e);
}
}
TLDR. I want to take a picture and send it to a server.
Use multipart
Upload(File img) async {
var uri = Uri.parse(uploadURL);
var request = new http.MultipartRequest("POST", uri);
request.files.add( new http.MultipartFile.fromBytes("file", img.readAsBytesSync(), filename: "Photo.jpg", contentType: new MediaType("image", "jpg")));
var response = await request.send();
print(response.statusCode);
response.stream.transform(utf8.decoder).listen((value) {
print(value);
});
}
Picture to text for archive this you need to convert image into base64. Check this link
However, it's generally a bad idea to store large blobs of binary data in your database. you will end up wasting bandwidth transmitting data.it also decrease mobile app performance while you read large blob data. you don't need as well as unnecessary encoding and decoding.
You can send picture to server using multipart api request.
So you can archive mutipart request with api in various packages
https - dart documentation
Dio
You can also check multipartRequest on Stackoverflow.
I managed to solve it with this function:
// Upload camera photo to server
Future uploadImage() async {
final uri = Uri.parse("url to the server");
var request = http.MultipartRequest('POST', uri);
var takenPicture = await http.MultipartFile.fromPath("image", _image!.path);
request.files.add(takenPicture);
var response = await request.send();
if(response.statusCode == 200){
print('Image uploaded!');
} else{
print('Image not uploaded');
}
}
file.path is path of image u can use file picker or image picker in flutter
baseimage = "";
if(file.path != '') {
List<int> imageBytes = file.readAsBytesSync();
baseimage = base64Encode(imageBytes);
}
send image as a string and decode base64 in your server
laravel exemple using spatie media
if (isset($request->image)) {
$fiche_client->addMediaFromBase64($request->image)
->usingFileName(Str::random(10).'.png')
->toMediaCollection('magasin');
}
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
I have been working for few since yesterday to try upload an image to azure blob storage taken using mobile camera form iOS/Android device.
I am able to upload the files but for some reason they being corrupted not able to open the image uploaded.
Please check the image error while opening the uploaded image
I am using flutter package http with different approach all work in uploading image file to azure blob store but it gets corrupted somehow , I tried forcing the ContentType to image/jpeg but no help.
Here is code I am using an http API -
takePicture() async {
final pickedFile = await picker.getImage(source: ImageSource.camera);
setState(() {
if (pickedFile != null) {
_image = File(pickedFile.path);
String fileName = basename(pickedFile.path);
uploadFile(fileName, image);
} else {
print('No image selected.');
}
});
}
First approach -->
http.Response response = await http.put(
uri,
headers: {
"Content-Type": 'image/jpeg',
"X-MS-BLOB-TYPE": "BlockBlob",
},
body: image.path,
);
print(response.statusCode);
Using Approach second -->
final data = image.readAsBytesSync();
var dio = Dio();
dio.options.headers['x-ms-blob-type'] = 'BlockBlob';
dio.options.headers['Content-Type'] = 'image/jpeg';
try {
final response = await dio.put(
'$url/$fileName?$token',
data: data,
onSendProgress: (int sent, int total) {
if (total != -1) {
print((sent / total * 100).toStringAsFixed(0) + "%");
}
},
);
print(response.statusCode);
} catch (e) {
print(e);
}
Approach third -->
var request = new http.MultipartRequest("PUT", postUri);
request.headers['X-MS-BLOB-TYPE'] = 'BlockBlob';
request.headers['Content-Type'] = 'image/jpeg';
request.files.add(
new http.MultipartFile.fromBytes(
'picture',
await image.readAsBytes(),
),
);
request.send().then((response) {
uploadResponse.add(response.statusCode);
}, onError: (err) {
print(err);
});
Help here is much appreciated.
If you want to upload the image to Azure Blob Storage in the flutter application, you can use the Dart Package azblob to implement it. Regarding how to use the package, please refer to here.
For example
import 'package:image_picker/image_picker.dart';
import 'package:flutter/material.dart';
import 'package:azblob/azblob.dart';
import 'package:mime/mime.dart';
...
//use image_picker to get image
Future uploadImageToAzure(BuildContext context) async {
try{
String fileName = basename(_imageFile.path);
// read file as Uint8List
Uint8List content = await _imageFile.readAsBytes();
var storage = AzureStorage.parse('<storage account connection string>');
String container="image";
// get the mine type of the file
String contentType= lookupMimeType(fileName);
await storage.putBlob('/$container/$fileName',bodyBytes: content,contentType: contentType,type: BlobType.BlockBlob);
print("done");
} on AzureStorageException catch(ex){
print(ex.message);
}catch(err){
print(err);
}
Unfortunately, the multipart form is causing break of image. I don't know how it works on azure side, because there is little or no information about multipart uploads, but it's clearly broken because of multipart form. I replicated the problem in .net core application and whenever i am using multipart form data to upload image - it is broken. When i am using simple ByteArrayContent - it works. I couldn't find flutter equivalent to ByteArrayContent, so i am lost now :( The package mentioned by #Jim is useless for me, because i want to give clients sas url, so they have permission to upload image on client side. I do not want to store azure storage account secrets in flutter app.
EDIT. I found the solution to send raw byte data with Dio package. You can do that also with http package.
final dio = new Dio();
final fileBytes = file.readAsBytesSync();
var streamData = Stream.fromIterable(fileBytes.map((e) => [e]));
await dio.put(uploadDestinationUrl,
data: streamData,
options: Options(headers: {
Headers.contentLengthHeader: fileBytes.length,
"x-ms-blob-type": "BlockBlob",
"content-type": "image/jpeg"
}));