第8回 入力されたデータを保存する:連載:Windowsストア・アプリ開発入門(6/6 ページ)
ファイルの読み書きについて学び、「お気に入り」の機能を実装してみよう。ファイルの保存場所としてアプリケーションデータ記憶域のローミングを利用する。
「お気に入り」のローミング対応を実装する
「お気に入り」のデータはローミングフォルダーに書き出すようにしたので、自動的にデバイス間で複製される。複製されるタイミングはアプリから制御できずシステム任せなのだが、実際にやってみると、1分もかからずに複製されることもあれば、30分以上待たされることもある。複製が終わってからアプリを起動した場合は問題ない。しかし、アプリの実行中に複製が発生して「お気に入り」のデータが上書きされることもある。そのときには、複製されたデータをアプリで読み込み直す必要がある。また、アプリが中断状態のときに複製が起きることもあるだろう。その場合には、中断から再開されたときに、複製されたデータをアプリで読み込み直さねばならない。すなわち、次の2つの場合に、「お気に入り」のデータを読み込み直す実装が必要だ。
- 実行中に、ローミングによるデータの上書きが発生したとき
- 中断から再開したとき(中断中にローミングされたかどうかは分からない)
「お気に入り」のデータを読み込み直すロジック
読み込み直すロジックは、プロジェクトのLogicフォルダーにあるFavoriteStorageクラスに実装する(次のコード)。といっても、既存のLoadDataAsyncメソッドを呼び出すだけだ。
public static async Task ReloadAsync()
{
if (theFeed == null)
{
theFeed = new Feed(FeedTitle);
}
await LoadDataAsync();
}
FavoriteStorageクラスにこのメソッドを追加する。
前出のGetFavoriteFeedAsyncメソッドを呼び出した後でこのメソッドを使うべきだが、間違えてこのメソッドを先に呼び出してしまったときのためにFeedオブジェクトを生成する予防措置をしてある。
中断から再開されたときのロジック
中断から再開されたときには、Webサービスからデータを再度ダウンロードするように前回実装してある。このときに「お気に入り」のデータも読み込み直せばよいだろう。プロジェクトのLogicフォルダーにあるDataLoaderクラスに、次のコードのように追加する。
// 【第7回】リロードする
public static async void ReloadStart(DataModel.FeedsData feedsData)
{
// 【第8回】「お気に入り」は常にリロードする
if (feedsData.Feeds.Find(Logic.FavoriteStorage.FeedTitle) != null)
await Logic.FavoriteStorage.ReloadAsync();
// 一定時間間隔内に呼び出された場合はネットワークからリロードしない
double elapsedMinutes = DateTimeOffset.Now.Subtract(_lastLoaded).TotalMinutes;
if (elapsedMinutes < LOAD_INTERVAL_MINUTE)
return;
// インターネット接続がないときは無視する
var connectionProfile = Windows.Networking.Connectivity.NetworkInformation.GetInternetConnectionProfile();
if (connectionProfile == null && feedsData.IsLoaded)
return;
await LoadAsync(feedsData);
}
DataLoaderクラスに太字の部分を追加する。
ネットワークからのリロード(末尾のLoadAsyncメソッド呼び出し)は条件によっては行わないので、その前に「お気に入り」データの再読み込みを行うようにする。
ローミングが発生したときのロジック
ローミングによってローミングフォルダー内のファイルが書き換えられたときに、ApplicationDataクラス(Windows.Storage名前空間)のDataChangedイベントが発生する。このタイミングで「お気に入り」データを再読み込みすればよい。その実装は、Appクラス(「App.xaml.cs」ファイル)に行う。
まず、アプリ起動時にイベントハンドラーを結び付ける(次のコード)。
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
// 【第7回】中断→再開時の処理
this.Resuming += OnResuming;
// 【第8回】ローミングデータが同期されたときの処理
Windows.Storage.ApplicationData.Current.DataChanged += Roaming_DataChanged;
……省略……
}
Appクラスのコンストラクターに太字の部分を追加する。
Roaming_DataChangedメソッドではFavoriteStorageクラスのReloadAsyncメソッドを呼び出せばよいのだが、このイベントは別スレッドで呼び出されるので工夫が必要になる。次のコードのように、UIスレッドに結び付けられたDispatcherオブジェクトをあらかじめ取得しておいて、それを介してReloadAsyncメソッドを呼び出さねばならない。
protected override async void OnLaunched(LaunchActivatedEventArgs e)
{
……省略……
// 【第6回】正式なロジックの呼び出しに置き換える
var feedsData = App.Current.Resources["feedsDataSource"] as DataModel.FeedsData;
Logic.DataLoader.LoadStart(feedsData);
// 【第8回】ローミングデータが同期されたときの処理に必要
_dispatcher = Windows.UI.Core.CoreWindow.GetForCurrentThread().Dispatcher;
Frame rootFrame = Window.Current.Content as Frame;
……省略……
}
// 【第8回】ローミングデータが同期されたときの処理
private Windows.UI.Core.CoreDispatcher _dispatcher;
private async void Roaming_DataChanged(Windows.Storage.ApplicationData sender, object args)
{
if (_dispatcher == null)
return;
// ここは別スレッドで呼び出されるため、UIに影響を及ぼす処理はDispatcherを介して呼び出さねばならない
await _dispatcher.RunAsync(
Windows.UI.Core.CoreDispatcherPriority.Normal,
async () =>
await AtmarkItReader.Logic.FavoriteStorage.ReloadAsync()
);
}
AppクラスのOnLaunchedメソッドに太字の部分を追加し、また、メンバー変数_dispatcherとRoaming_DataChangedメソッドも追加する。
以上で、ローミング対応も完成だ。
テストする
以上で完成だ。「お気に入り」に追加する機能をテストしてみてほしい。記事表示画面でアプリバーから「お気に入り」に追加できるだろうか? 追加した記事データは、メイン画面と記事一覧画面に表示されるだろうか。
ローミングのテストは、Windows 8.1の環境が2台分以上必要になる。VM上の仮想マシンでも構わない。VS 2013が入っていないPCへこのアプリをインストールする方法は、MSDNの「Windows RT への開発者パッケージのインストール」を参照してもらいたい。インストールできたら、いろんなパターンでローミングをテストしてみよう。
- 1台で「お気に入り」に追加。もう1台は、その後でアプリを起動
・ 2台ともアプリを起動しておいて、一方だけで「お気に入り」に追加
・ 2台でアプリを起動、一方は中断しておき、もう1台で「お気に入り」に追加し、ローミングでファイルが書き換えられたら中断から再開
・ 2台ともアプリを起動しておいて、両方で「お気に入り」に追加(これはマージされることはなく、「後勝ち」になる)
最後に、「お気に入り」に10件ほど登録し、そのデータファイルのサイズをチェックしよう。データファイル「Favorite.xml」は、次のフォルダーにある。
C:¥Users¥{ユーザー名}¥AppData¥Local¥Packages¥{マニフェストの「パッケージ化」タブにあるパッケージ名}¥RoamingState
筆者が試したところでは、お気に入りを10件登録した「Favorite.xml」ファイルのサイズは5KBytes弱であった。このことから、上限値の100件までお気に入りを登録しても必要とするサイズは50KBytes程度であり、ローミングの制限である100KBytesまでにはまだ余裕があるだろうと判断できる。ただし、長いコメントを大量に保存されたら制限を超えることもあるだろう。もしもここの判断を厳密にしたいなら、ファイルを保存した直後にファイルのサイズをチェックし、超えていたら古い記事から削除していくようなロジックとコメント文字列の長さ制限を追加するとよい。
また、テストしていて気付かれたと思うが、記事表示画面で見ているその記事がすでに「お気に入り」に登録したものかどうか分からない。今回実装しなかった削除や編集の機能と合わせて、読者の皆さまへの宿題とさせていただきたい。
まとめ
今回は、エンドユーザーが入力したコメントを含めて記事データをファイルに保存し、同時に画面にも反映させた。アプリケーションリソースに1つだけデータを持たせ、各画面はそれをデータバインドして表示するというアーキテクチャは、最初に作るときは面倒かもしれないが、今回実装したようにデータの追加や変更を全ての画面に反映させるときには威力を発揮する。
また今回は、ローミングの仕組みを利用した。ローミングはWindowsストアアプリの有用性を高める大きな武器になる。ローミングで上書きされたときの対応がちょっと厄介だが、ぜひうまく活用してほしい。
- ファイルの読み書きは、WinRTと.NET Frameworkのストリーム変換が必要なことが多く、Windowsストアアプリの安全性からの制限もあり、従来のデスクトップアプリに比べると難しい
- ボタンをタップしたときにフライアウトを出すのは簡単だ。ただし、複雑なフライアウトのコンテンツは、ユーザーコントロールとして切り出した方がよい
- ローミングフォルダーに保存したファイルは、自動的にローミングされる
- アプリの実行中に、ローミングによってファイルが上書きされるとDataChangedイベントが発生する。アプリが中断状態のときは、このイベントは発生しない。そこで、DataChangedイベントと中断からの再開時の両方のタイミングでデータを読み込み直す
次回は「効果的に情報を提示する」。スタート画面のタイルに新着記事を表示してみよう。また、バッジやトーストといった表示手段も紹介する予定だ。
Copyright© Digital Advantage Corp. All Rights Reserved.