Chapter 10. Building more advanced Android views

This chapter covers

  • Using recycler views
  • Configuring and handling menus
  • Activity navigation and the Back button
  • Setting app icons
  • Styles, themes, and XML drawables
  • MvvmCross application lifecycle and launch screens

In chapter 9 we looked at the basics of creating an Android UI, including layout files, images, and activities, and we ended up by building the complete UI for SquareRt. In this chapter we’ll be looking at some more advanced Android UI topics so we can build the UI for Countr: using recycler views to show lists of data, and adding menu items to the Android toolbar, for example. We’ll then look at improving our apps’ icons and launch screens.

10.1. Building the UI for Countr

SquareRt is done, so let’s move on to Countr. Start by launching the Countr solution from chapter 8 and the dummy first view activity and layout, just as you did for SquareRt. This is a more complicated UI that has two screens, so you’ll need two activities and two layouts. I’ll be showing you how to create these UIs in code as it’s easier to show in a book, but feel free to try to achieve the same results using the Designer (keeping in mind that you’ll have to switch back to the Source view to set up the MvvmCross bindings).

Figure 10.1 shows the UI we want to create.

Figure 10.1. The UI for Countr

10.1.1. Creating the UI for the master view

The first view we’ll create for the Countr app is the master view, using the layout shown in figure 10.2. It will have the same toolbar as the squarert_view layout, and it will need to contain a widget for showing a list of counters, as well as a floating button for adding a new counter.

Figure 10.2. The layout of the views and view groups in the master Countr UI

Create a new layout resource called counters_view and add the following code to it.

Listing 10.1. The basic outline of the counters view layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:local="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
   <android.support.design.widget.AppBarLayout
      android:layout_height="wrap_content"
      android:layout_width="match_parent"
      android:id="@+id/toolbar_layout">
      <include
         android:id="@+id/toolbar"
         layout="@layout/toolbar"
         local:layout_scrollFlags="scroll|enterAlways" />
   </android.support.design.widget.AppBarLayout>
   <FrameLayout                                     1
      android:layout_below="@id/toolbar_layout"
      android:layout_width="match_parent"
      android:layout_height="match_parent">
   </FrameLayout>
</RelativeLayout>

  • 1 Below the toolbar layout is a frame layout.

This is the same as the initial view code we wrote for the SquareRt layout file, except here we’re using a FrameLayout below the toolbar layout instead of a relative layout. We don’t need to position the other items on-screen relative to each other, so there’s no need to use a relative layout.

The children of a frame layout are laid out on top of each other in the order they’re added, so the first child is on the bottom, the next child is on top of the first child, and so on (if you have two views of the same size in the same position, the second will hide the first). Our frame layout needs two children to match the UI we’re aiming for: a view that shows a list of counters, and a floating button that floats over the list to add a new counter.

We need to start with the list of counters, and for this we need to add a recycler view.

10.1.2. Recycler views

A lot of apps need to show data as some form of list, and Android has a widget called ListView that contains a scrollable list of views representing all the items in the list. This is highly inefficient—if you have one thousand items in your list, the list view will contain one thousand views, one for each item. Most of them won’t be visible on-screen, but they’ll take up memory as well as UI thread time to draw and position them.

To improve on this, Android added a widget called RecyclerView as part of its support library. It works the same way as far as the user is concerned, but it’s implemented in a much more efficient way by recycling views. It creates only enough views to cover what can be shown on the UI, and as the user scrolls, views that are no longer visible are recycled and are moved to the other end of the list (figure 10.3).

Figure 10.3. Recycler views reuse views that are scrolled out of the visible part of the screen to save on creating views.

Recycler views have a number of components that need to be set up. As well as creating the recycler view on the layout, you also need to set up a layout manager, an adapter, and a view holder. The layout manager is responsible for laying out the items in the recycler view, and Android supplies two out of the box for laying items out in a vertical list or a grid (you can create your own layout manager if you need a different layout). The adapter needs to know about the list of items that you want to show in the recycler view, and it’s responsible for creating and updating the views that are shown in the list, including updating them when the data changes. The view holder is a wrapper that stores the view for each item (usually loaded from a layout file) in the list, allowing it to be recycled.

Recycler views take a bit of work to get working well, but luckily MvvmCross takes some of the hard work out of the process by providing its own recycler view implementation. This MvxRecyclerView view derives from RecyclerView but it has its own adapter that knows about observable collections. It can automatically create the views needed to show the items, binding them to the items in the observable collection to be handled. It also creates and manages view holders for you—you just need to specify the layout file to use, and it creates all the view holders, inflating the layout you give it and creating any bindings in that layout file to bind to each item in the collection that’s used as the item source of the recycler view. The only thing you need to set up manually is the layout manager.

If you want to set up your own recycler view, there’s some great RecyclerView documentation in the Xamarin documentation: http://mng.bz/79CO.

To add the MvvmCross recycler view to the UI, add the code in the following listing to the counters_view layout.

Listing 10.2. Adding an MvvmCross recycler view to the layout
<FrameLayout
   ...>
   <mvvmcross.droid.support.v7.recyclerview.MvxRecyclerView      1
      android:layout_width="match_parent"                        1
      android:layout_height="match_parent"                       1
      android:id="@+id/recycler_view"                            1
      local:MvxBind="ItemsSource Counters" />                    2
