How to update progressbar from customwebview renderer in xamarin forms for android?

Viewed 791

I'm currently developing an app in Xamarin Forms with an Android background so I wanted to create an Android app first and an iOS app later.

I'm new to Xamarin Forms and I'm struggling on how to update a ProgressBar from a WebView using a custom renderer for the WebView.

In Android, you can do something like this, with the ProgressBar and WebView that are in the main_layout.xml

public class MainActivity extends Activity {

    private ProgressBar progressBar;
    private WebView webView;    

    @Override
    protected void onCreate(Bundle savedInstanceState) {     
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_layout);

        progressBar = (ProgressBar) findViewById(R.id.progress);
        webView = (AdvancedWebView) findViewById(R.id.webView);
        // webview initialisation
        webView.setWebChromeClient(new WebChromeClient(){
            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                // update progressbar progress
                progressBar.setProgress(newProgress);
           }
       });
        webView.setWebViewClient(new WebViewClient(this) {
            @Override
            public void onPageFinished(WebView view, String url) {
                // hide progressbar when it's done
                progressBar.setVisibility(View.GONE);
            }
        });
    }
}

In Xamarin Forms I have this layout in MainPage.xaml in the shared project

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
NavigationPage.HasNavigationBar="False">

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <ProgressBar
        x:Name="progress"
        Grid.Row="3"
        HorizontalOptions="FillAndExpand"
        Progress="0"
        VerticalOptions="Center" />

    <WebView
        x:Name="webview"
        Grid.Row="0"
        Grid.RowSpan="4"
        Grid.Column="0"
        HorizontalOptions="FillAndExpand"
        IsVisible="False"
        Source="https://google.com"
        VerticalOptions="FillAndExpand" />

</Grid>

public partial class App : Application
{
    public App ()
    {
        InitializeComponent();

        MainPage = new MainPage(); 
    }
}

And this custom WebView render for android in the android project

[assembly: ExportRenderer(typeof(WebView), typeof(CustomWebviewRenderer))]
namespace MyApp.Droid
{
    public class CustomWebviewRenderer: WebViewRenderer
    {
        private readonly Context context;

        public CustomWebviewRenderer(Context context) : base(context)
        {
            this.context = context;
        }

        protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
        {
            base.OnElementChanged(e);
            var formsWebView = e.NewElement as Xamarin.Forms.WebView;

            if (formsWebView != null)
            {
                var webView = Control as Android.Webkit.WebView;
                webView.SetWebViewClient(new CustomWebViewClient()); 
                webView.SetWebChromeClient(new CustomWebChromeClient());     
                webView.Settings.LoadWithOverviewMode = true;
                webView.Settings.UseWideViewPort = true;
                SetNativeControl(webView);
            }
        }

        private class ScoritoWebChromeClient : WebChromeClient
        {
            public override void OnProgressChanged(Android.Webkit.WebView view, int newProgress)
            {
                // how to update progressbar progress?
                base.OnProgressChanged(view, newProgress);               
            }
        }

        private class CustomWebViewClient : WebViewClient
        {
            public override void OnPageFinished(Android.Webkit.WebView view, string url)
            {
                // how to hide progressbar?
                base.OnPageFinished(view, url);
            }

            public override void OnPageStarted(Android.Webkit.WebView view, string url, Bitmap favicon)
            {
                base.OnPageStarted(view, url, favicon);
            }

            public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, IWebResourceRequest request)
            {
                return base.ShouldOverrideUrlLoading(view, request);
            }
        }
    }
}

How can I update the ProgressBar that I have in my MainPage.xaml from the OnPageFinished in the CustomWebviewRenderer class to show the webpage has finished loading? Should I use the MainActivity.cs in the Android project?

Can someone point me in the right direction on how to solve this?

2 Answers

Update:

Create a custom WebView class in your PCL:

 public class CustomWebView: WebView
 {   
    public static readonly BindableProperty ActionProperty = BindableProperty.Create(propertyName: nameof(Action), returnType: typeof(Action),
        declaringType: typeof(CustomWebView),
        defaultValue: null,
        defaultBindingMode: BindingMode.OneWay);

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

    public Action Action
    {
        get { return (Action)GetValue(ActionProperty); }
        set { SetValue(ActionProperty, value); }
    }

 }

Use this CustomWebView with its namespace like this:

<namespace:CustomWebView  x:Name="webview"
    Grid.Row="0"
    Grid.RowSpan="4"
    Grid.Column="0"
    HorizontalOptions="FillAndExpand"
    IsVisible="False"
    Source="https://google.com"
    VerticalOptions="FillAndExpand"/>  

But if you still insist to use your renderer what you can do is update the progress bar using the element property and get a callback or change a property something like below:

[assembly: ExportRenderer(typeof(CustomWebView), typeof(CustomWebviewRenderer))]
 namespace MyApp.Droid
{
public class CustomWebviewRenderer: WebViewRenderer
{
    private readonly Context context;  

    public CustomWebviewRenderer(Context context) : base(context)
    {
        this.context = context;
    }

    protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
    {
        base.OnElementChanged(e);
        var formsWebView = e.NewElement as Xamarin.Forms.WebView;

        if (formsWebView != null)
        {
            var webView = Control as Android.Webkit.WebView;
            webView.SetWebViewClient(new CustomWebViewClient()); 
            webView.SetWebChromeClient(new CustomWebChromeClient());     
            webView.Settings.LoadWithOverviewMode = true;
            webView.Settings.UseWideViewPort = true;
            SetNativeControl(webView);
        }
    }

    private class ScoritoWebChromeClient : WebChromeClient
    {
        public override void OnProgressChanged(Android.Webkit.WebView view, int newProgress)
        {
            // how to update progressbar progress?
            base.OnProgressChanged(view, newProgress);               
        }
    }

    private class CustomWebViewClient : WebViewClient
    {
        private _webView;
        public CustomWebViewClient (Xamarin.Forms.WebView webView) 
        {
          _webView=webView;
        }
        public override void OnPageFinished(Android.Webkit.WebView view, string url)
        {
            // how to hide progressbar?
            _webView.InvokeAction();
            base.OnPageFinished(view, url);
        }

        public override void OnPageStarted(Android.Webkit.WebView view, string url, Bitmap favicon)
        {
            base.OnPageStarted(view, url, favicon);
        }

        public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, IWebResourceRequest request)
        {
            return base.ShouldOverrideUrlLoading(view, request);
        }
    }
  }
}   

Note that this is just an example and can be changed as per your requirements.

In case it doesn't work or you have queries revert!

So after 10 days I have finally found a solution to this by using bindableproperties...

So I've added a CustomWebView in the PCL

public class CustomWebView : WebView
{
    public static readonly BindableProperty PageProgressProperty = BindableProperty.Create(
        nameof(PageProgress),
        typeof(int),
        typeof(CustomWebView),
        default(int));

    public int PageProgress
    {
        get => (int)GetValue(PageProgressProperty);
        set => SetValue(PageProgressProperty, value);
    }      
}

Also a CustomProgressBar in the PCL

public class CustomProgressBar : ProgressBar
{
    public CustomProgressBar()
    {
    }

    public static readonly BindableProperty CurrentProgressProperty =
    BindableProperty.Create(
        nameof(CurrentProgress),
        typeof(int),
        typeof(CustomProgressBar),
        default(int),
        propertyChanged: CustomProgressPropertyChanged);

    private static void CustomProgressPropertyChanged(BindableObject sender, object oldValue, object newValue)
    {
        CustomProgressBar thisProgressBar = (CustomProgressBar)sender;
        double percent = Convert.ToDouble(newValue) / 100;
        thisProgressBar.ProgressTo(percent, 100, Easing.Linear);
    }

    public int CurrentProgress
    {
        get => (int)GetValue(CurrentProgressProperty);
        set => SetValue(CurrentProgressProperty, value);
    }
}

And now I can update the progress from my CustomWebviewRenderer as follows

public class CustomWebviewRenderer : WebViewRenderer
{
    private readonly Context context;

    public CustomWebviewRenderer(Context context) : base(context)
    {
        this.context = context;
    }

    protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
    {
        base.OnElementChanged(e);
        var formsWebView = e.NewElement as WebView;

        if (e.OldElement == null)
        {
            var x = Control;
            var webView = Control as Android.Webkit.WebView;
            webView.SetWebViewClient(new CustomWebViewClient(Element));
            webView.SetWebChromeClient(new CustomWebChromeClient(Element));
            webView.Settings.LoadWithOverviewMode = true;
            SetNativeControl(webView);
        }
    }

    private class CustomWebViewClient : WebViewClient
    {
        private readonly WebView formsWebView;

        public CustomWebViewClient(WebView webView)
        {
            formsWebView = webView;
        }

        public override void OnReceivedError(Android.Webkit.WebView view, IWebResourceRequest request, WebResourceError error)
        {
            base.OnReceivedError(view, request, error);
        }

        public override void OnPageFinished(Android.Webkit.WebView view, string url)
        {
            base.OnPageFinished(view, url);
        }

        public override void OnPageStarted(Android.Webkit.WebView view, string url, Bitmap favicon)
        {
            base.OnPageStarted(view, url, favicon);
        }

        public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, IWebResourceRequest request)
        {
            return base.ShouldOverrideUrlLoading(view, request);
        }
    }

    private class CustomWebChromeClient : WebChromeClient
    {
        private WebView element;

        public CustomWebChromeClient(WebView element)
        {
            this.element = element;
        }

        public override void OnProgressChanged(Android.Webkit.WebView view, int newProgress)
        {
            base.OnProgressChanged(view, newProgress);
            element.SetValueFromRenderer(CustomWebView.PageProgressProperty, newProgress);
        }          
    }
}

And finally in the .xaml views these properties: BindingContext and CurrentProgress. The BindingContext is set to the webview, and the CurrentProgress is a custom property bound to PageProgress which is a property that lives in the CustomWebView

    <local:CustomProgressBar
        x:Name="progress"
        Grid.Row="3"
        BindingContext="{x:Reference webview}"
        CurrentProgress="{Binding Path=PageProgress}"
        HorizontalOptions="FillAndExpand"
        ProgressColor="#FFB800"
        VerticalOptions="Center" />

    <local:CustomWebView
        x:Name="webview"
        Grid.Row="0"
        Grid.RowSpan="4"
        Grid.Column="0"
        HorizontalOptions="FillAndExpand"
        IsVisible="False"
        VerticalOptions="FillAndExpand" />

So yeah, took a while to figure this out...

Related