Niklas_S
Niklas_S

Reputation: 35

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

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://download.microsoft.com/download/7/8/8/788971A6-C4BB-43CA-91DC-557B8BE72928/Microsoft_Press_eBook_CreatingMobileAppswithXamarinForms_PDF.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 the microsoft forum: https://learn.microsoft.com/en-us/answers/questions/497906/how-to-download-files-like-the-native-browser-with.html

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

Regards Niklas

Upvotes: 2

Views: 1815

Answers (1)

Wendy Zang - MSFT
Wendy Zang - MSFT

Reputation: 10958

You could try the code below to use the custom renderer to download file via WebView.

[assembly: ExportRenderer(typeof(Xamarin.Forms.WebView), 
typeof(UpgradeWebViewRenderer))]
namespace App8.Droid
{
public class UpgradeWebViewRenderer : ViewRenderer<Xamarin.Forms.WebView, global::Android.Webkit.WebView>
{
    public UpgradeWebViewRenderer(Context context) : base(context)
    {


    }

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

        if (this.Control == null)
        {
            var webView = new global::Android.Webkit.WebView(this.Context);
            webView.SetWebViewClient(new WebViewClient());
            webView.SetWebChromeClient(new WebChromeClient());
            WebSettings webSettings = webView.Settings;
            webSettings.JavaScriptEnabled = true;
            webView.SetDownloadListener(new CustomDownloadListener());
            this.SetNativeControl(webView);
            var source = e.NewElement.Source as UrlWebViewSource;
            if (source != null)
            {
                webView.LoadUrl(source.Url);
            }
        }
    }
}

public class CustomDownloadListener : Java.Lang.Object, IDownloadListener
{
    public void OnDownloadStart(string url, string userAgent, string contentDisposition, string mimetype, long contentLength)
    {
        DownloadManager.Request request = new DownloadManager.Request(Android.Net.Uri.Parse(url));
        request.AllowScanningByMediaScanner();
        request.SetNotificationVisibility(DownloadVisibility.VisibleNotifyCompleted);
        request.SetDestinationInExternalFilesDir(Forms.Context, Android.OS.Environment.DirectoryDownloads, "hello.jpg");
        DownloadManager dm = (DownloadManager)Android.App.Application.Context.GetSystemService(Android.App.Application.DownloadService);
        dm.Enqueue(request);
    }
}

}

Upvotes: 1

Related Questions