Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Could not activate JNI Handle - Xamarin Android during adding Own LinearLayout

I am creating own calendar in Xamarin Android, base on this tutorial. I converted everything that is needed from Java to C# but right now when I start app and open fragment that contains custom calendar in the line:

public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        var view = inflater.Inflate(Resource.Layout.calendar_fragment_main, container, false); //System.NotSupportedException Error
        return view;
}

I got an error that says:

System.NotSupportedException - Could not activate JNI Handle 0xffcdb7e8 (key_handle 0x741b240) of Java type 'md57f15d2d0137b5b5d70f719ce3cee21d4/EstiCalendar' as managed type 'EstiMOBILE.Droid.Components.EstiCalendar.Component.Layouts.EstiCalendar'.

and also 2 other errors that probably come off from first error that says:

Java.Lang.UnsupportedOperationException - Binary XML file line #1: You must supply a layout_width attribute.

Android.Views.InflateException - Binary XML file line #1: Binary XML file line #1: You must supply a layout_width attribute.

Fragment (calendar_fragment_main.axml) that contains custom calendar:

    <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
                xmlns:esticalendar="http://schemas.android.com/apk/res-auto" 
                xmlns:app="http://schemas.android.com/apk/res-auto"
                xmlns:tools="http://schemas.android.com/tools"
                android:orientation="vertical" 
                android:layout_width="match_parent"
                android:layout_height="match_parent">
    <RelativeLayout 
        android:id="@+id/calendar_content_wrapper"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
        android:id="@+id/calendar_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        tools:text="Calendar" />
        <CalendarView 
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_below="@id/calendar_title"
        android:id="@+id/calendar_main_object" 
        android:visibility="gone" />
        <EstiMOBILE.Droid.Components.EstiCalendar.Component.Layouts.EstiCalendar
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/esti_calendar"
        esticalendar:date_format="MMMM yyyy" />
    </RelativeLayout>
</RelativeLayout>

My custom calendar class EstiCalendar.cs

using System;
using System.Collections.Generic;
using Android.Content;
using Android.Content.Res;
using Android.Graphics;
using Android.Runtime;
using Android.Util;
using Android.Views;
using Android.Widget;
using Java.Text;
using Java.Util;

namespace EstiMOBILE.Droid.Components.EstiCalendar.Component.Layouts
{
    public class EstiCalendar : LinearLayout
    {
        // how many days to show, defaults to six weeks, 42 days
        private static readonly int DAYS_COUNT = 42;

        // default date format
        private static readonly string DATE_FORMAT = "MMM yyyy";

        // date format
        private string dateFormat;

        // current displayed month
        private Calendar currentDate = Calendar.Instance;

        //event handling
        private IEventHandler eventHandler = null;

        // internal components
        private LinearLayout header;
        private ImageView btnPrev;
        private ImageView btnNext;
        private TextView txtDate;
        private GridView grid;

        // seasons' rainbow
        int[] rainbow = new int[] {
            Resource.Color.summer,
            Resource.Color.fall,
            Resource.Color.winter,
            Resource.Color.spring
    };

        // month-season association (northern hemisphere, sorry australia :)
        readonly int[] monthSeason = { 2, 2, 3, 3, 3, 0, 0, 0, 1, 1, 1, 2 };

        public EstiCalendar(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer)
        {
        }

        public EstiCalendar(Context context, IAttributeSet attrs) : base(context, attrs)
        {
            InitControl(context, attrs);
        }

        public EstiCalendar(Context context, IAttributeSet attrs, int defStyleAttr) : base(context, attrs, defStyleAttr)
        {
            InitControl(context, attrs);
        }

        /**
         * Load control xml layout
         */
        private void InitControl(Context context, IAttributeSet attrs)
        {
            LayoutInflater inflater = (LayoutInflater)context.GetSystemService(Context.LayoutInflaterService);
            inflater.Inflate(Resource.Layout.calendar_control, this);

            LoadDateFormat(attrs);
            AssignUiElements();
            AssignClickHandlers();

            UpdateCalendar();
        }

        private void LoadDateFormat(IAttributeSet attrs)
        {
            TypedArray ta = Context.ObtainStyledAttributes(attrs, Resource.Styleable.EstiCalendar);

            try
            {
                // try to load provided date format, and fallback to default otherwise
                dateFormat = ta.GetString(Resource.Styleable.EstiCalendar_date_format);
                if (dateFormat == null)
                    dateFormat = DATE_FORMAT;
            }
            finally
            {
                ta.Recycle();
            }
        }
        private void AssignUiElements()
        {
            // layout is inflated, assign local variables to components
            header = (LinearLayout)FindViewById(Resource.Id.calendar_header);
            btnPrev = (ImageView)FindViewById(Resource.Id.calendar_prev_button);
            btnNext = (ImageView)FindViewById(Resource.Id.calendar_next_button);
            txtDate = (TextView)FindViewById(Resource.Id.calendar_date_display);
            grid = (GridView)FindViewById(Resource.Id.calendar_grid);
        }

        private void AssignClickHandlers()
        {
            btnNext.Click += (sender, e) =>
            {
                currentDate.Add(Calendar.Month, 1);
                UpdateCalendar();
            };

            btnPrev.Click += (sender, e) =>
            {
                currentDate.Add(Calendar.Month, -1);
                UpdateCalendar();
            };

            grid.ItemLongClick += (object sender, AdapterView.ItemLongClickEventArgs e) => {
                if (eventHandler != null)
                {
                    eventHandler.OnDayLongPress((Date)e.Position);
                }
            };

        }

