WordPress-Seite als Xamarin.Forms App – Teil 4

Vor mehr als zwei Jahren habe ich euch bereits WordPressXF vorgestellt. Mein Kollege Thomas Pentenrieder hat eine .NET Library geschrieben, um auf einen WordPress-Blog von einer .NET App zugreifen zu können. Er hat sich dann dann primär um eine  UWP-Version gekümmert, welche die WordPressPCL-Library in Action zeigt. Ich habe mich selbst um eine Xamarin.Forms Version gekümmert. Nun dachte ich mir, dass man doch einmal die Solution updaten könnte, um so die neusten Xamarin.Forms Features verwenden zu können. Da ich jetzt nicht einfacher nur das Projekt updaten wollte, habe ich mir gedacht, dass die Umsetzung in Form von mehreren Blog-Beiträgen passiert und ich euch so zeige, wie eine kleine App entsteht, welche in der Lage ist die Beiträge eines Blogs unter Android und iOS an zu zeigen. Im vierten Teil wollen wir uns um die Kommentare kümmern und die auf einer separaten Seite anzeigen. Sollte es keine Kommentare zu einem Beitrag geben, so wollen wir eine entsprechende Nachricht anzeigen.

Ausgangslage bildet die Solution, welche im dritten Teil entwickelt worden ist. Ihr bekommt die Solution entweder im alten Beitrag oder ansonsten über den folgenden Download-Button.

Um die Kommentare zu einem Beitrag abzurufen, müssen wir zunächst unser IWordPressService-Interface um eine weitere Methode erweitern. Diese Methode wollen wir GetCommentsForPostAsync nennen und die Methode bekommt die ID des Beitrags als Parameter übergeben. Damit hat unser Interface nun den folgenden Inhalt:

public interface IWordPressService
{
    Task<IEnumerable<Post>> GetLatestPostsAsync(int page = 0, int perPage = 20);

    Task<List<CommentThreaded>> GetCommentsForPostAsync(int postid);
}

Nun können wir in die Klasse WordPressService wechseln und die neue Methode implementieren.

public async Task<List<CommentThreaded>> GetCommentsForPostAsync(int postid)
{
    var comments = await _client.Comments.Query(new CommentsQueryBuilder
    {
        Posts = new[] { postid },
        Page = 1,
        PerPage = 100
    });

    return ThreadedCommentsHelper.GetThreadedComments(comments);
}

Nun sind die Grundvoraussetzungen zum Abrufen der Kommentare zu einem Posts bereits gegeben, so dass wir das PostsViewModel öffnen und entsprechend erweitern können. Zunächst führen wir eine neue Property mit den Namen Comments ein.

private List<CommentThreaded> _comments;
public List<CommentThreaded> Comments
{
    get => _comments;
    set { _comments = value; OnPropertyChanged(); }
}

Wir erweitern nun noch die Methode SetSelectedPostAsync. Dazu setzen wir am Anfang der Methode die Property Comments auf null und nachdem wir navigiert sind, nutzen wir die neue Version vom WordPressService, um die Kommentare abzurufen.

private async Task SetSelectedPostAsync(Post selectedPost)
{
    try
    {
        IsLoading = true;
        
        Comments = null;

        SelectedPost = selectedPost;
        await NavigationService.NavigateToAsync(NavigationTarget.PostDetailOverviewPage);
        
        Comments = await _wordPressService.GetCommentsForPostAsync(selectedPost.Id);
    }
    catch(Exception ex)
    {
        Debug.WriteLine(
            $"{nameof(PostsViewModel)} | {nameof(SetSelectedPostAsync)} | {ex}");
    }
    finally
    {
        IsLoading = false;
    }
}

Keine Sorge, wenn euch Visual Studio darauf hinweist, dass unser NavigationTarget noch nicht über einen Eintrag PostDetailOverviewPage verfügt. Diese werden wir im Verlauf dieses Beitrags noch anlegen.

Aber zunächst wollen wir uns um die Seite für die Kommentare kümmern. Im Ordner Controls legen wir eine neue ContentView mit den Namen CommentControl an. Nun öffnen wir Code-Behind Datei, also CommentControl.xaml.cs und erstellen zwei neue BindableProperties, einmal für den Kommentar und einmal für den Autor.

public partial class CommentControl : ContentView
{
    public static readonly BindableProperty CommentProperty =
        BindableProperty.Create(nameof(Comment), typeof(string), 
            typeof(CommentControl));

    public string Comment
    {
        get => (string)GetValue(CommentProperty);
        set => SetValue(CommentProperty, value);
    }

    public static readonly BindableProperty AuthorProperty =
        BindableProperty.Create(nameof(Author), typeof(string), 
            typeof(CommentControl));

    public string Author
    {
        get => (string)GetValue(AuthorProperty);
        set => SetValue(AuthorProperty, value);
    }

    public CommentControl()
    {
        InitializeComponent();
    }
}

Anschließend können wir uns um das Layout kümmern. Wir verwenden ein relativ einfaches Layout für einen Kommentar, denn wir zeigen den Kommentartext und den Autor in einem Frame ein.

<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XFWordPress.Controls.CommentControl"
             x:Name="Root">

    <ContentView.Content>
        <Frame CornerRadius="8">
            <Grid RowDefinitions="Auto,Auto">
                <Label Text="{Binding Comment, 
                    Converter={StaticResource HtmlStringToDecodedStringConverter}, 
                    Source={x:Reference Root}}"
                       Grid.Row="0" />

                <Label Text="{Binding Author, Source={x:Reference Root}}"
                       Style="{DynamicResource PostCommentAuthorLabelStyle}"
                       Grid.Row="1" />
            </Grid>
        </Frame>
    </ContentView.Content>

