Developing For Null Doesn't Necessarily Mean It Has To Be For Nothing

I found myself at an interesting impasse yesterday while working on an application. This application is pretty much fully functional as is, but there has been some additional business logic added to the back-end and thus the front-end would need to accommodate. I discovered, while looking at the new logic, that my current front-end handling would need to change to support a specific use case… and I was really not sure how to compensate for it without saying “I can’t support that”.

This business logic was pretty simple in terms of what was required and what was returned. The logic dictated that through an API request, I (the client) would submit:

  • a beginning date and ending date

  • a few strings and integers

Seems pretty simple, right?
… Well, the strings and integers weren’t going to be a problem, but the start and end dates are where I got stuck. In Xamarin Forms the DatePicker control allows for a few properties by default: Date, MinimumDate, MaximumDate, Format (how it displays), etc.

  • The Date property require an input type of DateTime which defaults to DateTime.Today

  • The MinimumDate property requires an input type DateTime and defaults to Jan. 1, 1900

  • The MaximumDate property requires an input type DateTime and defaults to Dec. 31, 2100

Now, my new business logic insists that my starting date could not be in the past (or today), and the ending date was optional - yet the DateTime struct for Date in the DatePicker control doesn’t support an optional (or null) value by default, so I had to figure out how to handle this.

At first I thought submitting the MaximumDate could suffice, but then I discovered that the backend would handle it and hard code that date to the system - and there were other things that looked at that date and calculations would occur because of it. Thus, using maximum wouldn’t work and I started looking at other 3rd party controls like Telerik, Syncfusion, etc to see if I could gather any insight on how to handle a null date… Sadly I couldn’t find much out there that didn’t require a bunch of code for platform native renderers and I really didn’t want to go down that route because of how much more overhead it would add as the business logic would inevitably change again. I wanted something simple and something cross-platform - but everything I could find was telling me it wasn’t possible.

I may be new to Xamarin development, but I believed that I could come up with something that would achieve what I thought would work. Here’s what I came up with.

I’m going to make a custom control!

Logically, the only different between the XF control and what I needed was the ability to blank out the Date field - because handling a null vs a date string is handled in my MVVM pattern, so I figured that I really didn’t need a lot of logic in the control, and I was right.

Since the Xamarin DatePicker control is cross-platform, I decided that I didn’t need the UI xaml and a code behind class, I only needed the class itself since I was going to inherit the DatePicker control and everything it already had.

2021-07-06_07-59-05.png

First thing I did was add a ‘Controls’ folder to my project, because I like structure to my stuff. In the two years that I’ve been writing code, I’ve learned the hard way that the more effort you put into your structure here the better off you’ll be in the long run in terms of just remembering how to navigate your own code years later.

So now that I have my folder, I can start on the class definition.

I created my class, and named it quite obviously as NullableDatePicker, and set my inheritance as the Xamarin Forms DatePicker control.

namespace BaileyBrew.Portal.Controls { public class NullableDatePicker : DatePicker { } }

By setting the inheritance, I’m able to use all the logic that already exists in the control and I don’t have to do all the extra work that I’d have to do with custom renderers to take what I’m doing and give it to the native platform to handle.

Now, this is where the logic comes in - I needed to create a bindable property that I could bind to the new custom control that allowed ‘null’ as a valid submission. Since the Date field in the DatePicker control accepts only DateTime struct and it’s not nullable by default, I had to make something special.

I went with NullableDate for the property itself, and since I still wanted to keep all the other values matching the DateTime struct, I went with the fairly standard BindableProperty route because the rest of my application is build on data binding, styles and templates.

A BindableProperty instance allows my application to access the value that gets tied to my NullableDatePicker, as well as set some default values and even validate.

public static readonly BindableProperty NullableDateProperty = BindableProperty.Create( nameof(NullableDate), typeof(DateTime?), typeof(NullableDatePicker), null); public DateTime? NullableDate { get { return (DateTime?)GetValue(NullableDateProperty); } set { SetValue(NullableDateProperty, value); UpdateDate(); } }

Pretty straightforward, right?

My BindableProperty makes some parameter declarations in the BindableProperty.Create( ):

  1. Name —> nameof(NullableDate)

  2. Property Type —> typeof(DateTime?)

  3. Owning Object —> typeof(NullableDatePicker)

  4. Default Value —> null

Those four parameters are what the BindableProperty/Object class requires at a minimum from a XF standpoint, and since I don’t really need to do any validation or manipulation in my control, I opted to leave it there.

The final piece of the BindableProperty is to define the NullableDate property itself, and by defining it as DateTime? I’m essentially saying that my field can be null. Then to make it full function I need to add my property accessors so that I can actually get at the property itself. So I have to add the ‘get’ and ‘set’ accordingly.

My get accessor just returns the current BindableProperty value:

    get { return (DateTime?)GetValue(NullableDateProperty); }

My set accessor sets the BindableProperty value and calls a method that I’ll get into in a moment

    set { SetValue(NullableDateProperty, value); UpdateDate(); }
soclose.gif

With me so far?

Okay, then let’s keep going.

I mentioned that my set accessor called an additional method, which I call UpdateDate(), which as you can probably imagine, you know, updates the DateTime value. But it does a little more than that as well.

private string _format = null; private void UpdateDate() { if (NullableDate.HasValue) { if(NullableDate == DateTime.Today) { _format = Format; Format = "OPTIONAL..."; } else { if (null != _format) Format = _format; Date = NullableDate.Value; } } else { _format = Format; Format = "OPTIONAL..."; }

So for the date to update as ‘Optional…’ as in that appears in the DatePicker itself prior to getting the actual native element to select a date - I had to create a way to see if the NullableDate field has a value, and if it didn’t have value I wanted to replace the visible field with my text of OPTIONAL…

It’s pretty clear, but IF NullableDate has a value, then the field is set with the appropriate value, otherwise set the text as my predefined string.

And that is it.

I added a couple overrides at the end, just to track the changes as things change outside of the client actually physically doing something on the screen (like screen refresh and the initial load of the page itself.

using System; using Xamarin.Forms; namespace BaileyBrew.Portal.Controls { public class NullableDatePicker : DatePicker { public static readonly BindableProperty NullableDateProperty = BindableProperty.Create( nameof(NullableDate), typeof(DateTime?), typeof(NullableDatePicker), null); public DateTime? NullableDate { get { return (DateTime?)GetValue(NullableDateProperty); } set { SetValue(NullableDateProperty, value); UpdateDate(); } } private string _format = null; private void UpdateDate() { if (NullableDate.HasValue) { if(NullableDate == DateTime.Today) { _format = Format; Format = "OPTIONAL..."; } else { if (null != _format) Format = _format; Date = NullableDate.Value; } } else { _format = Format; Format = "OPTIONAL..."; } } protected override void OnBindingContextChanged() { base.OnBindingContextChanged(); UpdateDate(); } protected override void OnPropertyChanged(string propertyName = null) { base.OnPropertyChanged(propertyName); if (propertyName == "Date") NullableDate = Date; } } }

When all is said and done, it looks a little something like this:

2021-07-06_09-32-50 (1).gif