How to download files like the native browser with Xamarin WebView on Android?

Niklas_S 51 Reputation points
2021-08-02T06:45:23.097+00:00

I would like to download any kind of file with the WebView implemented in my app.
On iOS I noticed that the WebView can mostly only show the file inside itself.
On Android, the WebView is somehow caught in a loop or in the loading process. It probably tries to download the file, but can't.
The native behavior for iOS, so Safari -> show the pdf, doc or html in the WebView.
The native behavior for Android, i.e. Chrome -> download everything and then try to view it with the right app when trying to open it after download or open it right after download (tested on Huawei P30 Pro with Android 11.0.0.153).

Why is my WebView not able to do that. Can anyone help me with this?

I don't want to create a custom layout in the resources for Android and I can't take the method implemented in the git example for Android because it would be blocked on my website due to session hyjacking. If you want to see the android WebView endless loading just comment everything in this "if"-clause on the MainPage.xaml.cs:

if (Device.RuntimePlatform.Equals(Device.Android))

MainPage.xaml:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="WebViewExample.MainPage"
             xmlns:android="clr-namespace:Xamarin.Forms.PlatformConfiguration.AndroidSpecific;assembly=Xamarin.Forms.Core"
             xmlns:customhybridwebview="clr-namespace:WebViewExample.View.Custom">

    <ContentPage.Content>

        <StackLayout 
            BackgroundColor="#000"
            Spacing="0"
            HorizontalOptions="FillAndExpand"
            VerticalOptions="FillAndExpand">
            <ActivityIndicator x:Name="loadingIndicator"
                               HorizontalOptions="Center"
                               VerticalOptions="Center"
                               Color="Red"
                               BackgroundColor="Black">
                <ActivityIndicator.HeightRequest>
                    <OnPlatform x:TypeArguments="x:Double" iOS="60" Android="40"/>
                </ActivityIndicator.HeightRequest>
                <ActivityIndicator.WidthRequest>
                    <OnPlatform x:TypeArguments="x:Double" iOS="60" Android="40"/>
                </ActivityIndicator.WidthRequest>
            </ActivityIndicator>
            <customhybridwebview:HybridWebView  x:Name="hybridWebView"
                                                Source="{Binding SourceUrl}"
                                                HeightRequest="1000"
                                                WidthRequest="1000"
                                                HorizontalOptions="FillAndExpand"
                                                VerticalOptions="FillAndExpand"
                                                Navigating="HybridWebView_Navigating"
                                                Navigated="HybridWebView_Navigated"  
                                                android:WebView.DisplayZoomControls="False"
                                                android:WebView.EnableZoomControls="True"
                                                android:WebView.MixedContentMode="AlwaysAllow">
            </customhybridwebview:HybridWebView>
            <Button Text="Back"
                    TextColor="Red"
                    BackgroundColor="Black"
                    HorizontalOptions="CenterAndExpand"
                    Clicked="ClickedWebViewGoBack"
                    IsVisible="{Binding BackButtonIsVisible}"/>
        </StackLayout>

    </ContentPage.Content>

</ContentPage>

MainPage.xaml.cs:

using System;
using System.Diagnostics;
using WebViewExample.ViewModel;
using Xamarin.Essentials;
using Xamarin.Forms;

namespace WebViewExample
{
    public partial class MainPage : ContentPage
    {
        private bool navigatingIsGettingCanceled = false;
        private bool webViewBackButtonPressed = false;

        readonly MainPageViewModel mainPageViewModel;

        public MainPage()
        {
            InitializeComponent();
            mainPageViewModel = new MainPageViewModel();
            BindingContext = mainPageViewModel;

            mainPageViewModel.LoadURL();
        }

        protected override bool OnBackButtonPressed()
        {
            base.OnBackButtonPressed();

            if (hybridWebView.CanGoBack)
            {
                hybridWebView.GoBack();
                return true;
            }

            return false;
        }

        private void HybridWebView_Navigating(object sender, WebNavigatingEventArgs e)
        {
            Debug.WriteLine("Debug - Sender: " + sender.ToString());
            Debug.WriteLine("Debug - Url: " + e.Url.ToString());
            if (!loadingIndicator.IsRunning)
            {
                loadingIndicator.IsRunning = loadingIndicator.IsVisible = true;

                //check every new URL the WebView tries connecting to
                if (hybridWebView == null) { return; }
                if (hybridWebView.Source == null) { return; }

                string nextURL = e.Url;
                string urlProperty = hybridWebView.Source.GetValue(UrlWebViewSource.UrlProperty).ToString();

                if (String.IsNullOrEmpty(nextURL) || nextURL.Contains("about:blank"))
                {
                    return;
                }
                if (Device.RuntimePlatform.Equals(Device.Android))
                {
                    //navigatingIsGettingCanceled = true;
                    /*try
                    {
                        Browser.OpenAsync(nextURL, new BrowserLaunchOptions
                        {
                            LaunchMode = BrowserLaunchMode.SystemPreferred,
                            TitleMode = BrowserTitleMode.Show,
                            PreferredToolbarColor = Color.White,
                            PreferredControlColor = Color.Red
                        });
                    }
                    catch (Exception ex)
                    {
                        //An unexpected error occured. No browser may be installed on the device.
                        Debug.WriteLine(string.Format("Debug - Following Exception occured: {0}", ex));
                    }*/
                }
                if (Device.RuntimePlatform.Equals(Device.iOS))
                {
                    mainPageViewModel.ShowBackButton();
                }

                e.Cancel = navigatingIsGettingCanceled;
                if (navigatingIsGettingCanceled)
                {
                    loadingIndicator.IsRunning = loadingIndicator.IsVisible = navigatingIsGettingCanceled = false;
                    Debug.WriteLine("Debug - Navigation getting cancelled");
                    return;
                }

                //edited so it would always SetValue (still not loading the pdf on Android)
                hybridWebView.Source.SetValue(UrlWebViewSource.UrlProperty, nextURL);
                Debug.WriteLine("Debug - Source changed");

            }
        }

