Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Xamarin

Xamarin TV Scripts

5.00/5 (13 votes)
31 Mar 2018CPOL13 min read 21.1K   827  
An entertainment app written in C# language for the Xamarin Forms and Android, using a SQLite local database

Image 1

Introduction

Do you like watching that old TV show so much that you want to take it with yourself wherever you go? And what about silently reading the episodes, instead of listening?

In this article, I present Xamarin TV Scripts, a simple entertainment app for Xamarin Forms. If you are interested, please download the source code and enjoy the show!

Background

This is an app I've been long thinking of doing. Not from scratch, but by porting the concepts I once applied on another platform: Windows Phone.

Long time ago, in 2011, I posted an article here on Code Project, called The Big Bang Transcripts Viewer, in which I explored the wonders of my newly acquired Windows Phone 7 device.

Image 2

Figure: The Big Bang Transcript Viewer - the good ol' days of Windows Phone

I thought Windows Phone had so much growth potential, and for some time it seemed so. For users, the interface was fast, sleek and unique. The experience was nice, although it always played catch-up with Android and iOS in terms of features. For developers, it offered the convenience of Visual Studio IDE, the .NET Framework and a great emulator. But unfortunately, many manufacturers remained reluctant in embracing and adopting the platform for their "flagship" device models, which were reserved for the Android platform. Also, Windows Phone lacked some of the most critically demanded apps and games, and even after years after its release, WinPhone never got much traction in the US mobile market.

Enter Xamarin Forms

For those migrating from Windows Phone development to cross-platform development, Xamarin (and more specifically Xamarin Forms) is a natural next step.

Xamarin Forms development shares some similarities with Windows Forms, and I've been able to explore those to my advantage; both can be developed with Visual Studio using C# language and the user interface can be designed with XAML files. Both platforms were created with MVVM pattern in mind, which means they both share the concepts of view models and binding. Also, the preferred event notification technique is based on pub/sub messaging.

But of course, the differences between Windows Phone and Xamarin Forms development had to be addressed accordingly. Since Xamarin Forms is a cross-platform framework, one can't simply get access to the device's file system to read or write files, or save/retrieve data to/from the local database. Instead, each platform (Android, IoS, etc.) has its own file system API, database API, and so on. That's why I had to figure out how to invoke the device-level APIs of Xamarin Android from the Xamarin Forms (Net Standard) project.

The Solution

Image 3

Figure: The .NET Standard and the Xamarin Android projects

The solution is built on Visual Studio 2017 and, like most Xamarin solutions, it contains 2 projects: one for the target device's platform (Android) and the other one is the .NET Standard 2.0 project.

When you create a new Xamarin solution, you are presented with a set of options: Platform, UI Technology and Code Sharing Strategy.

Image 4

Because developing for iOS requires a Mac OS machine (which I don't have) and Windows (Universal Windows Platform) would only make sense in a Windows Phone, I only wanted an Android app, so I unchecked the iOS and Windows checkboxes.

As for the UI Technology, Xamarin Forms comes with a rich set of components, and I think I would be in trouble if I tried to recreate them using the Android Native UI Technology Android.

Component Type Components
Xamarin Forms View ActivityIndicator, BoxView, Button, DatePicker, Editor, Entry, Image, Label, ListView, OpenGLView, Picker, ProgressBar, SearchBar, Slider, Stepper, Switch, TableView, TimePicker, WebView

Xamarin Forms Pages

ContentPage, TabbedPage, MasterDetailPage, NavigationPage, Carousel, TemplatedPage
Xamarin Forms Layout AbsoluteLayout, ContentPresenter, ContentView, Frame, Grid, RelativeLayout, ScrollViewer, StackLayout, TemplatedLayout
Xamarin Forms Cells EditorCell, EntryCell, ImageCell, SwitchCell

The .NET Standard project type replaced the old PCL (Portable Class Library) and became the new de facto standard cross-platform project type for .NET projects. That is, you can share a .NET Standard assembly with .NET Framework, .NET Core and Xamarin apps. The same "Xamarin,TVScripts.csproj" .NET Standard project contains not only the Xamarin Forms dependencies, but also all the templates needed for adding new components and assets for your Xamarin Forms project.

Image 5

We can see in the image above the presence of the Xamarin.Forms.Xaml.dll assembly that comes with the Xamarin.Forms assembly. That assembly gives us the ability to design our user interfaces using XAML (eXtensible Markup Language), which is a declarative type of XML document language developed by Microsoft for user interfaces an other applications.

But when will our Xamarin Forms application start?

Since the Xamarin.TVScripts.Android must be the startup project for the solution, at some point, it will necessarily pass the control of the application to the Xamarin.TVScripts Net Standard code. It does so in the MainActivity class, which is in fact the application's entrypoint, and more specifically in the OnCreate method.

JavaScript
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
    ...
    protected override void OnCreate(Bundle bundle)
    {
        TabLayoutResource = Resource.Layout.Tabbar;
        ToolbarResource = Resource.Layout.Toolbar;
        UserDialogs.Init(this);
        base.OnCreate(bundle);

        global::Xamarin.Forms.Forms.Init(this, bundle);
        LoadApplication(new App());
    }
}
Listing: The MainActivity class

