locked
Uploading Via Webview RRS feed

  • Question

  • User19867 posted

    Hey Everyone!

    I am having trouble uploading a file from a android webview. I have followed all the examples on the web I could find and none of them seem to work properly inside of Xamarin. Natively they do seem to work.

    The problem comes with a crash using the IValueCallback after selecting a file in the File Chooser Intent. No matter how I call the IValueCallback it will crash. The file choose does show up properly and pass back a valid path to the selected image for javascript to use.

    I followed these tutorials before posting this question: http://stackoverflow.com/questions/5907369/file-upload-in-webview http://m0s-programming.blogspot.com/2011/02/file-upload-in-through-webview-on.html (simpler) http://stackoverflow.com/questions/9376142/mono-for-android-webview-input-field-filechooser-doesnt-work (Mono, this one seemed outdated since there is now a override in webview for OnShowFileChooser and the file chooser is indeed showing) and a few others. https://github.com/GoogleChrome/chromium-webview-samples/blob/master/input-file-example/app/src/main/java/inputfilesample/android/chrome/google/com/inputfilesample/MainFragment.java

    So here are the errors: • When I call the callback with the activity result as the above examples show it always says "android.net.uri cannot be converted to android.net.uri[]". • If I create a list out of the result it says it has already been completed and cannot be called again. • If I convert it to a string which someone on stack overflow suggested it says it is expecting a android.net.uri.

    Here is the code:

    `public override bool OnShowFileChooser (WebView webView, IValueCallback filePathCallback, FileChooserParams fileChooserParams) { if (mActivity == null) { return true; }

            if (mActivity is BaseActivity)
            {
                ((BaseActivity)mActivity).FILECHOOSER_CALLBACK = filePathCallback;
                Intent i = new Intent (Intent.ActionGetContent);
                i.AddCategory (Intent.CategoryOpenable);
                i.SetType ("image/*");
                mActivity.StartActivityForResult ( Intent.CreateChooser (i, "File Chooser"), ((BaseActivity)mActivity).FILECHOOSER_RESULTCODE );
                return false;
            } else {
                return true;
            }
        }`
    

    `protected override void OnActivityResult (int requestCode, Result resultCode, Intent data) { if(requestCode == FILECHOOSERRESULTCODE)
    {
    if (FILECHOOSER
    CALLBACK == null) { return; } if (resultCode == Result.Ok) { try{ Android.Net.Uri result = data == null || resultCode != Result.Ok ? null : data.Data; // throws a "bogey"

                        var a = new Android.Net.Uri[]{ result } ;
                        FILECHOOSER_CALLBACK.OnReceiveValue ( null );
    
                        // thows a string cannot be converted to android.net.uri[]
                        //FILECHOOSER_CALLBACK.OnReceiveValue ( (string)result  );
    
                        // throws a android.net.uri cannot be converted to  android.net.uri[]
                        //FILECHOOSER_CALLBACK.OnReceiveValue ( result  );
    
                        FILECHOOSER_CALLBACK = null;
                    }catch(Exception e) {
                        Console.WriteLine (e.Message);
                    }
                }
            }
        }`
    
    Tuesday, March 1, 2016 6:24 PM

