Continuing from the post for part 1 of creating the soundboard, we’ll add in a few more MP3 files to each platform, an interface that will retrieve a listing of all the available MP3 files, and a listview that will display buttons to play each of the MP3 files.
To add more MP3 files, we’ll use the same process we did in part 1 to add the first MP3, click on Add -> Existing Item and then select the MP3 files from your computer in each of the following locations:
- For Android, right click on the Assets folder
- For iOS, right click on the Resource folder
- For UWP, right click on the Assets folder
Before we create the interface for listing out the MP3 files, we’re going to add a very simple class to the PCL named MessageCenterObject, this will be used as an easy method to communicate back and forth between our platform specific code and the PCL, in case the loading of the MP3 files takes an extended period of time. Right click on the Portable project and select Add -> Class, then name the file MessageCenterObject.
We’ll adjust it to be a public class and add two simple string properties.
1 2 3 4 5 6 7 8 | namespace XamarinSoundboard { public class MessageCenterObject { public string result { get; set; } public string message { get; set; } } } |
This object will be used for passing messages back and forth between the platform-specific code and the PCL. To do this, we will be leveraging the Messaging Center.
Now that we have a collection of MP3 files added to the project(s) for our soundboard, we’ll create the interface that will allow each platform to load in a list of available MP3 files. Right click on the Portable project at the top of your solution and then select Add -> Add New Item. From the list of templates, select Visual C# -> Interface and give it a name of IListMP3Files.cs.
Add an accessor for the interface to make it public, and give it a two parameterless methods named GetMP3Filenames that returns a List of strings and a void method named LoadMP3Filenames.
1 2 3 4 5 6 7 8 9 10 11 | namespace XamarinSoundboard { public interface IListMP3Files { // This is a void method that will load the files on each platform as a background call void LoadMP3Filenames(); // This method will return the loaded list of file names List<String> GetMP3Filenames(); } } |
That’s all we need for the interface file, so we’ll move on to the interface implementation in each of the platforms. As before, we’ll begin with the Android implementation and allow the code explain itself. Right-click on the Android project in the solution and select Add -> Class and give it the name ListMP3FilesImplementation.
We want to add the public accessor to the new class, wire it up to the interface we created in the PCL (IListMP3Files), and then implement the functionality to load a list of MP3 file names from the assets folder.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | using System.Collections.Generic; using Xamarin.Forms; using XamarinSoundboard.Droid; using System.Linq; [assembly: Dependency(typeof(ListMP3FilesImplementation))] namespace XamarinSoundboard.Droid { public class ListMP3FilesImplementation : IListMP3Files { // This will hold the listing of the mp3 files we find in the assets folder private List<string> mp3Files { get; set; } // An instance of our MessageCenterObject to use for passing messages between the PCL and the platform code private MessageCenterObject messageCenterObject; public ListMP3FilesImplementation() { // When our class is instantiated, set up the message center object messageCenterObject = new MessageCenterObject(); // This tells the application that our class is listening for a message named "Load MP3 Files" acting against // a MessageCenterObject type of sender MessagingCenter.Subscribe<MessageCenterObject>(messageCenterObject, "Load MP3 Files", (sender) => { // When we see this message fired in the application, start the process of loading the MP3 files LoadMP3Filenames(); }); } public List<string> GetMP3Filenames() { // Simply return the list of MP3 files return mp3Files; } public void LoadMP3Filenames() { // Retrieve a listing of all the files in the assets var files = global::Android.App.Application.Context.Assets.List(""); // using Linq to retrieve a cleansed list of the names of the files that // ended with .mp3 mp3Files = files.Where(x => x.EndsWith("mp3")) .Select(x => x.Replace(".mp3", "")) .ToList<string>(); // Once all of the files have been loaded, fire off a message stating that the files have been loaded MessagingCenter.Send<MessageCenterObject>(messageCenterObject, "MP3 Files Loaded"); } } } |
We’ll move on to the iOS implementation, right click on the iOS project then choose Add -> Class and give it the same name of ListMP3FilesImplementation.
As before, give it a public accessor and wire it into the IListMP3Files interface.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | using Xamarin.Forms; using XamarinSoundboard; using XamarinSoundboard.iOS; using System.Collections.Generic; using Foundation; using System.Linq; [assembly: Dependency(typeof(ListMP3FilesImplementation))] namespace XamarinSoundboard.iOS { public class ListMP3FilesImplementation : IListMP3Files { // This will hold the listing of the mp3 files we find in the assets folder private List<string> mp3Files { get; set; } // An instance of our MessageCenterObject to use for passing messages between the PCL and the platform code private MessageCenterObject messageCenterObject; public ListMP3FilesImplementation() { // When our class is instantiated, set up the message center object messageCenterObject = new MessageCenterObject(); // This tells the application that our class is listening for a message named "Load MP3 Files" acting against // a MessageCenterObject type of sender MessagingCenter.Subscribe<MessageCenterObject>(messageCenterObject, "Load MP3 Files", (sender) => { // When we see this message fired in the application, start the process of loading the MP3 files LoadMP3Filenames(); }); } public List<string> GetMP3Filenames() { // Simply return the list of MP3 files return mp3Files; } public void LoadMP3Filenames() { // Many iOS methods require an NSError out parameter NSError error; // Retrieve a listing of all the files in the assets in the main bundle's resource path var files = NSFileManager.DefaultManager.GetDirectoryContent(NSBundle.MainBundle.ResourcePath, out error); // using Linq to retrieve a cleansed list of the names of the files that // ended with .mp3 mp3Files = files.Where(x => x.EndsWith("mp3")) .Select(x => x.Replace(".mp3", "")) .ToList<string>(); // Once all of the files have been loaded, fire off a message stating that the files have been loaded MessagingCenter.Send<MessageCenterObject>(messageCenterObject, "MP3 Files Loaded"); } } } |
Last, and quite possibly least, we’ll create the implementation class for UWP – right click on the Universal Windows project then choose Add -> Class and give it the same name of ListMP3FilesImplementation.
It probably won’t be surprising that I’m going to just provide the code for the class and not explain it beyond the comments…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | using System; using System.Collections.Generic; using System.Linq; using Windows.Storage; using Windows.Storage.FileProperties; using Windows.Storage.Search; using Xamarin.Forms; using XamarinSoundboard.UWP; [assembly: Dependency(typeof(ListMP3FilesImplementation))] namespace XamarinSoundboard.UWP { public class ListMP3FilesImplementation : IListMP3Files { // This will hold the listing of the mp3 files we find in the assets folder private List<string> mp3Files { get; set; } // An instance of our MessageCenterObject to use for passing messages between the PCL and the platform code private MessageCenterObject messageCenterObject; public ListMP3FilesImplementation() { // When our class is instantiated, set up the message center object messageCenterObject = new MessageCenterObject(); // This tells the application that our class is listening for a message named "Load MP3 Files" acting against // a MessageCenterObject type of sender MessagingCenter.Subscribe<MessageCenterObject>(messageCenterObject, "Load MP3 Files", (sender) => { // When we see this message fired in the application, start the process of loading the MP3 files LoadMP3Filenames(); }); } public List<string> GetMP3Filenames() { // Simply return the list of MP3 files return mp3Files; } public async void LoadMP3Filenames() { try { // With UWP, there are a lot of different ways to parse / process files and directories // In this case we're building out a QueryOptions object that will retrieve only the files that end with .mp3 var queryOptions = new QueryOptions(CommonFileQuery.OrderByName, new string[] { ".mp3" }); // When we query files in UWP, by default it will return the entire file object (including the binary data) // As you can imagine, that ends up being very slow if you are processing a lot of files, we instead modify the query // to only return the Name of all of the files queryOptions.SetPropertyPrefetch(PropertyPrefetchOptions.None, new string[] { "Name" }); // Retrieve the StorageFolder object representing the Assets folder for the UWP application StorageFolder folder = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFolderAsync("Assets"); // Execute the query we built at the top of the method to var queryResults = folder.CreateFileQueryWithOptions(queryOptions); var files = await queryResults.GetFilesAsync(); // using Linq to retrieve a cleansed list of the names of the files that ended with .mp3 mp3Files = files.Select(x => x.Name.Replace(".mp3", "")).ToList<string>(); // Once all of the files have been loaded, fire off a message stating that the files have been loaded MessagingCenter.Send<MessageCenterObject>(messageCenterObject, "MP3 Files Loaded"); } catch (Exception err) { string tmp = err.Message; } } } } |
Now that we have the implementation in place for each of our platforms, all that is left to do is connect the two interfaces together so we can have a soundboard.
Heading back up to the PCL, we need to open up MainPage.xaml to adjust it to list out all of our MP3 files. We accomplish this by making a very simple ListView that just contains a single button per row, the XAML will look like this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:XamarinSoundboard" x:Class="XamarinSoundboard.MainPage"> <!-- Create a ListView named listView --> <ListView x:Name="listView" RowHeight="75"> <!-- We need to define an item template to determine how we want each cell to look --> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <!-- our cells will consist of a simple black button with white text that displays the name of the MP3 file and fires our OnClick event --> <Button Text="{Binding .}" BackgroundColor="Black" TextColor="White" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Clicked="SoundButton_Clicked" /> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </ContentPage> |
The code-behind for the xaml file (MainPage.xaml.cs) is a bit more complex this time around. We need to set-up the MessagingCenter to work with the messages going back and forth with the ListMP3Files implementations. The other new piece is using the ItemsSource property of the ListView we created in the XAML file above to tell it what it needs to load / render for the items.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | using System; using System.Collections.Generic; using Xamarin.Forms; namespace XamarinSoundboard { public partial class MainPage : ContentPage { // An instance of our ListMP3Files interface private IListMP3Files listMP3Files; // An instance of our MessageCenterObject to use for passing messages between the PCL and the platform code private MessageCenterObject messageCenterObject; // The list of MP3 files List<string> mp3FileList; public MainPage() { InitializeComponent(); // retrieve an instance of our platform specific interface listMP3Files = DependencyService.Get<IListMP3Files>(); // set up the message center object messageCenterObject = new MessageCenterObject(); // This tells the application that our class is listening for a message named "MP3 Files Loaded" acting against // a MessageCenterObject type of sender MessagingCenter.Subscribe<MessageCenterObject>(messageCenterObject, "MP3 Files Loaded", (sender) => { // Once we receive the message, we need to update our list of mp3s from the implementation mp3FileList = listMP3Files.GetMP3Filenames(); // set the ItemsSource of the listview to be our mp3 file list listView.ItemsSource = mp3FileList; }); // Fire off a message to start Loading the MP3 files MessagingCenter.Send<MessageCenterObject>(messageCenterObject, "Load MP3 Files"); } private void SoundButton_Clicked(object sender, EventArgs e) { // Make use of the dependency service to call the platform specific implementation of our SoundPlayer // we send through the name of the of the file from the button DependencyService.Get<ISoundPlayer>().PlaySoundFile($"{((Button)sender).Text}.mp3"); } } } |
Once that code is in place, you can run the application on any of the three platforms and you will be presented with a scrollable list of the names of the MP3 files you previously added to your solution, and clicking (or tapping) on any of those names will play the sound files for you.
That wraps up the Cross-Platform Soundboard with Xamarin.Forms. I glossed over some items that are very important to understand if you want to continue with Xamarin.Forms development (Messaging Center and Dependency Service), so I recommend taking some time to dig into those and ensure they make sense (at least a little bit).