0

https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/hybridwebview

I saw above msdn document about HybridWebView of xamarin

I understood the way communicate to c# and vanillajs

HybridWebViewRenderer.cs

using Android.Content;
using CustomRenderer;
using CustomRenderer.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace CustomRenderer.Droid
{
    public class HybridWebViewRenderer : WebViewRenderer
    {
        const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
        Context _context;

        public HybridWebViewRenderer(Context context) : base(context)
        {
            _context = context;
        }

        protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
        {
            base.OnElementChanged(e);

            if (e.OldElement != null)
            {
                Control.RemoveJavascriptInterface("jsBridge");
                ((HybridWebView)Element).Cleanup();
            }
            if (e.NewElement != null)
            {
                Control.SetWebViewClient(new JavascriptWebViewClient(this, $"javascript: {JavascriptFunction}"));
                Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
                Control.LoadUrl($"file:///android_asset/Content/{((HybridWebView)Element).Uri}");
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                ((HybridWebView)Element).Cleanup();
            }
            base.Dispose(disposing);
        }
    }
}

index.html

<html>
<body>
    <script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
    <h1>HybridWebView Test</h1>
    <br />
    Enter name: <input type="text" id="name">
    <br />
    <br />
    <button type="button" onclick="javascript: invokeCSCode($('#name').val());">Invoke C# Code</button>
    <br />
    <p id="result">Result:</p>
    <script type="text/javascript">function log(str) {
            $('#result').text($('#result').text() + " " + str);
        }

        function invokeCSCode(data) {
            try {
                log("Sending Data:" + data);
                invokeCSharpAction(data);
            }
            catch (err) {
                log(err);
            }
        }</script>
</body>
</html>

but i can't think way calling c# code from jsx because As I understand it, in order for JavaScript to execute C# code, it has to execute a function with the same name as the JavaScript function in the string state passed when the JavaScript webview client class is initialized in html.

Usually, if you use the above method in React, you will get an error message.

I don't know much about react

If anyone has ever handled a react page through a Xamarin webview, please give me some tips or advice.

The error message I get when I try through react, or when I separate the js file and import it into html and run it

enter image description here

<html>
<body>
    <script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
    <h1>HybridWebView Test</h1>
    <br />
    Enter name: <input type="text" id="name">
    <br />
    <br />
    <button type="button" onclick="javascript: invokeCSCode('1234');">Invoke C# Code</button>
    <br />
    <p id="result">Result:</p>
    <script type="text/javascript" src="test.js"/>
</body>
</html>

index.html

function invokeCSCode(data) {
    invokeCSharpAction(data);
}

test.js

6
  • Images of code are useless, we cant copy and paste, we cant debug them, we cant write a minimal reproducible example with them, search engines cant index them. Commented Oct 22, 2021 at 7:30
  • sorry i replace it Commented Oct 22, 2021 at 7:33
  • The code you posted is just the same with the source code of the xamarin sample . In addition, if you use the above method in React, you will get an error message. how did you use it in React? Commented Oct 25, 2021 at 7:40
  • I just uploaded the results of yarn build to the server and tested it, but i get undefind function. Commented Oct 26, 2021 at 2:39
  • the same occurs when running the imported xamarin webview example using the script tag after creating a separate js file in the xamarin example. Commented Oct 26, 2021 at 2:42

1 Answer 1

1

I modified the React part after seeing the comments of the ToolmakerSteve.

