本アプリを使っていて気になることとして、データが更新されないことも挙げられるだろう。アプリ起動時にWebサービスからRSSフィードをダウンロードするだけなので、アプリが終了させられるまで*5、最初に読み込んだデータだけを表示し続けるのだ。この問題を解決するために、Webサービスからデータをリロードする機能を追加しよう。
*5Windowsストア・アプリでは、エンド・ユーザーがアプリを終了させるためのUIは提供されておらず、システムが管理するアプリのライフサイクルに従って終了させられる。システムのリソースがひっ迫しているような状況でない限りは、本アプリはWindowsを再起動するかエンド・ユーザーがログオフするまでメモリ上にとどまり続けるため、いつまでたっても新しいデータが表示されることがないのだ。アプリのライフサイクルについて、詳しくは「特集:Windows 8開発に向けて準備しよう〜Metroスタイル・アプリの開発者が知るべき3つのこと」の「その3: [設計の原則] 万全のリジューム機能を装備すべし!」を参照してほしい。
いつリロードするかは、2通り考えられる。1つは、エンド・ユーザーに指示してもらう。もう1つは、適当なタイミングで自動的に行う。今回は、自動的に行う方法を考えてみよう。
自動的にリロードする場合は、そのタイミングをまず考える。エンド・ユーザーが記事の一覧を真剣に読んでいるときにリロードして画面を書き換えてしまっては、エンド・ユーザーにとっては迷惑なだけだろう。画面を表示した直後なら、画面を書き換えてもまだ許されるだろう。そこで、メイン画面が表示されたタイミングと、中断から再開されたタイミングでリロードし、データが新しくなっていたら画面を書き換えることにしよう。
今、「画面を書き換える」といったが、データ・バインドで画面に表示しているので、実際には保持しているデータを書き換えるだけで画面も変わる。
自動的にリロードする時間間隔も考慮する。あまり短いインターバルでWebサービスにアクセスするのは、Webサービスへの負荷とインターネットのトラフィックを増やすだけだ。上述したリロードのタイミングが来たときでも、一定時間が経過していないときにはリロードしないようにしよう。また、インターネットに接続していないときもリロードしないようにしよう。
以上を整理して次の表にまとめておく。
項目 | 内容 |
---|---|
リロード機能を呼び出すタイミング | ・メイン画面を表示したとき ・アプリで中断から再開されたとき |
Webアクセスする条件 | ・インターネットに接続していること(切断時はリロードしない) ・前回のアクセスから一定の時間が経過していること(今回は15分とする) |
データを書き換える条件 | Webサービスからダウンロードしたデータが既存のデータと変わっているとき |
リロード機能のスペック |
Webサービスにアクセスしてデータを処理する部分は、リロードの場合でもほぼ同じなので、従来のコードを改修することにしよう。改修点は、既存のデータがあるときはそれを使い、変化があったときだけデータを書き換えるようにすることだ(=スペックの3番目)。そのほかのスペックを実現するには、新たにコードを付け加えないといけない。
それでは、スペックに書いた順にコードを紹介していこう。なお、実際には、逆の順序で開発した方がやりやすい。
リロード機能を呼び出す
まず、リロード機能を呼び出す部分から。これは2カ所ある。1つ目は、メイン画面が表示されたときだ(次のコード)。
private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
// TODO: 問題のドメインでサンプル データを置き換えるのに適したデータ モデルを作成します
// var sampleDataGroups = await SampleDataSource.GetGroupsAsync();
// this.DefaultViewModel["Groups"] = sampleDataGroups;
// 上の2行は不要なので削除する
// 【第7回】画面表示時にデータをダウンロードし直す
// (これは一定時間内の呼び出しの場合には無視される)
(App.Current as App).ReloadStart();
}
リロード機能を呼び出す2つ目のタイミングは、中断から再開されたときだ。これはAppクラスに実装する。イベント・ハンドラの追加とそのメソッドの実装を行う(次の2つのコード)。
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
// 【第7回】中断→再開時の処理
this.Resuming += OnResuming;
……省略……
private void OnResuming(object sender, object e)
{
ReloadStart();
}
リロード機能
次に、リロード機能そのものを実装していく。まず、AppクラスにReloadStartメソッドを作る。これは、App.xamlファイルにスタティック・リソースとして定義してあるFeedsDataオブジェクトを取り出して、それを引数としてDataLoaderクラスのReloadStartメソッド(まだ存在していない)を呼び出すだけのものだ。AppクラスのOnLaunchedメソッドにあるLoadStartメソッドの呼び出し(前回に記述したコード)と同様な呼び出し方法である。
public void ReloadStart()
{
var feedsData = App.Current.Resources["feedsDataSource"] as DataModel.FeedsData;
Logic.DataLoader.ReloadStart(feedsData);
}
リロード機能の本体は、Logicフォルダの「DataLoader.cs」ファイルに追加する(次のコード)。
public class DataLoader
{
static readonly string[] URLs = new []
{
"http://rss.rssad.jp/rss/itmatmarkit/fdotnet/rss.xml", // @IT Insider.NETフォーラム
"http://rss.rssad.jp/rss/itmatmarkit/news/rss.xml", // @IT News 最新記事一覧
};
// 【第7回】一定時間間隔内に呼び出された場合は無視するための、定数と変数
private const double LOAD_INTERVAL_MINUTE = 15.0; // ★1
private static DateTimeOffset _lastLoaded;
// 【第7回】リロードする
public static async void ReloadStart(DataModel.FeedsData feedsData)
{
// 一定時間間隔内に呼び出された場合は無視する
double elapsedMinutes = DateTimeOffset.Now.Subtract(_lastLoaded).TotalMinutes;
if (elapsedMinutes < LOAD_INTERVAL_MINUTE)
return;
// インターネット接続がないときは無視する
var connectionProfile
= Windows.Networking.Connectivity.NetworkInformation
.GetInternetConnectionProfile();
if (connectionProfile == null)
return;
// 実際にRSSフィードをダウンロードしてデータを更新する処理は、
// 最初にロードする処理と共通にする
await LoadAsync(feedsData);
}
……省略……
internal static async Task<DataModel.FeedsData> LoadAsync(DataModel.FeedsData feedsData)
{
feedsData.IsLoaded = false;
_lastLoaded = DateTimeOffset.Now;
……省略……
保持しているデータを書き換える
最後に、Webサービスからダウンロードしたデータが、以前のものと変わっているときだけ保持しているデータを書き換えるようにしよう(次の3つのコード)。
internal static class FeedsDataExtension
{
// 【第7回】IsLoadedフラグに関係なく、フィードのタイトルからFeedオブジェクトを得る
internal static Feed Find(
this System.Collections.ObjectModel.ObservableCollection<Feed> feeds,
string feedTitle)
{
foreach (var feed in feeds)
if (feed.Title == feedTitle)
return feed;
return null;
}
}
using AtmarkItReader.DataModel; // 【第7回】拡張メソッドの利用に必要
……省略……
internal static AtmarkItReader.DataModel.FeedsData Add(
Windows.Web.Syndication.SyndicationFeed syndicationFeed,
DataModel.FeedsData feedsData)
{
// 【第7回】まず変数名を変更する:newFeed→targetFeed
// var newFeed = new DataModel.Feed(syndicationFeed.Title.Text);
// feedsData.Feeds.Add(newFeed);
// ↓
// 【第7回】繰り返し呼び出されたときは、既存のFeedオブジェクトを使う
DataModel.Feed targetFeed = feedsData.Feeds.Find(syndicationFeed.Title.Text);
if (targetFeed == null)
{
// まだ存在していないときは、新しく生成する(既存のコード)
targetFeed = new DataModel.Feed(syndicationFeed.Title.Text);
feedsData.Feeds.Add(targetFeed);
}
else
{
// すでに存在していたら、データに変更があるかどうかをチェックする
if (targetFeed.Items.Count > 0 && syndicationFeed.Items.Count > 0
&& targetFeed.Items[0].Title == syndicationFeed.Items[0].Title.Text)
{
// 簡易的に、最初の記事タイトルが同じならデータに変更はないと判定してみた。
// データに変更が無ければ、何もせずリターン
return feedsData; // ★2
}
// データに変更があった時は、既存のデータを消す
targetFeed.Items.Clear();
}
foreach (var syndicationItem in syndicationFeed.Items)
{
……省略(newFeed→targetFeedの変更はある)……
using AtmarkItReader.DataModel; // 【第7回】拡張メソッドの利用に必要
private static void AddEmptyFavorite(DataModel.FeedsData feedsData)
{
// 【第7回】繰り返し呼び出されたときは、新たに追加せず、既存データを消す
var currentData = feedsData.Feeds.Find("お気に入り");
if (currentData != null)
{
currentData.Items.Clear();
return;
}
feedsData.Feeds.Add(new DataModel.Feed("お気に入り"));
}
以上で完成だ。実際に操作して試してみてほしい。
ただし、そのままテストしようとすると、15分以上の間隔を空けないとWebアクセスしてくれないし、1日に数回あるかないかのWebサービスのデータ更新を待つのも大変だ。テストのためには、(1)リロードを無視する期間(「★1」のコメントを付けた箇所)を例えば0.1分(=6秒)などと短くする、(2)データが変わっていないときは書き換えない(「★2」のコメント)としているところをコメント・アウトする、という一時的な変更をするとよい。そうすると、数秒待つだけでWebアクセスするし、Webサービス側でデータが更新されていなくても画面が書き換わる。テストが終わったら、以上の2カ所は元に戻しておこう。
今回は、ほかのアプリにデータを送る方法を考え、3通りの機能を追加した。また、自動的にWebサービスからリロードする機能も実装した。
次回は「入力されたデータを保存する」。ここまでダミーのままだった「お気に入り」の機能を実装する予定だ。
Copyright© Digital Advantage Corp. All Rights Reserved.