Xamarin - Other

Add mono.android reference Xamarin.Forms

  1. In solution explorer, right click on References.
  2. Click on Add Reference
  3. Click on Browse on the left panel. 
  4. Click on Browse in the bottom.
  5. Enter this path: C:\Program Files (x86)\Microsoft VisualStudio\2017\Community\Common7\IDE\ReferenceAssemblies\Microsoft\Framework\MonoAndroid\v8.1.
  6. Select Mono.Android.dll.
  7. Check and add to the project.

Android 12 New Bluetooth Permissions

MainActivity

namespace Xamarin.Android
{
  public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
  {
    protected override void OnCreate(Bundle savedInstanceState) 
    {
      base.OnCreate(savedInstanceState); 
      global::Xamarin.Forms.Forms.Init(this, savedInstanceState); 
      LoadApplication(new App()); 
      //Add this line
      await Permissions.RequestAsync<BLEPermission>();
    } 
  }   
}


BLEPermission Class

public class BLEPermission : Xamarin.Essentials.Permissions.BasePlatformPermission
{
    public override (string androidPermission, bool isRuntime)[] RequiredPermissions => new List<(string androidPermission, bool isRuntime)>
    {
        (Android.Manifest.Permission.BluetoothScan, true),
        (Android.Manifest.Permission.BluetoothConnect, true)
    }.ToArray();
}


AndroidManifest.xml

<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />

<uses-feature android:name="android.hardware.bluetooth" android:required="false" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false" />
<uses-feature android:name="android.hardware.location" android:required="false" />
<uses-feature android:name="android.hardware.location.gps" android:required="false" />
<uses-feature android:name="android.hardware.location.network" android:required="false" />

Check / Request Permission

PermissionStatus status = await Permissions.CheckStatusAsync<Permissions.xxx>();

Clear Cache in Webview Xamarin Forms

Xamarin.Android :

var cookieManager = CookieManager.Instance;
cookieManager.RemoveAllCookie();


Xamarin.iOS :

NSHttpCookieStorage CookieStorage = NSHttpCookieStorage.SharedStorage;
foreach (var cookie in CookieStorage.Cookies) { 
  CookieStorage.DeleteCookie(cookie); 
}

Custom error page

Xamarin Forms page

webView.Source = "https://example.com";


Xamarin.Android

