Open rsbichkar opened 1 year ago
replace the file path with some image from internet to check if everything works fine.
As far as i can see from exception PathNotFoundException
so it can't find a path
Thanks a lot. However, as I mentioned already, the images from Appwrite are already displayed properly if we use the 'Url' in SelectedFile in FormGroup.
It should be possible to convert the image file data on Appwrite backend to an in-memory file and then pass this file in FormControl<List<SelectedFile>>
. It is possible to use http
package to download a file and then use File
or XFile
to create in-memory file for the image. I have not yet figured how to do this. However, I would like to avoid creating image file on disk for efficiency reasons as there will be a large number of images the app will be processing.
Can we try something like this for the conversion of Url to a file? It uses getImageUrlFromId
local function to get the Appwrite storage Url for a given image id.
Future<File> getFileFromImageId(String id) async {
Uri uri = Uri.parse(getImageUrlFromId(id));
final http.Response responseData = await http.get(uri);
final file = XFile.fromData(responseData.bodyBytes);
return file as File; // ??????? We need File. Will it work?`
}
However, this gives Future and we may not be able to use it in the FormGroup
.
But in the mean time, I have continued to use the conversion approach between Form Value and model object and got a success to a great extent.
However, the logic becomes quite complex, particularly when reading multiple files and allowing users to change one or more of those images. In addition, I have been using Reactive forms in either read-only or edit mode. It complicates the things further.
We have to handle data from three locations:
The current implementation of personNameFromFormValue
function looks like this:
PersonName personNameFromFormValue(FormGroup form) {
Map<String, dynamic> formValue = form.value;
return PersonName(
id: formValue['id'] as String?,
first: formValue['first'] as String,
... // other fields
// profilePic gets a single image input
profilePic: formValue['profilePic'] != null
? form.control('profilePic').dirty
? formValue['profilePic'].isNotEmpty
? (formValue['profilePic'] as List<SelectedFile>)[0].file!.path
: null
: getIdFromImageUrl((formValue['profilePic'] as List<SelectedFileImage>)[0].url!)
: null,
// Just for testing...
// This friendPics field takes multiple images as input
friendPics: formValue['friendPics'] != null
? form.control('friendPics').dirty
? formValue['friendPics'].isNotEmpty
? (formValue['friendPics'] as List<SelectedFile>)
.map((e) => e.file != null ? e.file!.path : getIdFromImageUrl(e.url!))
.toList()
: null
: (formValue['friendPics'] as List<SelectedFileImage>)
.map((e) => getIdFromImageUrl(e.url!))
.toList()
: null,
);
}
Since image_picker returns XFile, why are we using SelectedFile? On web, I can't access the picked image.
inal images = formGroup.control('image').value
as List<SelectedFile>?;
MultipartFile? multiPartImageFile;
if (images != null && images.isNotEmpty) {
final selectedImageFile = images.first;
print(selectedImageFile);
XFile? file;
if (kIsWeb) {
Uint8List? imageBytes =
await selectedImageFile.file
?.readAsBytes(); // Throw error `Unsupported operation: _Namespace,`
@rsbichkar You could probably try this https://github.com/djangoflow/reactive_forms_widgets It returns XFile inside SelectedFile, from which you should be able to read image bytes.
For your case, I suppose what you can do is download the image to temp directory and get the image bytes and pass those bytes to ReactiveImagePicker as initial value?
@vasilich6107 Could you check this PR? https://github.com/artflutter/reactive_forms_widgets/pull/128
After that I will create the PR for File -> XFile change if that is okay.
@adar2378 Thanks for your help.
However, I could get the code working (for Linux platform at least). I am not converting image URLs and image Files anymore. Instead I am handling them separately. It is not necessary to download the file to temporary storage either.
First I have used the conversion functions fromFormValue
and toFormValue
as follows:
Map<String, dynamic> personNameToFormValue(PersonName personName) {
final map = <String, dynamic>{
'id': personName.id,
'name': personName.name,
...
'profilePic': personName.profilePic != null
? [
SelectedFileImage(
url: AppwriteConstants.getImageUrlFromId(personName.profilePic!))
]
: null,
'friendPics': personName.friendPics != null
? personName.friendPics!
.map((e) => SelectedFileImage(url: AppwriteConstants.getImageUrlFromId(e)))
.toList()
: null,
};
return map;
}
// Uses any valid value(s) for image fields. Their actual values are obtained in controller code
PersonName personNameFromFormValue(FormGroup form) {
Map<String, dynamic> formValue = form.value;
return PersonName(
id: formValue['id'] as String?,
name: formValue['name'] as String,
...
// use null values for these image fields
profilePic: null,
friendPics: null,
);
}
These functions are used in PersonNameReactiveFormView
class for conversion between PersonName
object and formValue.
The controller uses the createPersonName
and updatePersonName
functions as follows:
void createPersonName(
BuildContext context, PersonName personName, FormGroup form) async {
Map<String, dynamic> formValue = form.value;
state = true;
// Create image for profilePic field, if an image is entered by user
String? profilePicIds;
if (form.control('profilePic').dirty) {
// store new images, if any, to appwrite storage and create newPersonName with new image id
profilePicIds = formValue['profilePic'].isNotEmpty
? (await _storageRepo
.uploadImages([formValue['profilePic'][0].file]))[0]
: null;
}
// Create images for friendPics field, if one or more images are entered by user
List<String> friendPicsIds = [];
if (form.control('friendPics').dirty) {
// first create a list of image files added
final newImageFiles = <io.File>[]; // we can use XFile here for Platform Independence
formValue['friendPics'].forEach((e) {
if (e.file != null) newImageFiles.add(e.file);
});
// upload these files to Appwrite storage and get image ids
friendPicsIds = formValue['friendPics'].isNotEmpty
? await _storageRepo.uploadImages(newImageFiles)
: [];
}
// set new image ids for image field
final newPersonName = personName.copyWith(
profilePic: profilePicIds,
friendPics: friendPicsIds,
);
final res = await _personNameRepo.createPersonName(newPersonName);
state = false;
...
void updatePersonName(BuildContext context, PersonName personName,
PersonName prevPersonName, FormGroup form) async {
Map<String, dynamic> formValue = form.value;
state = true;
// Update image for profilePic field, if an image is changed by user
String? profilePicIds = formValue['profilePic'] != null
? form.control('profilePic').dirty
? formValue['profilePic'].isNotEmpty
? (await _storageRepo.uploadImages([formValue['profilePic'][0].file]))[0]
: null
: AppwriteConstants.getIdFromImageUrl(
(formValue['profilePic'] as List<SelectedFileImage>)[0].url!)
: null;
// delete previous image, if any
if (prevPersonName.profilePic != null &&
prevPersonName.profilePic != profilePicIds) {
await _storageRepo.deleteImage(prevPersonName.profilePic!);
}
// Update images for friendPics field, if one or more images are changed by user
List<String>? friendPicsIds;
if (formValue['friendPics'] != null) {
if (form.control('friendPics').dirty) {
if (formValue['friendPics'].isNotEmpty) {
// get set of ids of previous images, if any, in friendPics field
final idSet = prevPersonName.friendPics?.toSet();
// get a set of ids of images available in formValue urls after the update operation
final urlIds = <String>[];
formValue['friendPics']!.forEach((e) {
if (e.url != null) {
urlIds.add(AppwriteConstants.getIdFromImageUrl(e.url));
}
});
final urlIdsSet = urlIds.toSet();
// difference, idSet - urlSet, gives deleted images
if (idSet != null) {
Set deletedIdsSet = idSet.difference(urlIdsSet);
// remove these images from Appwrite storage
deletedIdsSet
.forEach((e) async => await _storageRepo.deleteImage(e));
}
// now add new files, if any, in formValue 'files' to Appwrite storage
// first create a list of files added
final newImageFiles = <io.File>[]; // we can use XFile here as well
formValue['friendPics'].forEach((e) {
if (e.file != null) newImageFiles.add(e.file);
});
// upload these files to Appwrite storage and get image ids
final List<String> newImageIds =
await _storageRepo.uploadImages(newImageFiles);
// prepare list of ids of all images (previous images not yet deleted and new images added)
friendPicsIds = [...urlIds, ...newImageIds];
} else {
// delete images, if any, present earlier
prevPersonName.friendPics!
.forEach((e) => _storageRepo.deleteImage(e));
friendPicsIds = [];
}
} else {
friendPicsIds = prevPersonName.friendPics;
}
} else {
friendPicsIds = null;
}
// set new image ids for image field
final newPersonName = personName.copyWith(
profilePic: profilePicIds,
friendPics: friendPicsIds,
);
final res = await _personNameRepo.updatePersonName(newPersonName);
state = false;
This code works correctly for adding new files, removing exising files, and any combination as well.
'You could probably try this https://github.com/djangoflow/reactive_forms_widgets'
@adar2378:
Will you please guide me how can I use reactive_image_picker from this repo?
I have tried to add entire repo as:
reactive_forms_widgets:
git:
url: https://github.com/djangoflow/reactive_forms_widgets
However, I am unable to use reactive_image_picker
from this repo.
On the other hand, if I use
reactive_image_picker:
git:
url: https://github.com/djangoflow/reactive_forms_widgets/tree/master/packages/reactive_image_picker
it results in github error
fatal: repository 'https://github.com/djangoflow/reactive_forms_widgets/tree/master/packages/reactive_image_picker/' not found
exit code: 128
exit code 69
I am using ReactiveForms along with ReactiveImagePicker and several other Reactive packages for the implementation of an application involving several forms. I am also using Freezed, JsonSerialization, and Riverpod packages and AppWrite for backend.
In ReactiveForms using ReactiveImagePicker, I am unable to pass 'PersonName' model class object to Reactive Form widget's form.value (for the editing mode) due to use of different types in model class and the form group (String? type in model class and List\<SelectedFile> type in form group) for the field using ReactiveImagePicker.
Extract of related code in my app is given below to understand the exact problem:
The model class file (PersonName) contains other fields besides profilePic, of type String?, which is used to store the id or Url of image file stored in Appwrite Storage bucket:
The FormGroup for this class is like this:
The main problem is how to convert PersonName object to form.value data (in form display widget) as the form.value has field of type List\<SelectedFile> whereas PersonName has the corresponding model class field is of type String?.
For this I am using PersonNameToFormValue (and corresponding PersonNameFromFormValue) functions:
'profilePic' field in above function uses condition to pass either a File or Url to display widget image in either read only or editing mode.
The line using 'url:' displays the image correctly in readonly mode. However, the line using 'file:' gives the following error:
Will you please help in this regard?