Daddoon / Xamarin.Android.GeckoView

A NuGet package and an example project to embed a GeckoView in your Xamarin Android App
MIT License
4 stars 1 forks source link

Xamarin.Android.GeckoView

Projects & Nuget packages in order to embed a GeckoView View & Engine into your Xamarin.Android App or Xamarin.Forms App (Android only of course)

GeckoView Version

The current version is: GeckoView 67.0.20190228011332 From a nightly-try test build, as the multi-arch support is new.

Compatibility

Usable from Android 4.4 to Android 9.0

Pre-requisite

SDK:

Nuget Dependencies:

Warning:

The NuGet package is pretty big, as the GeckoView native library is 150 MB because it's a multi-arch platform version.

If you build your own project with the one APK per ABI option checked, the library size will be reduced to ~50MB per APK architecture

Getting Started

From Source

From Nuget Package

Usage guide for Xamarin.Android

This guide is for Xamarin.Android. For Xamarin.Forms see the usage guide for Xamarin.Forms

As a first step, don't forget to add the Xamarin.Android.GeckoView NuGet package if you are not working from sources.

An Example how to use the Org.Mozilla.GeckoView:

Modify or create an Android layout resource, like the one in Resources > layout > activity_main.axml like this:

<?xml version="1.0" encoding="utf-8"?>
<org.mozilla.geckoview.GeckoView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/geckoview"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" />

And modify your MainActivity file like this:

using Android.App;
using Android.OS;
using Android.Support.V7.App;
using Org.Mozilla.Geckoview;

namespace Xam.Android.MyGeckoApp
{
    [Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true)]
    public class MainActivity : AppCompatActivity
    {
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.activity_main);

            Org.Mozilla.Geckoview.GeckoView view = (Org.Mozilla.Geckoview.GeckoView)FindViewById(Resource.Id.geckoview);
            GeckoSession session = new GeckoSession();
            GeckoRuntime runtime = GeckoRuntime.Create(this);
            session.Open(runtime);
            view.SetSession(session, runtime);

            //Use Mozilla GeckoView API with session object
            session.LoadUri("https://www.google.com/");
        }
    }
}

This is highly inspired from the original documentation.

Usage guide for Xamarin.Forms

Add GeckoViewRenderer.Init(this) in your OnCreate overrided in MainActivity. If we take our sample Xamarin.Forms project it shoud look like something like this:

using Android.App;
using Android.Content.PM;
using Android.OS;
using Android.Support.V4.App;
using Xam.Droid.GeckoView.Forms.Droid.Renderers;

namespace Xam.Droid.GeckoView.Test.Forms.Droid
{
    [Activity(Label = "Xam.Droid.GeckoView.Test.Forms", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        protected override void OnCreate(Bundle savedInstanceState)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(savedInstanceState);

            GeckoViewRenderer.Init(this);
            Xamarin.Essentials.Platform.Init(this, savedInstanceState);
            global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
            LoadApplication(new App());
        }
        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
        {
            Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);

            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
}

Then on the Xamarin.Forms page where you want to add your GeckoView component, you only just have to instanciate a new GeckoViewForms object and add it to your current page. If we take the example from the MainPage.xaml, things may look like this:

In 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"
             xmlns:local="clr-namespace:Xam.Droid.GeckoView.Test.Forms"
             x:Class="Xam.Droid.GeckoView.Test.Forms.MainPage">

    <StackLayout x:Name="stackLayout" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
    </StackLayout>

</ContentPage>

In MainPage.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xam.Droid.GeckoView.Forms;
using Xamarin.Forms;

namespace Xam.Droid.GeckoView.Test.Forms
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
            var geckoForms = new GeckoViewForms()
            {
                Source = "https://www.google.com/"
            };
            geckoForms.HorizontalOptions = LayoutOptions.FillAndExpand;
            geckoForms.VerticalOptions = LayoutOptions.FillAndExpand;

            stackLayout.Children.Add(geckoForms);
        }
    }
}

Additional notes:

