Introduction

In the previous 3 part series, you saw how to create a tile based game for the Universal Windows Platform (UWP).
In this article you look at porting the game to both iOS and Android; creating a cross-platform Xamarin solution in Visual Studio.

You see how nearly all code is shared across platforms. You observe some platform difference, such as those encountered when defining and referencing image resources, and at implementing audio playback for each platform. You look at abstracting platform these differences, providing platform specific implementations at runtime.

You see how to work with XAML in Xamarin forms. You explore rudimentary views, such as text boxes, labels and buttons. You also look at more advanced topics, such as creating a slide-in menu and defining reusable XAML resources. You also see how to use an AbsoluteLayout to layout the game grid, and at utilizing the game grid's available space.

You also explore touch gestures, and we introduce a new double tap move which causes the Sokoban character to push a treasure across multiple floor spaces.

Know Your Platforms

Xamarin Forms has come a long way over the last couple of years. It’s becoming an increasingly viable candidate for cross-platform development. Xamarin Forms is not a lookless GUI technology. Controls render as they would if implemented natively. Creating a rich multi-faceted app requires an understanding of the underlying platform. There are nuances to each platform that often require tweaking to get the desired look and feel. That’s why, if you’re considering creating a serious app using the Xamarin tooling, I recommend some preliminary reading on the underlying platform or platforms you’re targeting. Platform specific quirks invariably cannot be abstracted. That’s why a good knowledge of the underlying platform is essential to creating rich user interfaces. So, I encourage you to ground yourself in a reasonable knowledge of Android and iOS development before embarking on a complex Forms app.

With that said, you can still have fun creating a relatively simple app in Xamarin Forms without any prior platform specific knowledge. Let’s begin.

Creating a Xamarin Cross-Platform Solution

When creating a new Xamarin solution in Visual Studio there are several options to choose from in the New Project dialog. See Figure 1.

I’m a rather fond of XAML, so for this project I chose the Blank XAML App option. I also chose a PCL project type rather than the Shared project type because I knew that I wanted to constrain all platform specific code to each of the respective platform specific projects, as opposed to relying on preprocessor directives or some other mechanism to include or exclude code. One other reason I chose PCL over the Shared project type is that I have found intellisense to sometimes misbehave when using Shared projects.

Figure 1. Creating a Blank XAML App using the New Project Dialog

Understanding the Xamarin Forms Solution Structure

When you create a Xamarin Forms XAML project, four projects are created:

An Xamarin Android project

An Xamarin iOS project

A UWP project

And a Xamarin Forms project

In the downloadable solution I’ve grouped the three platform specific projects together. See Figure 2.

The platform specific projects for Android, iOS , and UWP; are entry point applications. While the Xamarin Forms project contains the majority of our GUI code and is effectively hosted within each of the platform specific projects.

With the new Xamarin Forms solution in place we can bring in the Sokoban game PCL and build out the Forms project.

Figure 2. Forms Sokoban Solution

The Sokoban PCL project (Sokoban.csproj) is platform agnostic, and is a carry over from the previous article series. To implement the game for Xamarin Forms we need to build out the Forms project (Outcoder.Sokoban.Launcher) and implement the infrastructure classes for each supported platform. The Forms launcher project contains the Xamarin Forms items that are used by each of the Android, iOS and UWP projects.

Constructing the Game UI in Xamarin Forms

The interface for the game consists of the following three sections:

a top toolbar section,

a lower section for the game tiles,

and a slide in menu.

Let’s start by taking a look at the slide-in menu.

Implementing a Slide-In Menu in Xamarin Forms

To achieve the same slide-out menu that I created for the UWP version of the app I use a MasterDetailPage. The MainPage.xaml file in the Outcoder.Sokoban.Launcher project inherits not from Page, but from MasterDetailPage. A MasterDetailPage allows you to split a page into two parts: the master part, which can be thought of as a set of items that, when selected, change the content in the detail part. The detail part displays one or more different pages representing the selected item in the master page.

Our use of the MasterDetailPage doesn’t quite fit that description; we use the master section as a menu, and the detail page as the tiled game area. In this app, the detail page doesn’t change.

When using a Xamarin Forms MasterDetailPage, there are two main elements you need to build out. The first corresponds to the MasterDetailPage.Master property, which is the slide in part. The second corresponds to the MasterDetailPage.Detail property, which, in this case, is the fixed view of the game. Both are populated with a Page instance; generally a single Page for the Master and one or more pages for the Detail property.

NOTE: Don’t expect the MasterDetailPage to behave the same on every device. The behaviour of the Master part may vary according to platform and screen size. If the screen size is substantial enough, then the Master part may remain permanently in view.

In the Sokoban game, the Master contains a StackLayout and several Label views that are bound to commands in the Game class. See Listing 1. The Game class is effectively the ViewModel of the MainPage.

NOTE: In UWP and WPF the term control is used frequently to denote interactive UI elements. In Xamarin Forms, however, the term view is used. This is because in Xamarin Forms UI elements derive from a base View class. If you’re coming from the Android development world you’ll feel at home with this nomenclature.

The base View class contains a GestureRecognizers property, which can be populated with a set of IGestureRecognizers. As well as a Tapped event, the TapGestureRecognizer has a convenient Command property that we attach to each of the Games commands.

I make use of both the Tapped event and the Command property. The Tapped event is used to trigger the closing of the menu. I’m not pleased with that, and I would have prefered if the command took care of setting a property to close the menu. But, this was simpler.

The Tapped handler for each menu item in the master page calls the CloseMenu method of the MainPage, which, in turn, sets the Page’s built-in IsPresented property to false. See Listing 2. When IsPresented is true, the menu expands. When false, it collapses.

Just like UWP and WPF, Xamarin Forms supports a StaticResource markup extension, which gives you a simple way to share resources across your XAML based app. The resources for the game are located in the App.xaml file in the Outcoder.Sokoban.Launcher project. See Listing 3.

Many of the structures present in Xamarin Forms resemble, or in some cases, match those in UWP or WPF. Here we see that the Application.Resources are populated with a ResourceDictionary with various colors used throughout the app.

The main game UI is defined in the Detail section of the MasterDetailPage in MainPage.xaml. See Listing 4.
Just like our UWP implementation in a previous article, the game UI is divided into a top section which shows the level number and so forth, and a bottom section which hosts the game tiles.

An Entry in Xamarin Forms parlance is a text box, where the user can enter text. The levelCodeTextBox is an Entry view that allows the user to jump to a different level if the user knows the correct code corresponding to that level. levelCodeTextBox retains the same name that I used in the UWP implementation, though it should probably be renamed levelCodeEntry or something like that.

The game grid is implemented using an AbsoluteLayout view. AbsoluteLayout is analogous to a UWP or WPF Canvas control and allows you to position items using X and Y coordinates.

The final element within the detail page is an overlay, which we use to obscure the game to display messages to the user upon level completion. The visibility of the overlay is bound to the FeedbackVisible property of the game object.

Implementing the Platform Specific Infrastructure

In the previous UWP article series, we needed a way for the Sokoban Game object to leverage some platform specific functionality. We did this by abstracting that code for each platform via a custom IInfrastructure implementation.

In the Xamarin Forms implementation of Sokoban, we have a base implementation of the IInfrastructure interface, named InfrastructureBase; which is located in the Outcoder.Sokoban.Launcher project. See Listing 5.

The Game object requires an instance of an implementation of the custom ISynchronizationContext.InfrastructureBase makes use of some of the Xamarin Forms APIs, which are available across all platforms. In particular the Application.Properties collection is an IDictionary<string, object> that allows you to persist key value pairs in a platform agnostic way.

Various IInfrastructure members, include LevelCount, GetMapAsync, and PlayAudioClip; need to be implemented for each platform. We turn our attention to that in a moment, but first let’s examine the ISynchronizationContext member.

Abstracting the Thread Synchronization Context

Upon instantiation, InfrastructureBase requires an object implementing ISynchronizationContext. You may recall from the previous series that ISynchronizationContext is used to invoke an action on the main thread. It does this in such a way that if the code is already executing on the UI thread then the action is not pushed onto the UI thread queue but rather executed immediately, which can improve performance.

The Xamarin Forms implementation of ISynchronizationContext is identical for both Android and iOS and is shown in Listing 6.

On Android and iOS the Mono framework exposes a Thread.CurrentThread.IsBackground property that enables you to determine if the current thread is the UI thread. UWP lacks such a property and requires a Dispatcher instance to ascertain that information. Hence the different implementation for UWP. The UWP implementation is described in the previous article series and won’t be covered here.

NOTE: When working with Xamarin Forms you’ll find that the .NET API surface area of iOS and Android is larger than UWP. The reason for this is that iOS and Android are able to leverage the Mono framework, which has a wider set of APIs that encompass much of the .NET FCL (Framework Class Library). I suspect that these differences will gradually disappear over time.

The Android and iOS implementations of the IInfrastructure class are both named Infrastructure and extend the InfrastructureBase class. See Listing 7.

In addtition to the ISynchronizationContext, the Infrastructure class requires an Activity instance to retrieve the level map assets. It also requires an AudioClips object, which is used to play the sound clips during gameplay.

The game’s level files have been linked into the Assets/Levels directory of the Android launcher project (Outcoder.Sokoban.Launcher.Droid). Each level file has its Build Action set to Android Asset.

Android is rather picky where you place resources and arbitrary files. In Xamarin Android, resources such as layout files, images, and audio files must be located in subdirectories of the resources directory. Images used in the game are located in the resources/drawable directory, while MP3 files used in the game are located in the resources/raw directory.

The AudioClips class creates multiple MediaPlayer objects; one for each sound file. See Listing 8.

The Context instance, which is passed to the AudioClips constructor, is used to retrieve each of the .mp3 files located in the Outcoder.Sokoban.Launcher.Droid project’s Resources/raw directory.

For performance reasons, playback is performed on a ThreadPool thread via a call to the asynchronous Task.Run in the AudioClips Play method.

The AudioClip enum contains values for each of the sound effects and is a cross-platform friendly way of indicating to the AudioClips class which sound clip to play.

LoadApplication of the Xamarin.Forms.Platform.Android.FormsAppCompatActivity class takes care of materializing the custom App class. See Listing 10. App extends Xamarin.Forms.Application, which the base class for any Xamarin Forms App.

The App object instantiates a MainPage object, passing it the specified IInfrastructure instance, which is subsequently passed down to the Sokoban Game object, allowing it to retrieve maps and play sound effects and so forth.

The Xamarin.Forms.Application class provides virtual methods for the various application lifecycle events: OnStart, OnSleep, and OnResume.

NOTE: There is no OnEnd virtual method in the Application class. One of the reasons is presumably because platforms like Android and UWP don’t offer a reliable way to detect when your app is exiting. That’s why it’s best to assume your app is going to be terminated when OnSleep is called. When OnSleep is called, save your app's state.

The MainPage class constructor subscribes to the page’s Appearing event. See Listing 11. The Appearing event allow us to wait until the grid layout has been laid out so that we can reliably ascertain its available size.

The Entry class (recall they are the text boxes) has a Keyboard property, which allows you to specify the type of software keyboard and its characteristics. For example, a numeric keyboard contains mostly digits while a URL keyboard will have characters commonly used for entering a web address.

The levelCodeTextBox’s text is capitalized using the CapitalizeSentence flag.

NOTE: The Keyboard property offers a nice abstraction but be mindful that the available keyboard types is the intersection of all keyboard types across all supported platforms. You may find that using a native API gives you access to a keyboard more appropriate to your needs.

The MainPage class’s HandleAppearing method uses a loaded flag to guarantee that its body runs only once.

The Game object is created using the IInfrastructure object. The BindingContext is set to the game object.

NOTE:BindingContext is analogous to the DataContext property of FrameworkElements in UWP and WPF.

The HandleAppearing method awaits Task.Yield a couple of times to ensure that the view has been laid out. Then, the StartGame method is called. See Listing 12.

If the size of the host window changes then the game grid needs to be redrawn. The SizeChanged event is raised when the orientation of the device is changed, which is handy as it gives us the opportunity to resize the cells.

Most of the UI logic has been moved into the Game class. The only state we’re interested in is the Loading state, upon which we initialize the level. See Listing 14. The rest of the states offer UI extensibility points. You could potentially start an animation when the level is loading for example.

