Shared Projects definitely take their share of bashing - if you spend any time at all talking to somebody who used Shared Projects in their app - they'll spend the entire conversation staring at the ground, eventually apologize profusely and then offer to buy you lunch - because well ... PCLs! In fact, one would think that compared to PCLs, Shared Projects are inferior and if you use them, your app will be full of bugs and will never get downloaded!
Now I'm not going to spend this entire post defending Shared Projects, or giving instructions on how to use them - I already did that here and here and here. But one of the things I hear people complain about with Shared Projects more than anything else is that you need... NEED... to use compiler directives with them.
Stop the presses! You don't need to use compiler directives! With some creative use of partial classes and what I call the poor man's bait and switch ... the only time you'll have to see that hashtag again is when you're tweeting how awesome this post is! (Let's agree to use #SharedProjectsRule, shall we?)
OK - I'm fooling around and being sarcastic ... but I am serious that you don't have to use compiler directives with Shared Projects. Let me demonstrate how to with the super cool Native Control Embedding feature from Xamarin.
I'm going to take James Montemagno's blog post introducing Native Embedding and refactor it so it doesn't use compiler directives. You can find the finished solution on GitHub here.
(That blog post creates an UISegmentedControl
in iOS and a FloatingActionButton
in Droid and adds them to the same screen in a Xamarin.Forms app.)
Partial Classes
The key to all of this is partial classes. That's right - the shining feature of C# 2 (yup, C# 2) saves the day when looking to avoid using compiler directives. As I'm sure you know already - partial classes allow the implementation of a class to be broken apart across multiple files.
That means there could be part of a class contained within the Shared Project and the other part contained within the platform project. When the app gets compiled - the two class files get combined together like they were one single file.
For the Native Embedding portion of the demo app ... or for any portion that needs to access platform specific code ... create a partial class and put the platform specific stuff in the portion that lives in the platform project.
So when we need to create a UISegmentedControl
- it will live in a function within the partial class in the iOS project.
public partial class NativeControls : INativeControls
{
public void GetControlForNoDirectivesPage(Layout<View>layout)
{
var segmentedControl = new UISegmentedControl();
segmentedControl.Frame = new CGRect(20, 20, 280, 40);
segmentedControl.InsertSegment("One", 0, false);
segmentedControl.InsertSegment("Two", 1, false);
segmentedControl.SelectedSegment = 1;
segmentedControl.ValueChanged += async (sender, e) =>
{
var selectedSegment = (sender as UISegmentedControl).SelectedSegment;
await App.Current.MainPage.DisplayAlert(
$"The selected segment is {selectedSegment}", "WHOA!!!!", "OK");
};
var holdingStack = new StackLayout { Padding = 8, HorizontalOptions = LayoutOptions.Center };
holdingStack.Children.Add(segmentedControl);
var overallStack = layout.FindByName<StackLayout>("overallStack");
overallStack.Children.Insert(0, holdingStack);
}
}
All of that could have been put within a compiler directive right inline of the Shared Project ... but we're staying away from those hashtags!
Then to consume it in the SharedProject - it will look like the following:
var nc = new NativeControls();
nc.GetControlForNoDirectivesPage(overallAbs); // Passing in an absolute layout
Cool ... but how to guarantee both the iOS and and Droid projects implement the same functions?
The Poor Man's Bait 'n Switch
The Bait 'n Switch trick was invented to provide platform specific functionality when using PCLs. The super brief, and not 100% correct summary is that Bait 'n Switch provides an "empty" implementation of a particular function in the PCL layer but a full implementation in the platform layer. Everything is programmed against an interface - that way when you invoke a function in the shared layer, it's really getting the platform version of it. There's more to it - read this post - it's cool.
In this scenario, the poor man's version of that is done. In the Shared Project there's an interface that defines the function GetControlForNoDirectivesPage
.
public interface INativeControls
{
void GetControlForNoDirectivesPage(Layout<View> layout);
}
Then there's an empty partial class in the Shared Project as well that implements that interface.
public partial class NativeControls : INativeControls
{
}
It's empty! It implements the interface! That means the other half of that partial class in the platform projects are going to have to implement it!
The key is the empty partial class in the Shared Project. Since the Shared Project doesn't emit a DLL, it becomes a part of whatever references it. So if there's a partial class in the Shared Project that doesn't fully implement an interface - there's going to have to be a full implementation of the interface in the platform project.
Poor man's bait 'n switch!
Shortcomings
There's a couple of things here that I don't love. The first is that I'm passing in the layout of where the native control will be inserted into - and in the iOS project then looking up by name a child of that layout. I don't like that. However ... since the Shared Project becomes one with the platform project ... it's not the end of the world ... and there's plenty of use cases where this wouldn't be an issue at all.
There's also the argument to be made - what's worse - compiler directives or multiple files? I'd say compiler directives - at least with multiple files all the platform specific code is within the platform's project.
Summary
Finally - with the use of partial classes and the poor man's bait 'n switch, you can free your Shared Projects of compiler directives once and for all! Now there's no excuse to use a PCL as the core project of a Xamarin.Forms app ever again! (Because when would you ever share that project with another app anyway?) :)
Now ... if you're writing a library ... PCL. All the way.
Comments