Also, as some Xamarin.Forms events on the webview are based on the custom delegates set by this package when constructing the GeckoView object, some behavior may broke if you change them. The most concern is about the various GeckoSession.SomethingDelegate objects.

At the moment, if you want to add your own logic:

On your own inherited CustomRenderer override CreateNewSession. Here is the original implementation:

public virtual Tuple<GeckoSession, GeckoRuntime> CreateNewSession()
{
    GeckoSession _session = new GeckoSession();
    GeckoRuntime _runtime = GeckoRuntime.Create(Context);
    _session.Open(_runtime);
    _session.ProgressDelegate = new ProgressDelegate(this);
    _session.ContentDelegate = new ContentDelegate(this);

    return Tuple.Create(_session, _runtime);
}

To something in your class like:

public override Tuple<GeckoSession, GeckoRuntime> CreateNewSession()
{
    GeckoSession _session = new GeckoSession();
    GeckoRuntime _runtime = GeckoRuntime.Create(Context);
    _session.Open(_runtime);
    _session.ProgressDelegate = new MyProgressDelegate(this);
    _session.ContentDelegate = new MyContentDelegate(this);
    _session.NavigationDelegate = new MyNavigationDelegate();

    return Tuple.Create(_session, _runtime);
}

So here, add your own additional Delegates on the GeckoSession object if needed, see the official GeckoView documentation for more info. If you want to do your own logic but preserve the basic behaviors set for the Forms implementation, you just have to inherit from ProgressDelegate and ContentDelegate classes, and override any methods you want to use.

Your own MyProgressDelegate class can look like this:

public class MyProgressDelegate : Xam.Droid.GeckoView.Forms.Droid.Handler.ProgressDelegate
{
    public MyProgressDelegate(GeckoViewRenderer renderer) : base(renderer)
    {
        //renderer is available as a protected object inherited in your subclass scope as _renderer
        //You may also remove this constructor if not needed
    }

    public override void OnPageStart(GeckoSession session, string url)
    {
        base.OnPageStart(session, url);
        //Do you own logic
    }

    public override void OnPageStop(GeckoSession session, bool success)
    {
        base.OnPageStop(session, sucess);
        //Do you own logic
    }

    public override void OnProgressChange(GeckoSession session, int progress)
    {
        base.OnProgressChange(session, progress);
        //Do you own logic
    }

    public override void OnSecurityChange(GeckoSession session, ProgressDelegateSecurityInformation securityInfo)
    {
        base.OnSecurityChange(session, securityInfo);
        //Do you own logic
    }
}

Permissions

According to the current AndroidManifest.xml in GeckoView read-only repository, the minimal Permissions required to run are:

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

If you want to access Hardware components, you have to add the permission accordingly to your AndroidManifest.xml file.

Additional

NOTE: From the GeckoView library native dependencies, version 26.1.0.0 is the bare minimum, but it seem that Xamarin fail to compile with the library on this version.

Even if the JavaDoc is included on the source code, it seem that JavaDocToMd fail to generate the corresponding Bindings documentation. So there is no code documentation available for this moment.

Feel free to see Mozilla: Documentation and Examples

Troubleshooting

You may encounter this error on your Android project:

COMPILETODALVIK : Uncaught translation error : com.android.dx.cf.code.SimException: invalid opcode ba (invokedynamic re2>quires --min-sdk-version >= 26)

From Visual Studio 2019, you may add this line

<AndroidDexTool>d8</AndroidDexTool>

in your .csproj file.

So it may look like:

...
    <AndroidApplication>True</AndroidApplication>
    <AndroidDexTool>d8</AndroidDexTool>
    <AndroidResgenFile>Resources\Resource.designer.cs</AndroidResgenFile>
    <AndroidResgenClass>Resource</AndroidResgenClass>
...

Authors

Inspiration

I took inspiration for this guide from Kevin Gliewe as his CrossWalk binding project was for the same type of purpose !

Thanks a lot for your work as a reference.

License

This project is licensed under the MIT License - see the LICENSE file for details