...

  • 1 Creates a recycler view that fills the available space and sets its ID
  • 2 Binds the items source to the Counters property of the view model

This adds a recycler view to your layout, sets it to fill all the available space inside the frame layout, and gives it the ID of @+id/recycler_view. It then binds the ItemsSource on the MvxRecyclerView to the Counters property on the view model—a property of type ObservableCollection<CounterViewModel> that exposes all the counters stored in the database.

You also need to tell the recycler view how to show the CounterViewModel instances, and to do this you need to define another layout that you set as the item template for the recycler view—item template is the MvvmCross terminology for the layout resource that describes how to show and bind each item. Let’s now look at creating the UI for the item templates.

10.1.3. Creating the UI for the recycler view items

You now have the recycler view set up in your layout, but you haven’t defined the layout for the items that are shown in the list—the CounterViewModel instances. You need to define how they’re going to be shown, and this is done by creating a new layout file. The layout needs to have a text view for the counter’s name, another text view that shows the current count, and a button that the user can tap to increment the counter (figure 10.4). This layout is used one per counter in the list, and when they’re created, they are bound to the instance of the CounterViewModel that they’re showing.

Figure 10.4. The layout of the views and view groups for each item in the recycler view

The layouts for individual items are once again defined in layout XML files, so add one called counter_recycler_view and start by adding the following code.

Listing 10.3. The initial layout for the individual items in the recycler view
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:local="http://schemas.android.com/apk/res-auto"
   android:orientation="vertical"
   android:layout_width="match_parent"
   android:layout_height="50dp">
</RelativeLayout>

This is a simple relative layout for an item that’s as wide as the available space on the screen but that’s only 50dp high, as this is all you’ll need to show the content.

You can now add the button to increment the counter. For this, you need an image for a plus symbol. If you look in the images folder in the Git repository for this book’s source, you’ll see an image called plus.png. Copy this from the various size folders to the relevant folders in the app. Then add the code in the following listing to the relative layout.

Listing 10.4. Adding the image button to the layout
<ImageButton
   android:id="@+id/add_image"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_alignParentRight="true"
   android:layout_centerVertical="true"
   android:layout_marginRight="20dp"
   android:src="@drawable/plus"
   android:background="#00ffffff"                      1
   local:MvxBind="Click IncrementCommand" />           2

  • 1 Configures how the button looks
  • 2 Binds the Click event to the IncrementCommand

ImageButton is a widget that’s pretty much identical to a Button, except that it shows an image instead of just showing text, and this image is set using the src property the same way an ImageView does.

Most of the layout should be pretty familiar: the image button is laid out in the vertical center of the parent relative layout, on the right side with a right margin of 20dp. The ID is also set so that you can position another text view relative to it. The only new property here is background. Buttons are normally styled using Android styles, and the default style for buttons is to have a background color. You don’t want this background color—you want the plus symbol to be on a white background—so the background is set to #00ffffff, the hexadecimal representation of transparent white.

Colors are defined using aRGB values in hex

When specifying colors in Android resources, you use aRGB values defined using hexadecimal numbers, which are always prefixed with a #. The four bytes represent values for the alpha (how transparent the color is, with 0 being transparent and FF being opaque), red, green, and blue components. The alpha value is optional, and if it’s not set, an alpha of FF is assumed. The values are set in this order: <alpha><red><green><blue>.

This is also the first time we’ve bound an event to a command. It’s pretty simple—you specify the name of the event on the control and the name of the command to bind to. In this case, you’re binding the Click event of the ImageButton to the IncrementCommand command on the counter view model.

The next thing you need to add are some text views to show the name and count of the counter. The following listing shows the code to add inside the relative layout.

Listing 10.5. Adding text views for the name of the counter and its count
<TextView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_centerVertical="true"
   android:layout_alignParentLeft="true"
   android:layout_marginLeft="20dp"
   android:textSize="16sp"
   local:MvxBind="Text Name"/>
<TextView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_centerVertical="true"
   android:layout_toLeftOf="@id/add_image"
   android:layout_marginRight="20dp"
   android:textSize="16sp"
   local:MvxBind="Text Count"/>

You now have your layout defined, so the last thing to do is to tell the recycler view to use it to show the counters. To do this, flip back to the counters_view and add the attribute in the following listing to the recycler view.

Listing 10.6. The recycler view needs to be told what layout to use to show items
local:MvxItemTemplate="@layout/counter_recycler_view"

This attribute tells the recycler view that every time it needs to create a new view for an item, it should inflate the counter_recycler_view layout and bind it to the counter view model. This is a recycler view, so only as many instances of the view as you need are created. When a view is recycled, the view model that’s bound to it is changed, and the binding layer will re-evaluate the properties and update the UI to reflect the new view model.

Adding UI awesomeness with coordinator layouts

Android has another layout called CoordinatorLayout that’s able to coordinate its behavior based on other controls on the screen. For example, you can use a coordinator layout as the top-level layout control for your activity, and set it up so that as you scroll the recycler view, the toolbar disappears, only reappearing as you scroll back to the top. You can read more about this in the Base Lab Blog’s “Nested Scrolling with CoordinatorLayout on Android” entry: http://mng.bz/Anun.

10.1.4. Floating action buttons

The final thing to add to the counters UI is a floating action button. These are reasonably new UI components, and they’re designed to provide access to a common action that a user wants to do on a screen. In the counters app, we’ll use this to create a new counter, in the same way that Google’s Gmail app uses a floating action button to create a new email. These floating buttons are a part of Google’s material design standard, and as such are available in the Android support libraries.

