Skip to content

Instantly share code, notes, and snippets.

@saamerm
Last active November 20, 2020 14:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save saamerm/bac9a150bc32a648dd55eea1a5670f9e to your computer and use it in GitHub Desktop.
Save saamerm/bac9a150bc32a648dd55eea1a5670f9e to your computer and use it in GitHub Desktop.
MvvmCross 5.2.1 MvxLayoutInflater.cs containing fixes to MvxLayoutInflater.cs from MvvmCross 6.4.1. Check the 4 steps mentioned in the commented code on the top for steps on how to implement this.
/* Steps:
1. Change the targetframework of your android project to Android 10 or above
2. Then add this file to your project either in one file or separate files.
3. Change the namespace from App.Droid to your Android project's namespace.
4. Then in all activities that inherit MvxAppCompatActivity or MvxActivity, add this override and build:
protected override void AttachBaseContext(Context @base)
{
base.AttachBaseContext(MvxContextWrapper2.Wrap(@base, this));
}
*/
using System;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Util;
using Android.Views;
using Java.Interop;
using Java.Lang;
using Java.Lang.Reflect;
using MvvmCross.Binding.BindingContext;
using MvvmCross.Binding.Droid.Binders;
using MvvmCross.Binding.Droid.BindingContext;
using MvvmCross.Binding.Droid.Views;
using MvvmCross.Platform;
using Boolean = Java.Lang.Boolean;
using Exception = Java.Lang.Exception;
using Object = Java.Lang.Object;
namespace App.Droid
{
public class MvxContextWrapper2 : MvxContextWrapper
{
private LayoutInflater _inflater;
private readonly IMvxBindingContextOwner _bindingContextOwner;
private static readonly string Tag = "MvxLayoutInflater";
public new static ContextWrapper Wrap(Context @base, IMvxBindingContextOwner bindingContextOwner)
{
return new MvxContextWrapper2(@base, bindingContextOwner);
}
protected MvxContextWrapper2(Context context, IMvxBindingContextOwner bindingContextOwner)
: base(context, bindingContextOwner)
{
if (bindingContextOwner == null)
throw new InvalidOperationException("Wrapper can only be set on IMvxBindingContextOwner");
_bindingContextOwner = bindingContextOwner;
}
public override Object GetSystemService(string name)
{
if (string.Equals(name, LayoutInflaterService, StringComparison.InvariantCulture))
{
return _inflater ??=
new MvxLayoutInflater2(LayoutInflater.From(BaseContext), this, null, false);
}
return base.GetSystemService(name);
}
}
public class MvxLayoutInflater2 : MvxLayoutInflater
{
public static bool Debug = false;
private static readonly string Tag = "MvxLayoutInflater2";
internal static BuildVersionCodes Sdk = Build.VERSION.SdkInt;
private static readonly string[] ClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
private readonly MvxBindingVisitor _bindingVisitor;
private IMvxAndroidViewFactory _androidViewFactory;
private IMvxLayoutInflaterHolderFactoryFactory _layoutInflaterHolderFactoryFactory;
private Field _constructorArgs;
private bool _setPrivateFactory;
public MvxLayoutInflater2(Context context)
: base(context)
{
_bindingVisitor = new MvxBindingVisitor();
SetupLayoutFactories(false);
}
public MvxLayoutInflater2(LayoutInflater original, Context newContext, MvxBindingVisitor bindingVisitor, bool cloned)
: base(original, newContext, bindingVisitor, cloned)
{
_bindingVisitor = bindingVisitor ?? new MvxBindingVisitor();
SetupLayoutFactories(cloned);
}
public MvxLayoutInflater2(IntPtr handle, JniHandleOwnership transfer)
: base(handle, transfer)
{
}
public override LayoutInflater CloneInContext(Context newContext)
{
return new MvxLayoutInflater(this, newContext, _bindingVisitor, true);
}
// We can't call this. See: https://bugzilla.xamarin.com/show_bug.cgi?id=30843
//public override View Inflate(XmlReader parser, ViewGroup root, bool attachToRoot)
//{
// SetPrivateFactoryInternal();
// return base.Inflate(parser, root, attachToRoot);
//}
// Calligraphy doesn't override this one...
public override View Inflate(int resource, ViewGroup root, bool attachToRoot)
{
// Make sure our private factory is set since LayoutInflater > Honeycomb
// uses a private factory.
SetPrivateFactoryInternal();
// Save the old factory in case we are recursing because of an MvxAdapter etc.
IMvxLayoutInflaterHolderFactory originalFactory = _bindingVisitor.Factory;
try
{
IMvxLayoutInflaterHolderFactory factory = null;
// Get the current binding context
var currentBindingContext = MvxAndroidBindingContextHelpers.Current();
if (currentBindingContext != null)
{
factory = FactoryFactory.Create(currentBindingContext.DataContext);
// Set the current factory used to generate bindings
if (factory != null)
_bindingVisitor.Factory = factory;
}
// Inflate the resource
var view = base.Inflate(resource, root, attachToRoot);
// Register bindings with clear key
if (currentBindingContext != null)
{
if (factory != null)
currentBindingContext.RegisterBindingsWithClearKey(view, factory.CreatedBindings);
}
return view;
}
finally
{
_bindingVisitor.Factory = originalFactory;
}
}
protected override View OnCreateView(View parent, string name, IAttributeSet attrs)
{
if (Debug)
Mvx.TaggedTrace(Tag, "... OnCreateView 3 ... {0}", name);
return _bindingVisitor.OnViewCreated(
base.OnCreateView(parent, name, attrs),
Context,
attrs);
}
protected override View OnCreateView(string name, IAttributeSet attrs)
{
if (Debug)
Mvx.TaggedTrace(Tag, "... OnCreateView 2 ... {0}", name);
View view = AndroidViewFactory.CreateView(null, name, Context, attrs) ??
PhoneLayoutInflaterOnCreateView(name, attrs) ??
base.OnCreateView(name, attrs);
return _bindingVisitor.OnViewCreated(view, Context, attrs);
}
// Dont need to add compiler symbols if your app is using Android 10 only.
// This function will give you an error if you don't have target framework Android 10 selected
// #if __ANDROID_29__
public override View OnCreateView(Context viewContext, View parent, string name, IAttributeSet attrs)
{
if (Debug)
Mvx.TaggedTrace(Tag, "... OnCreateView 4 ... {0}", name);
return _bindingVisitor.OnViewCreated(
base.OnCreateView(viewContext, parent, name, attrs),
viewContext,
attrs);
}
// #endif
// Mimic PhoneLayoutInflater's OnCreateView.
private View PhoneLayoutInflaterOnCreateView(string name, IAttributeSet attrs)
{
if (Debug)
Mvx.TaggedTrace(Tag, "... PhoneLayoutInflaterOnCreateView ... {0}", name);
foreach (var prefix in ClassPrefixList)
{
try
{
return CreateView(name, prefix, attrs);
}
catch (ClassNotFoundException)
{
}
}
return null;
}
// Note: setFactory/setFactory2 are implemented with export
// because there's a bug in the generator that doesn't
// mark the Factory/Factory2 setters as virtual.
// See: https://bugzilla.xamarin.com/show_bug.cgi?id=30764
[Export]
public void setFactory(IFactory factory)
{
// Wrap the incoming factory if we need to.
if (!(factory is FactoryWrapper))
{
Factory =
new FactoryWrapper(new DelegateFactory1(factory, _bindingVisitor));
return;
}
Factory = factory;
}
[Export]
public void setFactory2(IFactory2 factory2)
{
// Wrap the incoming factory if we need to.
if (!(factory2 is FactoryWrapper2))
{
Factory2 =
new FactoryWrapper2(new DelegateFactory2(factory2, _bindingVisitor));
return;
}
Factory2 = factory2;
}
private void SetupLayoutFactories(bool cloned)
{
if (cloned)
return;
// If factories are already set we need to wrap them in our
// own secret sauce.
if (Sdk > BuildVersionCodes.Honeycomb)
{
// Check for FactoryWrapper2 may be too loose
if (Factory2 != null && !(Factory2 is FactoryWrapper2))
{
MvxLayoutInflaterCompat.SetFactory(this, new DelegateFactory2(Factory2, _bindingVisitor));
}
}
// Check for FactoryWrapper may be too loose
if (Factory != null && !(Factory is FactoryWrapper))
{
MvxLayoutInflaterCompat.SetFactory(this, new DelegateFactory1(Factory, _bindingVisitor));
}
}
private void SetPrivateFactoryInternal()
{
if (_setPrivateFactory)
return;
if (Build.VERSION.SdkInt < BuildVersionCodes.Honeycomb)
return;
if (!(Context is IFactory2))
{
_setPrivateFactory = true;
return;
}
Class layoutInflaterClass = Class.FromType(typeof(LayoutInflater));
Method setPrivateFactoryMethod = layoutInflaterClass.GetMethod("setPrivateFactory", Class.FromType(typeof(IFactory2)));
if (setPrivateFactoryMethod != null)
{
try
{
setPrivateFactoryMethod.Accessible = true;
setPrivateFactoryMethod.Invoke(this,
new PrivateFactoryWrapper2((IFactory2)Context, this, _bindingVisitor));
}
catch (Exception ex)
{
Mvx.Warning("Cannot invoke LayoutInflater.setPrivateFactory :\n{0}", ex.StackTrace);
}
}
_setPrivateFactory = true;
}
protected View CreateCustomViewInternal(View parent, View view, string name, Context viewContext,
IAttributeSet attrs)
{
if (Debug)
Mvx.TaggedTrace(Tag, "... CreateCustomViewInternal ... {0}", name);
if (view == null && name.IndexOf('.') > -1)
{
// Attempt to inflate with MvvmCross unless we're trying to inflate an internal views
// since we don't resolve those.
if (!name.StartsWith("com.android.internal."))
{
view = AndroidViewFactory.CreateView(parent, name, viewContext, attrs);
}
if (view == null)
{
Object[] constructorArgsArr = null;
Object lastContext = null;
if (Build.VERSION.SdkInt <= BuildVersionCodes.P)
{
if (_constructorArgs == null)
{
Class layoutInflaterClass = Class.FromType(typeof(LayoutInflater));
_constructorArgs = layoutInflaterClass.GetDeclaredField("mConstructorArgs");
_constructorArgs.Accessible = true;
}
constructorArgsArr = (Object[])_constructorArgs.Get(this);
lastContext = constructorArgsArr[0];
// The LayoutInflater actually finds out the correct context to use. We just need to set
// it on the mConstructor for the internal method.
// Set the constructor args up for the createView, not sure why we can't pass these in.
constructorArgsArr[0] = viewContext;
_constructorArgs.Set(this, constructorArgsArr);
}
try
{
// Dont need to add compiler symbols if your app is using Android 10 only
// #if __ANDROID_29__
if (Build.VERSION.SdkInt > BuildVersionCodes.P)
// This line will give you an error if you don't have target framework Android 10 selected
view = CreateView(viewContext, name, null, attrs);
else
// #endif
view = CreateView(name, null, attrs);
}
catch (ClassNotFoundException)
{
}
finally
{
if (Build.VERSION.SdkInt <= BuildVersionCodes.P)
{
constructorArgsArr[0] = lastContext;
_constructorArgs.Set(this, constructorArgsArr);
}
}
}
}
return view;
}
protected IMvxAndroidViewFactory AndroidViewFactory => _androidViewFactory ?? (_androidViewFactory = Mvx.Resolve<IMvxAndroidViewFactory>());
protected IMvxLayoutInflaterHolderFactoryFactory FactoryFactory => _layoutInflaterHolderFactoryFactory ??
(_layoutInflaterHolderFactoryFactory = Mvx.Resolve<IMvxLayoutInflaterHolderFactoryFactory>());
private class DelegateFactory2 : IMvxLayoutInflaterFactory
{
private static readonly string Tag = "DelegateFactory2";
private readonly IFactory2 _factory;
private readonly MvxBindingVisitor _factoryPlaceholder;
public DelegateFactory2(IFactory2 factoryToWrap, MvxBindingVisitor binder)
{
_factory = factoryToWrap;
_factoryPlaceholder = binder;
}
public View OnCreateView(View parent, string name, Context context, IAttributeSet attrs)
{
if (Debug)
Mvx.TaggedTrace(Tag, "... OnCreateView ... {0}", name);
return _factoryPlaceholder.OnViewCreated(
_factory.OnCreateView(parent, name, context, attrs),
context, attrs);
}
}
private class DelegateFactory1 : IMvxLayoutInflaterFactory
{
private static readonly string Tag = "DelegateFactory1";
private readonly IFactory _factory;
private readonly MvxBindingVisitor _factoryPlaceholder;
public DelegateFactory1(IFactory factoryToWrap, MvxBindingVisitor bindingVisitor)
{
_factory = factoryToWrap;
_factoryPlaceholder = bindingVisitor;
}
public View OnCreateView(View parent, string name, Context context, IAttributeSet attrs)
{
if (Debug)
Mvx.TaggedTrace(Tag, "... OnCreateView ... {0}", name);
return _factoryPlaceholder.OnViewCreated(
_factory.OnCreateView(name, context, attrs),
context, attrs);
}
}
private class PrivateFactoryWrapper2 : Object, IFactory2
{
private static readonly string Tag = "PrivateFactoryWrapper2";
private readonly IFactory2 _factory2;
private readonly MvxBindingVisitor _bindingVisitor;
private readonly MvxLayoutInflater2 _inflater;
internal PrivateFactoryWrapper2(IFactory2 factory2, MvxLayoutInflater2 inflater,
MvxBindingVisitor bindingVisitor)
{
_factory2 = factory2;
_inflater = inflater;
_bindingVisitor = bindingVisitor;
}
public PrivateFactoryWrapper2(IntPtr handle, JniHandleOwnership transfer)
: base(handle, transfer)
{
}
public View OnCreateView(string name, Context context, IAttributeSet attrs)
{
if (Debug)
Mvx.TaggedTrace(Tag, "... OnCreateView 2 ... {0}", name);
return _bindingVisitor.OnViewCreated(
// The activity's OnCreateView
_factory2.OnCreateView(name, context, attrs),
context, attrs);
}
public View OnCreateView(View parent, string name, Context context, IAttributeSet attrs)
{
if (Debug)
Mvx.TaggedTrace(Tag, "... OnCreateView 3 ... {0}", name);
return _bindingVisitor.OnViewCreated(
_inflater.CreateCustomViewInternal(
parent,
// The activity's OnCreateView
_factory2.OnCreateView(parent, name, context, attrs),
name, context, attrs),
context, attrs);
}
}
}
internal class FactoryWrapper : Object, LayoutInflater.IFactory
{
protected readonly IMvxLayoutInflaterFactory DelegateFactory;
public FactoryWrapper(IntPtr handle, JniHandleOwnership ownership)
: base(handle, ownership)
{
}
public FactoryWrapper(IMvxLayoutInflaterFactory delegateFactory)
{
DelegateFactory = delegateFactory;
}
public View OnCreateView(string name, Context context, IAttributeSet attrs)
{
return DelegateFactory.OnCreateView(null, name, context, attrs);
}
}
internal class FactoryWrapper2 : FactoryWrapper, LayoutInflater.IFactory2
{
public FactoryWrapper2(IntPtr handle, JniHandleOwnership ownership)
: base(handle, ownership)
{
}
public FactoryWrapper2(IMvxLayoutInflaterFactory delegateFactory)
: base(delegateFactory)
{
}
public View OnCreateView(View parent, string name, Context context, IAttributeSet attrs)
{
return DelegateFactory.OnCreateView(parent, name, context, attrs);
}
}
}
@elpendor
Copy link

elpendor commented Aug 28, 2020

Hi, sorry to bother you. I'm guessing this is a workaround for this?

Can't really justify updating right now so I'm trying to patch this for now but I'm having some trouble getting this to work. The new inflater is called but apparently setFactory/setFactory2 are not being called.

Any ideas? (I'm also using mvvmcross 5.2.1)

@saamerm
Copy link
Author

saamerm commented Aug 29, 2020

Hey @elpendor! This was my attempt. Did you follow the 4 steps mentioned on the top?

@christophekindt
Copy link

I'm having the same problem as elpendor. setFactory methods are not being called..

@saamerm
Copy link
Author

saamerm commented Nov 20, 2020

What’s that set factory call used for? @christophekindt

@christophekindt
Copy link

Hi @saamerm,

I'm also trying to get this to work. I guess these factory methods have to get called in order to execute FactoryWrapper code. I've executed the four steps as described in the code comments, but I cant seem to get this to work. MvxLayoutInflater is still throwing NoSuchFieldException on mConstructorArgs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment