優れたタイル エクスペリエンスを開発する (パート 2)

この記事のパート 1 では、タイルの更新を設計する方法と、ライブ タイルに表示するコンテンツに合ったテンプレートを選択する方法について学びました。その中でアプリの既定のワイド タイルをセットアップしたので、タイルの更新を始める準備ができました。今回は具体的なコードを見ていきましょう。最初に、Contoso Food Trucks アプリ タイルでポーリングをセットアップする方法について説明します。Web サービス側のコードがどのようになるかもお見せします。次に、アプリにセカンダリ タイルを追加して、「Windows 8 SDK: アプリのタイルおよびバッジのサンプル」(英語) に含まれている NotificationsExtensions ライブラリを使ってそのタイルを更新します。では、早速始めましょう。

通知の配信方法の選択

タイルをどのように表示すればよいかはわかったので (パート 1 を思い出してください)、タイルを更新するタイミングを決める必要があります。

アプリでタイルを更新するには、4 とおりの方法があります (デベロッパー センターの「通知の配信方法の選択」(英語) を参照してください)。まず、ローカル通知を使ってタイルを更新することができます。これは、アプリの実行中に情報が変化する場合に便利です。また、アプリでタイルとトーストの更新をスケジュールして、正確な時刻に更新が行われるようにすることもできます。さらに、アプリが実行中でない場合にクラウドからタイル通知をプッシュまたはポーリングして、タイルを更新する方法もあります。ポーリングは、更新頻度の低いブロードキャスト コンテンツに適しています。プッシュは、すぐに届ける必要のあるトースト通知を送信する場合や、ユーザーごとに個別にタイルを更新する場合に適しています。この記事では、ポーリング更新とローカル更新に焦点を絞ってお話しします。

近くにいるフード トラックのためのポーリング

私たちのアプリでは、タイルの更新に 2 種類の異なる情報を使います。最も重要なのは、ユーザーが設定した既定のランチの場所の近くにいるフード トラックの情報です。アプリの実行時、ユーザーは、ランチを探す既定の場所をアプリ内で設定します。私たちは、その既定の場所の情報を使ってタイルを更新し、近くにいるトラックをユーザーに知らせます。下の画像は、この記事のパート 1 に掲載したアプリのタイルです。それでは、ポーリングを使ってこれらのタイルをアプリのタイルに表示する方法を見ていきましょう。

長方形のワイド タイル。表示は "Food Trucks Near You / Nom Nom Barbecue Truck / Sushi Truck / Macaroni Makin' Wagon"    正方形タイル。表示は "Near You / Nom Nom / Sushi Truck"

通常、フード トラックは、1 日中同じ場所に停まっているか、少なくともランチの時間帯は移動しません。フード トラックの場所はそれほど頻繁には変わらないので、タイルをリアルタイムで更新する必要はありません。このため、今回はプッシュ通知を除外することができます (プッシュ通知は、タイミングが重要となる通知に向いています)。ただし、このデータは少なくとも 1 日 1 回は更新したいので、定期的な通知を使って Web サービスをポーリングし、変更をチェックするのが最適な方法と考えられます。

クライアント側の実装: 近くにいるフード トラックのためのポーリング

定期通知をセットアップするクライアント側の実装は、ほんの数行のコードで完了します。ユーザーがアプリを起動するかアプリに切り替えたら、そのつど TileUpdater.startPeriodicUpdate (英語) を呼び出します。これにより、API に渡された URI が即座にポーリングされ、アプリの起動または切り替えのたびにタイルが更新されます。これは、API 呼び出しがすぐにクラウド サービスの URI にアクセスし、タイルを更新するからです。この動作はデバッグ時に非常に便利で、次のポーリング間隔を待たずにクラウド サービスのポーリングをテストすることができます。

ここで問題となるのは、startPeriodicUpdate API に指定する URI です。私たちのケースでは、特定の場所の情報でタイルを更新するようにクラウド サービスに伝える必要があります。ユーザーの位置情報を保護するという観点から、ユーザーのいる正確な場所をサービスに送信するのは望ましくありません。代わりに、アプリ内でユーザーが指定した郵便番号によって場所を判断することにします。

Web サービスに郵便番号を渡すために、ポーリング URI の末尾にクエリ文字列を追加します。こうすることで、タイルに表示するべき場所がクラウド サービスに伝わります。

https://www.contoso.com/foodtrucks/tile.xml?zipcode=98052

Web サービスでは、この URI の HTTP GET に応答して、URI で指定された郵便番号に対応する整形済みのタイル通知 XML を返します。ポーリングをセットアップするコードを以下に示します。郵便番号は URI 文字列にハードコードされています。

JavaScript:

 // update the tile poll URI
var notifications = Windows.UI.Notifications;
var polledUri = new Windows.Foundation.Uri("https://www.contoso.com/foodtrucks/tile.xml?zipcode=98052");
var recurrence = notifications.PeriodicUpdateRecurrence.hour;
var tileUpdater = notifications.TileUpdateManager.createTileUpdaterForApplication();
tileUpdater.startPeriodicUpdate(polledUri, recurrence);

C#:

 // update the tile poll URI
using Windows.UI.Notifications;
Uri polledUri = new Uri("https://www.contoso.com/foodtrucks/tile.xml?zipcode=98052");
PeriodicUpdateRecurrence recurrence = PeriodicUpdateRecurrence.Hour;
TileUpdateManager.CreateTileUpdaterForApplication().StartPeriodicUpdate(polledUri, recurrence);

フード トラックは 1 日の間に移動する可能性があるので、タイルは適度な頻度で更新したいと思います。今回の場合、バックエンド サービスの負荷とタイル上の情報の新しさのバランスを考慮して、更新間隔を 1 時間に設定しましょう。

一度 startPeriodicUpdate API を呼び出すと、それ以降はアプリが実行中でなくても、1 時間に 1 回の頻度でタイルが更新され続けます。ポーリング先の URI を変更したい場合は、もう一度 API を呼び出して別の URI を指定するだけです。たとえば、ユーザーが既定の場所の郵便番号を変更した場合は、startPeriodicUpdate をもう一度呼び出して、新しい郵便番号で URI を更新します。ユーザーが既定の場所を消去した場合や、タイルの更新を止めようとしている場合は、stopPeriodicUpdate API を呼び出すことで定期更新を停止できます。

startPeriodicUpdate API の使い方の詳細については、デベロッパー センターの「プッシュおよび定期通知のクライアント側のサンプル」(英語) と「タイルの定期通知をセットアップする方法」(英語) を参照してください。

サーバー側の実装: 近くにいるフード トラックのためのポーリング

ポーリングによってタイルを更新する場合のサーバー側のロジックは、ほぼどのようなサービス テクノロジでも実装することができます。ここでは、PHP と ASP.NET のサンプル コードを紹介します。

私たちの Web サービスでは、ポーリングされたら HTTP GET 要求に応答して、タイル XML スキーマに従った XML を返す必要があります。ネットワーク経由で送受信されるコンテンツを保護するために、HTTPS を使うこともできます。クッキーはサポートされません。要求に応答するためにサービスで必要となる情報はすべて、URI の一部に含める必要があります。このため、私たちのアプリでは、クエリ文字列を使って郵便番号を渡します。

次に紹介する PHP コードでは、Web サービスのデータベースへのアクセスに関する部分を省略しています。実際にコードが実行されると、get_trucks_from_database() 関数から返された情報が trucks 変数に格納されます。これには、指定された郵便番号に応じてタイル テンプレートにデータを流し込むために必要なすべての情報が含まれます。以下のサービスのサンプルは、サービスから返される XML に焦点を当てるために少々簡略化してあります。実際の Web サービスの展開時には、パフォーマンス、スケーラビリティ、セキュリティを考慮し、より管理しやすいアーキテクチャを採用する必要があります。

PHP:

 <?php

//
// set default query string parameters
// process query string and set parameters
//
if($_GET['zipcode']){
  $zipcode = $_GET['zipcode'];
}
else{
  $zipcode = 'default';
}