Add the following code to the frame layout in the counters_view layout, below the recycler view.

Listing 10.7. Adding a floating action button to the counters screen
<android.support.design.widget.FloatingActionButton
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_gravity="bottom|right"
   android:layout_margin="16dp"
   android:src="@drawable/plus"
   local:MvxBind="Click ShowAddNewCounterCommand" />         1

  • 1 Binds the click event

This floating action button reuses the plus image that you’ve used in the recycler view item layouts. It also binds the Click event to ShowAddNewCounterCommand on the counters view model.

That’s everything for the master counters view.

10.1.5. Creating the UI for the detail view

Now let’s turn our attention to the detail screen, which needs to show an EditText control where the user can enter the name of a new counter. It also needs Done and Back buttons on the toolbar so the user can either save the counter or go back and not add a counter. The layout for this is shown in figure 10.5.

Figure 10.5. The layout of the views and view groups in the detail counter UI

You can start building the counter detail view by creating a new layout called counter_view. Then copy the outline structure defined earlier in listing 10.1 into this layout file: the outer relative layout, app bar layout, toolbar include, and frame layout. Then add the following code to the frame layout. This code should look familiar by now.

Listing 10.8. Adding an EditText control for the counter name
<EditText
   android:layout_width="fill_parent"
   android:layout_height="wrap_content"
   local:MvxBind="Text Name" />

That’s the layout done. Now you need to define a menu to add the Done button to the toolbar. You don’t have to define the Back button in a layout; this is something you can add in code later, as it’s a standard activity feature.

10.1.6. Menu items

Menus are defined in resources that are stored in the menu folder. They’re XML files that contain a list of menu items and their configurations.

You need to add a menu to add the Done button to the toolbar, so create a folder called menu in the Resources folder and add an XML file to that folder called new_counter_menu.xml containing the following code.

Listing 10.9. Menus are defined in XML
<?xml version="1.0" encoding="utf-8" ?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"     1
      xmlns:local="http://schemas.android.com/apk/res-auto">         1
   <item android:id="@+id/action_save_counter"                       2
      android:title="Done"                                           2
      local:showAsAction="always"/>                                  2
</menu>

  • 1 The menu is defined as a top-level element.
  • 2 Menu items are elements under the top-level menu element.

These menu resources consist of a top-level menu element that contains one or more item elements that define the menu. As you’ll see later in this chapter, you can inflate one of these menu resources into a toolbar to get items on the menu. When these items are inflated, the OS will work out how much space you have and show only as many items as it can, with the rest being in an overflow menu that’s shown using vertical ellipses (three vertical dots). Against each menu item, you can specify the text or icon to show, and you can set whether you always want it on the menu, always want it on the overflow, or want it wherever it fits best.

In this case, you just have text for the menu, and this is set with the android:title="Done" property. This will be a popular option for the user to select, so you’ll want this to always be on the menu instead in an overflow menu. This is set using local:showAsAction="always". This menu also has an ID (android:id="@+ id/action_save_counter"), and it’s very important that this is set. There’s no direct way to wire up code to a menu, either via Android or using MvvmCross. Instead, when a menu item is tapped, a method in the Activity class is called with the ID of the menu item that was tapped. By setting the ID, you can identify which menu item was tapped inside the Activity.

That’s it for resources. Now it’s time to start on the view code itself.

10.2. Building the Countr activities

You have your layouts, so it’s time to work on the activities. You need to create two activities: one for the master view and one for the detail view.

Create two activities in the Views folder: one called CountersView and one called CounterView. Add the following code to the CountersView.

Listing 10.10. The counters master view
using Android.App;
using Android.OS;
using Android.Support.V7.Widget;
using Countr.Core.ViewModels;
using MvvmCross.Droid.Support.V7.AppCompat;

namespace Countr.Droid.Views
{
   [Activity(Label = "@string/ApplicationName")]
   public class CountersView : MvxAppCompatActivity<CountersViewModel>   1
   {
      protected override void OnCreate(Bundle bundle)
      {
         base.OnCreate(bundle);

         SetContentView(Resource.Layout.counters_view);                  2

         var toolbar = FindViewById<Toolbar>(Resource.Id.toolbar);
         SetSupportActionBar(toolbar);
      }
   }
}

  • 1 Uses the generic version of the MvvmCross activity
  • 2 Uses the counters_view layout

This code is the same as for the SquareRt activity, except that it uses a different layout resource, and it uses the generic version of MvxAppCompatActivity so that the ViewModel property is of the correct type (you’ll be using it later).

Now add the same code to the CounterView activity, except using CounterViewModel and the counter_view layout, and change the activity label to be “Add new counter”, as shown in the following listing.

Listing 10.11. The counter detail view
...
[Activity(Label = "Add a new counter")]
public class CounterView : MvxAppCompatActivity<CounterViewModel>
{
   protected override void OnCreate(Bundle bundle)
   {
      ...
      SetContentView(Resource.Layout.counter_view);
   }
}

As an exercise, you can try moving the “Add a new counter” label to a resource (the source code that accompanies this book has it as a resource).

10.2.1. Setting up master recycler views

Recycler views take a little bit of setup in the activities that use them. Recycler views use layout managers to determine how to lay out the items that they’re showing, and there’s no default for this, so you always need to set it up.

