Эффективное использование плиток (часть 2)

В части 1 этой статьи мы рассмотрели, как разрабатывать обновления плитки и выбирать шаблоны в соответствии с контентом, который требуется отобразить на динамической плитке. Мы настроили приложение с широкой плиткой по умолчанию и готовы начать обновление наших плиток. А теперь обратимся к коду. Во-первых, мы рассмотрим, как настроить опрос для плитки приложения Contoso Food Trucks, и представим обзор кода веб-службы. После этого мы добавим вторичную плитку к приложению и обновим ее с использованием библиотеки NotificationsExtension, представленной в примере плиток и индикаторов событий приложения в пакете Windows 8 SDK. Итак, начнем!

Выбор метода доставки уведомлений

Теперь, когда я знаю, как должны выглядеть плитки (об этом можно прочитать в части 1), необходимо выяснить, когда следует их обновлять.

Существует 4 способа, с помощью которых приложение может обновить свою плитку (см. статью, посвященную выбору способа доставки уведомлений, в Центре разработчиков). Приложения могут использовать локальные уведомления для обновления плитки. Это удобно, если во время работы приложения информация изменяется. Приложения могут запланировать обновления плитки и всплывающего уведомления на конкретный момент времени. Кроме того, приложения могут использовать push-уведомления или опрашивающие уведомления для обновления плиток из облака, пока сами приложения не выполняются. Опрашивающие уведомления отлично подходят для редко обновляемого и транслируемого контента. Push-уведомления прекрасно подходят для отправки всплывающих уведомлений, которые должны быть получены немедленно, и для обновлений плитки, предназначенных для отдельных пользователей. В данной статье я уделю основное внимание опрашивающим и локальным обновлениям.

Опрос для поиска кафе-фургонов, находящихся поблизости

Наше приложение обладает двумя разными видами информации, которые могут использоваться для обновления плитки. Наиболее важной информацией являются сведения о наличии кафе-фургонов поблизости от места обеда пользователя по умолчанию. Пользователи задают свое место обеда по умолчанию в запущенном приложении. Я использую эти данные, чтобы обновить плитку и сообщить пользователю о кафе-фургонах, находящихся поблизости от этого места. На приведенных здесь изображениях показаны плитки для приложения из части 1 данной статьи. Теперь давайте рассмотрим, как использовать опрос для отображения этих плиток для нашего приложения.

Информация на широкой прямоугольной плитке: Кафе-фургоны поблизости / Nom Nom Barbecue Truck / Sushi Truck / Macaroni Makin' Wagon    Информация на квадратной плитке: Поблизости / Nom Nom / Sushi Truck

Обычно кафе-фургоны весь день — или по крайней мере в обеденные часы — стоят на одном месте. Поскольку расположения кафе-фургонов меняются довольно редко, мне не нужно обновлять плитку в режиме реального времени. Поэтому я могу отказаться от push-уведомлений, которые больше подходят для критичных по времени уведомлений. Но мне нужно, чтобы эти данные обновлялись хотя бы один раз в день, поэтому я отдаю предпочтение периодическим уведомлениям, которые опрашивают мою веб-службу на наличие изменений.

Клиентская реализация: опрос для поиска кафе-фургонов, находящихся поблизости

Для настройки периодических уведомлений на стороне клиента достаточно всего нескольких строк кода. Мы вызываем TileUpdater.startPeriodicUpdate каждый раз, когда пользователь запускает приложение или переключается на него. В результате передаваемый в API универсальный код ресурса (URI) мгновенно опрашивается, и плитка обновляется при каждом запуске приложения или переключении на него. Это связано с тем, что вызов API мгновенно обращается к URI облачной службы и обновляет плитку. Такой режим работы очень удобен для отладки — мы можем тестировать опрос своей облачной службы, не дожидаясь истечения следующего интервала опроса.

Важный вопрос заключается в том, какой URI следует предоставить API startPeriodicUpdate. В рассматриваемом случае я хочу, чтобы облачная служба обновила плитку с использованием сведений о конкретном расположении. В целях защиты сведений о местонахождении пользователя я не хочу отправлять в службу данные о точном расположении. Вместо этого я различаю расположения по почтовому индексу, который пользователь указывает в приложении.

Для передачи почтового индекса в веб-службу я присоединяю строку запроса в конец URI опроса, чтобы указать облачной службе, какое расположение следует поместить на плитку:

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

В ответ на запрос HTTP GET этого URI наша веб-служба возвращает XML-код форматированного уведомления на плитке для указанного в URI почтового индекса. Ниже приведен код для настройки опроса. При этом почтовый индекс жестко запрограммирован в строке 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);

Поскольку в течение дня кафе-фургон может перемещаться, я хочу обновлять плитку достаточно часто. В нашем случае я использую интервал обновления, равный одному часу, чтобы сохранить баланс между нагрузкой на серверную службу и актуальностью информации на плитке.

После однократного вызова API startPeriodicUpdate плитка продолжает обновляться один раз в час, даже когда наше приложение не запущено. Если я захочу изменить URI опроса, мне будет достаточно повторно вызвать этот API с другим URI. Например, если пользователь изменяет свое расположение по умолчанию на другой почтовый индекс, я обновляю URI с использованием правильного почтового индекса, вызывая startPeriodicUpdate еще раз. Если пользователь удаляет свое расположение по умолчанию или хочет остановить обновление плитки, приложение может остановить этот процесс, вызвав API stopPeriodicUpdate.