//
// get item info from our database
// - this is placeholder code that you need to replace in a real implementation
// - the return value is a multidimensional array of the long and short strings
//   to place in the tile template
//
$trucks = get_trucks_from_database($zipcode);

?>
<?php echo '<?xml version="1.0" encoding="utf-8" ?>'?>
<tile>
  <visual>
    <binding template="TileWideText01">
      <text id="1">Food Trucks Near You</text>
      <text id="2"><?php echo $trucks[0][0]?></text>
      <text id="3"><?php echo $trucks[0][1]?></text>
      <text id="4"><?php echo $trucks[0][2]?></text>
    </binding>
    <binding template="TileSquareText03">
      <text id="1">Near You</text>
      <text id="2"><?php echo $trucks[1][0]?></text>
      <text id="3"><?php echo $trucks[1][1]?></text>
    </binding>
  </visual>
</tile>

次のコード サンプルも上の PHP コードと同等です。この ASP.NET Web ページのサンプルは、タイル サービスの簡単な実装を示しています。完全な機能を備えた ASP.NET サービスを構築する場合は、新しい ASP.NET Web API を利用するのが便利かもしれません。ASP.NET Web API は、このような HTTP サービスに特化して設計されています。ASP.NET Web API の詳細については、https://www.asp.net/web-api (英語) を参照してください。

ASP.NET:

 @{
  //
  // set default query string parameters  
  // process query string and set parameters
  //
  var zipcode = Request["zipcode"] ?? "default";
  //
  // get item info from our database
  // - this is placeholder code that you need to replace in a real implementation
  // - the return value is a multidimensional array of the long and short strings
  // to place in the tile template
  var trucks = get_trucks_from_database(zipcode);
 
}<?xml version="1.0" encoding="utf-8" ?>'?>
<tile>
  <visual>
    <binding template="TileWideText01">
      <text id="1">Food Trucks Near You</text>
      <text id="2">@trucks[0,0]</text>
      <text id="2">@trucks[0,1]</text>
    </binding>
    <binding template="TileSquareText03">
      <text id="1">Near You</text>
      <text id="2">@trucks[1,0]</text>
      <text id="2">@trucks[1,1]</text>
    </binding>
  </visual>
</tile>

お気に入りのフード トラック

ここまではアプリがメイン タイルに表示するコンテンツについて見てきましたが、特定のフード トラックをスタート画面のタイルで追跡したいと考えるユーザーもいるでしょう。そこで私たちのアプリでは、アプリ バーを利用して、ユーザーが特定のフード トラックをスタート画面にピン留めできるようにします。このようにピン留めされたタイルは、セカンダリ タイルと呼ばれます。ユーザーがセカンダリ タイルをピン留めしたら、そのタイルに通知を送信して、指定された特定のフード トラックの情報でタイルを更新します。

フード トラック タイルをピン留めする

タイルをピン留めすることで、ユーザーは、スタート画面からアプリの特定のコンテンツに直接アクセスできるようになります。セカンダリ タイルを使うと、アプリの一部分を直接呼び出して、ユーザーがピン留めしたフード トラックの部分だけを実行することができます。

タイルをピン留めする方法は、アプリ内から行う以外にありません。アプリ バーを呼び出してタイルをピン留めできるというのが、ユーザーの期待する動作です。アプリ バーには、ビュー内のコンテンツをピン留めできることを示す標準のピン アイコンが含まれています。

このピン ボタンをタップすると、ポップアップが表示され、ピン留めしようとしているタイルのプレビューを確認できます。

Nom Nom Barbecue Truck の画像と [Pin to Start] (スタート画面にピン留めする) というボタンが表示されたポップアップ

ここで私たちが行うべき作業は次のとおりです。

  1. アプリにアプリ バーを追加して、[Pin to Start] (スタート画面にピン留めする) と [Unpin from Start] (スタート画面からピン留めを外す) の操作のためのピン アイコンを含める
  2. アプリ バーのピン留め/ピン留め解除ボタンのクリックに対応するイベント ハンドラーを実装する
  3. ピン留め/ピン留め解除の操作に応答して新しいタイルをピン留めする、アプリ固有のロジックを追加する

ここでは、アプリ バーの作成にあたる最初の 2 つの手順については取り扱いません。タイルをピン留めする操作自体に集中して見ていきましょう。アプリ バーの実装方法の詳細については、以下のページでご確認いただけます。

私たちのアプリでは、手順 3 として、セカンダリ タイルを作成していくつかのプロパティを設定します。

JavaScript:

 // Keep track of your secondary tiles with a unique ID   
var nomNomTile = "SecondaryTile.NomNom";

// Set properties on the tile
var logo = new Windows.Foundation.Uri("ms-appx:///images/NomNomTruck-Logo.png");
var smallLogo = new Windows.Foundation.Uri("ms-appx:///images/NomNomTruck-SmallLogo.png");
var wideLogo = new Windows.Foundation.Uri("ms-appx:///images/NomNomTruck-WideLogo.png");
var TileActivationArguments = "TruckName=NomNom";

// Create the secondary tile
var tile = new Windows.UI.StartScreen.SecondaryTile(nomNomTile,
                                                    "Nom Nom",
                                                    "Nom Nom Barbecue Truck",
                                                    TileActivationArguments,
                                                    Windows.UI.StartScreen.TileOptions.sh
owNameOnWideLogo,
                                                    logo,
                                                    wideLogo);

tile.foregroundText = Windows.UI.StartScreen.ForegroundText.light;
tile.smallLogo = smallLogo;

// Request the user’s permission to create the secondary tile
// - we return the promise here, assuming that this code is embedded 
//   in a larger function. 
//   See the Windows 8 SDK Secondary Tiles sample for more info:
//   https://code.msdn.microsoft.com/windowsapps/Secondary-Tiles-Sample-edf2a178
return new WinJS.Promise(function (complete, error, progress) {
  tile.requestCreateAsync().then(function (isCreated) {
    if (isCreated) {
      complete(true);
    } else {
      complete(false);
    }
  });
});

C#:

 // Keep track of your secondary tiles with a unique ID   
const string nomNomTile = "SecondaryTile.NomNom"; 

// Set properties on the tile
Uri logo = new Uri("ms-appx:///images/NomNomTruck-Logo.png");
Uri smallLogo = new Uri("ms-appx:///images/NomNomTruck-SmallLogo.png");
Uri wideLogo = new Uri("ms-appx:///images/NomNomTruck-WideLogo.png");
string tileActivationArguments = "TruckName=NomNom";

// Create the secondary tile object
SecondaryTile secondaryTile = new SecondaryTile(nomNomTile,
                                                "Nom Nom",
                                                "Nom Nom Barbecue Truck",
                                                tileActivationArguments,
                                                Windows.UI.StartScreen.TileOptions.ShowNa
meOnWideLogo,
                                                logo,
                                                wideLogo);

secondaryTile.ForegroundText = Windows.UI.StartScreen.ForegroundText.Light;
secondaryTile.SmallLogo = smallLogo;

// Request the user’s permission to create the secondary tile
// - this code assumes that this code was called within an event handler which has
//   a ‘sender’ parameter.
//   See the Windows 8 SDK Secondary Tiles sample for more info:
//   https://code.msdn.microsoft.com/windowsapps/Secondary-Tiles-Sample-edf2a178
await secondaryTile.RequestCreateForSelectionAsync(MainPage.GetElementRect((FrameworkElement)se
nder), Windows.UI.Popups.Placement.Right);

セカンダリ タイルをピン留めする方法の詳細については、「セカンダリ タイルのガイドラインとチェック リスト」(英語) および Consumer Preview のセカンダリ タイルのサンプル (英語) を参照してください。

ローカル通知を使用してピン留めされたタイルを更新する