The LoadApplication(new App()); code will of course load and run the application, which resides in the other (.NET Standard) application.

Now we can talk about the App class.

The App class is in the App.Xaml.cs file, which is the code-behind file for the App.Xaml. Despite having the xaml extension, this file does not represent a view, but rather the Application class in XAML language.

Back to the App.Xaml.cs file: it implements the Xamarin.Forms.pplication superclass. When the application starts, it must define the MainPage property of the App class. Then Xamarin Forms will initiate the navigation flow through our custom XAML pages. The code below instantiates a new NavigationPage, starting at the HomePage:

JavaScript
public partial class App : Application
{
    public App ()
    {
        InitializeComponent();

        MainPage = new NavigationPage(new HomePage());
    }
}
Listing: The App class

The NavigationPage is a Xamarin component that will handle the navigation and user-experience of a stack made of our custom pages. This "stack" holds the navigation history and, just like in a web browser, allows us to navigate back and forth across our views.

User Interface

We have a user interface composed of 3 views: the HomePage, the SeasonPage and the EpisodePage.

The HomePage just shows the TV show cover image, along with a ListView containing the show seasons. The ListView is the Xamarin component and works as a view for presenting lists of data, especially long lists that require scrolling.

Image 6

Figure: Cover image and ListView from HomePage.xaml

The ListView can be used as a simple list of unformatted text or, like in our case, you can customize it to display a set of styled items. You can replace the default plain text presentation of the ListView by providing the ListView.ItemTemplate property with the desired layout, represented by any arrange of various Xamarin components:

Image 7

Figure: building the ListView's ItemTemplate

Since the season image is square, we can apply a little trick by using a image mask in order to transform it into a circular icon:

Image 8

Figure: Applying a circular mask on top of an image

Similarly, the SeasonPage shows a simple list of episodes, and it does not have much styling. Each episode name is defined by a TextBlock contained in a round-cornered Frame component.

Image 9

Figure: The SeasonPage.xaml view featuring the list of episodes of Season 2

The EpisodePage, on the other hand, is more carefully designed. It is by far the most important view, where users will spend most of the time in the app. This view holds similarities with the other views, because of the styled ListViews displaying a collection of data (the TV Show's quotes), but it does so in a very customized way. Each quote can be shown in one of these three styles:

  • commentary (without characters)
  • character speaking at the left
  • character speaking at the right

An episode commentary is displayed when no character name is found in the associated Quote instance. Otherwise, the character's picture will be displayed at one side, and the speech bubble will be shown at the other. The character's photo will be displayed at the left or right, depending on whether the quote number is even or odd. This way we can easily alternated between those 2 modes.

The character's pictures are retrieved by binding the character's name to the corresponding image residing in the "drawable" folder of the Droid project.

The speech bubble is composed by two parts: a TextBlock contained within a rounded-cornered Frame, and an Image of a triangle immediately connected to it. This gives us the impression of a speech bubble.

Image 10

Figure: The EpisodePage.xaml view displaying the episode's conversation

In the image below, we can identify the 3 different types of "cards". The first one is called Narration Card, and usually represents a scene description, not spoken by a character. The other two are called OddCards and Even Cards, which are scripts spoken by characters and are displayed in an alternated way.

Image 11

Figure: The 3 types of speeches in EpisodePage.xaml view

Each Odd/Even Card has its layout defined by a 2-column Grid to separate image from the speech bubble. Since the image column only takes 80 pixels, the speech bubble takes the rest of the space within the Grid. And it looks nice even when the device screen is rotated in landscape mode.

Image 12

Figure: Anatomy of a speech card

The speech bubble is tricky. At first, I tried to implement some sort of vectorized drawing, but unfortunately that was not possible with Xamarin. Then I ended up combining a round-shaped frame along with an image containing a triangle, and it looked nice.

Image 13

Figure: XAML code inside a speech bubble

Bindings

Xamarin Forms is all based on the MVVM (Model-View-ViewModel) pattern, so you should expect the extensive use of bindings. Since MVVM decouples the View layer from the data it shows, the bindings act like a glue that tie view and its data together dynamically, so that each change in data should be reflected on the view side.

In the fragment below, we can notice 3 bindings:

  • The label's Text property is bound to the Name property of the Season object
  • The label's Style property is bound to the ListItemTextStyle value
  • The label's TextColor property is bound to the ListFontColor value
JavaScript
<Label Text="{Binding Name}" 
   LineBreakMode="NoWrap" 
   TextColor="{DynamicResource ListFontColor}"
   Style="{DynamicResource ListItemTextStyle}" 
   FontSize="16"
   VerticalOptions="Center"
   VerticalTextAlignment="Center"
   HorizontalOptions="StartAndExpand"/>
Listing: the Label component inside a ListView inside the HomePage.xaml file

As mentioned, the data from the Name property comes from the Season class:

JavaScript
[Table("Seasons")]
public class Season : BaseModel
{
    public Season() { }

    public Season(int seasonNumber, string name)
    {
        SeasonNumber = seasonNumber;
        Name = name;
    }

    public int SeasonNumber { get; set; }
    public string Name { get; set; }

    [Ignore]
    public string ImageSource => $@"_season{SeasonNumber}.jpg";
}
Listing: The Season class from our model

The TextColor, on the other hand, does not come from the Season class, but is rather provided by a value included in the ResourceDictionary in the App.xaml file:

JavaScript
<?xml version="1.0" encoding="utf-8" ?>
<Application ...>
    <Application.Resources>
        <ResourceDictionary>
            ...
            <Color x:Key="ListBackGroundColor">#FFFFFF</Color>
            ...
        </ResourceDictionary>
    </Application.Resources>
</Application>
Listing: the App.xaml file

This happens because we explicitly defined this binding as a DynamicResource (TextColor="{DynamicResource ListFontColor}").

Files

The app data consists of 3 types of files: the seasons file, the episodes file and the quotes files (each episode has its own separated quotes file).

1    Season 1
2    Season 2
3    Season 3
4    Season 4
.    .
.    .
.    .
Listing: The seasons.txt file
.    .     .
.    .     .
.    .     .
2    23    The One With The Chicken Pox
2    24    The One With Barry and Mindy's Wedding
3    1     The One With The Princess Leia Fantasy
3    2     The One Where No One's Ready
3    3     The One With All The Jam
.    .     .
.    .     .
.    .     .
Listing: The episodes.txt file

The seasons.txt and the episodes.txt are plain text files that merely display the names of the seasons and the episodes.

2    [Scene     Chandler's Office, Chandler is on a coffee break. Shelley enters.) Shelley
3    Chandler     Dehydrated Japanese noodles under fluorescent lights... does it get better than this?
4    Shelley     Question. You're not dating anybody, are you, 
                 because I met somebody who would be perfect for you.
5    Chandler     Ah, y'see, perfect might be a problem. Had you said 'co-dependent', 
                  or 'self-destructive'...
6    Shelley     Do you want a date Saturday?
7    Chandler     Yes please.
Listing: The 0108.txt file (Season 01, Episode 08)

The quotes files, on the other hand, contains 3 columns: the quote sequence number, the character who is speaking, and the speech (quote) itself.

Since plain text files can hold a large amount of unstructured data, they are often hard to query. And if we could transfer their data to a local database residing in our device, it would be easier to query and fetch the data, as we want.

Local SQLite Database

Among local relational databases, SQLite stands out as a stable and reliable candidate. We can generate a local SQLite database using the code-first approach, that is, by first implementing the entities (season, episode, quote) as C# classes, and then using them as the source for the SQLite database schema.

The plain text files are accessed in the Xamarin Droid project., because the implementation depends upon the platform's file system API. So we place the file access methods directly in the Main Activity class, which contains the assets object from which the files can be accessed. Just like file access, the data base access is also implemented in the Droid project.

Following Xamarin Documentation Guide, it was quite easy to implement a class in the Xamarin.TVScripts.Android project to retrieve the SQLite Connection:

C#
[assembly: Xamarin.Forms.Dependency(typeof(SQLite_android))]
namespace Xamarin.TVScripts.Droid
{
    class SQLite_android : ISQLite
    {
        private const string dbFileName = "TVScripts.db3";

        public SQLiteConnection GetConnection()
        {
            string dbPath = Path.Combine(
                System.Environment
                    .GetFolderPath(System.Environment.SpecialFolder.Personal),
                        dbFileName);

            return new SQLite.SQLiteConnection(dbPath);
        }
    }
}
Listing: The SQLite_android class

Notice that the SQLite_android class implements the ISQLite interface (class SQLite_android : ISQLite), and that we use the annotation [assembly: Xamarin.Forms.Dependency(typeof(SQLite_android))] just above the namespace. What does that mean? Both the concrete class name and the interface are intended to be used for the built-in Dependency Injection Service of Xamarin Forms when the database functionalities are invoked from the other project. This is particularly useful because the Xamarin.TVScripts project does not reference Xamarin.TVScripts.Android project where the SQLite_android class resides (and it would never would, since this would result in a cross-reference error).

So, from where does exactly the SQLite_android.GetConnection() is called?

The Xamarin.TVScripts project has a set of Data Access Object classes that implement basic CRUD (Create, Read, Update, Delete) methods for the SQLite database. These classes correspond to the model classes of our model (Season, Episode and Quote) and they inherit from a common superclass called BaseDAO<T>. Whenever the data access classes need to access the database, they first obtain the connection through the GetConnection method in the base class:

C#
public class BaseDAO<T> where T : BaseModel, new()
{
    public BaseDAO()
    {
        using (SQLiteConnection connection = GetConnection())
        {
            connection.CreateTable<T>();
        }
    }

    public IList<T> GetList()
    {
        using (SQLiteConnection connection = GetConnection())
        {
            return new List<T>(connection.Table<T>());
        }
    }

    .
    .
    .
    protected SQLite.SQLiteConnection GetConnection()
    {
        return DependencyService.Get<ISQLite>().GetConnection();
    }
}
Listing: The BaseDAO<T> class

Notice how the BaseDAO<T> is a generic class that imposes a constraint for the T type. This improves code reusability, because we can put in the base class many methods that would otherwise become different methods in the derived classes, varying only in terms of the model entity being manipulated.

Now, let's take a look at the GetConnection() method:

C#
protected SQLite.SQLiteConnection GetConnection()
{ 
   return DependencyService.Get<ISQLite>().GetConnection();
}
Listing: The BaseDAO<T>.GetConnection method

Here, we can see how we obtain the ISQLite implementation using the DependencyService class. The Get<T>() method will make the Xamarin framework to look for a class that implements the interface and return a new instance from it.

Text-to-Speech

The app also offers a features for reading the speech bubbles text, using the features provided by the Text-To-Speech app available at Android Playstore.

C#
[assembly: Xamarin.Forms.Dependency(typeof(Xamarin.TVScripts.Droid.TextToSpeech))]
namespace Xamarin.TVScripts.Droid
{
    public class TextToSpeech : Java.Lang.Object, ITextToSpeech, 
                                global::Android.Speech.Tts.TextToSpeech.IOnInitListener
    {
        global::Android.Speech.Tts.TextToSpeech speaker;
        string toSpeak;

        public void Speak(string text)
        {
            toSpeak = text;
            if (speaker == null)
            {
                speaker = new global::Android.Speech.Tts.TextToSpeech(Application.Context, this);
            }
            else
            {
                speaker.Speak(toSpeak, QueueMode.Add, null, null);
            }
        }

        public void OnInit(OperationResult status)
        {
            if (status.Equals(OperationResult.Success))
            {
                speaker.Speak(toSpeak, QueueMode.Add, null, null);
            }
        }
    }
}
Listing: The TextSpeech class

Just like the SQLite_android class, the TextToSpeech is registered in the Xamarin Dependency Injection Container through the use of the Xamarin.Forms.DependencyAttribute annotation.

When a speech bubble is tapped, the ListView will raise an OnItemSelected event, which in turn will invoke the text-to-speech functionality to speak both the character's name and his/her speech.

C#
async void OnItemSelected(object sender, SelectedItemChangedEventArgs args)
{
    var quote = args.SelectedItem as Quote;
    if (quote == null)
        return;

    await Speak(quote.Character);
    await Speak(quote.Speech);
}
        
private async Task Speak(string text)
{
    await Task.Run(() =>
    {
        DependencyService.Get<ITextToSpeech>().Speak(text);
    });
}
Listing: The client code for the text-to-speech functionality in the EpisodePage.xaml.cs file

This showcases how you could leverage your app's capabilities (and also extend them) in terms of helping people with reading disabilities (both blindness and dyslexia). And you can obtain these results without having to develop a complex and secondary feature in your application. Now you can save that time to develop the critical code for your app.

Conclusion

If you have reached this line, thank you very much for your attention and patience. I really think Xamarin Forms makes Android development a lot of fun for Visual Studio/C# developers, not only for serious applications, but also for taking crazy ideas like mine and make them happen. I hope some of you get inspired to try something new.

So, do you like the article and the code? I'm looking forward to seeing your thoughts. Please give me feedback in the comments section below!

History

  • 2018-03-31: First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)