        public void UpdateCalendar()
        {
            UpdateCalendar(null);
        }

        /**
         * Display dates correctly in grid
         */
        public void UpdateCalendar(HashSet<Date> events)
        {
            List<Date> cells = new List<Date>();
            Calendar calendar = (Calendar)currentDate.Clone();

            // determine the cell for current month's beginning
            calendar.Set(Calendar.DayOfMonth, 1);
            int monthBeginningCell = calendar.Get(Calendar.DayOfWeek) - 1;

            // move calendar backwards to the beginning of the week
            calendar.Add(Calendar.DayOfMonth, -monthBeginningCell);

            // fill cells
            while (cells.Count < DAYS_COUNT)
            {
                cells.Add(calendar.Time);
                calendar.Add(Calendar.DayOfMonth, 1);
            }

            // update grid
            grid.Adapter = new CalendarAdapter(Context, cells, events);

            // update title
            SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
            txtDate.Text = sdf.Format(currentDate.Time);

            // set header color according to current season
            int month = currentDate.Get(Calendar.Month);
            int season = monthSeason[month];
            int color = rainbow[season];

            header.SetBackgroundColor(Resources.GetColor(color));
        }


        private class CalendarAdapter : BaseAdapter<Date>
        {
            // days with events
            private HashSet<Date> eventDays;

            private List<Date> days;

            // for view inflation
            private LayoutInflater inflater;

            private Context context;

            public CalendarAdapter(Context context, List<Date> days, HashSet<Date> eventDays) : base()
            {
                this.eventDays = eventDays;
                this.days = days;
                inflater = LayoutInflater.From(context);
                this.context = context;
            }

            public override Date this[int position] => days[position];

            public override int Count => days.Count;

            public override long GetItemId(int position)
            {
                return position;
            }

            public override View GetView(int position, View convertView, ViewGroup parent)
            {
                // day in question
                var date = GetItem(position) as Date;
                int day = date.GetDate();
                int month = date.Month;
                int year = date.Year;

                // today
                Date today = new Date();

                // inflate item if it does not exist yet
                if (convertView == null)
                    convertView = inflater.Inflate(Resource.Layout.control_calendar_day, parent, false);

                // if this day has an event, specify event image
                convertView.SetBackgroundResource(0);
                if (eventDays != null)
                {
                    foreach (Date eventDate in eventDays)
                    {
                        if (eventDate.GetDate() == day &&
                                eventDate.Month == month &&
                                eventDate.Year == year)
                        {
                            // mark this day for event
                            convertView.SetBackgroundResource(Resource.Drawable.reminder);
                            break;
                        }
                    }
                }

                // clear styling
                ((TextView)convertView).SetTypeface(null, TypefaceStyle.Normal);
                ((TextView)convertView).SetTextColor(Color.Black);

                if (month != today.Month || year != today.Year)
                {
                    // if this day is outside current month, grey it out
                    ((TextView)convertView).SetTextColor(context.Resources.GetColor(Resource.Color.greyed_out));
                }
                else if (day == today.GetDate())
                {
                    // if it is today, set it to blue/bold
                    ((TextView)convertView).SetTypeface(null, TypefaceStyle.Bold);
                    ((TextView)convertView).SetTextColor(context.Resources.GetColor(Resource.Color.today));
                }

                // set text
                ((TextView)convertView).Text = $"{date.GetDate()}";

                return convertView;
            }
        }


        public void SetEventHandler(IEventHandler eventHandler)
        {
            this.eventHandler = eventHandler;
        }


        public interface IEventHandler
        {
            void OnDayLongPress(Date date);
        }
    }
}

CalendarFragment.cs that contains error:

using System;
using System.Collections.Generic;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using EstiMOBILE.Droid.Components.EstiCalendar.Component.Layouts;
using EstiMOBILE.Droid.Fragments.BaseFragments;
using Java.Util;

namespace EstiMOBILE.Droid.Fragments.CustomFragments
{
    public class CalendarFragment : BaseFragment
    {
        public override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
        }

        public static CalendarFragment NewInstance()
        {
            var frag1 = new CalendarFragment { Arguments = new Bundle() };
            return frag1;
        }

        public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            var view = inflater.Inflate(Resource.Layout.calendar_fragment_main, container, false); //Error

            //InitCustomCalendar(view);
            return view;
        }

        public void InitCustomCalendar(View view)
        {
            HashSet<Date> events = new HashSet<Date>
            {
                new Date()
            };

            EstiCalendar cv = (EstiCalendar) view.FindViewById(Resource.Id.esti_calendar);
            cv.UpdateCalendar(events);

        }
    }
}

Do you have any help, how can I get rid of this error?

like image 271
Konrad Uciechowski Avatar asked Nov 06 '22 20:11

Konrad Uciechowski


2 Answers

Add the constructor with IntPtr and JniHandleOwnership

public EstiCalendar(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer)
{
}

This is how Java gets hold of the Android Callable Wrapper (ACW). So this ctor will be hit before any of the other ctors.

like image 172
Cheesebaron Avatar answered Nov 14 '22 23:11

Cheesebaron


You don't nee the additional constructor

public EstiCalendar(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer)
{
}

A problem is the namespace between Java and Xamarin, you should use lower case letters for the namespace and camelcase for the class name in the xml file. Change your xml file like:

<estimobile.droid.components.esticalendar.component.layouts.EstiCalendar
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/esti_calendar"
    esticalendar:date_format="MMMM yyyy" />
like image 24
eugstman Avatar answered Nov 14 '22 23:11

eugstman