To set up the recycler view, add the following code to the CountersView activity.

Listing 10.12. Setting up the recycler view to use a linear layout manager
using MvvmCross.Droid.Support.V7.RecyclerView;
...
protected override void OnCreate(Bundle bundle)
{
   ...
   var recyclerView = FindViewById<MvxRecyclerView>(Resource.Id.recycler_view);1
   recyclerView.SetLayoutManager(new LinearLayoutManager(this));               1
}

  • 1 Finds the recycler view in the UI, and sets its layout manager

This code finds the recycler view based on its ID, and then sets the layout manager for it. This layout manager arranges items in a vertical list.

You also want to support swipe-to-delete. This is a nice feature to have, but it’s not the easiest to set up. There’s no out-of-the-box swipe handling for recycler views. Instead you have to write your own using a class called ItemTouchHelper from the support library.

First, you define a callback class—a class that derives from the ItemTouchHelper.Callback class, which has a set of abstract methods to implement touch callbacks. Then you construct an ItemTouchHelper using the callback and attach this to the recycler view. This is quite a standard pattern on a lot of Android SDK classes—Java (the language used for the underlying Android SDK) doesn’t have events, so unlike C#, where you can wire behavior up to events, Java relies on callback classes with methods that are called when things happen. When Xamarin bound the Android SDKs, they converted some callbacks to events to make it easier for C# programmers to use them, but not all, and this is one they haven’t provided events for.

To create the callback, create a new class in the Views folder called SwipeItemTouchHelperCallback, and add the following code.

Listing 10.13. Creating the callback for swiping on the recycler views
using Android.Support.V7.Widget;
using Android.Support.V7.Widget.Helper;
using Countr.Core.ViewModels;

namespace Countr.Droid.Views
{
   public class SwipeItemTouchHelperCallback
    : ItemTouchHelper.SimpleCallback                                    1
   {
      readonly CountersViewModel viewModel;                             2

      public SwipeItemTouchHelperCallback(CountersViewModel viewModel)  2
         : base(0, ItemTouchHelper.Start)                               3
      {
         this.viewModel = viewModel;
      }
   }
}

  • 1 This derives from ItemTouchHelper.Callback, the base callback class.
  • 2 Stores an instance of the CountersViewModel that you can use to delete a counter
  • 3 Specifies the supported swipe directions

When the callback is constructed, it needs to call the base class constructor specifying the drag and swipe operations you want to support. Drag-and-drop is useful if you want to support rearranging items, but we don’t need it here. We just want to support swiping from end to start (from right to left), and this is what you’re specifying by passing 0 as the first argument to the base class constructor (meaning no drag support) and ItemTouchHelper.Start as the second parameter (meaning you’re supporting swiping toward the start).

The base callback class is an abstract class, so you have to implement a couple of methods, as shown in the following listing.

Listing 10.14. Implementing the required callback methods
public override bool OnMove(RecyclerView recyclerView,
                            RecyclerView.ViewHolder viewHolder,
                            RecyclerView.ViewHolder target)
{
   return true;
}

public override void OnSwiped(RecyclerView.ViewHolder viewHolder,
                              int direction)
{
   viewModel.Counters[viewHolder.AdapterPosition]     1
      .DeleteCommand.Execute();                       1
}

  • 1 When an item is swiped, delete it.

These two methods are abstract in the base class, so they have to be implemented. OnMove is called whenever drag-and-drop takes place, and as this is something you’re not supporting, you can just return true. OnSwiped is the interesting one—it’s called whenever an item is swiped, and the parameters include an instance of the ViewHolder class. ViewHolder is a backing class used to store data for each of the items in the recycler view, and it has an AdapterPosition property that tells you the position of the item that was swiped in the list—this position maps to the position of the CounterViewModel inside the collection on the CountersViewModel. When a swipe is detected, you can retrieve the counter view model from the counters view model stored in the viewModel backing field, and execute DeleteCommand on it. This will then delete the counter, which in turn will remove it from the observable collection in the view model, which will cause the UI to update to show the collection minus the deleted item.

It’s always good to provide feedback to the user

When users are swiping, it’s good to provide feedback as to what is happening, and a usual way to do this is to have some kind of delete indicator with a red background under the item as it’s swiped away. The callback has a method you can override called OnChildDraw, which is called every time an item needs to be drawn, such as when it’s swiped. You can override this method to add something behind the item as it’s swiped away. The sample code that accompanies this book does this, showing a red background as you swipe the item away.

Now that you have your callback, it’s time to wire it up to the recycler view in CountersView via a touch helper. Add the following code to the bottom of the OnCreate method.

Listing 10.15. Wiring up the callback to the recycler view
using Android.Support.V7.Widget.Helper;
...
protected override void OnCreate(Bundle bundle)
{
   ...
   var callback = new SwipeItemTouchHelperCallback(ViewModel);        1
   var touchHelper = new ItemTouchHelper(callback);                   1
   touchHelper.AttachToRecyclerView(recyclerView);                    1
}

  • 1 Creates the callback and uses it to construct a touch helper and then attach it to the recycler view

That’s the master view finished, so let’s move on to the detail view.

10.2.2. The detail view

You’ve already created the basic shell of your CounterView, which provides most of what you need, except for the toolbar and menu. You now need to provide a Back button so that the user can navigate back to the master view without adding a counter (referred to as an Up button in Android), and you need to wire up the menu resource for the Done button.

