MonoDroid: Error when calling constructor of custom view - TwoDScrollView - android

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!

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.

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)
{
}

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)
{
}

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!

Related

How to get the current webview object in Android?

I am using Datadog to track user activity in my app. Now I need to instrument webviews. After initializing datadog's sdk , its documentation says that I have to call the following code snippet:
DatadogEventBridge.setup(webView)
that is, I have to call the static method setup and pass to it a WebView object. But the problem is: my application has many objects like this (many webviews). Do I have to put this code in every class that has a WebView attribute? Or is it possible in somehow use a callback function which is called whenever a webview is instatiated, the in this callback I'd call DatadogEventBridge.setup(webView)?
I tried using lifecycle callbacks and then receive an Acitivty for every "onResume" method in order to check whether this activity has a webview. But it went wrong.
I'm not really familiar with the Datadog Sdk but you could try creating your own WebView by extending the standard one - and then replace all the other WebViews with it. Here's how it'll look like in practice:
class TrackableWebView : WebView {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
: super(context, attrs, defStyleAttr)
init {
DatadogEventBridge.setup(this)
}
}
I think we can assume that the Sdk will be initialized much earlier than the WebViews, so there should be no problem with calling the DatadogEventBridge.setup(this) before the Sdk is initialized.
Then if you use the XML layouts you just replace standard WebView with your custom one:
<com.example.TrackableWebView
android:layout_width="match_parent"
android:layout_height="match_parent"/>
You can use WebViewClient to intercept when a WebView is created and then call the DatadogEventBridge.setup(webView) from there.
You can create a class that extends WebViewClient and then add it to each of your WebViews using the WebView.setWebViewClient(WebViewClient wvc) method.
public class DatadogWebViewClient extends WebViewClient {
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, URL);
DatadogEventBridge.setup(view);
}
}

IDE warning in Custom views in Android

I'm really curious about constructors for custom views. Currently I have one which extends the RelativeLayout. Each view need context for initialization. So, I put super() in the constructor. Although, I need the activity in my class so I took context from it. As far as I know, activity is a context too so why Android Studio gives me an inspection that I am missing the constructor with context ? My class looks like this:
public class CustomView extends RelativeLayout {
private Activity activity;
public CustomView(Activity activity) {
super(activity.getApplicationContext());
this.activity = activity;
initialize(activity.getApplicationContext());
}
//initialize method
}
This is the exact message Android Studio gives me:
Custom view CustomView is missing constructor used by tools: (Context) or (Context,AttributeSet) or (Context,AttributeSet,int)
Should I ignore this or should I pass in constructor both: activity and context?
Thanks for all information!
Better pass Context instead of Activity, because its possible to call View with Context but without Activity. For example if you call Dialog from Service.
Also based on documentation there is only one constructor is really necessary. But that constructor has contain AttributeSet as argument.
In your case it should looks like
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
this.activity = (Activity) context; //better to hold context actually
initialize(context);
}
And for sure good practice is to use all available constructors

Bridgecontext can not be cast to Application

I create customview, then get Application from context. But it show error:
java.lang.ClassCastException:com.android.layoutlib.bridge.android.BridgeContext cannot be cast to com.ibsv.cheerupkpi.utilities.CheerupKPIApplication
This is my code:
public CheerupKPIApplication mApplication;
public MenuBottomBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
LayoutInflater.from(context).inflate(R.layout.menu_bottom_bar, this);
mApplication = (CheerupKPIApplication) context.getApplicationContext();
}
Please help me!
The layout editor in Android Studio only mimics the layout inflation using a sort of mock Context. It doesn't actually have a real Context like you would on a real device, and certainly doesn't have an Application Context since there is no Application running (you aren't even in the runtime of a device).
You can use isInEditMode() in your custom view to avoid running code that doesn't work in the layout editor. In this case you would need to skip the line
mApplication = (CheerupKPIApplication) context.getApplicationContext();
as well as anything that needs to use mApplication to do an initial measure, layout, and draw.

Errors on MvxBind and MvxItemTemplate with a custom control that inherits from MvxListView

I get bind error during the loading of the view, the App keeps running but no visuals in the ListView.
I get the following error on the MvxBind: (and something same on MvxItemTemplate)
MvxBind: 9.11 Problem seen during binding execution for binding
ItemsSource for IsolationCertificate.IsolationPoints - problem
TargetInvocationException: Exception has been thrown by the target of
an invocation.
This happens on a custom control where I manualy want to add a HeaderView and control it later on. Therefore I needed the following construction;
public class MvxPaddedListView : MvxListView
{
public View Padder;
public MvxPaddedListView(Context context, IAttributeSet attrs)
: base(context, attrs, null)
{
SetFlexibleHeader(context);
var itemTemplateId = MvxAttributeHelpers.ReadListItemTemplateId(context, attrs);
Adapter = new MvxAdapter(context) { ItemTemplateId = itemTemplateId };
}
protected MvxPaddedListView(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
}
private void SetFlexibleHeader(Context context)
{
try
{
Padder = new View(context);
AddHeaderView(Padder);
}
catch (Exception ex)
{
throw;
}
}
}
Is there a common reason Why the MvxBind and MvxItemTemplate won't work anymore when inheriting from MvxListView?
Edit: its maybe good to point out I use a NULL in the MvxListView constructor to prevent early adapter creation
Found a working solution on: http://blog.masterdevs.com/headers-and-footers-on-an-mvxlistview/
MVVMCross was discussing a simular issue on:
https://github.com/MvvmCross/MvvmCross/issues/602

Returning values from custom view

I have made a class ShowWeight extending LinearLayout, which has two custom Views as Inner classes. I am using this class by means of an XML tag in the main.xml :
<org.test.ShowWeight android:id="#+id/sw"..................../>
There are two public variables in the ShowWeight class, whose changing values need to be captured in the main activity, which uses main.xml as its view.
How do I do this?
I tried this in the main activity :
ShowWeight sw=(ShowWeight)this.findViewById(R.id.sw);
Log.d("test",sw.getContentDescription().toString());
and this in the showWeight class:
this.setContentDescription(/*required value */);
This resulted in a NullPointerException.
Would appreciate some suggestions (Database, static variables, not an option)
Update:
Unfortunately, I am not permitted to post any of the code, I apologize if I seem vague, nevertheless I'm sure the ShowWeight class hasn't altered anything that might be causing the problem.
The ShowWeight class, which I have added to the main.xml view by means of an XML tag appears fine and functions properly.
When I use ShowWeight sw=(ShowWeight)this.findViewById(R.id.sw); in the Main Activity and then Toast or print ShowWeight I am getting 'null' . Also the setContentDescription(),getContentDescription() shouldn't throw errors because I've given a default contentDescription in the XML tag for ShowWeight.
Posting your ShowWeight class will help us more.
Assuming that you have class like this.
public class ShowWeight extends LinearLayout {
private Object myObject;
public ShowWeight(Context context, AttributeSet attrs)
{
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.header, this);
}
public Object getMyObject()
{
return myObject;
}
}
and in you MainActivity.java
ShowWeight sw=(ShowWeight)this.findViewById(R.id.sw);
sw.getMyObject();

Categories

Resources