Petal

Creating a Kentico Cloud-powered Xamarin App—Part 2

In Part 1 of this series, I showed you how to build a base Xamarin app using Kentico Cloud. In that article, I covered creating the initial application, adding a service, and implementing calls to retrieve the data. In Part 2, I’ll cover how to display that content and build a cross-platform app using XAML.

Avatar fallback - no photo

Bryan SoltisPublished on Jul 26, 2017

Now that I had my initial app developed, I was ready to add the good stuff; XAML! In this article, I’m going to cover how I created my UI for the application, and hooked up my Kentico Cloud Delivery service calls to the interface. 

You can read about Part 1 of the series here.

Adding the BlogPostsPage

With the models and service in place, I was ready to create my layout. Building on top of the sample application, I created folders for Views, to hold my BlogPostsPage and BlogPostDetailPage views. 

Kontent.ai


Note that I have several sub-fodders for my different classes / files.

For my views, I created a BlogPostsPage ContentPage to display my list of articles. Specifically, I added a ListView control to display each blog post image, title, and other details. 

<ListView x:Name="lvBlogPosts" HasUnevenRows="True" ItemSelected="OnSelection" IsPullToRefreshEnabled="True"
           RefreshCommand="{Binding RefreshCommand}"
           IsRefreshing="{Binding IsRefreshing}" >
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Grid BackgroundColor="White" ColumnSpacing="0" RowSpacing="0">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="*" />
                                <RowDefinition Height="1" />
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*" />
                                <ColumnDefinition Width="8*" />
                                <ColumnDefinition Width="*" />
                            </Grid.ColumnDefinitions>
                            <Image Grid.Row="0" Grid.ColumnSpan="3" Source="{Binding ImageUrl}" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" BackgroundColor="{StaticResource colorPrimaryAppBackground}" Aspect="AspectFill"/>
                            <StackLayout Grid.Row="0" Grid.Column="1" VerticalOptions="End" HorizontalOptions="FillAndExpand" BackgroundColor="{StaticResource colorPrimaryAppBackground}" Padding="20" Opacity=".85">
                                <Label Text="{Binding Date,StringFormat='{0:MMMM dd, yyyy}'}" TextColor="{StaticResource colorPrimaryText}" FontSize="Small" Opacity="1"/>
                                <Label Text="{Binding Title}" TextColor="{StaticResource colorPrimaryText}" FontSize="Medium" Opacity="1"/>
                                <StackLayout Orientation="Horizontal">
                                    <Label Text="by "  TextColor="{StaticResource colorPrimaryText}" FontSize="Small" ></Label>
                                    <Label Text="{Binding AuthorName}"  TextColor="{StaticResource colorPrimaryText}" FontSize="Small" ></Label>
                                </StackLayout>
                            </StackLayout>
                        </Grid>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>


The use of grids and row/column definitions allowed me to position the elements exactly where I wanted them. Note that it can be a little tricky to get it perfect, so be sure to test your layouts often. And, as in my case, developing Silverlight for a about 3 years certainly helped, as well!