</ContentView>

Im Ordner Views erstellen wir eine neue ContentPage mit dem Namen PostCommentPage und dem folgenden Inhalt:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls="clr-namespace:XFWordPress.Controls"
             xmlns:markupextensions="clr-namespace:XFWordPress.MarkupExtensions"
             x:Class="XFWordPress.Views.PostCommentPage"
             Title="{markupextensions:Translate PostCommentPageTitle}"
             Padding="16">

    <ContentPage.Content>
        <CollectionView ItemsSource="{Binding Comments}">
            <CollectionView.ItemsLayout>
                <GridItemsLayout Orientation="Vertical"
                                 VerticalItemSpacing="12" />
            </CollectionView.ItemsLayout>
            <CollectionView.EmptyView>
                <Label Text="{markupextensions:Translate 
                    PostCommentPageNoCommentsLabel}" />
            </CollectionView.EmptyView>
            <CollectionView.ItemTemplate>
                <DataTemplate>
                    <controls:CommentControl Comment="{Binding Content.Rendered}"
                                             Author="{Binding AuthorName}" />
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </ContentPage.Content>
</ContentPage>

Um uns nun die Kommentare zu einem Beitrag anzeigen zu lassen, erstellen wir eine TabbedPage im Ordner Views. Diese wollen wir PostDetailOverviewPage nennen. Diese beinhaltet zwei Unterseiten, nämlich einmal die PostDetailPage und einmal die PostCommentPage. Der Aufbau der PostDetailOverviewPage ist entsprechend sehr simpel.

<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
            xmlns:views="clr-namespace:XFWordPress.Views"
            xmlns:effects="clr-namespace:XFWordPress.Effects"
            x:Class="XFWordPress.Views.PostDetailOverviewPage"
            Title="{Binding SelectedPost.Title.Rendered, 
              Converter={StaticResource HtmlStringToDecodedStringConverter}}"
            Padding="0">

    <views:PostDetailPage />

    <views:PostCommentPage />

</TabbedPage>

In der Code-Behind Datei zur Page setzen wir nun noch den BindingContext auf unser PostViewModel.

public partial class PostDetailOverviewPage : TabbedPage
{
    public PostDetailOverviewPage()
    {
        InitializeComponent();

        BindingContext = ServiceLocator.Current.GetInstance<PostsViewModel>();
    }
}

Wir ersetzen nun in dem Enum NavigationTarget entsprechend PostDetailPage durch PostDetailOverviewPage und passen natürlich auch den Bootstrapper entsprechend an, so dass die passende Seite registriert wird.

private static INavigationService CreateNavigationService()
{
    var navigationService = new NavigationService();

    navigationService.Configure(NavigationTarget.LoginPage, 
        typeof(LoadingPage));
    navigationService.Configure(NavigationTarget.PostsOverviewPage, 
        typeof(PostsOverviewPage));
    navigationService.Configure(NavigationTarget.PostDetailOverviewPage, 
        typeof(PostDetailOverviewPage));

    return navigationService;
}

Die Grundlagen sind jetzt bereits geschaffen und nun müssen wir noch ein paar Stylinganpassungen vornehmen, so dass sich insgesamt ein stimmiges Bild ergibt. Dazu öffnen wir zunächst die Datei Styles.xaml und ergänzen einen Style für die TabbedPage.

<Style TargetType="TabbedPage"
       ApplyToDerivedTypes="True">
    <Setter Property="BarBackgroundColor"
            Value="{DynamicResource TabbedPageBarBackgroundColor}" />
    <Setter Property="BarTextColor"
            Value="{DynamicResource TabbedPageBarTextColor}" />
</Style>

Unter iOS müssen wir jetzt noch jeweils ein Icon für die PostDetailPage und die PostCommentPage definieren. Unter Android wollen wir kein Icon anzeigen. Daher öffnen wir jeweils die Code-Behind Dateien der beiden Seiten und wir überprüfen im Konstruktor, ob wir aktuell auf einem iOS-Device laufen und dann setzen wir die IconImageSource-Property der Seite entsprechend.

public PostDetailPage()
{
    InitializeComponent();

    if (Device.RuntimePlatform == Device.iOS)
        IconImageSource = new FileImageSource { File = "post.png" };
}

Am Ende des Beitrags findet ihr den gesamten Code und dort findet ihr auch die notwendigen Dateien, welche ihr dem iOS-Projekt im Resources-Ordner hinzufügen müsst. Auch die notwendigen Anpassungen an den Lokalisierungs-Strings findet ihr entsprechend in der finalen ZIP-Datei.

Hier könnt ihr nun einmal das Ergebnis aus diesem Blog-Beitrag sehen. Wir können jetzt also den gesamten Blog-Beitrag lesen und haben gleichzeitig noch  Zugriff auf die Kommentare zu einem Beitrag.

Der folgende Download-Button beinhaltet die aktuelle Solution, so dass ihr Zugriff auf die verwendeten Assets habt.

Im nächsten Teil der Serie wollen wir unsere App nun so erweitern, dass wir uns an der Seite anmelden und dann einen Kommentar verfassen kann. Seid also auf den nächsten Beitrag gespannt.

WordPress-Seite als Xamarin.Forms App – Teil 1 URL Shortener als minimale API Extension Methods: Dictionary