Tuesday, May 13, 2014

Simple App Orientation for Windows Store Applications With Prism

In this post we are trying to focus how we can control application state for an App. We have chosen Windows Store Apps as the platform of choice. It provides SimpleOrientationSensor as the device orientation changes.

Let's start with PCL
Let's create a simple Portable class library project. We are naming it as SimpleOrientationEx.Portable. The project is supposed to hold the orientation state type. The application state would be based on an enumeration type, also defined in the same PCL project. For this simpleton project, let's use the maximum numbers of target frameworks.

These are the simple possible orientations of the app. It includes portrait, landscape and their contra-states. It also has face-up and face-down states. These are the states when the device is held in a perfect horizontal position either face-up or down. As a developer, you might have realized that it is really difficult for an app to determine how the device is being held, and should the display be using portrait or landscape layout.

Now let's introduce an interface type to hold the current application orientation. It holds the state in a CurrentOrientation property. We are naming the interface-type as IAppOrientation.

Let's implement this now. In addition to implementing the CurrentOrientation member from the interface type, it also introduces a protected member to update the current orientation of the App.

Windows Store App Orientation
As an example application, let us consider Windows Store Apps for Windows 8.1. We are creating a specialized type of AppOrientationBase class to hold the orientation state of a Windows Store App. The specialized type is named as AppOrientationWindowsStore.

Windows Store Apps can use SimpleOrientationSensor to determine the device orientation. The sensor type is available in Windows.Devices.Sensors namespace. It provides the application orientation using enumeration type.

The enumeration type has similar values as our AppOrientationState. We can define a mapping between the enumeration values of AppOrientationState and SimpleOrientation. Here MapOrientation() method is defining this mapping for us.

Let's introduce Reactive Extensions
Here we are adding reactive extensions core nuget package [Reactive Extensions Homepage]. Not only this makes throttling these events a child's play but also provides the events as Obervable.OnNext() sequence. This can be achieved using Obervable.FromEventPattern().

Since updating the layout for orientation change can be very involved process rendering heavy load elements, let us throttle the orientation changes a little. Here we are throttling the OrientationChanged event from SimpleOrientationSensor for one second. The throttled events are not raised in the publisher's thread but a different thread pool thread. Since we are updating the UI in the subscription code, we can observe the sequence using SynchronizationContext.Current. This would execute the handler code in the required UI thread.

Prism for Windows Store App
Now let us add Prism for Windows Store App nuget package. We introduce the main view model for the App. It just has a string based property to hold some text data. The view model inherits from ViewModel type from Prism.

The view model keeps the App orientation. It instantiate the member using default SimpleOrientationSensor. Like other Windows Store App sensors, SimpleOrienationSensor singleton instance can be obtained using the static GetDefault() method.

Prism for Windows Store App provides the support of restoring the application state using RestorableState attribute. We are decorating the only property with this attribute. This should restore the application state when application is restored to Running from Suspended state. [MSDN: App Lifecycle] [Handling Suspend, Resume & Activation in a Windows Store App]

Prism for Windows Store App also provides MvvmAppBase type. We can inherit from this to further control the App. Here we are overriding OnLaunchApplication and OnNavigationFailed methods. Here OnLaunchApplication is being used to navigate to MainPage.xaml as the app is launched. Based on the convention, we must have [X]Page.xaml. Here [X] is the first argument to the NavigationService.Navigate() method. We can override this mapping by overriding GetPageType() method. It is passed with the same string as pageToken and it should return the mapped Page type.

Now we add an IValueConverter to convert the enumeration value to string.

App Orientation and View Updates
Now we turn our attention to the view. We have a simple view with a TextBlock and TextBox. They are bound to the same property in the view model. Here we are using Prism's view model auto-wiring capabilities. The view model should be named following the Prism's convention. For our case, it is named as MainPageViewModel [ [X]PageViewModel ].

Here we have also introduce a RotateTransform for the Grid to the RenderTransform property. We are defining the center of the grid as the origin of the transformation by assigning RenderTransformOrigin as "0.5, 0.5".

Now we add Visual states to the page. All of these states correspond to different states in AppOrientationState enumeration. In order to keep the example simple, we are just animating the RotateTransform defined above for the Grid. It just rotates it keeping the view always upright. We are also defining transitions to these states to ease the rotation effect.

Introduce Behavior SDK (XAML) extension
Now we need to define the transitions between these states. These transitions should be based on the states in IAppOrientationState in MainPageViewModel. This is very tricky in Windows Store Apps as there is no DataTrigger support for the platform. In Visual Studio 2013, Microsoft added support for some behaviors including DataTriggerBehavior. Let us add the extension SDK using Reference Manager dialog.

In addition to DataTriggerBehavior, we are also using GotToStateAction from the same extension. This can be used to transition to a particular visual state.

Get Code from GitHub

No comments: