Flutter/Dart overwriting File in getApplicationDocumentsDirectory not working immediately - dart

I'm trying to overwrite a file in my apps temporary directory but for some reason the overwrite is not taking effect until I fully hot restart my app.
I'm trying to set my _pickedImage variable to the new changedImage once it has been copied to the directory, however when using setState it is always keeping the first image that was placed into the directory and not overwriting it each time. So when I display the _pickedImage it will always show the first initial image until i fully restart, once i fully restart the app the change is taking place. The reason for wanting to do this is so that users can effectively change the image if they wish. Hope this makes sense any help would be massively appreciated
var image = await ImagePicker.pickImage(source: source, maxWidth: 800.0);
if (image != null) {
final Directory extDir = await getApplicationDocumentsDirectory();
final String dirPath = '${extDir.path}/image';
if (Directory(dirPath).existsSync()) {
print('it exists');
var dir = new Directory(dirPath);
dir.deleteSync(recursive: true);
if (Directory(dirPath).existsSync()) {
print('still exists');
} else {
//It is getting in here so seemingly its deleting the orignal directory
print('does not exist');
}
}
new Directory(dirPath).createSync(recursive: true);
String path =
'$dirPath/temporaryImage.jpg';
File changedImage = image.copySync(path);`
setState(() {
//this is where the problem lies
_pickedImage = changedImage;
});

Cache is the problem. See issue https://github.com/flutter/flutter/issues/24858
From documentation https://api.flutter.dev/flutter/painting/ImageProvider-class.html
ImageProvider uses the global imageCache to cache images.
You can use
import 'package:flutter/painting.dart'
// to clear specific cache
imageCache.evict(FileImage(processedImage));
// to clear all cache
imageCache.clear();
It did not directly solve issue I had, however, it was the key.

Related

How to enable annotation in PDFJS viewer

I am using PDFJS and the viewer. I do however have the problem that annotation are not shown correctly like the are in the pdfs demo viewer https://mozilla.github.io/pdf.js/web/viewer.html.
Annotation correctly displayed in pdfs demo viewer:
Here is now it is displayed in my app using Chrome:
Here is how it is displayed I Safari using my app:
This is now I initialise the pdfs viewer:
function initPdfjs() {
// Enable hyperlinks within PDF files.
pdfLinkService = new (pdfjsViewer as any).PDFLinkService({
eventBus,
});
// Enable find controller.
pdfFindController = new (pdfjsViewer as any).PDFFindController({
eventBus,
linkService: pdfLinkService,
});
const container = document.getElementById('viewerContainer');
if (container) {
// Initialize PDFViewer
pdfViewer = new (pdfjsViewer as any).PDFViewer({
eventBus,
container,
removePageBorders: true,
linkService: pdfLinkService,
findController: pdfFindController,
});
// pdfViewer.textLayerMode = Utils.enableTextSelection() ? TextLayerMode.ENABLE : TextLayerMode.DISABLE;
pdfViewer.textLayerMode = TextLayerMode.ENABLE_ENHANCE;
// See https://github.com/mozilla/pdf.js/issues/11245
if (Utils.isIos()) {
pdfViewer.maxCanvasPixels = 4000 * 4000;
}
pdfLinkService.setViewer(pdfViewer);
return;
} else {
console.error(`getElementById('viewerContainer') failed`);
}
}
What do I need to do in order to get the annotations to display correctly in my app?
I got it working. I don't know if it is the right way, but I post it in case somebody can use it.
First I setup webpack to copy the content from ./node_modules/pdfjs-dist/web/images to my dist folder so the images got included. That solved all the display errors except {{date}}, {{time}}.
new CopyPlugin({
patterns: [
{ from: './node_modules/pdfjs-dist/web/images', to: '' },
{ from: './l10n', to: 'l10n' },
],
}),
To solve the {{date}}, {{time}} problem I set up a localisation service. I did that by copying the file ng2-pdfjs-viewer-master/pdfjs/web/locale/en-US/viewer.properties to ./l10n/local.properties in my project. Then it is copied to the dist folder by above webpack plugin. I then setup the l10n service in my pdfjs by adding this code:
// initialize localization service, so time stamp in embedded comments are shown correctly
l10n = new (pdfjsViewer as any).GenericL10n('en-US');
const dir = await l10n.getDirection();
document.getElementsByTagName('html')[0].dir = dir;
and added l10n to PDFViewer initialisation:
// Initialize PDFViewer
pdfViewer = new (pdfjsViewer as any).PDFViewer({
eventBus,
container,
removePageBorders: true,
linkService: pdfLinkService,
findController: pdfFindController,
l10n,
});
And now annotations is shown correctly:
What I find a bit weird is the date format. I used en-US as locale, so I would expect it to be mm/dd/yyyy (American way), but it is dd/mm/yyyy (like a dane would prefer it). I have tried to fool around with the date settings on my Mac and language settings in Chrome, but it doesn't look like it has any effect, so I don't know what to do if an American customer complains.

FileProvider: "CopyItem()" is called twice -> error (FTP download)

The first view of my app (Swift 5, Xcode 10, iOS 12) has a "username" TextField and a "login" Button. Clicking on the button checks if there's a file for the entered username on my FTP server and downloads it to the Documents folder on the device. For this I'm using FileProvider.
My code:
private func download() {
print("start download") //Only called once!
let foldername = "myfolder"
let filename = "mytestfile.txf"
let server = "192.0.0.1"
let username = "testuser"
let password = "testpw"
let credential = URLCredential(user: username, password: password, persistence: .permanent)
let ftpProvider = FTPFileProvider(baseURL: server, mode: FTPFileProvider.Mode.passive, credential: credential, cache: URLCache())
ftpProvider?.delegate = self as FileProviderDelegate
let fileManager = FileManager.default
let source = "/\(foldername)/\(filename)"
let dest = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent(filename)
let destPath = dest.path
if fileManager.fileExists(atPath: destPath) {
print("file already exists!")
do {
try fileManager.removeItem(atPath: destPath)
} catch {
print("error removing!") //TODO: Error
}
print("still exists: \(fileManager.fileExists(atPath: destPath))")
} else {
print("file doesn't already exist!")
}
let progress = ftpProvider?.copyItem(path: source, toLocalURL: dest, completionHandler: nil)
progressBar.observedProgress = progress
}
I'm checking if the file already exists on the device because FileProvider doesn't seem to provide a copyItem function for downloading that also lets you overwrite the local file.
The problem is that copyItem tries to do everything twice: Downloading the file the first time succeeds (and it actually exists in Documents, I checked) because I manually delete the file if it already exists. The second try fails because the file already exists and this copyItem function doesn't know how to overwrite and of course doesn't call my code to delete the original again.
What can I do to fix this?
Edit/Update:
I created a simple "sample.txt" at the root of my ftp server (text inside :"Hello world from sample.txt!"), then tried to just read the file to later save it myself. For this I'm using this code from the "Sample-iOS.swift" file here.
ftpProvider?.contents(path: source, completionHandler: {
contents, error in
if let contents = contents {
print(String(data: contents, encoding: .utf8))
}
})
But it also does this twice! The output for the "sample.txt" file is:
Optional("Hello world from sample.txt!")
Fetching on sample.txt succeed.
Optional("Hello world from sample.txt!Hello world from sample.txt!")
Fetching on sample.txt succeed.
Why is it calling this twice too? I'm only calling my function once and "start download" is also only printed once.
Edit/Update 2:
I did some more investigating and found out what's called twice in the contents function:
It's the whole self.ftpDownload section!
And inside FTPHelper.ftpLogin the whole self.ftpRetrieve section is
called twice.
And inside FTPHelper.ftpRetrieve the whole self.attributesOfItem
section is called twice.
And probably so on...
ftpProvider?.copyItem uses the same ftpDownload func, so at least I know why both contents() and copyItem() are affected.
The same question remains though: Why is it calling these functions twice and how do I fix this?
This isn't an answer that shows an actual fix for FileProvider!
Unfortunately the library is pretty buggy currently, with functions being called twice (which you can kind of prevent by using a "firstTimeCalled" bool check) and if the server's slow(-ish), you also might not get e.g. the full list of files in a directory because FileProvider stops receiving answers before the server's actually done.
I haven't found any other FTP libraries for Swift that work (and are still supported), so now I'm using BlueSocket (which is able to open sockets, send commands to the server and receive commands from it) and built my own small library that can send/receive,... files (using the FTP codes) around it.

Does Xam.Plugin.Media Clean up the images it leaves in the app directory?

I can't seem to find an answer to this. If I am taking a photo on iOS and I supply a directory and name, what happens to the photo if I don't do anything with it.
I see the path leads /var/mobile/Containers/Data/Application/0727A3EF-A10B-416D-B9C2-6EDC4EC4AB42/Documents/WorkingImages/BaseImage_5.jpg
What happens to BaseImage_5.jpg? Am I responsible for deleting it?
Code:
var imageFile = await Current.TakePhotoAsync(new StoreCameraMediaOptions()
{
Directory = "WorkingImages",
Name = "BaseImage",
CompressionQuality = 92,
PhotoSize = PhotoSize.Large
});

SetUbiquitous showing 'file already exists' for file that doesn't

In AppDelegate.swift, on first launch, the intent is to place some sample docs in the local Documents folder, or in the iCloud Documents folder if iCloud is enabled.
var templates = NSBundle.mainBundle().pathsForResourcesOfType(AppDelegate.myExtension, inDirectory: "Templates")
dispatch_async(appDelegateQueue) {
self.ubiquityURL = NSFileManager.defaultManager().URLForUbiquityContainerIdentifier(nil)
if self.ubiquityURL != nil && templates.count != 0 {
// Move sample documents from Templates to iCloud directory on initial launch
for template in templates {
let tempurl = NSURL(fileURLWithPath: template)
let title = tempurl.URLByDeletingPathExtension?.lastPathComponent
let ubiquitousDestinationURL = self.ubiquityURL?.URLByAppendingPathComponent(title!).URLByAppendingPathExtension(AppDelegate.myExtension)
// let exists = NSFileManager().isUbiquitousItemAtURL(ubiquitousDestinationURL!)
do {
try NSFileManager.defaultManager().setUbiquitous(true, itemAtURL: tempurl, destinationURL: ubiquitousDestinationURL!)
}
catch let error as NSError {
print("Failed to move file \(title!) to iCloud: \(error)")
}
}
}
return
}
Before running this, I delete the app from the device and make sure no doc of that name is in iCloud. On first launch, without iCloud, the sample docs copy properly into the local Documents folder. With iCloud, this code runs, and the setUbiquitous call results in an error that says the file already exists. The commented call to isUbiquitousItemAtURL also returns true.
What might be making these calls register that a file exists that I'm pretty sure doesn't? Thank you!
The file already exists, so just replace it
The primary solution...in all the trial and error, I'd forgotten to put "Documents" back in the url. Should be:
let ubiquitousDestinationURL = self.ubiquityURL?.URLByAppendingPathComponent("Documents").URLByAppendingPathComponent(title!).URLByAppendingPathExtension(AppDelegate.myExtension)
Without that, wrote the file to the wrong directory, and so I couldn't see it by normal means.

Titanium / How to temporarily store a photo to use in a later function

Titanium / for an iOS App:
How can I manage to take a photo, and then use this one later in a new function to for example show the photo, and put a slightly larger duplicate of it with a transparency on top of itself?
Note that I tried to edit the answer from Mitul Bhalia with the following, but the edit got knocked back. So here's how you do it:
After taking the image, you can store it as a variable in the global object, Alloy.Globals. You can then access this else where or later on in your app.
For example:
takePhotoButton.addEventListener('click', function(){
Titanium.Media.showCamera({
success:function(event) {
if(event.mediaType === Ti.Media.MEDIA_TYPE_PHOTO) {
// Store the file in a variable
var image = event.media;
// Store the image in the global object
Alloy.Globals.temporaryImage = image;
} else {
alert("got the wrong type back ="+event.mediaType);
}
},
...
And somewhere else in your app, after the image has been stored, for example:
var anImage = Ti.UI.createImageView({ image: Alloy.Globals.temporaryImage })
Also note that extensive use of the global object can cause memory issues, so try not to overdo it.
If you want to save photo then you can use Alloy.Globals to save data globally so you can use it later.
Alloy.Globals.photo = blob object;
Here is how I am currently solving my problem:
takePhotoButton.addEventListener('click', function(){
Titanium.Media.showCamera({
success:function(event) {
if(event.mediaType === Ti.Media.MEDIA_TYPE_PHOTO) {
// Store the file in a variable
var image = event.media;
var filename = 'myPhoto.jpg';
takenPhoto = Titanium.Filesystem.getFile(Titanium.Filesystem.applicationDataDirectory, filename);
takenPhoto.write(image);
} else {
alert("got the wrong type back ="+event.mediaType);
}
},
...
and then later I can use takenPhoto to show the picture I have taken with the camera.
But I do not really know if this is the best way and if it is even correct, as I do not use 'var' to initiate takenPhoto. But if I use 'var' I cannot use takenPhoto outside of the function.

Resources