しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

Xamarin.Forms で RSS フィードを取得したい

今年も開催される予定らしい Microsoft MVP Global Summit にせっかくなので参加しようと思っているのですが、心配なのが英語・・・

流れてくる情報のほとんどが英語のため、理解できないとだいぶ損をしてしまうことは必至

ということで勉強もかねて TechCrunch 英語版のニュースリーダーアプリを Xamarin で作ってみたいと思います

まずは RSS フィードの読み取りから


WinRT の場合 SyndicationClient という便利クラスがあるのですが、残念ながら PCL ではありません

そのため簡易的な読み取り処理クラスを作ってみます

NuGet で下記の Microsoft.Net.Http のパッケージ をインストールしてから下記のコードを書きます

/// <summary>
/// News feed management service class
/// </summary>
public class NewsFeedService
{
    /// <summary>
    /// Feed endpoint uri
    /// </summary>
    private static readonly string Endpoint = @"http://feeds.feedburner.com/TechCrunch/";

    /// <summary>
    /// Feed items
    /// </summary>
    public Rss Feed { get; set; }

    /// <summary>
    /// Constructor
    /// </summary>
    public NewsFeedService()
    {
        this.Feed = new Rss();
    }

    /// <summary>
    /// Update feeds
    /// </summary>
    /// <returns>Task</returns>
   public async Task Update()
    {
        var client = new HttpClient();
        Rss latest;

        using (var reader = new StringReader(await client.GetStringAsync(Endpoint)))
        {
            var desirializer = new XmlSerializer(typeof(Rss));
            latest = desirializer.Deserialize(reader) as Rss;
        }

        if (latest == null || latest.Channel.LastBuildDate.CompareTo(this.Feed.Channel.LastBuildDate) <= 0)
        {
            return;
        }

        // Merge old feed items and new feed items
        var oldItems = (from f in this.Feed.Channel.Items
                        where !latest.Channel.Items.Any(l => l.Equals(f.Guid))
                        orderby f.PubDate descending 
                        select f).ToList();
        foreach (var old in oldItems)
        {
            latest.Channel.Items.Add(old);
        }

        this.Feed = latest;
    }
}

やっていることは RSSUri から XML をダウンロードしてデシリアライズしているだけ

ほんとは HttpClient.GetStreamAsync() でバッファリングしたいところですが、Windows Phone だと NotSupportedException が起きたので、仕方なく一気にダウンロードしています

入れものになっている RSS クラス配下のエンティティは次のような感じ

/// <summary>
/// RSS 2.0 enitity root class
/// </summary>
[XmlRoot("rss")]
public class Rss
{
    /// <summary>
    /// RSS channel
    /// </summary>
    [XmlElement("channel")]
    public RssChannel Channel { get; set; }

    /// <summary>
    /// Constructor
    /// </summary>
    public Rss()
    {
        this.Channel = new RssChannel();
    }
}

/// <summary>
/// RSS channel entity class
/// </summary>
public class RssChannel
{
    /// <summary>
    /// Title
    /// </summary>
    [XmlElement("title")]
    public string Title { get; set; }

    /// <summary>
    /// Description
    /// </summary>
    [XmlElement("description")]
    public string Description { get; set; }

    /// <summary>
    /// Link uri
    /// </summary>
    [XmlElement("link")]
    public string Link { get; set; }

    /// <summary>
    /// Last update datetime
    /// </summary>
    [XmlIgnore]
    public DateTime LastBuildDate { get; set; }

    /// <summary>
    /// Serializable last update datetime
    /// </summary>
    [XmlElement("lastBuildDate")]
    public string LastBuildDateString
    {
        get { return this.LastBuildDate.ToString(); }
        set
        {
            DateTime date;
            if (DateTime.TryParse(value, out date))
            {
                this.LastBuildDate = date;
            }
        }
    }

    /// <summary>
    /// Editor
    /// </summary>
    [XmlElement("managingEditor")]
    public string ManagingEditor { get; set; }

    /// <summary>
    /// Feed items
    /// </summary>
    [XmlElement("item")]
    public List<RssItem> Items { get; set; }

    /// <summary>
    /// Constructor
    /// </summary>
    public RssChannel()
    {
        this.Items = new List<RssItem>();
        this.LastBuildDate = DateTime.MinValue;
    }
}

/// <summary>
/// RSS feed item entity class
/// </summary>
public class RssItem
{
    /// <summary>
    /// Title
    /// </summary>
    [XmlElement("title")]
    public string Title { get; set; }

    /// <summary>
    /// Link uri
    /// </summary>
    [XmlElement("link")]
    public string Link { get; set; }

    /// <summary>
    /// Item ID
    /// </summary>
    [XmlElement("guid")]
    public string Guid { get; set; }

    /// <summary>
    /// Publish datetime
    /// </summary>
    [XmlIgnore]
    public DateTime PubDate { get; set; }

    /// <summary>
    /// Serializable publish datetime
    /// </summary>
    [XmlElement("pubDate")]
    public string PubDateString
    {
        get { return this.PubDate.ToString(); }
        set
        {
            DateTime date;
            if (DateTime.TryParse(value, out date))
            {
                this.PubDate = date;
            }
        }
    }

    /// <summary>
    /// Description
    /// </summary>
    [XmlElement("description")]
    public string Description { get; set; }

    /// <summary>
    /// Description
    /// </summary>
    [XmlElement("category")]
    public List<string> Category { get; set; }

    /// <summary>
    /// Constructor
    /// </summary>
    public RssItem()
    {
        this.Category = new List<string>();
    }
}

DateTime 型のプロパティだけちょっと特別なことをしていますが基本的に XmlSerializer 用の属性をつけているだけです

さて、ちゃんと TechCrunch の RSS フィードがデシリアライズできるか試してみます

/// <summary>
/// Top page
/// </summary>
public partial class TopPage : ContentPage
{
    /// <summary>
    /// Constructir
    /// </summary>
    public TopPage()
    {
        InitializeComponent();
    }

    /// <summary>
    /// Appearing event handler
    /// </summary>
    protected override async void OnAppearing()
    {
        base.OnAppearing();

        await new NewsFeedService().Update();
    }
}

最初の画面のコードビハインドで上記のようなコードを書いて実行

f:id:matatabi_ux:20150814171029p:plain

日付文字列もちゃんと DateTime 型に変換できていますね

Description の中身は HTML のようです・・・これをどう表示しようか;