xamarin android tv,Xamarin TV Scripts

cb66745318ca41f78c6c0bcc83f4ef52.png

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.

Screenshot.png

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

7138ceccae534c981ec64c243ae9f215.png

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.

409abd2a1452de4e2c8996b01ae877cc.png

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 TypeComponentsXamarin Forms ViewActivityIndicator, BoxView, Button, DatePicker, Editor, Entry, Image, Label, ListView, OpenGLView, Picker, ProgressBar, SearchBar, Slider, Stepper, Switch, TableView, TimePicker, WebView

Xamarin Forms PagesContentPage, TabbedPage, MasterDetailPage, NavigationPage, Carousel, TemplatedPage

Xamarin Forms LayoutAbsoluteLayout, ContentPresenter, ContentView, Frame, Grid, RelativeLayout, ScrollViewer, StackLayout, TemplatedLayout

Xamarin Forms CellsEditorCell, 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.

1eebec7b828008a59701e7c2f3f4a393.png

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 MainActivityclass, which is in fact the application's entrypoint, and more specifically in the OnCreate method.

JavaScript

Copy Code

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.csfile, 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.pplicationsuperclass. 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

Copy Code

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 SeasonPageand the EpisodePage.

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

fc63317eebcc878ab4f02e6c5cf246d7.png

Figure: Cover image and ListView from HomePage.xaml

The ListViewcan 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 ListViewby providing the ListView.ItemTemplate property with the desired layout, represented by any arrange of various Xamarin components:

072392179238b2d2cae0bfd5bd10af41.png

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:

d7eae4500186f626ff9181d440aac691.png

Figure: Applying a circular mask on top of an image

Similarly, the SeasonPageshows a simple list of episodes, and it does not have much styling. Each episode name is defined by a TextBlockcontained in a round-cornered Framecomponent.

2e3b161ecf4f23aaaac0552a8b4a699b.png

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.

58c63f63af2e443862c46ca7c665d3dd.png

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.

4f410966c528b099422c43e4ec67ecaf.png

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.

4b09c1d85fe8c374cda5b385e60e9b7f.png

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.

ae41505ba1540eff34ec869da2c31102.png

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 bindingsact like a gluethat 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 Textproperty is bound to the Name property of the Seasonobject

The label's Styleproperty is bound to the ListItemTextStylevalue

The label's TextColorproperty is bound to the ListFontColorvalue

JavaScript

Copy Code

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

Copy Code

[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 ResourceDictionaryin the App.xaml file:

JavaScript

Copy Code

...

#FFFFFF

...

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 episodesfile and the quotes files (each episode has its own separated quotes file).

Copy Code

1 Season 1

2 Season 2

3 Season 3

4 Season 4

. .

. .

. .

Listing: The seasons.txt file

Copy Code

. . .

. . .

. . .

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.

Copy Code

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, SQLitestands 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#

Copy Code

[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_androidclass 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.Androidproject where the SQLite_androidclass 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. Whenever the data access classes need to access the database, they first obtain the connection through the GetConnection method in the base class:

C#

Copy Code

public class BaseDAO where T : BaseModel, new()

{

public BaseDAO()

{

using (SQLiteConnection connection = GetConnection())

{

connection.CreateTable();

}

}

public IList GetList()

{

using (SQLiteConnection connection = GetConnection())

{

return new List(connection.Table());

}

}

.

.

.

protected SQLite.SQLiteConnection GetConnection()

{

return DependencyService.Get().GetConnection();

}

}

Listing: The BaseDAO class

Notice how the BaseDAO 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#

Copy Code

protected SQLite.SQLiteConnection GetConnection()

{

return DependencyService.Get().GetConnection();

}

Listing: The BaseDAO.GetConnection method

Here, we can see how we obtain the ISQLite implementation using the DependencyServiceclass. The Get() 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#

Copy Code

[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#

Copy Code

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().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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值