Adding the Back button

The Back button is easy—toolbars have one built in, but it’s not shown by default. You can show it by adding the following code to the OnCreate method.

Listing 10.16. Showing the Back button on the toolbar
protected override void OnCreate(Bundle bundle)
{
   ...
   SupportActionBar.SetDisplayHomeAsUpEnabled(true);         1
}

  • 1 Shows the Up button

This method turns on the Up button in the toolbar—the Back arrow on the left side pointing left. This button doesn’t do the navigation by itself; instead you need to handle it when it’s tapped manually.

All buttons and menu items in the toolbar are referred to as options in Android, and you can create options either by turning them on in the toolbar (as you just did) or by adding more menu options (as you’ll see later in this chapter). When any menu item is tapped, the OnOptionsItemSelected method on the activity is called, with the menu item that was tapped being passed as a parameter. To handle any toolbar menu, you need to override this method, check the ID for the item that was tapped, and respond accordingly. The following listing shows the code to handle the Up button.

Listing 10.17. Handling the up button
using Android.Views;
...
public override bool OnOptionsItemSelected(IMenuItem item)       1
{
   switch (item.ItemId)                                          2
   {
      case Android.Resource.Id.Home:                             3
         ViewModel.CancelCommand.Execute(null);                  3
         return true;
      default:
         return base.OnOptionsItemSelected(item);
   }
}

  • 1 Overrides the OnOptionsItemSelected method
  • 2 ItemId is the ID of the menu item.
  • 3 Android.Resource.Id.Home is the ID of the Up button, and it comes from the Android SDK.

The Up button has a hard-coded ID that comes from the Android SDK. If the options item that’s selected has this ID, you can execute CancelCommand on the view model to navigate back to the master view model, therefore closing the detail view and showing the master view.

Android devices have hardware Back buttons

Instead of using the Up button, the user could also tap the hardware Back button (all Android devices have a Back button, either a physical button or a software button that’s pretty much always visible). This will bypass the OnOptionsItemSelected method and just close the current activity, removing it from the navigation stack and going back to the previous activity. This isn’t a problem for us, as this will stop the new counter activity and not save the counter, so it will be the same as tapping the Up button.

If you don’t want the activity to close when the user taps the Back button, and instead to perform some action (such as stopping the button from working or doing some kind of cleanup or saving some data) you could override the OnBackPressed method on the activity, and not call the base version.

Adding the Done menu item

We’ve handled the Up button, so now we need to add the Done button to save the new counter. You’ve already defined a menu resource for this, so you need to add it to the toolbar.

As part of the activity’s creation, the virtual activity method OnCreateOptionsMenu is called by the Android OS to configure the toolbar, and you can override this to configure your own toolbar menu items. Add the following code to the view to do this.

Listing 10.18. Inflating menu items into the toolbar
public override bool OnCreateOptionsMenu(IMenu menu)               1
{
   base.OnCreateOptionsMenu(menu);
   var toolbar = FindViewById<Toolbar>(Resource.Id.toolbar);       2
   toolbar.InflateMenu(Resource.Menu.new_counter_menu);            3
   return true;
}

  • 1 Overrides the options menu creation
  • 2 Finds the toolbar in the UI
  • 3 Inflates the new counter menu resource

As with layouts, the term for reading the menu XML and creating the menu items is inflating. The toolbar can inflate the menu resource, adding the menu items to it.

You can now wire up this new menu item in the same way as the Up button: add another case statement for the ID of the Done button to the OnOptionsItemSelected method that executes SaveCommand on the view model. The following listing shows this.

Listing 10.19. Adding the Done button to the options menu handling
switch (item.ItemId)
{
   case Resource.Id.action_save_counter:        1
      ViewModel.SaveCommand.Execute(null);      1
      return true;
   ...
}

  • 1 If the Done menu item is tapped, execute the save command

10.2.3. Running the app

Your app is now fully implemented, so launch it in the default emulator and try creating some counters, incrementing them, and deleting them. Figure 10.6 shows the two screens in the working app. You can also kill and relaunch the app, and see that the counters have been persisted.

Figure 10.6. The fully working Countr app

If you have any issues running the app, such as crashes or hangs, they may be caused by known issues in different versions of Xamarin or MvvmCross. Check out the Troubleshooting thread on the Xamarin In Action forum on the book’s website for more details: http://mng.bz/9JAY.

When you run the app, the following things will happen:

  1. The Android OS looks at the manifest, finds the activity that’s set as the main launcher, and starts it (the splash screen that was automatically created for you and that can be found in SplashScreen.cs in the root of the project).
  2. The splash screen uses MvvmCross framework code to find the view model registered as the app start in the App class in the Countr.Core project—in this case, it’s CountersViewModel.
  3. Based on the name of the view model that’s set as the app start (CountersViewModel), it finds the CountersView activity and launches it.
  4. As part of the activity lifecycle, the OnCreate method is called by the Android OS.
  5. In OnCreate, the layout file with the ID of counters_view is inflated, creating all the views and view groups defined in the file and setting all the relevant properties. This method also wires up the layout manager for the recycler view and sets the touch handler to support swipe-to-delete.
  6. The MvvmCross framework finds all the MvxBind attributes that are set and creates the bindings, wiring up the counters observable collection in the view model as the source for the items in the recycler view, and connects the click event of the floating action button to the relevant command.
  7. When a user swipes on a counter, the touch handler callback’s OnSwiped method is called, and the code manually executes the delete command.
  8. When a user taps the floating action button, the command is executed, which navigates to the CounterViewModel. The MvvmCross framework finds the CounterView based on the name of the view model and shows this view. During the inflation of the layout for this view, the MvvmCross layout inflater looks for the MxBind attributes and binds the EditText to the name property on the view model.
  9. In the counter view, if the user taps the Up button, the cancel command is executed. If they tap the Done button, the save command is executed. Both commands tell MvvmCross to close the current view model, which closes the view and navigates back to the master view.

