Monodroid: Error When Calling Constructor of Custom View - Twodscrollview

MonoDroid: Error when calling constructor of custom view - TwoDScrollView

Congratulations! You've hit a leaky abstraction. :-/

The problem is this: for better or worse, virtual method calls from constructors invoke the most derived method implementation. C# is the same as Java in this respect; consider the following program:

using System;

class Base {
public Base ()
{
Console.WriteLine ("Base..ctor");
M ();
}

public virtual void M ()
{
Console.WriteLine ("Base.M");
}
}

class Derived : Base {

public Derived ()
{
Console.WriteLine ("Derived..ctor");
}

public override void M ()
{
Console.WriteLine ("Derived.M");
}
}

static class Demo {
public static void Main ()
{
new Derived ();
}
}

When run, the output is:

Base..ctor
Derived.M
Derived..ctor

That is, the Derived.M() method is invoked before the Derived constructor has executed.

In Mono for Android, things get more...complicated. The Android Callable Wrapper (ACW)'s constructor is invoked by Java and is responsible for creating the peer C# instance and mapping the Java instance to the C# instance. However, if a virtual method is invoked from the Java constructor, then the method will be dispatched before there is a C# instance to invoke the method upon!

Let that sink in a bit.

I don't know which method is triggering the scenario for your specific code (the code fragment you provided works fine), but we do have a sample which hits this scenario: LogTextBox overrides the TextView.DefaultMovementMethod property, and the TextView constructor invokes the getDefaultMovementMethod() method. The result is that Android tries to invoke LogTextBox.DefaultMovementMethod before a LogTextBox instance even exists.

So what does Mono for Android do? Mono for Android created the ACW, and thus knows which C# type the getDefaultMovementMethod() method should be delegated to. What it doesn't have is an instance, because one hasn't been created. So Mono for Android creates an instance of the appropriate type...via the (IntPtr, JniHandleOwnership) constructor, and generates an error if this constructor cannot be found.

Once the (in this case) TextView constructor finishes executing, the LogTextBox's ACW constructor will execute, at which point Mono for Android will go "aha! we've already created a C# instance for this Java instance", and will then invoke the appropriate constructor on the already created instance. Meaning that for a single instance, two constructors will be executed: the (IntPtr, JniHandleOwnership) constructor, and (later) the (Context, IAttributeSet, int) constructor.

Mono Android: Java Android custom view JNI not calling constructors in xml layout

The problem is that I haven't fully documented/explained what RegisterAttribute.DoNotGenerateAcw does, for which I must apologize.

Specifically, the problem is this: Android Layout XML can only refer to Java types. Normally this isn't a problem, as Android Callable Wrappers are generated at build-time, providing Java types for every C# type which subclasses Java.Lang.Object.

However, Android Callable Wrappers are special: they contain Java native method declarations, and Android Callable Wrapper constructors call into the Mono for Android runtime to create the relevant C# class. (See the example at the above url, and notice that the constructor body calls mono.android.TypeManager.Activate().)

Your example, however, completely bypasses all of that, because your layout XML isn't referencing an Android Callable Wrapper, it's instead referencing your Java type, and your Java type doesn't have a constructor that calls mono.android.TypeManager.Activate().

The result is that happens is exactly what you told it to happen: Your Java type is instantiated, and because there is no "plumbing" to associate the Java instance with a (to be) created C# instance (the mono.android.TypeManager.Activate() call), no C# instance is created, which is the behavior you're seeing.

All of which sounds entirely sane to me, but I'm the guy who wrote it, so I'm biased.

Thus, what do you want to have happen? If you really want a hand-written Java ImageView, as you've done, then there's only one reasonable C# constructor to have invoked: the (IntPtr, JniHandleOwnership) constructor. All that's missing is the mapping between the Java type and the C# type, which you can do "somewhere" during app startup via TypeManager.RegisterType():

Android.Runtime.TypeManager("com/example/widget/ImageView",
typeof(Example.Widgets.ImageView));

If you have that mapping in place, then when the com.example.widget.ImageView instance is surfaced in managed code, it will be wrapped with an Example.Widgets.ImageView instance, using the (IntPtr, JniHandleOwnership) constructor.

If, instead, you want to have your C# (Context context, IAttributeSet attrs, int defStyle) constructor invoked, you should bypass your wrapper type and just stick to C# code:

namespace Example.Widgets {
public class ImageView : global::Android.Widget.ImageView {
...

In summary, RegisterAttribute.DoNotGenerateAcw is "special": it means that you're "aliasing" an existing Java type, and that the "normal" Android Callable Wrapper generation should be skipped. This allows things to work (you can't have two different types with the same fully qualified name), but adds a different set of complications.

How to properly subclass SeekBar in MonoDroid?

Just tested this on API Level 8 and it seems to work.

using System;
using System.Globalization;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Util;
using Android.Widget;
using Android.OS;

namespace AndroidApplication1
{
[Activity(Label = "AndroidApplication1", MainLauncher = true, Icon = "@drawable/icon")]
public class Activity1 : Activity
{
int count = 1;

protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);

// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);

// Get our button from the layout resource,
// and attach an event to it
Button button = FindViewById<Button>(Resource.Id.MyButton);

button.Click += delegate { button.Text = string.Format("{0} clicks!", count++); };

var seekbar = new TTSeekBar(this);

var ll = FindViewById<LinearLayout>(Resource.Id.LinearLayout);

ll.AddView(seekbar);
}
}

public class TTSeekBar : SeekBar
{
protected TTSeekBar(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
}

public TTSeekBar(Context context) : base(context)
{
}

public TTSeekBar(Context context, IAttributeSet attrs) : base(context, attrs)
{
}

public TTSeekBar(Context context, IAttributeSet attrs, int defStyle) : base(context, attrs, defStyle)
{
}

private int _min = 0;
public int Min { get { return _min; } set { _min = value; } }

public override int Progress
{
get
{
return base.Progress + _min;
}
set
{
base.Progress = value;
}
}

public override int Max
{
get
{
return base.Max + _min;
}
set
{
base.Max = value + _min;
}
}

public object GetInputData()
{
return (Progress + _min).ToString(CultureInfo.InvariantCulture);
}
}
}

So as I said you just need to implement the right constructors and it should work just fine.

There is an explanation as to why here: MonoDroid: Error when calling constructor of custom view - TwoDScrollView

[MissingMethodException: No constructor found for Xamarin.Forms.Maps.Android.MapRenderer::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)]

However I do not have enough knowledge of Java or Android development to know how to resolve this issue. I am hoping that someone can explain where and how I can handle this exception when it occurs.

I think the reason is when you are navigating the pages, the Dispose method of Xamarin.Forms.Maps.Android.MapRenderer is called before the loading of the rest of the map. ACW need to create a new instance of MapRenderer but failed to create a new one because MapRenderer has no constructor method of (IntPtr, JniHandleOwnership).

If you refer to Premature Dispose() Calls you can find following statement:

If the subclass does contain an (IntPtr, JniHandleOwnership) constructor, then a new instance of the type will be created.

So the workaround for this exception is to create a subclass(Let's say MyMapRenderer) for Xamarin.Forms.Maps.Android.MapRenderer,which has a constructor with two arguments: (IntPtr, JniHandleOwnership), and use MyMapRenderer for map rendering:

  1. In PCL create a custom control for Xamarin.Forms.Map.Map and use MyMap instead in your project:

    public class MyMap:Map{}
  2. Create a Custom MapRenderer in Droid project,which has a (IntPtr, JniHandleOwnership) contructor:

    [assembly:ExportRenderer(typeof(MyMap),typeof(MyMapRenderer))]
    namespace YourNameSpace.Droid
    {
    public class MyMapRenderer:MapRenderer
    {
    public MyMapRenderer(IntPtr handle, JniHandleOwnership transfer) { }
    }
    }

For details about create a custom MapRenderer, please refer to Custom a Map.

Custom Application child class in Mono for Android

You need to add this constructor to your class in order to make it work:

public AcraApp (IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}

jump to custom view when phone rings

Android 8 added some actions related to this.

The ANSWER_PHONE_CALLS permission allows your app to answer incoming phone calls programmatically. To handle an incoming phone call in your app, you can use the acceptRingingCall() method. You could use this method to implement your custom view.

These permission are both classified as dangerous however and are both part of the PHONE permission group.

MonoDroid: Debugger timeout

Why do you need it to be longer?

30 seconds should be enough time for your app to start up and check that flag.

monodroid activity - wrong activity at start up

I occasionally see the wrong activity on startup.

I think this generally seems to be when I haven't changed any code and try to restart my application for a second debugging session.

I think what happens in this case, it that Android tries to restart my application on the page/Activity where I last left it. This situation also happens when the app is in general use - so it's good practice to write your code so that the app works in this situation (e.g. using the saved instance state bundle)



Related Topics



Leave a reply



Submit