        private void HybridWebView_Navigated(object sender, WebNavigatedEventArgs e)
        {
            loadingIndicator.IsRunning = loadingIndicator.IsVisible = false;

            if (webViewBackButtonPressed)
            {
                hybridWebView.GoBack();
                webViewBackButtonPressed = false;
            }
        }

        void ClickedWebViewGoBack(System.Object sender, System.EventArgs e)
        {
            if (hybridWebView.CanGoBack)
            {
                hybridWebView.GoBack();
                mainPageViewModel.HideBackButton();
                webViewBackButtonPressed = true;
            }
        }
    }
}

MainPageViewModel.cs:

using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Input;
using WebViewExample.Model;

namespace WebViewExample.ViewModel
{

    public class MainPageViewModel : INotifyPropertyChanged
    {
        public string SourceUrl { get; set; }
        public bool BackButtonIsVisible { get; set; } = false;

        public MainPageViewModel() { }

        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public void LoadURL()
        {
            SourceUrl = Settings.SourceURL;
            Debug.WriteLine("Debug - Load new URL: " + SourceUrl.ToString());

            RefreshURL();
        }

        public void RefreshURL() => OnPropertyChanged(nameof(SourceUrl));

        public void ShowBackButton()
        {
            BackButtonIsVisible = true;
            OnPropertyChanged(nameof(BackButtonIsVisible));
        }

        public void HideBackButton()
        {
            BackButtonIsVisible = false;
            OnPropertyChanged(nameof(BackButtonIsVisible));
        }

    }

}

Settings.cs:

using System;
using Xamarin.Essentials;

namespace WebViewExample.Model
{
    public static class Settings
    {
        #region setting Constants
        private const string KeySourceURL = "sourceURL";
        private static readonly string SourceURLDEFAULT = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf";
        #endregion

        #region setting Properties
        public static string SourceURL
        {
            get { return Preferences.Get(KeySourceURL, SourceURLDEFAULT); }
            set { Preferences.Set(KeySourceURL, value); }
        }
        #endregion

    }
}

HybridWebView.cs:

using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace WebViewExample.View.Custom
{

    public class HybridWebView : WebView
    {

        Action<string> action;

        public static readonly BindableProperty UriProperty = BindableProperty.Create(
            propertyName: "Uri",
            returnType: typeof(string),
            declaringType: typeof(HybridWebView),
            defaultValue: default(string));

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

        public void RegisterAction(Action<string> callback)
        {
            Debug.WriteLine("Debug - Register Action");
            action = callback;
        }

        public void Cleanup()
        {
            Debug.WriteLine("Debug - Clear Action");
            action = null;
        }

        public void InvokeAction(string data)
        {
            Debug.WriteLine("Debug - Invoke Action");
            if (action == null || data == null)
            {
                return;
            }
            Debug.WriteLine("Debug - Data: " + data.ToString());
            action.Invoke(data);
        }

    }

}

The git repo example:
https://github.com/Nitroklas/WebViewDownloadingExample

Same Post on Stackoverflow:
https://stackoverflow.com/questions/68576352

Thanks for any help on this matter.
Everything I have found on this topic is from 2019 or older ....

Regards
Niklas

Xamarin
Xamarin
A Microsoft open-source app platform for building Android and iOS apps with .NET and C#.
5,295 questions
{count} vote

Accepted answer
  1. JarvanZhang 23,951 Reputation points
    2021-08-04T07:51:16.757+00:00

    Hello,​

    Welcome to our Microsoft Q&A platform!

    In Android, we need to write the function code to download a file in webView directly. Try detecting the Download event of the webView and use DownloadManager to download the file.

    For the Xamarin.Forms.WebView, please create a custom renderer to define the control on Android platform.

       [assembly: ExportRenderer(typeof(HybridWebView), typeof(CustomWebViewRenderer))]  
       namespace TestApplication_6.Droid  
       {  
           public class CustomWebViewRenderer : WebViewRenderer  
           {  
               public CustomWebViewRenderer(Context context) : base(context)  
               {  
               }  
         
               protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)  
               {  
                   base.OnElementChanged(e);  
                   Control.Download += Control_Download;  
               }  
         
               private void Control_Download(object sender, Android.Webkit.DownloadEventArgs e)  
               {  
                   var url = e.Url;  
                   DownloadManager.Request request = new DownloadManager.Request(Android.Net.Uri.Parse(url));  
                   request.AllowScanningByMediaScanner();  
                   request.SetNotificationVisibility(DownloadVisibility.VisibleNotifyCompleted);  
                   request.SetDestinationInExternalPublicDir(Android.OS.Environment.DirectoryDownloads, "CPPPrimer");  
                   DownloadManager dm = (DownloadManager)Android.App.Application.Context.GetSystemService("download");  
                   dm.Enqueue(request);  
         
                   Toast.MakeText(Android.App.Application.Context, "Downloading File", ToastLength.Long).Show();  
               }  
           }  
       }  
    

    Best Regards,

    Jarvan Zhang


    If the response is helpful, please click "Accept Answer" and upvote it.

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    2 people found this answer helpful.
    0 comments No comments

0 additional answers

Sort by: Most helpful