VLC for WinRT: a “true, Universal App”
One of the big points Thomas made in the new VLC for WinRT release was it becoming a “true, Universal App”
As the frontend guy, my job this last month was to completely merge the Windows and Windows Phone projects, and move quite everything in the Shared project. Today we’re sharing ~80% of our XAML code and >95% of the C# code, and >this will keep improving during the next couple of months.
The tech press picked up on those stats. But it’s quite clear to me that they don’t actually know what that means. Seeing “move quite everything in the Shared project” is really cool, but it’s not without issues of its own (Especially with numbers that big). But let’s go into why, and how this approach could potentially make more sense with Windows 10.
At Build 2014, Microsoft announced WinRT (The framework used for building “Modern” Windows Store applications) for Windows Phone 8.1, as well as concept of “Universal apps”. When you either enable an existing app to be a universal app, or create a new one, you get three projects: The Windows project, the Windows Phone project, and a shared project. Here you can place platform specific XAML or view code (AKA, what you see on screen) in the platform specific projects, and shared code (Like, for example, view models, commands, or shared views like user controls) in the shared solution. If you’re good and know how to properly follow the MVVM (Model-View-View-Model) pattern, it’s relatively easy to leverage the code in the shared project into the platform specific views in the other projects. So once you finish working on one project (be it Windows 8 or Windows Phone), it’s just a matter, more or less, of building the views and remaining platform specific logic for the other.
This is the (closed) solution for VLC for WinRT. The important bits for us are the Windows, Windows Phone, and Shared projects.
In the shared solution, we see tons of views. Let’s look AlbumCollectionBase. This is used to show you your list of Albums.
Here is the XAML preview. The design window is set to 300px, but when placed in our views it scales to whatever the size is set to it (Normally, it’s set to match the total width of the device size). Now, remember that this code is in the shared solution. This gets run in both the Windows Phone and Windows 8 apps.
Now, the bit I’m interested in is the list view, which actually shows you the albums. Let’s look at it.
<ListView Grid.Row="1" ItemsSource="{Binding Albums}" ItemTemplate="{StaticResource PhoneSemanticZoomInViewAlbumTemplateLittleSize}" ItemContainerStyle="{StaticResource ListViewItemStyle}" IsItemClickEnabled="True" SelectionMode="None" ContainerContentChanging="ListViewBase_OnContainerContentChanging" IsSwipeEnabled="False"> <triggers:Interactions.Triggers> <triggers:EventTrigger EventName="ItemClick"> <triggers:InvokeCommandAction Command="{Binding AlbumClickedCommand}" PassEventArgsToCommand="True" /> </triggers:EventTrigger> </triggers:Interactions.Triggers> <ListView.ItemsPanel> <ItemsPanelTemplate> <ItemsWrapGrid SizeChanged="AlbumsWrapGrid_SizeChanged" Orientation="Horizontal" Margin="0,0,0,60" /> </ItemsPanelTemplate> </ListView.ItemsPanel> </ListView>
And more importantly, the itemswrapgrid, used to define the constraints on the items themselves in the listview:
<ListView.ItemsPanel> <ItemsPanelTemplate> <ItemsWrapGrid SizeChanged="AlbumsWrapGrid_SizeChanged" Orientation="Horizontal" Margin="0,0,0,60" /> </ItemsPanelTemplate> </ListView.ItemsPanel>
“SizeChanged="AlbumsWrapGrid_SizeChanged"”. This means that when the size on the ItemsWrapGrid changes (What the albums themselves are housed in), it fires this command in the code behind on the view. Let’s look at that command.
private void AlbumsWrapGrid_SizeChanged(object sender, SizeChangedEventArgs e) { TemplateSizer.ComputeAlbums(sender as ItemsWrapGrid, TemplateSize.Compact, this.ActualWidth); }
So it sends the width of the grid, as well as the grid itself, to another helper function in order to set how many items you see.
public static void ComputeAlbums(ItemsWrapGrid wrapGrid, TemplateSize size = TemplateSize.Compact, double? width = null) { if (width == null) width = Window.Current.Bounds.Width; var splitScreen = (size == TemplateSize.Compact) ? 3 : 2; #if WINDOWS_PHONE_APP if(width == 400) size = TemplateSize.Normal; if (!DisplayHelper.IsPortrait()) splitScreen = 5; var itemWidth = (width.Value / splitScreen); var itemHeight = itemWidth + 40; wrapGrid.ItemWidth = itemWidth; wrapGrid.ItemHeight = itemHeight; #else if (width > tileSize*3) { wrapGrid.ItemWidth = tileSize; wrapGrid.ItemHeight = tileSize + contentGridWindows; } else { wrapGrid.ItemWidth = (width.Value / splitScreen); wrapGrid.ItemHeight = wrapGrid.ItemWidth + contentGridWindows; } #endif }
“#if WINDOWS_PHONE_APP” is a conditional statement that only gets fired when the app is being run on Windows Phone. Likewise if you use “#if WINDOWS _APP”. So we are getting conditional code for each platform. So we are in the shared solution doing platform specific view logic… which seems to defeat the purpose of putting it here. This happens all over the place in the shared project view files. Granted, it’s mostly trivial changes (Changing the thickness of lines, or margins, what have you), but these little statements can add up, making it very hard to debug and make changes without screwing up the other project.
Let's say we have an issue with one of these views, or decided we want it to behave differenty or have platform specific commands be shown to the user. By having it layed out like this, it's much more difficult to do then it needs to be.
What we really want is more platform independent XAML. It makes sense to build user controls that work across Windows Phone and Windows 8 and putting them into the shared solution. But right now, you can still end up with too many platform specific If-Else statements that’ll just make it a pain in the ass to maintain.
But with Windows 10, Microsoft seems to be making moves to make this sort of thing possible “and easier to do”
They’ve been talking a lot “Adaptive UX” and having one “experience” work across multiple platforms. By having better handling of device profiles and layouts, us developers hopefully won’t have to write these elaborate views to try and get everything to scale ourselves. We can write just one layout and have it scale across phones, tablets, and desktops. That’s what VLC tried to do here, but just a bit too soon. It’s too fragile for my taste to have THIS much code placed in the shared project, with the amount of conditional statements needed to make it work. But seeing where Microsoft is heading, porting our current stack to the true Windows 10 APIs might actually be easier to do, seeing that most of the code is already in the right locations. We would just have to get rid of the conditionals.
We’ll see what Build brings.