Подробнее об использовании API startPeriodicUpdate см. в статьях Пример push-уведомлений и периодических уведомлений на стороне клиента и Настройка периодических уведомлений для плиток в Центре разработчиков.

Серверная реализация: опрос для поиска кафе-фургонов, находящихся поблизости

Мы можем реализовать серверную часть нашей плитки в рамках практически любой технологии службы. Здесь я привожу пример кода PHP и ASP.NET.

При опросе наша веб-служба должна в ответ на запрос HTTP GET возвращать XML-код, соответствующий XML-схеме плитки. Можно также использовать HTTPS, чтобы веб-служба могла защищать содержимое, передаваемое по проводным сетям. Файлы cookie не поддерживаются. Все сведения, необходимые вашей службе для ответа на запрос, следует включать в состав URI. Поэтому наше приложение использует строки запросов для передачи почтового индекса.

В приведенном ниже коде PHP я абстрагируюсь от доступа к базе данных нашей службы. В реальном коде переменная trucks, возвращенная функцией get_trucks_from_database() , содержала бы все сведения, необходимые веб-службе для заполнения шаблона плитки для указанного почтового индекса. Я немного упростил этот пример кода службы, чтобы уделить основное внимание возвращаемому службой XML-коду. В реальной развернутой версии веб-службы следовало бы учесть аспекты производительности, масштабируемости, безопасности и удобства обслуживания архитектуры.

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 показана простая реализация службы плитки. Для получения полнофункциональной службы ASP.NET вы, скорее всего, захотите использовать новый веб-API ASP.NET. Этот веб-API создан специально для таких HTTP-служб. Для получения дополнительной информации о веб-API ASP.NET см. 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 и кнопкой "Закрепить на начальном экране"

Теперь нам нужно:

  1. Добавить в приложение панель приложения, включая значок закрепления для действий "Закрепить на начальном экране" и "Открепить от начального экрана".
  2. Реализовать обработчик событий для нажатия кнопки закрепления/открепления на панели приложения.
  3. Добавить специальную логическую схему для закрепления новой плитки в ответ на действие закрепления/открепления.

Мы не будем рассматривать два первых действия по созданию панели приложения, чтобы сконцентрировать внимание непосредственно на закреплении плиток. Подробные сведения о реализации панели приложения можно найти в следующих статьях:

На этапе 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 локальных уведомлений для обновления вторичных плиток во время работы приложения вместо использования одного из облачных механизмов обновления. Я привел здесь локальные уведомления, чтобы показать, как они работают. Аналогичным образом вы можете реализовать обновление из облака. Для этого приложения можно было бы легко реализовать и сценарий с опросом.

В коде, приведенном в этом разделе, я использую библиотеку NotificationsExtensions, входящую в пример плиток и индикаторов событий приложения в пакете Windows 8 SDK. Вы можете включить эту библиотеку в свои проекты приложений, чтобы упростить локальное обновление плиток. Эта библиотека предоставляет объектную модель, которая располагается поверх API обновления плиток Windows и позволяет избежать управления XML-кодом изнутри приложений, написанных на JavaScript, C# и C++. Кроме того, она упрощает разработку благодаря предоставлению технологии IntelliSense.

С помощью API локальных уведомлений я могу обновлять плитку в любой момент во время работы приложения. Для закрепленных плиток кафе-фургонов я хочу обновлять сведения о предложениях конкретного кафе-фургона при каждом запуске приложения пользователем.

Перечисление вторичных плиток

Поскольку пользователь может откреплять вторичные плитки с начального экрана, пока приложение не работает, при запуске оно прежде всего должно проверить имеющиеся закрепленные вторичные плитки. Каждая перечисленная плитка содержит однозначно определяющий ее идентификатор tileId. Поскольку приложение задает tileId при создании плитки, мы можем использовать этот идентификатор для определения способа обновления каждой из найденных плиток. Вот как это реализуется:

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 и 3-й, до 3    Изображение мяса на гриле, эмблема в виде фургона, текст обновления: Nom Nom на углу Washer Ave и 3-й до 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 для обнаружения свойств шаблонов плиток. Это позволяет значительно ускорить разработку локальных обновлений плиток. Надеюсь, что библиотека NotificationsExtensions поможет вам в разработке проектов на JavaScript, C# и C++.

Заключение

Если пользователи увидят на плитках что-то веселое и интересное, они с большей вероятностью запустят ваше приложение, чтобы узнать подробности. Надеюсь, что данная статья побудила вас задуматься о том, как добавить динамическую плитку и продемонстрировать все лучшее, что есть в ваших приложениях. Если вы хотите больше узнать об уведомлениях, прочитайте статьи Обзор плиток и уведомлений и Выбор метода доставки уведомлений в Центре разработчиков.

-- Кевин Майкл Воули (Kevin Michael Woley), руководитель программы, Windows

В работе над этой статьей участвовало несколько специалистов. Благодарю Тайлера Донахью (Tyler Donahue), Дэниела Оливера (Daniel Oliver) и Джона Галловея (Jon Galloway) за их вклад в создание этой статьи.