UWP open pdf fails when publish - xamarin

i have this methods working fine when i'm on debug and on my pc:
public void ShowPdf(byte[] pdfInfo)
{
...
Device.BeginInvokeOnMainThread(async () =>
{
var intentHelper = DependencyService.Get<IIntentHelper>();
intentHelper.File(pdfInfo);
});
}
And the dependency service like that:
[assembly: Xamarin.Forms.Dependency(typeof(IntentHelperUWP))]
namespace myApp.UWP
{
class IntentHelperUWP : IIntentHelper
{
public async Task FileAsync2(byte[] array)
{
var baseUrl = DependencyService.Get<IBaseUrl>().Get();
StorageFolder storageFolder = ApplicationData.Current.LocalFolder;
StorageFile pdfFile = await storageFolder.CreateFileAsync("test.pdf", CreationCollisionOption.ReplaceExisting);
//write data to created file
await FileIO.WriteBytesAsync(pdfFile, array);
//get asets folder
StorageFolder appInstalledFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
StorageFolder assetsFolder = await appInstalledFolder.GetFolderAsync("Assets");
//move file from local folder to assets
await pdfFile.MoveAsync(assetsFolder, "test.pdf", NameCollisionOption.ReplaceExisting);
Device.BeginInvokeOnMainThread(async () =>
{
Windows.System.LauncherOptions options = new Windows.System.LauncherOptions();
options.DisplayApplicationPicker = true;
options.ContentType = "application/pdf";
Windows.System.Launcher.LaunchFileAsync(pdfFile);
});
}
Why it's working fine in debug with visual studio but not when i publish? i tried to publish release and debug, look if the pdf is set to content and copy all in properties, but every time i publish and test, the button to download pdf does nothing, but in my debug open the Adode reader with the PDF. Some hints of what i can do or test?

Once packaged the install folder is read only. You cannot copy the file into the assets folder. This is the same in UWP, iOS, and Android.
You’ll need to launch it from the app data location you create it in rather than trying to move it into the installed package.
It works when debugging without publishing because the unpackaged app has full access to its working directory.

Related

NativeScript: How to copy a file from an apps folder to a user accessible folder?

I want to copy storage.db to documents or downloads folder. It's very easy to get the file path:
const filePath = application.android.context.getDatabasePath("storage.db").getAbsolutePath();
But, what isn't that easy is to copy that file to a folder users have access to. I searched this whole forum, and I found nothing useful for my case.
I'm using NativeScript 4.0.1 with vanilla JS.
If you want to share the DB file, the easiest way is to use nativescript-share-file plugin, send the file path and it will give you a nice dialog with intent picker, user may choose to Email the file Or save it to local folder etc.,
const shareFile = new ShareFile();
shareFile.open({
path: filePath,
});
I finally found the solution. I've seen so many users trying to achieve this, and I hope this will help all of you.
Add this to your AndroidManifest.xml:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Install nativescript-permissions:
npm i nativescript-permissions
Asking for permission:
const permissions = require('nativescript-permissions');
permissions.requestPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE, "");
Require the necessary modules:
const fileSystemModule = require("tns-core-modules/file-system");
const application = require("application");
Then, create this function where you need to use it:
function copyFile() {
var myInput = new java.io.FileInputStream(appModule.android.context.getDatabasePath("storage.db").getAbsolutePath());
var myOutput = new java.io.FileOutputStream("/storage/emulated/0/databases/storage.db");
try {
var buffer = java.lang.reflect.Array.newInstance(java.lang.Byte.class.getField("TYPE").get(null), 1024);
var length;
while ((length = myInput.read(buffer)) > 0) {
myOutput.write(buffer, 0, length);
}
}
catch (err) {
console.info("Error", err);
}
//Close the streams
myOutput.flush();
myOutput.close();
myInput.close();
}
exports.copyFile = copyFile;
In my case, the file storage.db will be copied to /storage/emulated/0/databases. If you need to create a folder, just do the following:
try {
var javaFile = new java.io.File("/storage/emulated/0/newfolder");
if (!javaFile.exists()) {
javaFile.mkdirs();
javaFile.setReadable(true);
javaFile.setWritable(true);
}
}
catch (err) {
console.info("Error", err);
}
If the destination folder has a file with the same name as the one you want to copy, you need to remove it first. That's why you should create a specific folder to guarantee it's empty.

How to show all images from a folder in Xamarin Cross-Platform app?

I am using VS 2017 to create a cross platform (UWP, Android, iOS) Xamarin app. I am trying to show all images from a folder on device as thumbnails (similar to gallery app, sample screenshot attached).
I have looked into WrapLayout sample code provided on Xamarin website (Link), but it's loading all images from internet using JSON
protected override async void OnAppearing()
{
base.OnAppearing();
var images = await GetImageListAsync();
foreach (var photo in images.Photos)
{
var image = new Image
{
Source = ImageSource.FromUri(new Uri(photo + string.Format("?width={0}&height={0}&mode=max", Device.OnPlatform(240, 240, 120))))
};
wrapLayout.Children.Add(image);
}
}
async Task<ImageList> GetImageListAsync()
{
var requestUri = "https://docs.xamarin.com/demo/stock.json";
using (var client = new HttpClient())
{
var result = await client.GetStringAsync(requestUri);
return JsonConvert.DeserializeObject<ImageList>(result);
}
}
I have also looked into Xamarin Media Plugin (Link), but it shows only one image at a time. Sample code -
await CrossMedia.Current.Initialize();
var file = await CrossMedia.Current.PickPhotoAsync();
if (file == null)
return;
MainImage.Source = ImageSource.FromStream(() =>
{
var stream = file.GetStream();
file.Dispose();
return stream;
});
But I am unable to find a way to implement these two (or any other methods) in such a way that I can create my own gallery section in my app.
You need to create an Activity in your specific platform. This activity will be launched as an intent throught your PCL project using, for instance, Dependency Services.
In this custom Activity you should have a GridView which fills its source from the current directory if the file fits your restrictions, such a specific extension, size, etc.
Finally, to get the selected image you just send the image path or whatever you need to the PCL project with DependencyService.

Is it possible to drag and drop a UWP StorageFile to desktop, created using CreateStreamedFileFromUriAsyc

The goal:
What I want to do is drag a GridViewItem from a GridView in a UWP app outside of the app to the desktop explorer. During the drop event, I want to download a file from the internet and create a StorageFile that will be used to populate the DataPackage. I want this StorageFile to be copied to the desktop. Unfortunately, when a deferral is used for UWP Drag&Drop (using SetDataProvider), as soon as you leave the app window, the request is activated and you have to populate the DataPackage with the object to be transferred. So, it seemed to me that I would need to use a deferred type of StorageFile created with CreateStreamedFileFromUriAsyc.
I do not want to pre-download the data every time I start to do a drag and drop operation. I only want to download the data when I'm actually dropping it somewhere legitimate to copy.
I know how to drag and drop a pre-existing StorageFile from UWP to Explorer (desktop) using a deferred request.
I also know how to create a StorageFile using CreateStreamedFileFromUriAsyc that will gives you a StorageFile that downloads data only when the data is requested.
When I try to combine these two ideas, windows Explorer gives me the error 'interface is not supported.'.
If I use the exact same code, but just get the file contents by calling something like GetBasicPropertiesAsync during the deferred drag handler, it works only if I hold the drag over the desktop until the file is downloaded. I can see it finishing when the drag icon changes from the 'prohibited' icon to 'copy'. If I let go of the mouse button before it is done, no copy will occur and no errors are raised.
Obviously, I would like the drag and drop to download without having to manually start it on the deferred handler. Any ideas? Is this possible?
(Yes, I realize the code to create the correct file extension is wrong/incomplete, but that's irrelevant here...)
//DragStarted Handler in constructor
DragItemsStartedCommand = ReactiveCommand.Create<DragItemsStartingEventArgs>((e) =>
{
_dragItems = e.Items.Cast<ItemViewModel>();
e.Data.Properties.Title = "Transfer file";
e.Data.Properties.Description = "desscription of transfering a file";
e.Data.Properties.FileTypes.Add(StandardDataFormats.StorageItems);
e.Data.SetDataProvider(Windows.ApplicationModel.DataTransfer.StandardDataFormats.StorageItems, OnDeferredStorageFileRequestedHandler);
e.Data.RequestedOperation = DataPackageOperation.Copy;
});
//Deferred request handler
async void OnDeferredStorageFileRequestedHandler(DataProviderRequest request)
{
DataProviderDeferral deferral = request.GetDeferral();
try
{
Task<IEnumerable<StorageFile>> task = null;
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
task = DownloadStorageFiles();
});
var result = await task;
request.SetData(result);
}
catch (Exception ex)
{
// Handle the exception
}
finally
{
deferral.Complete();
Debug.WriteLine("deferral complete!!!");
}
}
//Create StorageFile with deferred loading Task
async Task<IEnumerable<StorageFile>> DownloadStorageFiles()
{
List<StorageFile> storageItems = new List<StorageFile>();
foreach (var item in _dragItems)
{
var request = new RestSharp.RestRequest();
var defaultItemType = ItemType.MSWord;
switch (item.MimeTypeTranslated)
{
case ItemType.GoogleDocument:
case ItemType.GoogleSpreadsheet:
case ItemType.GooglePresentation:
case ItemType.GoogleDrawing:
case ItemType.GoogleScript:
request.Resource = $"files/{item.File.id}/export";
request.AddParameter("mimeType", Statics.ItemTypeDictionary.First(x => x.Value == defaultItemType).Key);
break;
default:
request.Resource = $"files/{item.File.id}";
request.AddParameter("alt", "media");
break;
}
string fileName = "";
if (item.File.name.EndsWith($".{Statics.ItemExtensionDictionary[defaultItemType]}"))
fileName = $"{item.File.name}";
else
fileName = $"{item.File.name}.{Statics.ItemExtensionDictionary[defaultItemType]}";
var uri = account.Client.GetAuthorizedUriForDownload(request);
var thumbnail = RandomAccessStreamReference.CreateFromFile(await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///StoreLogo.png")));
var storageFileDeferred = await StorageFile.CreateStreamedFileFromUriAsync(fileName, uri , thumbnail);
//var props = await storageFileDeferred.GetBasicPropertiesAsync();
storageItems.Add(file);
}
return storageItems;
}
GitHub repro of this problem:
https://github.com/limefrogyank/DragDeferredFileToDesktop
First page is a regular drag to desktop that works because StorageFile (+ underlying data) is already in assets folder
Second page shows the error generated when using a StorageFile that is created with CreateStreamedFileFromUriAsync.
Third page uses the same type of StorageFile, but with a hack to force the retrieval of data synchronously. Desktop freezes for a second until data is ready.
As of the recent Windows updates the drag n' drop of storage files on UWP apps is automatically handled by the system.
example gif.

Set Image.Source to file in external storage in Xamarin.Forms

I have a picture of item in external storage (that was saved by intent in my app). I want to display this picture in Image view in my shared project.
Image.Source takes object of ImageSource type. I tried ImageSource.FromFile, ImageSource.FromStream and even ImageSource.FromUri. The result is always that image is not displayed (no error or exception). I validated that the path to file is correct by first opening it with File.Open one line above.
What is the correct way of displaying pictures from normal storage, not from assets/resources/etc?
This code does not work:
var path = "/storage/emulated/0/Pictures/6afbd8c6-bb1e-49d3-838c-0fa809e97cf1.jpg" //in real app the path is taken from DB
var image = new Image() {Aspect = Aspect.AspectFit, WidthRequest = 200, HeightRequest = 200};
image.Source = ImageSource.FromFile(path);
Your Xamarin Forms PCL don't know what its a URI from Android beacuse its platform specific, so:
ImageSource.FromFile(path);
won't work.
In that case you are handling platform specific features, that is loading an image from Android.
I suggest this approach:
Create an interface on Xamarin Forms PCL like:
public interface IPhoto
{
Task<Stream> GetPhoto ();
}
Then in Android you implement that interface and register the implementation in DependencyService:
[assembly: Xamarin.Forms.Dependency(typeof(PhotoImplementation))]
namespace xpto
{
public class PhotoImplementation : Java.Lang.Object, IPhoto
{
public async Task<Stream> GetPhoto()
{
// Open the photo and put it in a Stream to return
var memoryStream = new MemoryStream();
using (var source = System.IO.File.OpenRead(path))
{
await source.CopyToAsync(memoryStream);
}
return memoryStream;
}
}
}
In the Xamarin Forms PCL code get the image:
var image = ImageSource.FromStream ( () => await DependencyService.Get<IPhoto>().GetPhoto());
For more detail you can consult this.
NOTE1: This will work on iOS if you implement the interface IPhoto too.
NOTE2: There exist a helpful library for this kind of features from Xamarin-Forms-Labs called Camera.
UPDATE (Shared Project solution)
As requested in the comments, to use this in a Shared Project instead of PCL we could do this.
1 - Place the IPhotoInterface in the Shared Project.
2 - Implement the interface in Android/iOS project:
public class PhotoImplementation : IPhoto
{
public async Task<Stream> GetPhoto()
{
// Open the photo and put it in a Stream to return.
}
}
3 - Use it in the Shared Project:
IPhoto iPhotoImplementation;
#if __ANDROID__
iPhotoImplementation = new shared_native.Droid.GetPicture();
#elif __IOS__
iPhotoImplementation = new shared_native.iOS.GetPicture();
#endif
var image = ImageSource.FromStream ( () => await iPhotoImplementation.GetPhoto());
NOTE: shared_native is the namespace of my solution and Droid/iOS are the projects for Android and iOS.

How to include resources to application for windows phone?

I have a problem: I created new c# project for windows phone (in VS 2013) and set test file property as "Copy if newer", but I cannot see file in emulator's Local folder. What do I do wrong?
More detailed:
Create app:
File->New->Project->Templates->Visual C#->Store Apps->Windows Phone Apps->Blank App (Windows Phone)
set test file property
run on emulator (there is a button for this) and list files with code:
async void listFolder()
{
StorageFolder local = Windows.Storage.ApplicationData.Current.LocalFolder;
Stack<StorageFolder> stack = new Stack<StorageFolder>();
stack.Push(local);
StorageFolder current;
string path;
byte[] bytes;
StorageFile logFile = await local.CreateFileAsync("log.txt", CreationCollisionOption.ReplaceExisting);
using (var s = await logFile.OpenStreamForWriteAsync())
{
while (stack.Count > 0)
{
current = stack.Pop();
foreach (StorageFolder f in await current.GetFoldersAsync())
{
stack.Push(f);
}
path = current.Path;
bytes = Encoding.UTF8.GetBytes(current.Path + "\n");
s.Write(bytes, 0, bytes.Length);
foreach (StorageFile f in await current.GetFilesAsync())
{
bytes = Encoding.UTF8.GetBytes(f.Path + "\n");
s.Write(bytes, 0, bytes.Length);
}
s.Flush();
}
}
}
Check file with Windows Phone Power Tools. Local folder contains log.txt only. Log contains Local directory and log file. No TestText.txt
How do I include file to application and access it on emulator?
Limitations:
I do need to held data on local storage (no web links, no cloud)
If you want to access files that come with your package, then you need to use Package.InstalledLocation, you won't find those files in ApplicationData.LocalFolder.
Note that files included in Package are read-only and you won't be able to write them.
Some more information you will also find at this answer.

Resources