danialfarid / ng-file-upload

Lightweight Angular directive to upload files with optional FileAPI shim for cross browser support
MIT License
7.87k stars 1.59k forks source link

ngf-select doing nothing in cordova/webview on Android 4.4.2 #1696

Open Arxi opened 8 years ago

Arxi commented 8 years ago

I use the following code:

<div class="button"
     ngf-select
     ng-model="files"
>
    Choose a file
</div>

but I also tried

<button class="button"
        type="file" 
        ngf-select="personal.uploadFiles($file, $invalidFiles, personal.photoFileInfo)"
        accept="image/*"
>
    Choose a file
</button>

and many other variations.

The problem I can't get any response when tapping them on physical device (Android tablet) while using cordova/ionic - no dialog appears, no error in console, simply nothing.

When I try opening the Demo page from Chrome/Browser/Firefox on the same tablet, the file picker buttons work fine.

This is my setup:

tablet Asus K012, Android 4.4.2 ng-file-upload 12.2.8 Cordova 6.1.1 Ionic 1.7.15

I also noticed that simple <input type="file" /> does not work in the webview - which seems to be an unresolved Android 4.4 issue. Could this be related?

Arxi commented 8 years ago

Update: after installing latest crosswalk, the file picker button reacts and I can select a file, but it seems it returns empty object {} in callback. I'll investigate some more

Arxi commented 8 years ago

So, update: it seems the object returned in callback isn't empty, and thus ngf-select works as expected while using latest crosswalk.

I haven't found any answer to the question why the button doesn't do anything while not using crosswalk.

graemedownes commented 8 years ago

Which version of Crosswalk did you use? I'm trying with v2.1.0 and I'm getting a "Choose an action" popup with the message "No apps can perform this action"

Arxi commented 8 years ago

Currently using v2.1.0 as well and everything seems to be in order. Have you used any accept or ngf-accept attributes? I think these might have an effect on the selection in the "Choose an action" popup.

WuglyakBolgoink commented 7 years ago

Hi, I buld my Cordova App on Android:

now on my Android 4.4 with Crosswalk 22.52.561.4 the button work, but I have dialog:

Choose an action
-----------------
No apps can perform this action.

On Android 6.x -> nothing.... without crosswalk button worked fine... now only popup:

Choose an action
-----------------

open dialog was fixed with adding "cordova-plugin-file"

Arxi commented 7 years ago

Were you able to fix this? On Android 6.x this migt be caused by ngf-file-upload not asking for permissions. I have not yet found a way to overcome this.

WuglyakBolgoink commented 7 years ago

Hi @Arxi on Android 6.0+ you must ask permissions before click

(https://github.com/NeoLSN/cordova-plugin-android-permission)

Arxi commented 7 years ago

Thanks @WuglyakBolgoink which permissions did you have to ask for?

demirag commented 7 years ago

I think this bug has to be solved in ng-file-upload itself. I have added

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<preference name="AndroidPersistentFileLocation" value="Compatibility" />

Also tried to install "cordova-plugin-file", nothing worked. Somehow permission diolag does not fire thus I am getting error

E/XWalkViewInternal: Unable to create Image File, please make sure permission 'WRITE_EXTERNAL_STORAGE' was added.

If I go to android settings-> myapp -> storage and manually enable it, then it works as expected.

Has anyone solved the problem? @WuglyakBolgoink answer is right but I do not want to control this myself on code just for android. My code works for ios, browser but not on android.

EDIT: I have tried also WuglyakBolgoink solution but it also does not help, permissions.hasPermission(permissions.WRITE_EXTERNAL_STORAGE,...) returns true, so still user has to go and manually change this setting.

Arxi commented 7 years ago

As for the Android 6 permission problem, I haven't found any other way than to handle this manually. So in the end, I used this plugin: https://github.com/NeoLSN/cordova-plugin-android-permission

I also have cordova-plugin-file and cordova-plugin-file-transfer installed which I use for other purposes - it's possible it can affect this.

I have a method which checks whether the app has WRITE_EXTERNAL_STORAGE permission, and if not, prompts user to grant it.

Here's my solution. Note: I haven't used it in production yet, it may contain bugs.

function requestPermission() {
    if (!ionic.Platform.isAndroid()) {
        return $q.when();
    }

    var deferred = $q.defer();

    var permissionErrorCallback = function(reason) {
        deferred.reject(reason);
    };

    var permissions = cordova.plugins.permissions;

    permissions.hasPermission(
        permissions.WRITE_EXTERNAL_STORAGE,

        // hasPermission success
        function(status) {
            console.log("WRITE_EXTERNAL_STORAGE: " + status.hasPermission);

            if (status.hasPermission) {
                console.log("WRITE_EXTERNAL_STORAGE permission already granted, doing nothing");
                deferred.resolve();
                return;
            }

            // so we don't have the permission - request it from the user
            permissions.requestPermission(
                permissions.WRITE_EXTERNAL_STORAGE,

                // requestPermission success
                function(status) {
                    if(!status.hasPermission) { 
                        permissionErrorCallback("Permission not granted");
                    } else {
                        deferred.resolve();
                    }
                },

                // requestPermission error. How can this happen?
                function() {
                    permissionErrorCallback("requestPermission itself failed!");
                }
            );
        }, 

        // hasPermission error. How can this happen?
        function() {
            permissionErrorCallback("hasPermission itself failed!");
        }
    );

    return deferred.promise;
}

Now, I have this method in my own service, mine is called fileService. I can call it when convenient, for example when entering a controller which handles some files. But even more convenient is to use a directive which you can attach to the same element you have ngf-select on. That way, the permission will be checked and requested (if needed) every time the button is rendered. Something like this:

angular
    .module('YourModuleName')
    .directive('fileUpload', fileUploadDirective);

/* @ngInject */               
function fileUploadDirective(
    lodash,
    fileService,
    notificationService
) {
    var debouncedAskForPermission = lodash.debounce(askForPermission, 2000, { "leading" : true, "trailing" : false });

    // check storage permissions and request them if needed
    function askForPermission() {
        fileService.requestPermission() // this is the call to the method described higher up
        .then(function() {
            console.log("Storage permissions OK");
        })
        .catch(function(reason) {
            notificationService.showGenericToast("Please allow this app to manipulate your Storage to enable more functionality");
        });
    }

    return {
        restrict: 'EA',
        link: function(scope, element, attrs, ctrl) {
            // check storage permissions and request them if needed
            debouncedAskForPermission();
        }
    };
}

And this is how the directive is used in template:

<button class="button button-block button-positive"
        file-upload
        ngf-select="generalTravel.storeFile($file, $invalidFiles, generalTravel.healthFileHolder)"
>
    Upload Copy
</button>

So in conclusion, the directive just checks for the permission. If the app has the permission, it does nothing; if it doesn't, it prompts the user. If the user declines or something bad happens, it throws a toast - notificationService is another of my services for showing various notifications to user.

I use ng-lodash for debouncing, so that the check is not done too often and the user is not flooded with requests for permission or toasts. This is vital especially if you plan to use multiple elements with this directive in one template. I guess you could create your own helper for debounce if you do not wish the whole lodash/underscore library.

WuglyakBolgoink commented 7 years ago

@demirag

EDIT: I have tried also WuglyakBolgoink solution but it also does not help, permissions.hasPermission(permissions.WRITE_EXTERNAL_STORAGE,...) returns true, so still user has to go and manually change this setting. @Arxi

What the problem have you now? If you use Cordova -> install cordova-plugin-android-permission plugin. Check in manifest READ-*/WRITE_EXTERNAL_STORAGE permissions. and do check before open file this permissions.

All work on my app.