Uploading Via Webview

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 == FILECHOOSER_RESULTCODE)
{
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);
                }
            }
        }
    }`

Answers

  • TylerFoucheTylerFouche USMember ✭✭

    Anyone?

  • JamesLeichterJamesLeichter USMember

    Have you found the fix for this?

  • TylerFoucheTylerFouche USMember ✭✭

    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 {
            }
        }
    
  • TylerFoucheTylerFouche USMember ✭✭

    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 ("android_choose_image")) {
    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);
        }
    
  • nlapham21nlapham21 Member

    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);
                }
            }
        }
    
  • KhairulFLPMKhairulFLPM Member ✭✭
    edited October 8

    @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?

Sign In or Register to comment.