Cells that are placed in the game grid are retained in a dictionary. This improves performance when we need to refresh the size of the grid.

Initializing a level involves discarding existing cells and then calling the LayoutLevel method. See Listing 15.

The levelCodeTextBoxText property is set to the game’s LevelCode property. It’s not directly bound to the game property because we allow the user to attempt to enter a level code. It should really be bound to a game property, with the logic contained with the Game class, but I didn’t get around to doing that.

The ProcessCellTap method performs either a jump or a push. See Listing 18. When the player walks in a straight line and potentially pushes a treasure, this is called a push. Alternatively, a jump moves the player to any reachable cell without pushing any treasures in its path.

I’ve added a double tap gesture to the user's arsenal. If a CellView is double tapped, the game will attempt to walk the player in a straight line; pushing a treasure if there is one in the player’s path. Again, the event is handled by the Game object’s ProcessCellTap method. See Listing 19.

When the size of the host view changes the app resizes the cells in its game grid to utilize all available space. Recall that the Page object’s SizeChanged event is raised when the orientation of the device changes.

HandleWindowSizeChanged schedules a layout update one second after a size change occurs. See Listing 20. This prevents the app from being slowed down by multiple size changed events occurring at about the same time.

The Xamarin.Forms.Device.StartTimer event provides a platform agnostic way to schedule work on the UI thread. If the action provided to the StartTimer method returns true, the action will recur after the specified interval; otherwise the action isn’t invoked again.

Implementing the Forms CellView

The CellView class in the Outcoder.Sokoban.Launcher project represents a tile in the game. It may appear as a wall or a floor tile containing content. A floor cell’s content can be the player, a treasure, a goal; or a combination of these.

CellView is a subclass of Xamarin.Forms.ContentView, which I chose because it allows layering of multiple child views and appears to be fairly lightweight, which is important because the game grid requires many CellView objects to be created. There is, however, room for optimization here. The CellView’s Content property is populated with a Grid view. I had some spacing a layout issues that were solved by this configuration, however it’s not optimal and if you plan on leveraging this code I recommend looking at improving the structure of CellView.

When a CellView is created it sets itself up to respond to tap gestures. See Listing 21. In Xamarin Forms touch events are generally implemented using the GestureRecognizers property of the View class and not by, for exmple, direct subscription to a ‘Tap’ event. So, there’s a little extra plumbing you need to put in place; create a TapGestureRecognizer, subscribe to it’s Tapped event, and add it to the view’s GestureRecognizers property.

The image or images that are displayed in the CellView depend on the type of its associated Sokoban Cell object.

An image must be created for each cell type and its content. See Listing 22.

The Xamarin.Forms.Image class is derived from View and views are always limited to a single parent view. It cannot, therefore, be cached and used multiple times within a page. However, each Image relies on an ImageSource object, which may be cached to reduce the app’s memory footprint.

Within the CellView class there is a static Dictionary<string, ImageSource> named imageCache. This is where ImageSource objects for each cell type and content are placed.

The ImageSource.FromFile method allows you to retrieve your image data on any of the Xamarin supported platforms. If it's being used on iOS or Android, the directory path should not be included. On UWP, however, the full path to the image content resource must be supplied.

The CellView class contains a static constructor that sets the imageDir field according to the platform. See Listing 23. You use the Xamarin.Forms.Device class’s static OS property to determine what platform the app is running on. In this case if the app is running on iOS, Android, or some other platform then the image directory is set to an empty string. On iOS and Android resources are placed in a specific directory and are located using a unique name. In a UWP app, however, images can exist as content anywhere with the project.

When the CellView is first initialized or its associated Cell object’s content changes the UpdateDisplay method is called. See Listing 25. The visibility of each image within the CellView is determined by the Cell object’s type and it’s content.

Handling the CellView Tapped Gesture

One of the things I like most about this Sokoban game implementation is its touch friendly interface. Because of the game’s pathfinding capability, the user is able to move the player character to any reachable place on the game grid with a single tap. However, pusing a tile is not so easy without a keyboard. In a previous article I showed how when holding down the shift key while tapping, the user could push a cell multiple cells in a linear direction.
Well, of course, most mobile devices these days don’t have a hardware keyboard, so I needed another way to perform this move. I chose a double tap gesture. When the user double taps on a cell, if the player character is able, it will push a treasure to that cell.

