Search code examples
c#xamarinxamarin.android

How do I define and use IOn[X]SetListenerImplementor


I am reading through the source code for Xamarin and came across these two dialogs: the TimePickerDialog and DatePickerDialog.

When reading through these, I see that there is an object being instantiated that I am unable to find anywhere else.

I'm wondering what the purpose of these listener implementors are as well as how to actually go about creating my own (or if I even need to make my own). My goal was to create some custom PickerDialogs so I was checking to see how they are already implemented.

Here's the TimePickerDialog:

using System;
using System.Collections.Generic;
using Android.Runtime;

namespace Android.App {

    public partial class TimePickerDialog {

        public TimePickerDialog (Android.Content.Context context, EventHandler<TimeSetEventArgs> callBack, int hourOfDay, int minute, bool is24HourView) 
            // ** The IOnTimeSetListenerImplementor here **
            : this (context, new IOnTimeSetListenerImplementor () { Handler = callBack }, hourOfDay, minute, is24HourView) {}

        public TimePickerDialog (Android.Content.Context context, int theme, EventHandler<TimeSetEventArgs> callBack, int hourOfDay, int minute, bool is24HourView) 
            : this (context, theme, new IOnTimeSetListenerImplementor () { Handler = callBack }, hourOfDay, minute, is24HourView) {}

    }
}

Here's the DatePickerDialog:

using System;
using System.Collections.Generic;
using Android.Runtime;

namespace Android.App {

    public partial class DatePickerDialog {

        public partial class DateSetEventArgs {

            public DateTime Date {
                get { return new DateTime (Year, Month + 1, DayOfMonth); }
            }

#if ANDROID_24
            [Obsolete ("This parameter in DateTimePickerDialog constructor is removed in Android API, so it will vanish from this automatically generated type too.")]
            public int MonthOfYear {
                get { return Month; }
            }
#else
            public int Month {
                get { return monthOfYear; }
            }
#endif
        }

        public DatePickerDialog (Android.Content.Context context, EventHandler<DateSetEventArgs> callBack, int year, int monthOfYear, int dayOfMonth) 
            // ** The IOnDateSetListenerImplementor here **
            : this (context, new IOnDateSetListenerImplementor () { Handler = callBack }, year, monthOfYear, dayOfMonth) {}

        public DatePickerDialog (Android.Content.Context context, int theme, EventHandler<DateSetEventArgs> callBack, int year, int monthOfYear, int dayOfMonth) 
            : this (context, theme, new IOnDateSetListenerImplementor () { Handler = callBack }, year, monthOfYear, dayOfMonth) {}

        public void UpdateDate (DateTime date)
        {
            UpdateDate (date.Year, date.Month - 1, date.Day);
        }
    }
}

Bonus question: Why are these two classes defined as partial classes? I can't find the other parts anywhere else in this repo. Perhaps I am not searching correctly, or maybe it's for future expansion?

Thanks for the help!