ピン留めされたタイルは、スタート画面上の追加のタイルであり、アプリで更新する必要があります。このタイルの更新方法は、アプリのメイン タイルの更新方法とまったく変わりません。今回のアプリでは、クラウド更新のメカニズムを利用するのではなく、ローカル通知 API を使用して、アプリの実行中にセカンダリ タイルを更新します。具体的にどのように動作するかを把握していただけるように、以下にローカル通知のコードを示します。クラウドからも同じように更新できます。このアプリにポーリングのシナリオを実装するのも簡単です。

このセクションのコードでは、「Windows 8 SDK: アプリのタイルおよびバッジのサンプル」(英語) に含まれている NotificationsExtensions ライブラリを使用します。このライブラリをアプリのプロジェクトに追加すると、ローカルでのタイルの更新が簡単になります。このライブラリは、Windows のタイル更新 API の上にオブジェクト モデルを提供するものです。これにより、JavaScript、C#、C++ のアプリから XML を操作する必要がなくなります。また、よりスムーズに開発を進められるように、IntelliSense にも対応しています。

ローカル通知 API を使うと、アプリの実行中にいつでもタイルを更新できます。ピン留めされたフード トラック タイルでは、ユーザーがアプリを起動するたびに、その特定のフード トラックの営業情報でタイルを更新したいと思います。

セカンダリ タイルを列挙する

セカンダリ タイルは、アプリが実行されていない間にスタート画面から外されることもあります。したがって、アプリの起動時に最初に行うべき操作は、現在ピン留めされているセカンダリ タイルを確認することです。列挙された各タイルには、それぞれを一意に識別する tileId が含まれています。tileId はタイルの作成時にアプリが設定するものなので、この ID を使うことで、見つかった各タイルの更新方法を判断できます。例を見てみましょう。

JavaScript:

 // Get secondary tile ids
Windows.UI.StartScreen.SecondaryTile.findAllAsync().done(function (tiles) {
  if (tiles) {
    tiles.forEach(function (tile) {
      switch (tile.tileId) {
        case nomNomTile:
          updateNomNomTruck();
          break;
        // add cases for all the food trucks this app supports
        default:
          break;
      }
    });
  }
});

C#:

 // Get secondary tile ids
IReadOnlyList<SecondaryTile> tilelist = await 
Windows.UI.StartScreen.SecondaryTile.FindAllAsync();

foreach (var tile in tilelist)
{
  switch (tile.TileId)
  {
    case nomNomTile:
      updateNomNomTruck();
      break;
    // add cases for all the food trucks this app supports
    default:
      break;
  }
}

ローカル更新

ピン留めされたフード トラック タイルは、それぞれのトラックがその日にどこにいるかという最新の情報で更新します。メイン タイルを更新するためにローカル更新を使っている場合は、API の既定の動作として呼び出し元アプリのメイン タイルが更新されるため、セカンダリ タイルを列挙する必要はありません。ここで確認のために、パート 1 で説明したアプリのタイルをもう一度見ておきましょう。

グリルで焼かれている肉の画像、トラックのロゴ、更新テキスト "Nom Nom Barbecue Truck, Washer Ave and 3rd until 3" を表示    グリルで焼かれている肉の画像、トラックのロゴ、更新テキスト "Nom Nom @ Washer Ave and 3rd until 3" を表示

先ほどの列挙処理で呼び出されていた関数を次に示します。この関数が実際にセカンダリ タイルに通知を送信します。

JavaScript:       

 function updateNomNomTruck() {
  // Business logic for retrieving Nom Nom BBQ truck deals
  // ...
  var result = "Washer Ave and 3rd until 3";

  // We can send a notification only for a tile that is pinned. 
  // Lets make sure the tile is pinned before we try to send the notification.
  if (Windows.UI.StartScreen.SecondaryTile.exists(nomNomTile)) {

    // Construct the wide template
    var wideTile = 
NotificationsExtensions.TileContent.TileContentFactory.createTileWideImageAndText02();
    wideTile.image.src = "https://www.contoso.com/foodtrucks/nomnombbq.png";
    wideTile.textCaption1.text = "Nom Nom Barbecue Truck";
    wideTile.textCaption2.text = result;

    // Construct the square template
    var squareTile = 
NotificationsExtensions.TileContent.TileContentFactory.createTileSquarePeekImageAndText04
();
    squareTile.image.src = "https://www.contoso.com/foodtrucks/nomnombbq.png";
    squareTile.textBodyWrap.text = "Nom Nom @ " + result;

    // Attach the square template to the notification
    wideTile.squareContent = squareTile;

    // send the notification to the secondary tile
    Windows.UI.Notifications.TileUpdateManager.createTileUpdaterForSecondaryTile(nomNomTi
le).update(wideTile.createNotification());
  }
}

