Flutter - How to send multiple images in formdata - flutter

I want to upload multiple images in flutter using dio and formData. What I did is I created a for loop for storing MultipartFile in an array and then I passed the array to the data of "img[]". However, I cannot upload the image if I passed the array but it works perfectly fine if I upload a single image.
Here's my code.
var arr = new List(3);
for (var i=0; i <_tryPath.length; i++) {
arr[i] = await MultipartFile.fromFile(imageFile[i].path, filename:
imageFile[i].path.split('/').last, contentType: new MediaType('image','jpg'));
};
print('this is arr = $arr');
FormData formData = new FormData.fromMap({
"name": "Max",
"location": "Paris",
"age": 21,
"img[]": arr,
});
// dio is in another class here in AuthService.
AuthService().donateRegister(formData).then((val) async {
print('successful');
print(val.data);
});
Can you help me uploading many images in formData? I also would like not to limit the MediaType. What will I do if I will also upload pdf/docu or jpg MediaType? I will appreciate your time and answer. Thank you!

You can use a for loop to set image lists as show below
here I'm using multi_image_picker
Future<int> addMultiImage({MyData myData, List<Asset> files}) async {
var formData = FormData.fromMap({
"name": "Max",
"location": "Paris",
"age": 21,
});
for (int i = 0; i < files.length; i++) {
var path = await FlutterAbsolutePath.getAbsolutePath(files[i].identifier);
formData.files.addAll([
MapEntry("img", await MultipartFile.fromFile(path, filename: path))
]);
}
Response response = await dioHelper.post(
"myPaths",
queryParameters: {
},
data: formData,
);

You can use mime package to get mime type.
import 'package:path/path.dart' as path;
import 'package:mime/mime.dart';
String getFileName(String _path){
return path.basename(_path)
}
FilePickerResult? result = await FilePicker.platform.pickFiles(
allowMultiple: true,
type: FileType.custom,
allowedExtensions: ['jpg', 'pdf', 'doc'],
);
if(result != null) {
List<MultipartFile> files = result.paths.map((path) =>
MultipartFile.fromFileSync(
path,
filename: getFileName(path),
contentType: MediaType(
lookupMimeType(getFileName(path)).split('/')[0],
lookupMimeType(getFileName(path)).split('/')[1],
),
)
).toList();
var dio = Dio();
var formData = FormData.fromMap({
'name': 'wendux', // other parameter may you need to send
'age': 25, // other parameter may you need to send
'files': files,
});
var response = await dio.post('url', data: formData);
// check response status code
if(response.statusCode == 200){
// it's uploaded
}
} else {
// User canceled the picker
}

Related

Upload images list with dio

I've use this code in my application with Dio: 2.2.2 and it works fine, but after upgrading my Dio package version, I must use the MultipartFile option. I tried to use it, but all data sent ok without the images. How can I upgrade this code to use with the latest Dio package version?
Old code:
Future add(name,desc,address,images) async {
Map ad_data;
await dio
.post("${host}/add",
data: FormData.from({
"name": "${name}",
"desc": "${desc}",
"address": "${address}",
"image[]": images
.map((image) =>
UploadFileInfo(File(image.path), basename(image.path)))
.toList(),
}),).then((data) {
ad_data = data.data;
});
return ad_data;
}
Update your add function with the below one... I am using dio: ^4.0.0.
import 'package:path/path.dart';
Future add(name,desc,address,images) async {
var formData = FormData.fromMap({
'name': name,
'desc': desc,
'address': address,
'files': [
for (var item in images)
{
await MultipartFile.fromFile(item.path,
filename: basename(item.path))
}.toList()
],
});
await dio.post("${host}/add",data:formData).then((data) {
ad_data = data.data;
});
return ad_data;
}
I have a costum function for uploading one file, you can change it and use.
Dio getBaseDio() {
var token = "AccessToken";
Dio dio = Dio(BaseOptions(
baseUrl: "your base url",
headers: {
"Authorization": "Bearer $token",
"Accept": "application/json",
}));
return dio;
}
after this :
Future<Response> postWithFile(
String url,
dynamic body) async {
return await getBaseDio().post(url, data: body)
.catchError(
(error) {
return error.response;
},
);
}
now you can use postWithFile function like this:
FormData formData = FormData.fromMap({
"param1": "value1",
"param2": "value2,
"fileParam": await MultipartFile.fromFile(attachPath, filename: fileName),
});
var result = await postWithFile(url, formData);
I hope I was able to help.
add content type
contentType: MediaType(
"image", "${item.path.split(".").last}"),

Flutter Dio add MultipartFile object to Map dynamicaly

I have custom data:
File avatar = File('path/to/file');
Map<String, dynamic> data = {
'name': 'John',
'avatar': avatar
}
How I can send this my data as FormData object to server?
I tried create object of MultipartFile class by looping my data but in my case sending file path as string instance of file. Here is my code:
data.forEach((key, value) {
if (value is File) {
String fileName = value.path.split('/').last;
data.update(key, (value) async {
return await MultipartFile.fromFile(value.path, filename: fileName);
});
}
});
FormData formData = FormData.fromMap(data);
var response = await dio.post("/send", data: formData);
But using Dio I can upload file something like this:
FormData formData = FormData.fromMap({
"name": "wendux",
"age": 25,
"file": await MultipartFile.fromFile(file.path,filename: fileName)
});
Why I can't dynamically add MultipartFile to my Map ?
You are not awaiting for your data
await Future.wait(data.map((k, v){
if(v is File){
v = await MultipartFile.fromFile(value.path, filename: fileName);
}
return MapEntry(k, v);
}));
Please look into My async call is returning before list is populated in forEach loop
You can send it without dio. Here I write the whole code including the libraries and response.Please test the below code becuase in my case dio is not working and your case is very similar to my case.
Just send it with simple http request
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart';
import 'package:mime/mime.dart';
Map<String, String> headers = {
'Content-Type': 'multipart/form-data',
};
Map jsonMap = {
"name": "wendux",
"age": 25,
};
String imagePath, // add the image path in varible
var request = http.MultipartRequest('POST', Uri.parse(url));
request.headers.addAll(headers);
request.files.add(
http.MultipartFile.fromBytes(
'orderStatusUpdate',
utf8.encode(json.encode(jsonMap)),
contentType: MediaType(
'application',
'json',
{'charset': 'utf-8'},
),
),
);
if (imagePath != null) {
final mimeTypeData = lookupMimeType(imagePath).split('/');
final file = await http.MultipartFile.fromPath('images', imagePath,
contentType: MediaType(mimeTypeData[0], mimeTypeData[1]));
print((mimeTypeData[0].toString())); // tells picture or not
print((mimeTypeData[1].toString())); // return extention
request.files.add(file);
}
http.StreamedResponse streamedResponse = await request.send();
var response = await http.Response.fromStream(streamedResponse);
print(response.statusCode);
print(response.body);

Issues sending multiple images on Flutter using Multipart and Dio

I'm trying to send multiple files that came from Multi Image Picker pluggin on Flutter to my server, for this I'm trying to use Dio to send it. But the Multipart tag isn't uploading the files too. How to proceed?
Future<Response> _uploadFile() async {
var catUpload = jsonEncode(incluirCategoriasParaUpload());
final FormData formData = FormData.fromMap({
"action": 'add_Anuncio',
"filial_id": "1",
"titulo": tituloControler.text,
"descricao": descricaoControler.text,
"categorias": catUpload
});
for (var val in listaImagensParaUpload) {
ByteData byteData = await val.getByteData();
Uint8List pngBytes = byteData.buffer.asUint8List();
formData.files.add(
MapEntry("arquivos",
await MultipartFile.fromBytes(pngBytes, filename: "teste")
)
);
}
Dio dio = new Dio();
dio.interceptors.add(alice.getDioInterceptor());
return await dio
.post(URL_WS + WS_PAGE,
data: formData,
options: Options(method: 'POST', responseType: ResponseType.json))
.timeout(Duration(seconds: 2000));
// .catchError(dioError);
}
You must declare as toList() this for loop.
Here is an example how to make it.
formData = FormData.fromMap({
'id': '1',
'title': 'myTitle',
'files': [
for (var file in listFiles)
{await MultipartFile.fromFile(item.path, filename: 'fileName')}
.toList()
]
});
I am usign Dio library working with MultiPartFile.
I am posting this solution with dio and image_picker dependency. And it will definitely work. I have spent 2 days for this solution.
FormData formData = new FormData.fromMap({
"name": "Max",
"location": "Paris",
"age": 21,
"image[]": [
await MultipartFile.fromFile(
_imageFile.path,
),
await MultipartFile.fromFile(
_imageFile.path,
),
],
});
print(FormData1().then((value) {
print(value);
}));
response = await dio.post(
"http://143.110.244.110/radius/frontuser/eventsubmitbutton",
data: formData,
onSendProgress: (received, total) {
if (total != -1) {
print((received / total * 100).toStringAsFixed(0) + '%');
}
},
);
print(response);

flutter dio upload files [pdf/ docs]

I am trying to upload files using dio package in my flutter application. I am sending my files through formdata. Here is my implementation:
Future<FormData> formData1() async {
return FormData.fromMap({
"title": "from app2",
"description": "app upload test",
"files": [
for (var i = 0; i < pathNames.length; i++)
await MultipartFile.fromFile(pathNames[i],
filename: fileNames[i])
]
});
}
Here is how I am sending my files.
_sendToServer() async {
Dio dio = Dio(
BaseOptions(
contentType: 'multipart/form-data',
headers: {
"Authorization": "$token",
},
),
);
dio.interceptors.add(
LogInterceptor(requestBody: true, request: true, responseBody: true));
FormData formData = await formData1();
try {
var response = await dio.post("http://url/api/upload",
data: formData, onSendProgress: (int send, int total) {
print((send / total) * 100);
});
print(response);
} on DioError catch (e) {
if (e.response != null) {
print(e.response.data);
print(e.response.headers);
print(e.response.request);
} else {
print(e.request.headers);
print(e.message);
}
}
}
The other fields in formdata are sent to the server but not the multipartfile. When I try and do the same from postman form-data, it uploads correctly. Am I doing something wrong here?
If you want to upload the file you can convert multipart array before calling API function because even if you put await in form data dio response will not wait for formdata object or you can use MultipartFile.fromFileSync() to get rid of await.
Let me show you in a simple way using my example. try to understand.
Multipart conversion
List multipartArray = [];
for (var i = 0; i < pathNames.length; i++){
multipartArray.add(MultipartFile.fromFileSync(pathNames[i], filename:
basename(pathNames[i])));
}
Api side
static Future<Response> createPostApi(multipartArray) async {
var uri = Uri.parse('http://your_base_url/post');
return await Dio()
.post('$uri',
data: FormData.fromMap({
"title": "from app2",
"description": "app upload test",
"files": multipartArray
}))
.catchError((e) {
print(e.response.data);
print(e.response.headers);
print(e.response.request);
});
}
Here is my code where I used file_picker flutter library and MediaType('application', 'pdf') to ensure that the content passed to the API was indeed a .pdf file.
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:http_parser/http_parser.dart';
static Future<dynamic> uploadfile(int userid, File file, String token) async {
var fileName = file.path.split('/').last;
print(fileName);
var formData = FormData.fromMap({
'title': 'Upload Dokumen',
'uploaded_file': await MultipartFile.fromFile(file.path,
filename: fileName, contentType: MediaType('application', 'pdf')),
"type": "application/pdf"
});
var response = await Dio().post('${urlapi}request/',
options: Options(
contentType: 'multipart/form-data',
headers: {HttpHeaders.authorizationHeader: 'Token $token'}),
data: formData);
print(response);
return response;
}
The file picker:
FilePickerResult? result = await FilePicker.platform.pickFiles();
if (result != null) {
File file = File(result.files.single.path ??'file.pdf');
BlocProvider.of<UploadCubit>(context)
.uploadFile(statelogin.user.id, file,
statelogin.user.token);
}
Change formdata with following rest is fine
import 'package:path/path.dart' as pathManager;
import 'package:mime/mime.dart' as mimeManager;
FormData formdata = FormData();
formdata.add(
"files",
[UploadFileInfo(img, pathManager.basename(img.path),
contentType:
ContentType.parse(mimeManager.lookupMimeType(img.path)))]);
// here attachmentFile is File instance, which is set by File Picker
Map<String, dynamic> _documentFormData = {};
if (attachmentFile != null) {
_documentFormData['document_file'] = MultipartFile.fromFileSync(attachmentFile.path);
}
FormData formData = FormData.fromMap(_documentFormData);
try {
var response = await dio.post("http://url/api/upload",
data: formData, onSendProgress: (int send, int total) {
print((send / total) * 100);
});
print(response);
} on DioError catch (e) {
if (e.response != null) {
print(e.response.data);
print(e.response.headers);
print(e.response.request);
} else {
print(e.request.headers);
print(e.message);
}
}
Here you can use MultipartRequest class without using any of library to upload any kind of files using restAPI.
void uploadFile(File file) async {
// string to uri
var uri = Uri.parse("enter here upload URL");
// create multipart request
var request = new http.MultipartRequest("POST", uri);
// if you need more parameters to parse, add those like this. i added "user_id". here this "user_id" is a key of the API request
request.fields["user_id"] = "text";
// multipart that takes file.. here this "idDocumentOne_1" is a key of the API request
MultipartFile multipartFile = await http.MultipartFile.fromPath(
'idDocumentOne_1',
file.path
);
// add file to multipart
request.files.add(multipartFile);
// send request to upload file
await request.send().then((response) async {
// listen for response
response.stream.transform(utf8.decoder).listen((value) {
print(value);
});
}).catchError((e) {
print(e);
});
}
I used file picker to pick file. Here is the codes for pick file.
Future getPdfAndUpload(int position) async {
File file = await FilePicker.getFile(
type: FileType.custom,
allowedExtensions: ['pdf','docx'],
);
if(file != null) {
setState(() {
file1 = file; //file1 is a global variable which i created
});
}
}
here file_picker flutter library.

How to upload images to server in Flutter?

I would like to upload a image, I am using http.Client() for making requests,
static uploadImage(String id, File file) {
var httpClient = createHttpClient();
Map<String, String> headers = new Map<String, String>();
headers.putIfAbsent("Authorization", () => "---");
headers.putIfAbsent("Content-Type", () => "application/json");
var body=new List();
body.add(id.)
httpClient.post(URL_UPLOADIMAGE,headers: headers,body: ,encoding: )
}
What should be the body and encoding part for the request ?
Use MultipartRequest class
Upload(File imageFile) async {
var stream = new http.ByteStream(DelegatingStream.typed(imageFile.openRead()));
var length = await imageFile.length();
var uri = Uri.parse(uploadURL);
var request = new http.MultipartRequest("POST", uri);
var multipartFile = new http.MultipartFile('file', stream, length,
filename: basename(imageFile.path));
//contentType: new MediaType('image', 'png'));
request.files.add(multipartFile);
var response = await request.send();
print(response.statusCode);
response.stream.transform(utf8.decoder).listen((value) {
print(value);
});
}
name spaces:
import 'package:path/path.dart';
import 'package:async/async.dart';
import 'dart:io';
import 'package:http/http.dart' as http;
The easiest way is to use the http library,
import 'dart:io';
import 'package:http/http.dart' as http;
_asyncFileUpload(String text, File file) async{
//create multipart request for POST or PATCH method
var request = http.MultipartRequest("POST", Uri.parse("<url>"));
//add text fields
request.fields["text_field"] = text;
//create multipart using filepath, string or bytes
var pic = await http.MultipartFile.fromPath("file_field", file.path);
//add multipart to request
request.files.add(pic);
var response = await request.send();
//Get the response from the server
var responseData = await response.stream.toBytes();
var responseString = String.fromCharCodes(responseData);
print(responseString);
}
Checkout the body in submitForm() method.
File _image;
Future cameraImage() async {
var image = await ImagePicker.pickImage(
source: ImageSource.camera,
maxHeight: 240.0,
maxWidth: 240.0,
);
setState(() {
_image = image;
});
}
submitForm() async {
final response = await http.post(
uri,
headers: {
AuthUtils.AUTH_HEADER: _authToken
},
body: {
'user_id': userId
'photo': _image != null ? 'data:image/png;base64,' +
base64Encode(_image.readAsBytesSync()) : '',
},
);
final responseJson = json.decode(response.body);
print(responseJson);
}
I have tried all the above but none worked for me to upload a file to a server.
After a deep search, I got a plugin the same as Dio.
The following code uploads a file to a server.
uploadFileFromDio(UserProfile userProfile, File photoFile) async {
var dio = new Dio();
dio.options.baseUrl = url;
dio.options.connectTimeout = 5000; //5s
dio.options.receiveTimeout = 5000;
dio.options.headers = <Header Json>;
FormData formData = new FormData();
formData.add("user_id", userProfile.userId);
formData.add("name", userProfile.name);
formData.add("email", userProfile.email);
if (photoFile != null &&
photoFile.path != null &&
photoFile.path.isNotEmpty) {
// Create a FormData
String fileName = basename(photoFile.path);
print("File Name : $fileName");
print("File Size : ${photoFile.lengthSync()}");
formData.add("user_picture", new UploadFileInfo(photoFile, fileName));
}
var response = await dio.post("user/manage_profile",
data: formData,
options: Options(
method: 'POST',
responseType: ResponseType.PLAIN // or ResponseType.JSON
));
print("Response status: ${response.statusCode}");
print("Response data: ${response.data}");
}
I found a working example without using any external plugin , this
only uses
import 'package:http/http.dart' as http;
Code
var stream =
new http.ByteStream(DelegatingStream.typed(imageFile.openRead()));
// get file length
var length = await imageFile.length(); //imageFile is your image file
Map<String, String> headers = {
"Accept": "application/json",
"Authorization": "Bearer " + token
}; // ignore this headers if there is no authentication
// string to uri
var uri = Uri.parse(Constants.BASE_URL + "api endpoint here");
// create multipart request
var request = new http.MultipartRequest("POST", uri);
// multipart that takes file
var multipartFileSign = new http.MultipartFile('profile_pic', stream, length,
filename: basename(imageFile.path));
// add file to multipart
request.files.add(multipartFileSign);
//add headers
request.headers.addAll(headers);
//adding params
request.fields['loginId'] = '12';
request.fields['firstName'] = 'abc';
// request.fields['lastName'] = 'efg';
// send
var response = await request.send();
print(response.statusCode);
// listen for response
response.stream.transform(utf8.decoder).listen((value) {
print(value);
});
Please try below solution
Future<String> uploadImageHTTP(file, url) async {
var request = http.MultipartRequest('POST', Uri.parse(url));
request.files.add(await http.MultipartFile.fromPath('picture', file.path));
var res = await request.send();
return res.reasonPhrase;
}
Consider using Flutter's Firebase Storage plugin -- it has features that could be useful for uploading large image files on a mobile connection.
I wrote the plugin, contributions and feedback are welcome!
First of all choose your image from gallery or Camera
File _image;
Future _getImage() async {
var image = await ImagePicker.pickImage(source: ImageSource.gallery);
setState(() {
_image = image;
});
}
Now call the below function on button click or inside the _getImage() function. With the file i'm uploading other fields also you see in the saveInAttendance()
Don't forget to import package :
import 'package:dio/dio.dart';
import 'package:path/path.dart';
Future saveInAttendance( BuildContext context,String entryType,String mode) async {
Dio dio = new Dio();
FormData formData = new FormData(); // just like JS
formData.add("inimageFile", new UploadFileInfo(_image, basename(_image.path)));
formData.add("compID",2);
formData.add("company_id",2);
formData.add("EntryType", entryType);
formData.add("emp_code", 5);
formData.add("Mode",mode);
formData.add("location",""+_startLocation.latitude.toString()+"-"+_startLocation.longitude.toString());
dio.post(url_save_attendance, data: formData, options: Options(
method: 'POST',
responseType: ResponseType.json // or ResponseType.JSON
))
.then((r) {
setState(() {
var data = json.decode(r.toString());
if(data["apiMessage"].contains('Saved')){
warningAlert("Attendance Saved", "Your attendance saved Successfully",context);
}
});
}).catchError(print);
}
For more Info you can visit Here
my working code below, based on #TejaDroid's sample,
it upload one image via the AWS Gateway API with a lambda function behind to store the image into S3.
uploadImageWithhttp(File imageFile, int serialno) async {
var postBody= {
'username': 'test#gmail.com',
"productid": "1000123", //TODO
"imageno": serialno.toString(),
'image': imageFile != null ? base64Encode(imageFile.readAsBytesSync()) : '',
};
final response = await http.post(
constAWSAPIGateway_UploadImage[CONST_API_STAGE],
headers: {
//AuthUtils.AUTH_HEADER: _authToken
'Content-Type' : 'application/json',
},
body: json.encode(postBody),
);
final responseJson = json.decode(response.body);
print(responseJson);
}
to get Body from request Instead of
response.stream.transform(utf8.decoder).listen((value) {
print(value);
});
I use:
String body=await response.stream.bytesToString()
updateProfile() async {
try {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
var dio = new Dio();
var formData = FormData.fromMap({
'name': name,
'con_person_name': concernedPersonName,
'email': email,
'phone': phoneNumber,
'password': password,
'token': token,
'user_type': '3',
'license_no': licenceNumber,
'gstno': gstNumber,
'address': address,
'hospital_id': '102'
'image': await MultipartFile.fromFile(_image?.path,
filename: _image.path.split('/').last ?? 'image.jpeg'),
});
var response = await dio.post('$SERVER_ADDRESS/api/doctorregister',
data: formData);
print(response.statusCode);
print(response.data);
}
} catch (error) {
print(error.toString());
}
}
Import dio, image_picker library
Future _onGalleryPressed() async {
Future<File> image = ImagePicker.pickImage(source: ImageSource.gallery);
setState(() {
this._imageFile = image;
});
File img = await image;
Navigator.of(context).pop();
if (img != null) {
//API CALL
try {
FormData formData = new FormData.from({"file": path});
var url = backendUrl + "/upload-image";
var token = await _getMobileToken();
Map<String, String> headers = {
'Authorization': 'Bearer $token',
"Content-Type": "multipart/form-data",
"X-Requested-With": "XMLHttpRequest"
};
await dio.post(url,
data: formData,
options: Options(
method: 'POST',
headers: headers,
responseType: ResponseType.json // or ResponseType.JSON
));
Navigator.pop(context);
} catch (e) {}
}
}
If you want to upload it as a binary file.
static uploadFile(File imageFile) async {
final response = await http.post(postURL, body: imageFile.readAsBytesSync());
return json.decode(response.body);
}
Thank you
I have checked about it at multiple places finally i found a solution -
var objToSend = {
"file": await MultipartFile.fromFile(
file.path,
filename: filename,
),
};
FormData formData = FormData.fromMap(objToSend);
print(formData.files.toString());
Dio dio = new Dio();
await dio
.post(_toSend,
data: formData,
options: Options(
method: 'POST',
headers: <String, String>{
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Authorization": 'Bearer ' + token
},
))
.whenComplete(() {
print('uploaded');
}).catchError((onError) {
print('failed');
});
I have found a easy way to upload images in flutter and then even receiving it on the server.
Flutter:
MaterialButton(
color: Colors.blue,
child: Text(
"Pick Image from Camera",
style: TextStyle(
color: Colors.white70, fontWeight: FontWeight.bold),
),
onPressed: () async {
final XFile? photo =
await _picker.pickImage(source: ImageSource.camera);
print(photo!.path);
await uploadImage(photo.path);
},
),
'uploadImage' function:
uploadImage(String filepath) async {
var url = 'http://192.168.75.57:4000/upload';
var request = http.MultipartRequest('POST', Uri.parse(url));
request.files.add(await http.MultipartFile.fromPath("img", filepath));
request.fields['_id'] = "abcdef";
request.headers.addAll({
"Content-type": "multipart/form-data",
});
var response = request.send();
return response;
}
On the server Side: (Nodejs)
For this, first install multer (npm install multer)
const multer = require('multer');
const path = require('path')
const storage = multer.diskStorage({
destination: './uploads',
filename: (req, file, cb) => {
cb(null, (new Date()).getTime().toString() + ".jpg");
},
});
const fileFilter = (req, file, cb) => {
if (file.mimetype == "image/jpeg" || file.mimetype == "image/png") {
cb(null, true);
} else {
cb(null, false);
}
};
const upload = multer({
storage: storage,
limits: {
fileSize: 1024 * 1024 * 6,
},
fileFilter: fileFilter,
});
Finally, honoring the request from flutter application:
(In router.js)
router.post('/upload', upload.single("img"), function (req, res) {
console.log("hit")
console.log(req.body._id)
res.send("ok")
})
This method worked for me and I found it comparatively easier than other methods.