10.3. App icons and launch screens

You’ve completed both the SquareRt and Countr Android apps, but they still need a bit of improvement. If you look at the Android launch screen, you’ll see that both apps have a default MvvmCross icon, and if you launch either one of the apps, you’ll see an MvvmCross launch screen. Let’s now improve these both.

10.3.1. App icons

The first thing to update is the app icon. Here I’ll only show you how to update the icon for Countr, but the same principle applies for all Android apps, so feel free to update the SquareRt icon too.

Generating app icon images

As with image resources, Android app icons come in different sizes so that they can look the same on devices with different screen densities. This means that you’ll need to generate multiple versions of the same icon. Table 10.1 shows the sizes you’ll need.

Table 10.1. Sizes of Android launcher images for different screen resolutions

Density

Size (pixels)

mdpi 48 × 48
hdpi 72 × 72
xhdpi 96 × 96
xxhdpi 144 × 144
xxxhdpi 192 × 192

You could do this manually, but it’s a lot of work and not really necessary, as you’ll usually want an identical icon on all platforms and screen sizes. Luckily there are a plethora of tools and websites that can help by taking a large image and creating scaled-down versions for the image sizes you’ll need. These are two of my favorites:

  • MakeAppIcon (https://makeappicon.com)—On this website, you provide a large image, and they email you a zip file containing all the icons you’ll need. It’s free to use, but they also have Mac and Windows apps you can buy to do conversions on the desktop. They recommend providing an image at 1536 × 1536 pixels, and they support Photoshop, JPEG, and PNG files. In the ImagesCountrAppIcons folder in the source code that accompanies this book, you’ll find an image called CountrIconSource.png. Upload this image (or create your own image in your favorite drawing tool) to https://makeappicon.com. You’ll need to give them your email address, and they do tick a “Subscribe to our newsletter” checkbox by default, so uncheck this if you don’t want their newsletter. After a couple of minutes, you’ll receive an email with a zip file of icons.
  • Sketch (https://sketchapp.com—Mac only)—Sketch is an amazing vector-based drawing tool, and it’s one of the best tools on a Mac for designing your app and any app assets, like icons. There are a number of great templates available that do the same thing as MakeAppIcon—you give it one image, and it creates all the different sizes for you. You can find a lot of these templates on www.sketchappsources.com. The one I use is App Icon Template: http://mng.bz/NOxV.

Create some icons using whatever method you prefer, or you can find some in the ImagesCountrAppIcons folder in the source code that accompanies this book.

Updating the app icons

Android icons can be treated like any Android image resource and be added to the drawable resource folders, but this isn’t the recommended way. It’s better to put them in the mipmap resource folders—these are special drawable folders that follow the same naming convention for different screen densities (for example, mipmap-hdpi, mipmap-xhdpi, and so on) but they’re only used for launcher images. Some devices have launchers that display larger images than normal for their screen density, so they need access to different image densities. By default, all launcher images are called ic_launcher.png (Google seems to like to start all icon names with ic_ to distinguish icons used for the launcher, toolbars, and menus from other image assets).

To update the launcher images, copy yours (either created or downloaded) into the relevant mipmap folders. If you used MakeAppIcon, the Sketch template mentioned previously, or the icons from the book’s source code, they’ll already be in the correctly named mipmap folders, so just copy them over. Once they’re there, rebuild the app and redeploy it to see the new icon. You may need to delete it from your emulator to see the change.

The app icon is set inside the Android manifest file, so if you want to change it to a different icon filename, you can do that there (figure 10.7).

Figure 10.7. The app’s icon can be configured from the application manifest.

10.3.2. Launch screens

You’ve fixed your icon, so now let’s fix up the launch screen. Again I’ll focus on Countr here, but you can follow the same steps to update SquareRt. Launch screens are also referred to as splash screens (a term that MvvmCross seems to prefer and that’s more popular with desktop apps), but launch screen seems to be the term preferred by Google and Apple.

Out of the box, there’s nothing in Android to create explicit launch screens. In fact, Google has been against launch screens in the past, calling them a UX anti-pattern, but now they’re a part of the material design spec. The standard pattern for them is to have the initial activity act as a launch screen, and this launch screen activity should then load the main app activity.

Android application lifecycle and the main launcher activity

All Android apps have a main application class derived from Android.App.Application that defines the application name, icon, and theme, and that on startup will launch your main launcher activity. You don’t need to have the application class explicitly defined in your project—if no application class is found when your app is compiled, one will be created for you automatically using the values defined in the application manifest.

When this application class starts, it will look for an activity with the MainLauncher property set to true, construct it, show it, and then start the activity lifecycle. For MvvmCross apps, the main launcher is an activity derived from MvxSplashScreenActivity, and there’s one called SplashScreen in the root of Android projects. If you look at the source for this activity, you’ll see that its constructor calls the base class constructor, passing a layout resource ID:

public SplashScreen() : base(Resource.Layout.SplashScreen)

If you look at the SplashScreen.axml layout file, you’ll see that it contains only a single text view with the text “Loading...” and nothing else. If you run your app and watch it load, you’ll see more on the launch screen—it will start with a black screen with MvvmCross written across it in white letters, and then a few seconds later the text “Loading...” will appear at the top left (figure 10.8).

Figure 10.8. The launch screen shows a black screen with white text for a few seconds (left) before showing the “Loading...” text (right).

The black screen with the MvvmCross text doesn’t come from the layout resource; instead it comes from the activity’s style. All Android UI components, be they activities or widgets, can be styled, and a style defines how the widget or activity looks on-screen, including colors, fonts, layouts, and even what’s shown as the background.

When the Android application starts up, it launches the main launcher activity, showing it on-screen. As soon as the activity is shown, it’s rendered using its style, and then the activity lifecycle kicks off, calling the OnCreate method (figure 10.9). In this method, MvvmCross sets up everything it needs to run the app—it initializes all its internals, loads everything into the IoC container, and works out the views for the view models. Once everything is loaded, it loads the layout resource and updates the UI to show the Loading... message.

Figure 10.9. When your app starts up, it loads the splash screen activity marked as the main launcher, and this initializes MvvmCross before loading a layout.

If you want to personalize the launch screen for your app, you’ll need to change the splash screen activity’s style.

Styles

Styles are defined in XML resources in the values folder. Usually styles are defined in a file called styles.xml, but they can be defined in any XML file in that folder. Each style is an XML style node with a name attribute that defines the name of the style. Styles can also have parents, defined using the parent attribute, and they’ll inherit all the style settings from the parent. (For example, if style A sets a font to bold, and style B uses style A as its parent, anything styled with style B will also have its font set to bold.) This style node then contains one or more item elements that define the values in the style.

Widget styles are outside the scope of this book. I’ll just be focusing here on application and activity styles. You can read more on styling in the Android “Look and Feel” API guide: http://mng.bz/l1TV.

If you open the SplashScreen.cs file and look at the Activity attribute on the class, you’ll see the activity’s theme set using Theme="@style/Theme.Splash". The term theme seems to be used interchangeably with style, but a theme is something you set for the whole application in the application manifest or on an activity using the Theme property in the Activity attribute. Themes can also be used to link styles together. For example, as a part of setting the application theme, you can set individual styles to apply to different types of widgets. All activities have a theme, either set explicitly as in the SplashScreen activity, or set on the application in the manifest. If you don’t set a theme in the manifest, and you don’t set one on your activity, your app will crash when it tries to load the activity.

If you open the SplashStyle.xml file in the values folder, you’ll see the Theme.Splash style, shown in the following listing.

Listing 10.20. The style for the splash screen
<style name="Theme.Splash" parent="android:Theme">
   <item name="android:windowBackground">@drawable/splash</item>
   <item name="android:windowNoTitle">true</item>
</style>

This style element has the name Theme.Splash, and it inherits all the values from the Android-supplied Theme style. It defines two elements:

  • windowBackground—Identifies the drawable to show as the background for the activity when it’s loaded
  • windowNoTitle—Specifies whether the window should be shown without a title bar

When this style is applied to the splash screen activity, it sets the value of these two properties on the activity as it’s loaded and before it’s shown on screen. This means that the activity is fully styled before the user sees it, showing @drawable/splash as the activity’s background as soon as it’s loaded. This is shown while the activity is created, and then the layout resource is loaded over the top. If you look at the Splash.png file in the drawable folder, you’ll see a black background with the MvvmCross text on it—this is what you’ll see when you launch the app, and it’s shown in figure 10.10.

Figure 10.10. When activities are shown, they’re drawn using their theme. This happens before the activity lifecycle is run, OnCreate is called, and layouts are loaded.

Styles for Android v21 and above

In the resources folder, you’ll see two “values” folders: values and values-v21. The values folder contains values that apply to all devices and OS versions, whereas the values-v21 folder contains styles for devices running API level 21 and higher, essentially allowing you to style material design properties that aren’t available on older devices. You can read more about this in the Android “Look and Feel” API guide: http://mng.bz/X0vg.

If you want to update the launch screen, the simplest thing to do would be to change the image. This would work, but it wouldn’t be ideal as these images are made to fit on whatever device your app runs on, so depending on the aspect ratio or orientation, the image could be distorted. Instead you can use an XML drawable.

XML drawables

On Android, drawables can be bitmap-based image files, such as PNG files, or they can be XML files that have drawing instructions (similar to vector-based image formats, such as SVG). Having a bitmap for a launch screen is no good, because it doesn’t scale correctly for different aspect ratios. Although you can provide different bitmaps for different screen densities, multiple devices with the same density might have different aspect ratios, leading to the bitmap being stretched differently. Using an XML drawable allows you to define a background once that scales correctly for any aspect ratio or orientation.

We’ll only look at a simple drawable here that allows us to show a bitmap without scaling, but there’s a lot you can do with XML drawables. You can read more on them in the Android “Resource Types” API guide: http://mng.bz/qQMU.

Let’s start by creating a new XML file called splashscreen.xml in the drawable folder. You can also delete the Splash.png image file, as we won’t be using it anymore. For our new splash screen, we’ll have an image in the center with a colored background, so that it looks just like the app icon (figure 10.11).

Figure 10.11. The layout of the splash screen XML drawable

The first step is to define the colored background. Listing 10.21 shows the code to add to the splashscreen.xml file to do this.

Listing 10.21. Adding a gray rectangle to the splash screen XML drawable
<layer-list                                                           1
      xmlns:android="http://schemas.android.com/apk/res/android" >    1
   <item>
      <shape android:shape="rectangle" >                              2
         <solid android:color="#555555" />                            2
      </shape>                                                        2
   </item>
</layer-list>

  • 1 Defines a layer list—a list of drawables drawn one on top of the other
  • 2 Defines a dark gray rectangle

For this drawable we need a background and an image, so we can use a layer list—this is a drawable element that contains other elements, and it draws them one on top of the other with the first item in the list drawn first, the second on top of that, and so on. Our layer list only has a single item, defined by the item node and containing a shape. Shapes can be rectangle, oval, line, or ring, and by default they scale to fill the available space—so our rectangle will scale to fill the entire screen. This shape is filled with a solid color of #555555, a nice dark gray.

The next thing to add is a bitmap in the middle. You can find the bitmap to use in the images in the book’s source code. Copy the launch_image.png file from the various Android drawable folders in the source code into the same drawable folders in your app. Once the image is there, add it as a bitmap item to the bottom of the layer list in the XML drawable.

Listing 10.22. Adding a bitmap to the splash screen XML drawable
<layer-list
      xmlns:android="http://schemas.android.com/apk/res/android" >
   ...
   <item>
      <bitmap                                         1
         android:gravity="center"                     1
         android:src="@drawable/launch_image" />      1
   </item>
</layer-list>

  • 1 Adds a bitmap item to show a bitmap in the drawable

This adds a new item to the layer list containing the bitmap image you’ve just copied. When adding bitmaps, you can specify the gravity—the layout positioning. A gravity of center maintains the image size and positions it in the horizontal and vertical center. You can use any of the standard Android gravity values to position the bitmap wherever you want, or resize it to fill the screen.

You can’t have text in an XML drawable

One surprising omission from XML drawables is text—there’s no way to render any form of text in your drawable. The only way to include text is to create a bitmap containing the text you want, and then use that.

The splash screen is now complete, so you need to tell your app to use it. Open the SplashStyle.xml file from the values folder, and update the window background to be the new drawable, as shown in the following listing.

Listing 10.23. Updating background of splash screen style to be the new drawable
<style name="Theme.Splash" parent="android:Theme">
  <item name="android:windowBackground">@drawable/splashscreen</item>     1
  ...
</style>

  • 1 Sets the new background

If you build and run the app, you’ll now see the nice new splash screen.

Styling our app

There is a huge array of things you can style in your apps—probably enough to warrant a separate book—but one simple one that’s worth looking at is colors. The built-in Android styles have a set of named colors that are used for different parts of the UI, and it’s really easy to override these to make your app look totally different. Figure 10.12 shows some of these named colors and how they’re used.

Figure 10.12. Some of the standard named colors used in an Android app

The colors for an app are defined as named color resources in a resource file called colors.xml that lives in the values folder, and these are applied to the named colors used in the styles.xml file. To change the color of your app, all you need to do is change these values depending on your preferred app color scheme. Google has a material design color tool to help you define your app color schemes: https://material.io/color/.

Let’s change the default blue toolbar of the app to a much nicer orange color. Make the following changes to the colors.xml file.

Listing 10.24. Updating some of the app colors
...
<color name="primary">#FF9800</color>                1
<color name="primaryDark">#F57C00</color>            1
...

  • 1 Updates the primary and primaryDark colors

Make this change, and build and run the app—you’ll see that the app now has a nice orange toolbar.

We’re now done with our Android apps. Over the last chapter and this one, you’ve built two apps, one with a simple UI and another with a more complicated multiscreen UI with a recycler view. You’ve set up app icons and a launch screen. In the next two chapters we’ll do the same thing, but on iOS.

Be wary of poor Android performance

Android apps can suffer badly from poor performance, and this isn’t helped by the “race to the bottom” for some Android device manufacturers focused on making the cheapest devices possible by using older and slower hardware. When building complicated Android apps, it’s worth trying to reduce the number of views in your layout, as well as avoiding overdraw—the drawing of the same pixel multiple times with the same color. For example, if your style has a red background, and you put a layout with a red background on top, the OS has to draw each red pixel twice—once for the background and again for the layout. If your layout doesn’t have a background set, it will still appear red (due to the red background underneath), but only one red pixel will be drawn, improving performance.

You can read about improving performance in Android apps in Tomasz Cielecki’s “Improving layout performance on Android” blog entry: http://mng.bz/r7MU.

Summary

In this chapter you learned

  • Recycler views can be used to show lists of items.
  • Menu items can be added to the toolbar.
  • App icons come in different resolutions to support different screen densities.
  • Activities can be styled, and their background drawable is shown while the activity is loading.

You also learned how to

  • Configure a recycler view to bind to an items collection in a view model, and to show these items using a custom layout.
  • Add menus and handle when the user taps on them.
  • Handle the toolbar Up button.
  • Create XML drawables.
  • Change app colors.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.150.89