C#:    

 private void updateNomNomTruck()
{
  // Business logic for retrieving Nom Nom BBQ truck deals
  // ...
  string result = "Washer Ave and 3rd until 3";

  // We can send a notification only for a tile that is pinned. 
  // Lets make sure the tile is pinned before we try to send the notification.
  if (Windows.UI.StartScreen.SecondaryTile.Exists(nomNomTile))
  {

    // Construct the wide template
    NotificationsExtensions.TileContent.ITileWideImageAndText02 wideTile = 
NotificationsExtensions.TileContent.TileContentFactory.CreateTileWideImageAndText02();
    wideTile.Image.Src = "https://www.contoso.com/foodtrucks/nomnombbq.png";
    wideTile.TextCaption1.Text = "Nom Nom Barbecue Truck";
    wideTile.TextCaption2.Text = result;

    // Construct the square template
    NotificationsExtensions.TileContent.ITileSquarePeekImageAndText04 squareTile = 
NotificationsExtensions.TileContent.TileContentFactory.CreateTileSquarePeekImageAndText04();
    squareTile.Image.Src = "https://www.contoso.com/foodtrucks/nomnombbq.png";
    squareTile.TextBodyWrap.Text = "Nom Nom @ " + result;

    // Attach the square template to the notification
    wideTile.SquareContent = squareTile;

    // Send the notification to the secondary tile
    Windows.UI.Notifications.TileUpdateManager.CreateTileUpdaterForSecondaryTile(nomNomTi
le).Update(wideTile.CreateNotification());
  }
}

ここでは NotificationsExtensions ライブラリを使っているので、ローカル コードで XML を操作する必要はありません。代わりに、NotificationsExtensions によって提供されるオブジェクト モデルを使用します。これは IntelliSense に対応しているため、各通知テンプレートに用意されているさまざまなプロパティを見つけることができます。

タイル更新の XML は次のようになります。

 <?xml version="1.0" encoding="utf-8" ?>
<tile>
  <visual>
    <binding template="TileWideImageAndText02">
      <image id="1" src="https://www.contoso.com/foodtrucks/nomnombbq.png"/>
      <text id="1">Nom Nom Barbecue Truck</text>
      <text id="1">Washer Ave and 3rd until 3</text>
    </binding>
    <binding template="TileSquarePeekImageAndText04">
      <image id="1" src=" https://www.contoso.com/foodtrucks/nomnombbq-square.png"/>
      <text id="1">Nom Nom @ Washer Ave and 3rd until 3</text>
    </binding>
  </visual>
</tile>

NotificationsExtensions ライブラリを使うと、IntelliSense を通じてタイル テンプレートのプロパティを参照できるため、ローカル タイル更新の開発効率が大幅に上がります。JavaScript、C#、C++ のプロジェクトにおいて、NotificationsExtensions が皆さんのお役に立てば嬉しく思います。

まとめ

楽しくて興味深い情報がタイル上に表示されれば、詳細を求めてユーザーがそのアプリを起動する可能性も大きくなります。ライブ タイルを追加してアプリの魅力をアピールするにはどうすればよいかを考えるうえで、この記事が皆さんの参考になればさいわいです。通知の詳細については、デベロッパー センターの「タイルと通知の概要」(英語) および「通知の配信方法の選択」(英語) を参照してください。

-- Windows プログラム マネージャー Kevin Michael Woley

この記事はグループで作成されました。Tyler Donahue、Daniel Oliver、Jon Galloway (英語) の協力に感謝します。