Download a file from a URL to a user accessible location
Asked Answered
F

6

5

I am building an app using Nativescript/Angular 2

I want to be able to download a file from a URL and save it to the device in a location the average user would have no problems finding it. I believe downloads would be the best place for this on both iOS and Android. Please correct me if I am wrong.

The file can be any file type, not just an image. So mainly spreadsheet, word document, pdf, png, jpg, etc.

I have searched online and through the documentation. The documentation describes a method called getFile which gets a file and saves it to your device.

I have implemented this in my code as follows:

download (id) {
  console.log('Download Started');
  getFile("https://raw.githubusercontent.com/NativeScript/NativeScript/master/apps/tests/logo.png").then(function (r) {
    console.log(r.path);
  }, function (e) {
    //// Argument (e) is Error!
  });
}

The problem with this is that it saves it to a non-user accessible location such as:

/data/user/0/com.myapp.example/files/logo.png

Update:

I have also tried specifying the path directly with:

fs.knownFolders.documents();

However, this method gets the documents folder for the current application that is NOT accessible by the user or external applications

Fecundity answered 13/12, 2016 at 16:7 Comment(0)
S
15

After some unsuccessful attempts, I finally found how to save file to user "Downloads" folder (something like sdcard/Download). You can use android.os.Environment method to get this folder.

Add this in your component:

import { getFile } from 'tns-core-modules/http';
import * as fileSystem from "tns-core-modules/file-system";
import { isAndroid } from "tns-core-modules/platform";
import { alert } from "tns-core-modules/ui/dialogs";

declare var android;

<...>

public download (url, fileName) {
    if (isAndroid) {
        const permissions = require("nativescript-permissions");
        permissions.requestPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE, "I need these permissions because I'm cool")
            .then(() => {
                let downloadedFilePath = fileSystem.path.join(android.os.Environment.getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(), fileName);
                getFile(url, downloadedFilePath).then(resultFile => {
                    alert({
                        title: 'Saved!',
                        okButtonText: 'OK',
                        message: `File saved here:\n${resultFile.path}`
                    });
                }, error => {
                    alert({
                        title: 'Error',
                        okButtonText: 'OK',
                        message: `${error}`
                    });
                });
            });
    }
}

What else you should know:

1) There is no any kind of download indicator, standard system download bar also not appears, and I don't know how to solve this.

2) For iOS you may try to use

const filePath = fileSystem.path.join(fileSystem.knownFolders.ios.downloads().path, fileName);
getFile(url, filePath).then((resultFile) => {}, (error) => {});

I think, it's the shame that NS docs don't talk straight, that you can't save files in user accessible location only with NS functions. I figured it out only when I read comments in file /node_modules/tns-core-modules/file-system/file-system.d.ts

Hope this helps you.

Sceptic answered 20/11, 2018 at 18:54 Comment(0)
V
2

To get it working on iPhone, you can do the following (TypeScript):

import { knownFolders, path } from "tns-core-modules/file-system";

let destination = path.join(knownFolders.documents(), "file_name.txt");
// logic to save your file here ...
// the important thing is that you have to save your file in knownFolders.documents()

Then in Info.plist, you have to add the following permissions:

<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>UIFileSharingEnabled</key>
<true/>

Now if you go to your iPhone's Files app > On My iPhone > Your App's Name, you should see the file there.

Basically, the Documents folder is a private folder inside your application's directory that only you can see. However, when you enable the two permissions above, it allows file sharing so that your user can access the folder and its contents.

Voodooism answered 14/1, 2021 at 18:47 Comment(0)
D
1

The same documentation says that you can specify the file location like this:

download (id) {
  console.log('Download Started');
  var folder = fs.knownFolders.documents();
  var file = fs.path.join(folder.path, "logo.png");
  var url = "https://raw.githubusercontent.com/NativeScript/NativeScript/master/apps/tests/logo.png"
  getFile(url, file).then(function (r) {
    console.log(r.path);
  }, function (e) {
    //// Argument (e) is Error!
  });
}

disclaimer: never tried it myself, just read the docs ...

Dich answered 13/12, 2016 at 17:26 Comment(4)
I was aware of this, the fs.knownFolders.documents() method gets the documents folder for the current application that is NOT accessible by the user or external applications.Fecundity
Not being able to specify the file location and the location being private to the application seem like two different problems to me. In any case, you just have to specify a public path instead of using fs.knownFolders.documents(). I think fs.knownFolders.ios.downloads()should give you the downloads folder of ios, but there's no equivalent for android or others, so you'll have to build your ownDich
I have also tried that path on ios, doesnt work. This is the reason I am posting on stack overflow for some help.Fecundity
You might have to handle each platform separately - relatively easy with {N}. On the android the only other publicly accessible folder are the SD card (internal and external) storage folders.Jointer
K
1

You can specify a filesystem path directly, like this:

var folder = fs.Folder.fromPath('/sdcard/Download');

Note that /sdcard/Download will only work on Android; you can replace it with whatever (publicly accessible) folder you want to save your data to.

There doesn't yet seem to be a cross-platform way to choose a folder path, so you'll have to work out something manually. See this GitHub thread for more.

Katabatic answered 21/10, 2018 at 14:55 Comment(0)
A
1

I know this thread is 3 years ago but in case you have the same issue, I hope this solution will save time for you.

I solved the same issue by adding android:requestLegacyExternalStorage="true" inside the AndroidManifest.xml file follow the thread here

Asia answered 17/7, 2020 at 1:31 Comment(0)
B
0

I realize that this is an older thread, but perhaps this can help someone:

If you use currentApp(), instead of documents(), you can access the folder you need. For example:

var directories = fs.knownFolders.currentApp();
var folder = directories.getFolder('./nameofaccessiblefolder');
Backman answered 1/9, 2018 at 20:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.