All replies

  • User19867 posted

    Anyone?

    Friday, March 4, 2016 10:44 PM
  • User21404 posted

    Have you found the fix for this?

    Monday, August 1, 2016 6:34 PM
  • User19867 posted

    No, I couldn't find any help either. We did "get around it" for image uploads though.

    We took the chosen file and convert it to an android bitmap and upload that. We just never called the webview callback because its impossible to keep it from crashing.

    Once we have the android bitmap we upload it on the android side and send the javascript the image back to display so it sorta feels like the javascript is uploading! We just run a evaluate javascript on "OnPhotoUploadSuccess".

    protected override async void OnActivityResult (int requestCode, Result resultCode, Intent data) { if (resultCode != Result.Canceled) { if(requestCode == FILECHOOSER_RESULTCODE)
    {

                    if (resultCode == Result.Ok) {
                        try{
                            Android.Net.Uri imageUri = (data == null || resultCode != Result.Ok) ? null : data.Data;
                            Android.Graphics.Bitmap bitmap;
                            if(imageUri.Scheme.Equals("file")) {
                                bitmap = await AndroidTools.LoadLargeImage(imageUri.ToString());
                            } else {
                                bitmap = BitmapFactory.DecodeStream(ContentResolver.OpenInputStream(imageUri));
                            }
                            System.IO.MemoryStream memStream = new System.IO.MemoryStream();
                            bitmap.Compress(Bitmap.CompressFormat.Jpeg, 80, memStream);
                            if(memStream.Length > 1) {
                                CrowdHub_Tools.EvaluateJavascript("CrowdHub_Event.INSTANCE.showSpinner();", mTaggedWebView);
                                this.UploadPhoto(memStream.ToArray(), 128, 800, this.OnPhotoUploadSuccess, this.OnPhotoUploadError);
                            }
                        }catch(Exception e) {
                            System.Console.WriteLine (e.Message);
                        }
                    }
                }
            } else {
            }
        }
    
    Monday, August 1, 2016 6:53 PM
  • User19867 posted

    Forgot to add that we did not use the webview OnShowFileChooser overrides at all either. We send a message from javascript back to the C# side, so we aren't even really using a file upload form in the HTML.

    EmbeddableMessageReceived is a function we have in our framework to handle javascript communication, but the same can be achieved by just catching a url load attempt on a button click in javascript.

    protected override void EmbeddableMessageReceived (string aMessage, NameValueCollection aCollection) { #if ANDROID if (aMessage.Contains ("androidchooseimage")) { Intent i = new Intent (Intent.ActionGetContent); i.AddCategory (Intent.CategoryOpenable); i.SetType ("image/*"); this.StartActivityForResult ( Intent.CreateChooser (i, "File Chooser"), FILECHOOSER_RESULTCODE ); } #endif

            base.EmbeddableMessageReceived (aMessage, aCollection);
        }
    
    Monday, August 1, 2016 6:55 PM
  • User387410 posted

    This took a very long time, but i FINALLY got it to work successfully. The key is that the code below needs to go in the MainActivity. ``` private static int FILECHOOSER_RESULTCODE = 1; public IValueCallback mUploadMessage;

        protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
        {
            if (data != null)
            {
                if (requestCode == FILECHOOSER_RESULTCODE)
                {
                    if (null == mUploadMessage)
                    {
                        return;
                    }
    
                    mUploadMessage.OnReceiveValue(WebChromeClient.FileChooserParams.ParseResult((int)resultCode, data));
                    mUploadMessage = null;
                }
            }
        }
    

    ``` Then in the WebChromeClient you need to pass in the Main Activity, and set the callback (mUploadMessage) in the OnShowFileChooser delegate before calling the StartActivityForResult (shown below)

    ``` public class MyFormsWebviewClient : Android.Webkit.WebChromeClient { private MainActivity activity;

        public MyFormsWebviewClient(MainActivity context)
        {
            activity = context;
        }
    
        public override bool OnShowFileChooser(Android.Webkit.WebView webView, IValueCallback filePathCallback, FileChooserParams fileChooserParams)
        {
            activity.mUploadMessage = filePathCallback;
            Intent i = new Intent(Intent.ActionGetContent);
            i.AddCategory(Intent.CategoryOpenable);
            i.SetType("*/*");
    
            // The camera intent
            Intent captureIntent = new Intent(Android.Provider.MediaStore.ActionImageCapture);
    
            List<IParcelable> targetedShareIntents = new List<IParcelable>();
            targetedShareIntents.Add(captureIntent);
            captureIntent.AddCategory(Intent.ActionCameraButton);
    
            //add camera intent to the main intent (i)
            i.PutExtra(Intent.ExtraInitialIntents, targetedShareIntents.ToArray());
            activity.StartActivityForResult(Intent.CreateChooser(i, "File Chooser"), 1);
            return true;
        }
    }
    

    ```

    Then the WebviewRenderer will look like the below (to set the WebChromeClient on the webview)

    ``` public class DroidWebViewRenderer : WebViewRenderer { Context _context;

        public DroidWebViewRenderer(Context context) : base(context)
        {
            _context = context;
        }
    
        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
        {
            base.OnElementChanged(e);
    
            if (Control != null)
            {
                var newWebChromeClient = new MyFormsWebviewClient((_context as MainActivity));
                Control.SetWebChromeClient(newWebChromeClient);
            }
        }
    }
    

    ```

    Monday, July 15, 2019 9:37 PM
  • User384637 posted

    @nlapham21 great explanation! Just want to know, I try your code on emulator, all works well, but it seems no camera option for taking picture and upload it. Is there any additional code or setting required?

    Tuesday, October 8, 2019 4:39 AM
  • User178355 posted

    I have used the code from @nlapham21 , it's working but I have the same problem as @KhairulFLPM : no camera option for taking picture and upload it. Any news ?! I'm using Xamarin.Forms

    Monday, November 18, 2019 5:14 PM
  • User373022 posted

    This works for me : Don't forget to add camera,read,write permissions and include providerpaths in manifest to enable file sharing //In Webchrome client public override bool OnShowFileChooser(Android.Webkit.WebView webView, IValueCallback filePathCallback, FileChooserParams fileChooserParams) { activity.UpMessage = filePathCallback; Intent Fileintent = new Intent(Intent.ActionGetContent); Fileintent.AddCategory(Intent.CategoryOpenable); Fileintent.SetType("*/*"); var intents = new List < Intent > (); if (context.PackageManager.HasSystemFeature(PackageManager.FeatureCameraAny)) { Intent captureIntent = new Intent(Android.Provider.MediaStore.ActionImageCapture); var resolveinfolist = _context ? .PackageManager ? .QueryIntentActivities(captureIntent, 0); if (resolveinfolist != null && resolveinfolist.Count > 0) { var item = resolveinfolist[0]; var packagename = item.ActivityInfo ? .PackageName; var intent = new Intent(captureIntent); intent.SetComponent(new ComponentName(packagename, item.ActivityInfo.Name)); intent.SetPackage(packagename); intent.AddFlags(ActivityFlags.GrantReadUriPermission); intent.AddFlags(ActivityFlags.GrantWriteUriPermission);

    File storageDir = new File(Environment.ExternalStorageDirectory, "filename"); var file = Android.OS.Environment.GetExternalStoragePublicDirectory(Environment.DirectoryPictures); activity.ImageFile = storageDir;

    var uri = FileProvider.GetUriForFile(_context, _context.PackageName + ".provider", storageDir); intent.PutExtra(Android.Provider.MediaStore.ExtraOutput, uri);

    intents.Add(intent); } }

    intents.Add(Fileintent); var res = Intent.CreateChooser(intents[0], fileChooserParams.Title); intents ? .RemoveAt(0); res.PutExtra(Intent.ExtraInitialIntents, intents ? .ToArray()); activity.StartActivityForResult(res, 1000); return true;

    }

    mainactivity
    protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data) { if (requestCode == 1000) { if (UpMessage == null) return; if (resultCode == Result.Ok) { if (data ? .Data != null) { UpMessage.OnReceiveValue(WebChromeClient.FileChooserParams.ParseResult((int) resultCode, data)); } else {

    Java.IO.File file = new Java.IO.File(Android.OS.Environment.ExternalStorageDirectory, "filename");
    
    Android.Net.Uri uri = FileProvider.GetUriForFile(this, this.ApplicationContext.PackageName + ".provider", file);
    UpMessage.OnReceiveValue(new Android.Net.Uri[] {
     uri
    });
    

    } } else { UpMessage.OnReceiveValue(null); } UpMessage = null; }

    }

    Wednesday, November 27, 2019 6:59 PM
  • User384637 posted

    @yarramreddy can I know where are these come from (on OnShowFileChooser method)? * _context * resolveinfolist.Count * File * Environment * FileProvider

    Maybe you can share the namespace needed or any related. Thanks in advance!

    Thursday, December 19, 2019 5:13 AM
  • User373022 posted

    _context: Android.App.Application.Context (App context) resolveinfolist.Count: resolveinfolist is list of activities that can be performed for the given intent File: Java.IO (to create a file) Environment: Android.OS(to fetxh the directory location) FileProvider: Android.Support.V4.Content(facilitates secure sharing of files associated with an app)

    Hope this helps @KhairulFLPM

    Thursday, December 19, 2019 8:05 PM
  • User384637 posted

    @y_reddy thanks for the fast reply.. i will try later and update here soon.

    Friday, December 20, 2019 2:24 AM