[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace YourAppNamespace.Droid
{
    public class HybridWebViewRenderer : WebViewRenderer
    {
        public HybridWebViewRenderer(Context context) : base(context) { }


        protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
        {
            base.OnElementChanged(e);
            if (Control != null)
            {
                Control.SetWebViewClient(new CustomWebViewClient());
            }
        }
    }


    public class CustomWebViewClient : WebViewClient
    {
        public override void OnReceivedError(WebView view, WebResourceRequest request, WebResourceError error)
        {
            string customErrorPage = "<html><body><h1>Page Not Found</h1><p>Oops! Something went wrong.</p></body></html>";
            view.LoadData(customErrorPage, "text/html", "UTF-8");
        }


        public override void OnReceivedError(WebView view, ClientError errorCode, string description, string failingUrl)
        {
            string customErrorPage = "<html><body><h1>Page Not Found</h1><p>Oops! Something went wrong.</p></body></html>";
            view.LoadData(customErrorPage, "text/html", "UTF-8");
        }
    }
}


Xamarin.iOS

[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace YourAppNamespace.iOS
{
    public class HybridWebViewRenderer : WkWebViewRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
        {
            base.OnElementChanged(e);
            if (Control != null)
            {
                Control.NavigationDelegate = new CustomNavigationDelegate();
            }
        }
    }


    public class CustomNavigationDelegate : WKNavigationDelegate
    {
        public override void DidFailNavigation(WKWebView webView, WKNavigation navigation, NSError error)
        {
            string customErrorPage = "<html><body><h1>Page Not Found</h1><p>Oops! Something went wrong.</p></body></html>";
            webView.LoadHtmlString(customErrorPage, null);
        }


        public override void DidFailProvisionalNavigation(WKWebView webView, WKNavigation navigation, NSError error)
        {
            string customErrorPage = "<html><body><h1>Page Not Found</h1><p>Oops! Something went wrong.</p></body></html>";
            webView.LoadHtmlString(customErrorPage, null);
        }
    }
}

Dismiss keyboard on button press

Xamarin Forms project

public interface IKeyboardHelper
{
    void HideKeyboard();
}  


Xamarin Forms page

DependencyService.Get<IKeyboardHelper>().HideKeyboard();


Xamarin.Android

using System;
using Xamarin.Forms;
using ProjectName;
using ProjectName.Droid;
using Xamarin.Forms.Platform.Android;
using Android.Views.InputMethods;
using Android.App;
using Android.Content;

[assembly: Xamarin.Forms.Dependency(typeof(DroidKeyboardHelper))] 
namespace ProjectName.Droid
{
    public class DroidKeyboardHelper : IKeyboardHelper
    {
        public void HideKeyboard()
        {
            var context = Android.App.Application.Context;
            var inputMethodManager = context.GetSystemService(Context.InputMethodService) as InputMethodManager;
            if (inputMethodManager != null && context is Activity)
            {
                var activity = context as Activity;
                var token = activity.CurrentFocus?.WindowToken;
                inputMethodManager.HideSoftInputFromWindow(token, HideSoftInputFlags.None);

                activity.Window.DecorView.ClearFocus();
            }
        }
    }
}


Xamarin.iOS

using System;
using Xamarin.Forms;
using ProjectName;
using ProjectName.iOS;
using UIKit;

[assembly: Dependency(typeof(iOSKeyboardHelper))]
namespace ProjectName.iOS
{
    public class iOSKeyboardHelper : IKeyboardHelper
    {
        public void HideKeyboard()
        {
            UIApplication.SharedApplication.KeyWindow.EndEditing(true);
        }
    }
}


AndroidManifest.xml

<application
   //Add this attribute
   android:windowSoftInputMode="adjustResize"
</application>

Don't let the WebView create new Tabs

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

  WebSettings settings = view.Settings;

  settings.JavaScriptCanOpenWindowsAutomatically = false;
  settings.SetSupportMultipleWindows(false);
}

Download Files Using Dependency Service

.NET standard application

MainPage.xaml.cs

public partial class MainPage : ContentPage
{
  IDownloader downloader = DependencyService.Get<IDownloader>();

  public MainPage()
  {
    InitializeComponent();
    downloader.OnFileDownloaded += Downloader_OnFileDownload;
  }

  private void Downloader_OnFileDownload(object sender, DownloadEventArgs e)
  {
    if (e.FileSaved)
    {
      //File Saved Successfully. Do some action.
    }
    else
    {
      //Error while saving the file. Do some action.
    }
  }

  //download method
  protected void DownloadMethod()
  {
    downloader.DownloadFile("url of the download item");
  }
}


IDownloader.cs

namespace Xamarin
{
    public interface IDownloader
    {
        void DownloadFile(string url);
        event EventHandler<DownloadEventArgs> OnFileDownloaded;
    }

    public class DownloadEventArgs : EventArgs
    {
        public bool FileSaved = false;
        public DownloadEventArgs(bool fileSaved)
        {
            FileSaved = fileSaved;
        }
    }
}


Android App

AndroidDownloader.cs

[assembly: Dependency(typeof(AndroidDownloader))]
namespace Xamarin.Droid
{
    public class AndroidDownloader : IDownloader
    {
        public event EventHandler<DownloadEventArgs> OnFileDownloaded;

        public void DownloadFile(string url)
        {
            var context = Android.App.Application.Context;
            var pathToNewFolder = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.AbsolutePath, Android.OS.Environment.DirectoryDownloads);
            
            if (!Directory.Exists(pathToNewFolder))
            {
                _ = Directory.CreateDirectory(pathToNewFolder);
            }

