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?
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.
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" />
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With