Solution

  • I found the following when I searched through the metadata of Android.App.DatePickerDialog (easy enough to do by Right-Clicking a class name in Visual Studio and selecting Go To Definition):

    #region Assembly Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=84e04ff9cfb79065
    // C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\ReferenceAssemblies\Microsoft\Framework\MonoAndroid\v11.0\Mono.Android.dll
    #endregion
    
    #nullable enable
    
    using Android.Content;
    using Android.Runtime;
    using Android.Widget;
    using Java.Interop;
    using System;
    using System.ComponentModel;
    using System.Diagnostics;
    
    namespace Android.App
    {
        [Register("android/app/DatePickerDialog", DoNotGenerateAcw = true)]
        public class DatePickerDialog : AlertDialog, IDialogInterfaceOnClickListener, IJavaObject, IDisposable, IJavaPeerable, DatePicker.IOnDateChangedListener
        {
            [Register(".ctor", "(Landroid/content/Context;)V", "", ApiSince = 24)]
            public DatePickerDialog(Context context);
            [Register(".ctor", "(Landroid/content/Context;I)V", "", ApiSince = 24)]
            public DatePickerDialog(Context context, int themeResId);
            public DatePickerDialog(Context context, EventHandler<DateSetEventArgs> callBack, int year, int monthOfYear, int dayOfMonth);
            [Register(".ctor", "(Landroid/content/Context;Landroid/app/DatePickerDialog$OnDateSetListener;III)V", "")]
            public DatePickerDialog(Context context, IOnDateSetListener? listener, int year, int month, int dayOfMonth);
            public DatePickerDialog(Context context, int theme, EventHandler<DateSetEventArgs> callBack, int year, int monthOfYear, int dayOfMonth);
            [Register(".ctor", "(Landroid/content/Context;ILandroid/app/DatePickerDialog$OnDateSetListener;III)V", "")]
            public DatePickerDialog(Context context, int themeResId, IOnDateSetListener? listener, int year, int monthOfYear, int dayOfMonth);
            protected DatePickerDialog(IntPtr javaReference, JniHandleOwnership transfer);
    
            public virtual DatePicker DatePicker { get; }
            [DebuggerBrowsable(DebuggerBrowsableState.Never)]
            [EditorBrowsable(EditorBrowsableState.Never)]
            public override JniPeerMembers JniPeerMembers { get; }
            [DebuggerBrowsable(DebuggerBrowsableState.Never)]
            [EditorBrowsable(EditorBrowsableState.Never)]
            protected override Type ThresholdType { get; }
            [DebuggerBrowsable(DebuggerBrowsableState.Never)]
            [EditorBrowsable(EditorBrowsableState.Never)]
            protected override IntPtr ThresholdClass { get; }
    
            public event EventHandler<DateSetEventArgs> DateSet;
    
            [Register("onClick", "(Landroid/content/DialogInterface;I)V", "GetOnClick_Landroid_content_DialogInterface_IHandler")]
            public virtual void OnClick(IDialogInterface dialog, int which);
            [Register("onDateChanged", "(Landroid/widget/DatePicker;III)V", "GetOnDateChanged_Landroid_widget_DatePicker_IIIHandler")]
            public virtual void OnDateChanged(DatePicker view, int year, int month, int dayOfMonth);
            [Register("setOnDateSetListener", "(Landroid/app/DatePickerDialog$OnDateSetListener;)V", "GetSetOnDateSetListener_Landroid_app_DatePickerDialog_OnDateSetListener_Handler", ApiSince = 24)]
            public virtual void SetOnDateSetListener(IOnDateSetListener? listener);
            public void UpdateDate(DateTime date);
            [Register("updateDate", "(III)V", "GetUpdateDate_IIIHandler")]
            public virtual void UpdateDate(int year, int month, int dayOfMonth);
    
            [Register("android/app/DatePickerDialog$OnDateSetListener", "", "Android.App.DatePickerDialog/IOnDateSetListenerInvoker")]
            public interface IOnDateSetListener : IJavaObject, IDisposable, IJavaPeerable
            {
                [Register("onDateSet", "(Landroid/widget/DatePicker;III)V", "GetOnDateSet_Landroid_widget_DatePicker_IIIHandler:Android.App.DatePickerDialog/IOnDateSetListenerInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null")]
                void OnDateSet(DatePicker? view, int year, int month, int dayOfMonth);
            }
    
            public class DateSetEventArgs : EventArgs
            {
                public DateSetEventArgs(int year, int month, int dayOfMonth);
    
                public DateTime Date { get; }
                [Obsolete("This parameter in DateTimePickerDialog constructor is removed in Android API, so it will vanish from this automatically generated type too.")]
                public int MonthOfYear { get; }
                public int Year { get; }
                public int Month { get; }
                public int DayOfMonth { get; }
            }
        }
    }
    

    I saw that the IOnDateSetListener interface was being defined right in the class. I also noticed the class was no longer marked partial.

    I then was also able to find DatePickerDialog+IOnDateSetListener.xml:

    <Type Name="DatePickerDialog+IOnDateSetListener" FullName="Android.App.DatePickerDialog+IOnDateSetListener">
      <TypeSignature Language="C#" Value="public interface DatePickerDialog.IOnDateSetListener : Android.Runtime.IJavaObject, IDisposable, Java.Interop.IJavaPeerable" />
      <TypeSignature Language="ILAsm" Value=".class nested public interface auto ansi abstract DatePickerDialog/IOnDateSetListener implements class Android.Runtime.IJavaObject, class Java.Interop.IJavaPeerable, class System.IDisposable" />
      <TypeSignature Language="DocId" Value="T:Android.App.DatePickerDialog.IOnDateSetListener" />
      <TypeSignature Language="F#" Value="type DatePickerDialog.IOnDateSetListener = interface&#xA;    interface IJavaObject&#xA;    interface IDisposable&#xA;    interface IJavaPeerable" />
    
    ...
    
     <AttributeName Language="C#">[Android.Runtime.Register("android/app/DatePickerDialog$OnDateSetListener", "", "Android.App.DatePickerDialog/IOnDateSetListenerInvoker")]</AttributeName>
          <AttributeName Language="F#">[&lt;Android.Runtime.Register("android/app/DatePickerDialog$OnDateSetListener", "", "Android.App.DatePickerDialog/IOnDateSetListenerInvoker")&gt;]</AttributeName>
       
    ...
    
    <a href="https://developer.android.com/reference/android/app/DatePickerDialog$OnDateSetListener" title="Reference documentation">Android platform documentation</a>
    
    ...
    
    

    and DatePickerDialog.xml:

    <Type Name="DatePickerDialog" FullName="Android.App.DatePickerDialog">
      <TypeSignature Language="C#" Value="public class DatePickerDialog : Android.App.AlertDialog, Android.Content.IDialogInterfaceOnClickListener, Android.Widget.DatePicker.IOnDateChangedListener, IDisposable, Java.Interop.IJavaPeerable" />
      <TypeSignature Language="ILAsm" Value=".class public auto ansi beforefieldinit DatePickerDialog extends Android.App.AlertDialog implements class Android.Content.IDialogInterfaceOnClickListener, class Android.Runtime.IJavaObject, class Android.Widget.DatePicker/IOnDateChangedListener, class Java.Interop.IJavaPeerable, class System.IDisposable" />
      <TypeSignature Language="DocId" Value="T:Android.App.DatePickerDialog" />
      <TypeSignature Language="F#" Value="type DatePickerDialog = class&#xA;    inherit AlertDialog&#xA;    interface IDialogInterfaceOnClickListener&#xA;    interface IJavaObject&#xA;    interface IDisposable&#xA;    interface IJavaPeerable&#xA;    interface DatePicker.IOnDateChangedListener" />
      <AssemblyInfo>
        <AssemblyName>Mono.Android</AssemblyName>
        <AssemblyVersion>0.0.0.0</AssemblyVersion>
      </AssemblyInfo>
      <Base>
        <BaseTypeName>Android.App.AlertDialog</BaseTypeName>
      </Base>
      <Interfaces>
        <Interface>
          <InterfaceName>Android.Content.IDialogInterfaceOnClickListener</InterfaceName>
        </Interface>
        <Interface>
          <InterfaceName>Android.Runtime.IJavaObject</InterfaceName>
        </Interface>
        <Interface>
          <InterfaceName>Android.Widget.DatePicker+IOnDateChangedListener</InterfaceName>
        </Interface>
    ...
    

    Both of the aforementioned files mention it. My best guess would be that there are build events that use this information to build cross-language methods and inject them into partial classes. The c# DatePickerDialog(from metadata) may be the result of compile time events merging the DatePickerDialog - mentioned in the question - with the information contained in the XML files when the Xamarin libraries are built and packaged.

    The listener interfaces seem to be specific to the behavior of DatePicker and TimePicker, and don't seem to be necessary in every situation.

    Instead of trying to replicate that behavior, creating a custom dialog that extends AlertDialog and calling the SetView method may be all that is needed.

    EDIT: There is also a Popup class included in the Xamarin Community Toolkit that makes it very easy to make custom dialogs like the ones referenced in the question. See the documentation here.