At first I thought that the function could not be found because the function was renamed during a production build of react, but I found that it works regardless (It was just my misunderstanding that the method executed in JavaScript and the method executed in C# had the same name.)

Also I didn't know eval() how to do <button type="button" onclick="javascript: invokeCSCode(data);" >Invoke C# Code</button> in jsx I was wondering if I could use a method like

Below is an example of the code I have successfully received data from react.

React Part

TestFunctions.ts

export default function test(data: string){
    eval(`invokeTest(${data})`);
}

SendButton.tsx

export default function Button(props: ButtonProps){
    const dispatch = useAppDispatch()

    const handleClick = () =>{
        test('1234');
    }

    return(
        <ButtonWrapper heightValue={props.heightValue} widthValue={props.widthValue} marginValue={props.marginValue}>
            <Button variant="outlined" onClick={() => handleClick()} style={{height: '56px', width: '100%', fontSize: 'large'}}>
                send
            </Button>
        </ButtonWrapper>
    )
}

Xamarin Part

WebViewer.cs

public class WebViewer : WebView
{
    public static readonly BindableProperty UriProperty = BindableProperty.Create(
            propertyName: "Uri",
            returnType: typeof(string),
            declaringType: typeof(WebViewer),
            defaultValue: default(string),
            propertyChanged: UriChanged);

    private static void UriChanged(BindableObject bindable, object oldValue, object newValue)
    {
         if (newValue != null && bindable is WebViewer webViewer)
         {
            if(webViewer.CookieList != null && (string)newValue == (string)oldValue)
            {
                Uri uri = new Uri((string)newValue, UriKind.RelativeOrAbsolute);

                CookieContainer cookieContainer = new CookieContainer();

                foreach (Cookie cookie in webViewer.CookieList)
                {
                    cookie.Domain = uri.Host;
                    cookie.Path = uri.PathAndQuery;
                    cookieContainer.Add(cookie);
                }
                webViewer.Cookies = cookieContainer;
             }
             webViewer.Source = (string)newValue;
          }
     }

     public string Uri
     {
         get => (string)GetValue(UriProperty);
         set => SetValue(UriProperty, value);
     }

     public static readonly BindableProperty CookieListProperty = BindableProperty.Create(
            propertyName: "Cookies",
            returnType: typeof(List<Cookie>),
            declaringType: typeof(WebViewer),
            defaultValue: null,
            propertyChanged: CookieListChanged);
        
     public List<Cookie> CookieList
     {
         get => (List<Cookie>)GetValue(CookieListProperty);
         set => SetValue(CookieListProperty, value);
     }
        
     private static void CookieListChanged(BindableObject bindable, object oldValue, object newValue)
     {
         if (newValue != null && bindable is WebViewer webViewer)
         {
             Uri uri = new Uri(webViewer.Uri, UriKind.RelativeOrAbsolute);

             CookieContainer cookieContainer = new CookieContainer();

             foreach (Cookie cookie in (List<Cookie>)newValue)
             {
                 cookie.Domain = uri.Host;
                 cookie.Path = uri.PathAndQuery;
                 cookieContainer.Add(cookie);
             }
             webViewer.Cookies = cookieContainer;

             if (webViewer.Uri != default(string))
             {
                 webViewer.Source = webViewer.Uri;
             }
         }
     }

     Action<string> action;
     public void RegisterAction(Action<string> callback)
     {
         action = callback;
     }

     public void Cleanup()
     {
         action = null;
     }

     public void InvokeAction(string data)
     {
         if (action == null || data == null)
         {
             return;
         }
         action.Invoke(data);
     }
}

WebViewerRenderer.cs

using Android.Content;
using Android.Views;
using Android.Webkit;
using BDApp.Mobile.Droid.CustomRenderer;
using BDApp.Mobile.Views;
using Java.Interop;
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(WebViewer), typeof(WebViewerRenderer))]
namespace BDApp.Mobile.Droid.CustomRenderer
{
    public class WebViewerRenderer : WebViewRenderer
    {
        const string JavascriptFunction = "function invokeTest(data){jsBridge.invokeAction(data);}";
        public WebViewerRenderer(Context context) : base(context)
        {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
        {
            base.OnElementChanged(e);

            if(e.OldElement != null)
            {
                Control.RemoveJavascriptInterface("jsBridge");
                ((WebViewer)Element).Cleanup();
            }
            if(e.NewElement != null)
            {
                Control.SetWebViewClient(new JavascriptWebViewClient(this, $"javascript: {JavascriptFunction}"));
                Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
            }
            Control.Settings.SetAppCacheEnabled(false);
            Control.Settings.CacheMode = CacheModes.NoCache;
        }

        public override bool DispatchTouchEvent(MotionEvent e)
        {
            Parent.RequestDisallowInterceptTouchEvent(true);
            return base.DispatchTouchEvent(e);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                ((WebViewer)Element).Cleanup();
            }
            base.Dispose(disposing);
        }
    }
    public class JavascriptWebViewClient : FormsWebViewClient
    {
        string _javascript;

        public JavascriptWebViewClient(WebViewerRenderer renderer, string javascript) : base(renderer)
        {
            _javascript = javascript;
        }

        public override void OnPageFinished(Android.Webkit.WebView view, string url)
        {
            base.OnPageFinished(view, url);
            view.EvaluateJavascript(_javascript, null);
        }
    }
    public class JSBridge : Java.Lang.Object
    {
        readonly WeakReference<WebViewerRenderer> hybridWebViewRenderer;

        public JSBridge(WebViewerRenderer hybridRenderer)
        {
            hybridWebViewRenderer = new WeakReference<WebViewerRenderer>(hybridRenderer);
        }

        [JavascriptInterface]
        [Export("invokeAction")]
        public void InvokeAction(string data)
        {
            WebViewerRenderer hybridRenderer;

            if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
            {
                ((WebViewer)hybridRenderer.Element).InvokeAction(data);
            }
        }
    }
}

TestWebViewPage.xaml

<StackLayout>
        <views:WebViewer Uri="{Binding Uri}" x:Name="webView" CookieList="{Binding CookieList}"
                         HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"/>
</StackLayout>
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.