In my code-behind, I added functionality to populate the list, using my service.

        public async Task RefreshBlogPosts(int intDelay = 0)
        {
            try
            {
                await General.Sleep(intDelay);
                var blogposts = await _viewModel.GetBlogPosts();
                lvBlogPosts.ItemsSource = blogposts;
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
        }


Next, I added an OnSelection event to the class to handle when a user selects an article. If that happens, I wanted the user to be redirected to the BlogPostDetailPage, where they would see additional information about the article. 

        async void OnSelection(object sender, SelectedItemChangedEventArgs e)
        {
            if (e.SelectedItem == null)
            {
                return;
            }

            try
            {
                BlogPost blog = (BlogPost)e.SelectedItem;
                await Navigation.PushAsync(new BlogPostDetailPage(new BlogPostDetailViewModel(blog)));

                // Manually deselect item
                lvBlogPosts.SelectedItem = null;

            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
        }


Note that the BlogPostDetailPage object is created using the selected BlogPost. This populates the ViewModel with all the blog details.

Adding the BlogPostDetailPage

Clicking an article loads the BlogPostDetailPage, which shows the additional information, as well as a list of other articles by the author. I created a new view for this detailed layout, showing my information about the article. 

                <StackLayout Grid.Row="0" Padding="10">
                    <Label Text="Topic:" TextColor="{StaticResource colorSecondaryText}" FontAttributes="Bold"></Label>
                    <Label Text="{Binding BlogPost.TopicName}" TextColor="{StaticResource colorSecondaryText}"></Label>
                    <Label Text="Description" TextColor="{StaticResource colorSecondaryText}" FontAttributes="Bold"></Label>
                    <Label Text="{Binding BlogPost.PerexText}" TextColor="{StaticResource colorSecondaryText}"></Label>
                    <StackLayout Orientation="Horizontal" HorizontalOptions="Center" VerticalOptions="End" Padding="0,10,0,0">
                        <StackLayout Grid.Column="0" HorizontalOptions="Center" BackgroundColor="{StaticResource colorHighlight}" Padding="5">
                            <Button Text="View" VerticalOptions="End" HorizontalOptions="Center" BackgroundColor="{StaticResource colorHighlight}" TextColor="{StaticResource colorPrimaryText}" Clicked="ViewArticleClick" CommandParameter="{Binding BlogPost.UrlSlug}"></Button>
                        </StackLayout>
                        <StackLayout Grid.Column="1" HorizontalOptions="Center" BackgroundColor="{StaticResource colorHighlight}" Padding="5">
                            <Button Text="Back" VerticalOptions="End" HorizontalOptions="Center" BackgroundColor="{StaticResource colorHighlight}" TextColor="{StaticResource colorPrimaryText}" Clicked="BackClick"></Button>
                        </StackLayout>
                    </StackLayout>
                </StackLayout>


Additionally, I wanted to show a list of other blogs by the author, so I added another ListView for that content.

                <StackLayout Grid.Row="1" Orientation="Horizontal" Padding="10">
                    <Label Text="All posts from " TextColor="{StaticResource colorSecondaryText}" FontAttributes="Bold"></Label>
                    <Label Text="{Binding BlogPost.AuthorName}" TextColor="{StaticResource colorSecondaryText}" FontAttributes="Bold"></Label>
                </StackLayout>
                <ListView Grid.Row="2" x:Name="lvAuthorBlogPosts" HasUnevenRows="True" ItemSelected="OnSelection" HorizontalOptions="Fill" Margin="10">
                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <ViewCell>
                                <Grid Padding="5">
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="2*"></ColumnDefinition>
                                        <ColumnDefinition Width="8*"></ColumnDefinition>
                                    </Grid.ColumnDefinitions>
                                    <Label Grid.Column="0" Text="{Binding Date,StringFormat='{0:yyyy-MM-dd }'}" TextColor="{StaticResource colorHighlight}" FontSize="Small"/>
                                    <Label Grid.Column="1" Text="{Binding Title}" TextColor="{StaticResource colorHighlight}" FontSize="Small"/>
                                </Grid>
                            </ViewCell>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>


In the code-behind, I added calls to the Kentico Cloud service to retrieve the content. Because the ViewModel was populated with the selected BlogPost, I only needed to add a call the GetAuthorBlogPosts function in my service. 

        protected override void OnAppearing()
        {
            base.OnAppearing();
            GetAuthorBlogPosts(viewModel.BlogPost.AuthorInternalName);
        }
        public async void GetAuthorBlogPosts(string strAuthor)
        {
            // Get author blogs            
            Services.KenticoCloudService service = new Services.KenticoCloudService();
            var authorblogposts = await service.GetAuthorBlogPosts(strAuthor);
            lvAuthorBlogPosts.ItemsSource = authorblogposts;
        }

Testing

With the app created, I tested the functionality on several devices. First, I tried UWP.

UWP Home

Kontent.ai


UWP Detail

Kontent.ai


Next, I tried Android, using the Visual Studio emulators.

Android Home

Kontent.ai


Android Detail

Kontent.ai


For iOS, you need compile the application against a Mac. I created a VM with OS Sierra on it and launched the iOS simulator to test.

iOS Home

Kontent.ai


iOS Detail

Kontent.ai


Lastly, I tried Windows 10 Mobile (because some of us still have one of those around).

Windows 10 Mobile Home

Kontent.ai


Windows 10 Mobile Detail

Kontent.ai


Important notes

During the development process, I came across several bits of information that I wanted share. Knowing some of these can save you HOURS of work and headache, so be sure to note them.

- To use the Kentico Cloud Delivery API in Xamarin, you must use .NET Standard 1.4 or higher. The notes on how to do this are in the sample app, so be sure to read up on that.

- There’s no “defined” way to store AppSettings in Xamarin. In my case, I made a const in my service to store the KenticoCloudProjectID. You may need a different solution for your applications.  

- If your Android emulators aren’t running well, be sure you enable Host CPU for the configuration. Just know that some displays / docking stations may not support this. (FYI: This one issue cost me about 1.5 days of development to track down!).

- In order to get the iOS app running, I had to add a reference to the KenticoCloud.Delivery NuGet package to the iOS project. The Android and UWP apps did not require this. I also had to clean the project after adding the reference. 

- You may have heard about the Xamarin Live Player. This new app allows you to deploy directly to an Android or IOS device. While this sounds great, there are some limitations:

o You cannot use a PCL project., Only “Shared” libraries are supported.

o You must use the Visual Studio 2017 Preview version to use the player. This is separate from your standard VS 2017 install. Installing this new version also requires installing separate version of the SDKs and emulators. That means a LOT of space on your drive!

- You can use #if comments in your code to conditionally load logic. This helps you quickly modify the output, depending on the environment. Here’s an example in my exception handling:

        protected void HandleException(Exception ex)
        {
#if DEBUG
            ShowMessage(ex.Message);
#else
                ShowMessage(strStandardMessage);
#endif
}

Moving Forward

Xamarin is a great tool for building cross-platform mobile apps. With Kentico Cloud now supporting the architecture, you can quickly build your iOS, Android, and UWP apps using the same API calls you are used to in your web apps. I can’t wait to see what great mobile experiences our partners come up with. Good luck!

View the full source code

Avatar fallback - no photo
Written by

Bryan Soltis