MonoDroid: Error when calling constructor of custom view - TwoDScrollView

34,828

Solution 1

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.

Solution 2

The error message says:

System.Exception: No constructor found for MyProject.TwoDScrollView::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)

Try adding a constructor like it says and see if that helps:

public TwoDScrollView (IntPtr a, JniHandleOwnership b) : base (a, b) { }

Solution 3

I had the same problem with a custom imageview and the answer for jpobst certainly fixed the problem completely :

public CircularImageView(Context context)
            :base(context) 
        {

            init (context, null, 0);
        }

        public CircularImageView(Context context, IAttributeSet attrs)
            : base(context, attrs)
        {
            init (context, attrs, Resource.Attribute.circularImageViewStyle);
        }

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

            init(context, attrs, defStyle);
        }
        public CircularImageView (IntPtr a, JniHandleOwnership b) : base (a, b)
        {
        }

Solution 4

I was using custom list view renderer, but none of the work arounds worked for me. But delaying the base.Dispose method helped me fix the crash, probably this gives the mono android, the chance to initialize the proxy instance.

Xamarin.Forms.Device.BeginInvokeOnMainThread(base.Dispose);

I don't see any crashes now!

Share:
34,828
David
Author by

David

Biomedical engineer and neuroscientist

Updated on July 12, 2022

Comments

  • David
    David almost 2 years

    I am building an Android application that uses the custom-built TwoDScrollView found here:

    http://blog.gorges.us/2010/06/android-two-dimensional-scrollview/

    This same class can be found referenced at several other websites, and others on Stack Overflow have asked questions with regard to it. I was using it in a previous Android application that I was building using Java/Eclipse, and I was having success.

    With my current application, I wanted to use C# and MonoDroid. I decided to rewrite the entire TwoDScrollView class in C#. After rewriting it, and then using it in some layout XML, I get the following exceptions when trying to run my code:

    System.NotSupportedException has been thrown. Unable to activate instance of type MyProject.TwoDScrollView from native handle 44f4d310.

    System.Exception: No constructor found for MyProject.TwoDScrollView::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership) ......with more text that follows....

    My layout XML is as follows:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        >
    
    <myproject.TwoDScrollView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
    
    </myproject.TwoDScrollView>
    
    </RelativeLayout>
    

    Per the instructions at the following link on using custom views in layout XML in MonoDroid: http://docs.xamarin.com/android/advanced_topics/using_custom_views_in_a_layout

    The constructors to the TwoDScrollView class look as follows:

    public TwoDScrollView(Context context) 
        : base(context)
    {
        initTwoDScrollView();
    }
    
    public TwoDScrollView(Context context, IAttributeSet attrs) 
        : base(context, attrs)
    {
        initTwoDScrollView();
    }
    
    public TwoDScrollView(Context context, IAttributeSet attrs, int defStyle) 
        : base(context, attrs, defStyle)
    {
        initTwoDScrollView();
    }
    

    The same constructors exist in the C# version as in the Java version (which you can find at the above link). Any idea on what could be going wrong? I can post the full C# code of my TwoDScrollView if anyone would like to see it. It's essentially the same as the Java code bit for bit - except rewritten in C#.

    Thanks for any help!

  • David
    David almost 12 years
    You mean I should try the logical answer? ;-) So, yes, your suggestion does indeed fix the problem as described my original question (as in: it gets rid of the error message, but the behavior of the application is still incorrect - the view doesn't display), but it doesn't really fix the problem that is at the heart of the issue: Why is it even searching for that constructor in the first place? The constructor that it calls is not one of the standard constructors for an Android custom view class. The standard constructors that one is supposed to create are the ones that I listed above.
  • jonp
    jonp almost 12 years
    @David: Why is it searching for the (IntPtr, JniHandleOwnership) constructor in the first place? Leaky abstractions. See my answer: stackoverflow.com/a/10603714/83444
  • David
    David almost 12 years
    thanks a lot! That was very helpful. I need to re-read your post and let it sink in a bit, and then take a look at my code with this new knowledge and see where my problem could be. I'll post another comment when I find something out.
  • David
    David almost 12 years
    I have simple code that reproduces the issue (sorry for the long amount of time it has been since the original question). Using the code sample above, simply add one method: public override void RequestLayout(). Overriding RequestLayout reproduces the issue. Also, overriding OnLayout() will reproduce it as well...but not quite as reliably (still can't figure out the exact cases under which it does).
  • ForceMagic
    ForceMagic about 10 years
    Hello Jonp, Is it possible that this issue is now fixed in the latest Xamarin Android version 4.12.2 ?
  • Mark13426
    Mark13426 about 8 years
    @jonp Shouldn't Xamarin add this constructor to all classes by default? Programmers can provide this in their own code, but Mono and Forms also have issues. I keep getting No constructor found for Java.Lang.Thread+RunnableImplementor and for Xamarin.Forms.Platform.Android.Platform+DefaultRenderer
  • jonp
    jonp about 8 years
    No, we shouldn't add that constructor by default, because it will hide bugs. If you're getting a No constructor found... message, that means that the mapped instance was prematurely collected, which means either there's a Dispose() call that shouldn't be there, or there's a GC bug, or something else that's Very Important And Shouldn't Be Hidden™. You need to understand why the exception is occurring lest you cargo cult "fix the problem."
  • SubqueryCrunch
    SubqueryCrunch about 8 years
    I had the same problem with instantiating a Class and this resolved my problem.
  • Mark13426
    Mark13426 about 8 years
    @jonp Is there anything I could do on my end to prevent those exceptions I mentioned earlier? At this point, I feel clueless. :| For the view renderers that other people mentioned, it seems easy to provide an extra constructor, but I have no idea what Java.Lang.Thread+RunnableImplementor and Xamarin.Forms.Platform.Android.Platform+DefaultRenderer are.
  • mayabelle
    mayabelle about 8 years
    I am also wondering about the Xamarin.Forms.Platform.Android.Platform+DefaultRenderer errors. Any way to add the constructor these are asking for?
  • rideintothesun
    rideintothesun over 6 years
    Does anyone know of a way to find out where the disposed view is getting called in the first place?
  • Renaud
    Renaud over 6 years
    I had the same problem with BroadcastReceiver for bluetooth. I had to UnRegister properly it, but thank you @jpobst it helps me a lot to figure it out.
  • Joyce de Lanna
    Joyce de Lanna about 6 years
    where did you write this?
  • zafar
    zafar about 6 years
    Inside your ListViewRenderer class, override the Dispose method and invoke the base.Dispose(disposing) as mentioned above
  • Joyce de Lanna
    Joyce de Lanna about 6 years
    it seems that it solved my problem!! I searched so much and nothing could solve...thanks!!!
  • Joyce de Lanna
    Joyce de Lanna about 6 years
    It's happen again..I don't know what to do anymore
  • Jamie M.
    Jamie M. over 5 years
    So, @jonp Since we shouldn't supply these missing constructors and there is likely a GC bug etc., what is the best course of action for Xamarin Forms users to avoid this issue? Is there any more insight into this now?
  • Karan Harsh Wardhan
    Karan Harsh Wardhan over 5 years
    so what is the solution for this @jonp?
  • nerlijma
    nerlijma almost 5 years
    @JoycedeLanna This thread is quite old, but I am facing the same issue, any clues how to fix it?