            try
            {
                Uri uri = new UriBuilder(url).Uri;
                WebClient webClient = new WebClient();
                ServicePointManager.ServerCertificateValidationCallback = new
                RemoteCertificateValidationCallback
                (
                   delegate { return true; }
                );

                webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(Completed);
                string pathToNewFile = Path.Combine(pathToNewFolder, Path.GetFileName(url));
                webClient.DownloadFileAsync(uri, pathToNewFile);
                webClient.Dispose();
            }
            catch(Exception)
            {
                if(OnFileDownloaded != null)
                {
                    OnFileDownloaded.Invoke(this, new DownloadEventArgs(false));
                }
            }
        }


        private void Completed(object sender, AsyncCompletedEventArgs e)
        {
            if(e.Error != null)
            {
                if (OnFileDownloaded != null)
                {
                    OnFileDownloaded.Invoke(this, new DownloadEventArgs(false));
                }
            }
            else
            {
                if (OnFileDownloaded != null)
                {
                    OnFileDownloaded.Invoke(this, new DownloadEventArgs(true));
                }
            }
        }
    }
}


AndroidManifest.xml

Add the following two permissions:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />


iOS App

IosDownloader.cs

[assembly: Dependency(typeof(IosDownloader))]
namespace Xamarin.iOS
{
    public class IosDownloader : IDownloader
    {
        public event EventHandler<DownloadEventArgs> OnFileDownloaded;

        public void DownloadFile(string url)
        {
            string pathToNewFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal));
            
            if (!Directory.Exists(pathToNewFolder))
            {
                _ = Directory.CreateDirectory(pathToNewFolder);
            }

            try
            {
                Uri uri = new UriBuilder(url).Uri;
                WebClient webClient = new WebClient();
                ServicePointManager.ServerCertificateValidationCallback = new
                RemoteCertificateValidationCallback
                (
                   delegate { return true; }
                );

                webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(Completed);
                string pathToNewFile = Path.Combine(pathToNewFolder, Path.GetFileName(url));
                webClient.DownloadFileAsync(uri, pathToNewFile);
                webClient.Dispose();
            }
            catch (Exception)
            {
                if (OnFileDownloaded != null)
                {
                    OnFileDownloaded.Invoke(this, new DownloadEventArgs(false));
                }
            }
        }


        private void Completed(object sender, AsyncCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                if (OnFileDownloaded != null)
                {
                    OnFileDownloaded.Invoke(this, new DownloadEventArgs(false));
                }
            }
            else
            {
                if (OnFileDownloaded != null)
                {
                    OnFileDownloaded.Invoke(this, new DownloadEventArgs(true));
                }
            }
        }
    }
}


Info.plist

Add the following two permissions:

<key>Privacy - Media Library Usage Description</key>
<string>The app requires access to media.</string>

<key>Privacy - Photo Library Usage Description</key>
<string>The app requires access to photo.</string>

<key>Privacy - Photo Library Additions Usage Description</key>
<string>The app requires access to photo gallery.</string>

You must reboot your iPhone for the changes to take effect.

Error MSB6006: "mtouch" exited with code 134. (MSB6006)

  1. Right click AppName.iOS and click on options.
  2. Under Build section, go to iOS Build.
  3. Unchecked enable device specific builds.
  4. Click OK.

Execute Javascript in Xamarin.Forms WebView

await webview.EvaluateJavaScriptAsync($"myFunction({p1}, {p2})");

Fast deployment is disabled (Android)

  1. Unload the project, open .csproj file by IDE.
  2. Find the PropertyGroup containing the Condition for your build configuration (Debug or Release).
  3. Add this line : <EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>.

How do I embed/bundle assemblies in native code ? (Android only)

Prerequisites:

  1. Requires an Enterprise License.
  2. Should install and map Android NDK to IDE.


Visual Studio Users:

  1. Go to Android Project > Properties > Android Options.
  2. In the packaging properties section, check the Bundle assemblies in native code option.