Recall that we use a TapGestureRecognizer to be notified when the user taps the CellView. Unfortunately, in Xamarin Forms there isn’t a DoubleTapGestureRecognizer, so we have to come up with another way to recognize when the user double-taps a CellView. See Listing 26.

A tapCount field is used to record the number of times the user has tapped the cell within the short time-frame of 500 milliseconds. If the user taps two times within this timeframe it’s considered a double tap; otherwise it’s considered a single tap.

When a DoubleTap event occurs, the MainPage’s HandleCellDoubleTap method is called. See Listing 27. The method calls the game’s ProcessCellTap method with the cell and a true shiftPressed argument; indicating a push move is requested.

iOS Implementation of the Sokoban Game

The Infrastructure implementation for iOS uses the Directory class to enumerate the level files in the Levels directory of the Outcoder.Sokoban.Launcher.iOS project. See Listing 28.

The iOS implementation uses the same level naming strategy and relies on the level (.skbn) files having the same naming convention. Level files are linked files. That is, they were added to the project using the Add Existing Item dialog in combination with the Add As Link drop down. See Figure 3.

The level files have been linked into the Levels directory. The build action of each level file in the iOS project is set to BundleResource. Unlike the Android implementation, resources, such as the level files, don’t need to be located within a centralized Resources directory, but rather can be strewn throughout your project; much like in UWP or WPF.

The AudioClips class in the Outcoder.Sokoban.Launcher.iOS project is responsible for playing each sound effect. See Listing 29.

This class follows the same pattern as the Android implementation. An AVAudioPlayer is created to play back each of the sound effects .mp3 files. When the AudioClips object receives a request to play an AudioClip, it selects the applicable AVAudioPlayer instance and calls its PlayAtTime method.

The AppDelegate class in the Outcoder.Sokoban.Launcher.iOS project is analogous to the MainActivity in the Android implementation. See Listing 30.

The Infrastructure class constructor receives a UISynchronizationContext instance and an AudioClips instance. The App instance is then provided to the FormsApplicationDelegate.LoadApplication method, et voilà, the iOS app comes alive.

Conclusion

In this article you looked at porting the Sokoban game to both iOS and Android. You saw how nearly all code is shared across both platforms. You observed some platform difference, such as those encountered when defining and referencing image resources, and at implementing audio playback for each platform. You looked at abstracting platform differences and how to provide platform specific implementations at runtime.

You saw how to work with XAML in Xamarin forms. You explored rudimentary views, such as text boxes (aka Entry views), labels and buttons. You also looked at more advanced topics, such as creating a slide-in menu and defining reusable XAML resources. You also saw how to use an AbsoluteLayout to layout the game grid, and at utilizing the game grid's available space.

You also explored touch gestures, and we introduced a new double tap move which allows the Sokoban character to push a treasure across multiple floor spaces.

I hope you find this project useful. If so, then please rate it and/or leave feedback below.

History

November 25 2016

First published

License

Share

About the Author

Daniel Vaughan is a eight-time Microsoft MVP and co-founder of Outcoder, a Swiss software and consulting company dedicated to creating best-of-breed user experiences and leading-edge back-end solutions, using the Microsoft stack of technologies--in particular Xamarin, WPF, and the UWP.

Daniel is the author of Windows Phone 8 Unleashed and Windows Phone 7.5 Unleashed, both published by SAMS.

Daniel is the developer behind several acclaimed mobile apps including Surfy Browser for Android and Windows Phone. Daniel is the creator of a number of popular open-source projects, most notably Codon.

Would you like Daniel to bring value to your organisation? Please contact

Thank you for sharing of this article. The ability to do cross platform code after that depends greatly on how you structure your code and your ability to adhere to separation of concerns. Even then there will be many instances where you will find that you need to access the platform specific API's and write platform specific code. Even Xamarin Forms will not completely relieve you of this need, even on the UI level. You will find there are times when you need to modify how the UI is being displayed per platform.

And lots of other words ending in ummy. I like this Dan. I like this lots. In fact, I love this. This is an outstanding article and should go a long way towards helping people make the move into Xamarin. The future's bright in tham thar Forms.