Контракт «share» — передача данных в метро приложениях Windows 8

в 2:13, , рубрики: .net, contracts, microsoft, windows, Windows 8, WinRT, XAML, метки: , , , ,

В Windows 8 в метро-приложениях появилась возможность передачи данных (Sharing) между приложениями. В дальнейшем для простоты в статье буду употреблять термин «шаринг».
В целях безопасности передачей данных управляет сам пользователь и вызывается с боковой панели соответствующей чудо-кнопкой Share
image
или сочетанием клавиш Win + H.

В качестве примера можно привести возможность передачи своего местоположения с карт или понравившуюся фотографию в почтовое приложение или твиттер.

Реализация контракта «шаринг» может стать очень мощным маркетинговым инструментом. Вы можете предоставить возможность поделиться своими достижениями в игре или приложении с друзьями, что может способствовать увеличению популярности вашего приложения.

Здесь я буду употреблять термины приложение-поставщик для приложения которое «расшаривает» данные. И приложение-приемник для приложения который может принять расшаренные данные.

Статья получилась довольно объемная и здесь представлено краткое содержание статьи:

Шаринг. Поставщик данных
Шаринг. Приемник данных.
Отправка и прием стандартных форматов.
        Отправка и прием текстовых форматов данных.
        Отправка и прием ссылки
        Отправка и прием графики
        Отправка и прием файлов
Отправка и прием нестандартных типов данных
        Отправка и прием объектов с сериализацией в строку
        Стандартные «нестандартные типы». Данные на основе схем schema.org
        Отправка и прием бинарных данных
        Отложенная отправка данных (Передача провайдера данных).
        Правильная организация передачи больших объемов данных (музыка, видео)
Добавление ссылок на панель шаринга (Быстрые ссылки)
Обрабатываем логические ошибки.
Улучшаем шаринг данных. Сбор статистики
Общие рекомендации по реализации шаринга. Избегаем ошибки в реализациях.
        Рекомендации для приложения-поставщика.
        Рекомендации для приложения-поставщика.

Для подробного изучения контракта Шаринга рекомендую скачать два великолепных примера реализации в MSDN:
Sharing content source app sample
Sharing content target app sample

Шаринг. Поставщик данных.

Реализовать приложение поставляющие данные достаточно просто. Допустим мы хотим «расшарить» какой нибудь текст.
На странице в методе OnNavigatedTo добавляем обработчик запроса на шаринг данных. И отписываемся от события шаринга в методе OnNavigatingFrom

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            DataTransferManager.GetForCurrentView().DataRequested += Share_DataRequested;
        }

        protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
        {
            DataTransferManager.GetForCurrentView().DataRequested -= Share_DataRequested;
        } 

В обработчике мы можем указать те данные что мы хотим «расшарить». Допустим хотим опубликовать какой нибудь текст.

        void Share_DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
        {
            args.Request.Data.Properties.Title = "Simple notepad";
            args.Request.Data.Properties.Description = "Publication of the text";
            args.Request.Data.SetText("Hello! World!");
        }

Свойство Title является обязательным, без его указания получим ошибку при попытке «расшаривания» данных. В то же время свойство Description является опциональным.

Теперь если мы вызовем шаринг данных мы увидим справа панель с предложением выбрать приложение которое может принять данные.

image

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

Шаринг. Приемник.

Реализовать приложение-приемник «приемника» поставщика данных тоже достаточно просто. Здесь мы можем воспользоваться готовым шаблоном в студии:

image

При выборе этого шаблона у нас будет добавлена запись в манифест, переопределен метод в App.xaml и будет добавлена новая страница.

Мы не будем рассматривать использование этого шаблона и «вручную» пошагово добавим необходимые элементы.

В первую очередь в файле манифесте Package.appxmanifest надо добавить поддержку контракта шаринга приложением, и указываем тип данных которые приложение может поддерживать:

image

Здесь мы можем видеть что также мы можем добавить поддержку Uri, Bitmap, HTML, StorageItems, RTF.

Мы рассмотрим поддержку этих типов данных и нестандартных форматов чуть позже.

Далее нам нужно переопределить OnShareTargetActivation в App.xaml и в этом методе можем передать ссылку откуда можем извлечь текст:

        protected override void OnShareTargetActivated(ShareTargetActivatedEventArgs args)
        {
            var frame = new Frame();
            frame.Navigate(typeof(TargetPage), args.ShareOperation);
            Window.Current.Content = frame;
            Window.Current.Activate();
        }

Теперь на странице TargetPage мы можем извлечь переданный текст. Для простоты добавим на страницу TextBlock и установим свойство text для него.

Для этого в коде страницы переопределим метод OnNavigateTo и извлечем текст

        protected async override void OnNavigatedTo(NavigationEventArgs e)
        {
            if (e.Parameter != null)
            {
                var shareOperation = (ShareOperation)e.Parameter;
                txtBlock.Text=await shareOperation.Data.GetTextAsync();
            }
        }

Это все. Теперь, если мы установим приложение приемник и после этого запустим приложение-отправитель и выбрав при шаринге наше приложение-приемник можем увидеть следующее окно:

image

Наше приложение получило переданную строку из другого приложения.

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

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

Отправка и прием стандартных форматов.

Довольно часто нам требуется передать и получить чуть более сложные форматы данных нежели строки. Для наиболее часто востребованных форматов добавлены соответствующие поддержки в API. Мы можем передавать в качестве стандартных форматов Text, Rtf, Html, Uri, Bitmap, StorageItems (файлы). Каждый из них можно передавать одновременно. Т.е. мы можем одновременно отправить и извлечь, к примеру, Text, Html и Bitmap

Отправка и прием текстовых форматов данных

Первые три формата Text, Rtf, Html передаются и принимаются одинаково:

Передача:

            args.Request.Data.SetText("...");
            args.Request.Data.SetRtf("...");
            args.Request.Data.SetHtmlFormat("...");

Прием:

В первую очередь для поддержки «приема» форматов Text, HTML, Uri мы добавляем соответствующую запись в манифест.

И соответствующий код получения данных:

            shareOperation.Data.GetTextAsync();
            shareOperation.Data.GetRtfAsync();
            shareOperation.Data.GetHtmlFormatAsync();

Если мы поддерживаем сразу несколько форматов то скорее всего нам придет только один определенный тип данных и мы должны проверить «содержание» данных в пакете.

              if(shareOperation.Data.Contains(StandardDataFormats.Text))
              {
                  var text = shareOperation.Data.GetTextAsync();
              }
              if(shareOperation.Data.Contains(StandardDataFormats.Html))
              {
                  var html = shareOperation.Data.GetHtmlFormatAsync();
              }
              if(shareOperation.Data.Contains(StandardDataFormats.Rtf))
              {
                  var rtf = shareOperation.Data.GetRtfAsync();
              }

Отправка и прием ссылки

Передача и прием Uri практически идентичен:

args.Request.Data.SetUri(new Uri("http://akhmed.ru/post/2012/07/19/share_contract_win8.aspx"));

Прием:

shareOperation.Data.GetUriAsync();

Отправка и прием графики

Немного интереснее передача и прием картинки.

Можно передавать как картинки с другого сервера

args.Request.Data.SetBitmap(RandomAccessStreamReference.CreateFromUri(new Uri("http://freshyourmood.com/wp-content/uploads/2012/04/pure-water-aquatecuk.wordpress.com_.jpg")));

так и локальные из проекта

args.Request.Data.SetBitmap(RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///apple.jpg")));

извлечение картинки тоже достаточно простая операция. Сначала надо добавить соответствующую запись в манифест приложения-приемник: Bitmap.

Далее для примера отобразим текст и картинку на странице в соответствующем xaml коде:

        <StackPanel Orientation="Horizontal" VerticalAlignment="Top">
            <Image x:Name="img" Width="200" Height="200"></Image>
            <TextBlock x:Name="txtBlock" Margin="24" Text="start page text" Style="{StaticResource HeaderTextStyle}" />    
       </StackPanel>

Код «извлечения» данных:

        protected async override void OnNavigatedTo(NavigationEventArgs e)
        {
            if (e.Parameter != null)
            {
                var shareOperation = (ShareOperation) e.Parameter;
                    
                if (shareOperation.Data.Contains(StandardDataFormats.Text))
                {
                    txtBlock.Text = await shareOperation.Data.GetTextAsync();
                }

                if (shareOperation.Data.Contains(StandardDataFormats.Bitmap))
                {
                    var bitmapReference = await shareOperation.Data.GetBitmapAsync();
                    var bitmapImage = new BitmapImage();
                    bitmapImage.SetSource(await bitmapReference.OpenReadAsync());
                    img.Source = bitmapImage;
                }            
            }
        }

И можем любоваться результатом:

image

По такому же принципу мы можем передавать картинки иконки. Синтаксис выглядит немного по другому.

Передача:

    args.Request.Data.Properties.Thumbnail = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///apple.jpg"));

Прием:

    var bitmapReference = shareOperation.Data.Properties.Thumbnail;

Таким образом мы можем передавать сразу две картинки но из соображений производительности использовать второй подход стоит только в том случае если картинки расположены локально и они не большого размера.

Отправка и прием файлов

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

К примеру во время работы с приложением мы можем загрузить несколько файлов в наше приложение.

Для простоты добавим кнопку по клику на которую выбираем файлы:

<Button Content="LoadFiles" Click="LoadFiles_Click" HorizontalAlignment="Left" Margin="268,82,0,0" VerticalAlignment="Top" Width="139"/>

И сохраняем ссылку на выбранные пользователем файлы:

        private IReadOnlyList<StorageFile> files;
        private async void LoadFiles_Click(object sender, RoutedEventArgs e)
        {
            var filePicker = new FileOpenPicker
            {
                FileTypeFilter = {"*"}
            };
            files = await filePicker.PickMultipleFilesAsync();
        }

Теперь, на событие вызова шаринга мы укажем что хотим передать эти файлы:

        void Share_DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
        {
            args.Request.Data.Properties.Title = "file sharing";
            if(files!=null)
            {
                args.Request.Data.SetStorageItems(files);
            } 
        }

Реализация «приемника» файлов тоже достаточно простая. В манифесте мы должны добавить поддержку файлов StorageItems или поставить галочку на Supports any file types

Далее, для примера, мы выведем список файлов в ListBox:

        <ListBox x:Name="listBox"></ListBox>

И привяжем к этому списку имена полученных файлов

        protected async override void OnNavigatedTo(NavigationEventArgs e)
        {
            if (e.Parameter != null)
            {
                var shareOperation = (ShareOperation)e.Parameter;
                var files=await shareOperation.Data.GetStorageItemsAsync();
                listBox.ItemsSource = files.Select(i=>i.Name); 
            }
       }

В итоге мы можем увидеть примерно следующий результат:

image

Отправка и прием нестандартных типов данных.

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

Отправка и прием объектов с сериализацией в строку.

Мы можем сериализовать наши данные в какой нибудь формат (CSV, XML, JSON) и распарсить его для получения. Но нам не подойдут стандартные форматы. Во первых если мы используем для этого Text или другой формат то откроются все приложения которые поддерживают стандартный формат и выбрав приложение почты пользователь увидит письмо наполненное каким то непонятным для него текстом. Кроме того мы все же хотим сохранить возможность использовать формат Text или HTML для того, что бы была возможность отправить данные другому пользователю список продуктов без деталей.

В этом случае нам может помочь специальный формат данных.

В первую очередь нам необходимо придумать название формата, например Product. (Возможно до или после нас кому то придет в голову гениальная идея использовать формат с таким же названием и тоже назовет свой формат Product, что бы избежать конфликтов можно использовать Product_{GUID} (Например Product_F6FBDBFB-5703-44F6-ACBE-A2D25EF4D6CE). Здесь же для простоты оставим Product).

Допустим и в приемнике и в потавщике у нас есть следующий класс Product:

    public class Product
    {
        public string Name { get; set; }
        public int Count { get; set; }
    }

И сериализатор:

    public class JsonSerializer
    {
        public static string Serialize<T>(T item)
        {
            var dataSerializer = new DataContractJsonSerializer(typeof(T));
            var ms = new MemoryStream();
            dataSerializer.WriteObject(ms,item);
            ms.Position = 0;
            return new StreamReader(ms).ReadToEnd();
        }

        public static T Deserialize<T>(string value)
        {
            var dataSerializer = new DataContractJsonSerializer(typeof(T));
            return (T)dataSerializer.ReadObject(new MemoryStream(Encoding.UTF8.GetBytes(value)));
        }
    }	

В приложении поставщике мы теперь можем сериализовать продукт и отправить указав идентификатор формата:

        void Share_DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
        {
            args.Request.Data.Properties.Title = "Product sharing";
            var product = new Product()
                              {
                                  Name = "Bread", Count = 2
                              };
            var productJson = JsonSerializer.Serialize(product);
            args.Request.Data.SetData("product",productJson);
        }

Реализация «получателя»:

В приложении-получателе мы должны в манифесте указать что теперь мы поддерживаем формат product и можем использовать следующий код для получения продукта:

        protected async override void OnNavigatedTo(NavigationEventArgs e)
        {
            if (e.Parameter != null)
            {
                var shareOperation = (ShareOperation)e.Parameter;
                if(shareOperation.Data.Contains("product"))
                {
                    var productJson = await shareOperation.Data.GetTextAsync("product");
                    var product = JsonSerializer.Deserialize<Product>(productJson);
                    txtBlock.Text = product.Name + " " + product.Count;
                } 
            }
       }

Запустив приложение можно увидеть что наши данные были успешно переданы.

image

В данном случае мы передали и получили обычную строку в специальном виде. Мы по прежнему может получить строку без указания формата GetTextAsync();

Стандартные «нестандартные типы». Данные на основе схем schema.org

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

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

Официально рекомендуется в таких случаях использовать схемы по ресурсу schema.org.

В первую очередь нам нужно определиться с типом сущности (книги, музыка, фильмы, продукты)

Выбрав соответствующую сущность, мы должны передавать или принимать в JSON строку согласно данным схемы.

Допустим мы выбрали сущность «Продукт» (http://schema.org/Product)

Реализация приложения поставщика:

Передача данных полностью аналогична передаче собственного формата рассмотренного выше, отличие только в формате JSON:

        async void Share_DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
        {
            args.Request.Data.Properties.Title = "Product sharing";

            var productFormat =
            @"{
               ""type"" : ""http://schema.org/Product"",
               ""properties"" :
               {
                ""name"" : ""{0}"",
                ""description"" : ""{1}"",
                ""productID"" : ""{2}""
                }
            }";

            var productJson = String.Format(productFormat, "Olive oil", "description of product", "8410660101481");
       args.Request.Data.SetData("http://schema.org/Product",productJson);
       }

Реализация приложения приемника

Для поддержки этой схемы мы должны в манифесте указать формат данных schema.org/Product

image

Чтение данных осуществляется аналогично чтению строки которую мы рассматривали выше:

      protected async override void OnNavigatedTo(NavigationEventArgs e)
       {
            if (e.Parameter != null)
            {
                
                shareOperation = (ShareOperation)e.Parameter;
                var productJson = await shareOperation.Data.GetTextAsync("http://schema.org/Product");
                JsonObject productObject = JsonObject.Parse(productJson);
                JsonObject properties = productObject["properties"].GetObject();

                var productId = properties["productID"];
                var productName = properties["name"];
                var productDescriptions = properties["description"];

            }
       }

Отправка и прием бинарных данных

В некоторых случаях проще передавать вместо строки бинарные данные. (К примеру у нас djvu редактор и мы хотим передавать данные в сыром виде).

Мы можем открыть файл передать ссылку на поток с открытого файла и прочитать этот поток в приложении приемнике.

Рассмотрим более интересный пример передачи данных из потока в памяти:

        async void Share_DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
        {
            args.Request.Data.Properties.Title = "Product sharing";
            
            var product = new Product()
                              {
                                  Name = "Bread", Count = 2
                              };

            var stream = new InMemoryRandomAccessStream();
            
            using(var writer=new DataWriter(stream))
            {
                writer.WriteInt32(product.Name.Length);
                writer.WriteString(product.Name);
                writer.WriteInt32(product.Count);
                await writer.StoreAsync();
                await writer.FlushAsync();
                writer.DetachStream();
            }
            stream.Seek(0);
            args.Request.Data.SetData("product", stream);
        }

В приложении-приемнике мы можем прочитать и разобрать этот поток.

        protected async override void OnNavigatedTo(NavigationEventArgs e)
        {
            if (e.Parameter != null)
            {

                var shareOperation = (ShareOperation)e.Parameter;
                if (shareOperation.Data.Contains("product"))
                {
                    var stream = await shareOperation.Data.GetDataAsync("product") as IRandomAccessStream;
                    var product = new Product();
                    using(var streamReader=new DataReader(stream))
                    {
                        await streamReader.LoadAsync((uint) stream.Size);
                        var len = streamReader.ReadInt32();
                        product.Name = streamReader.ReadString((uint) len);
                        product.Count = streamReader.ReadInt32();
                    }
                    
                    txtBlock.Text = product.Name + " " + product.Count;
                }
                
            }
       }

Отложенная отправка данных (Передача провайдера данных)

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

        async void Share_DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
        {
            args.Request.Data.Properties.Title = "Product sharing";
            args.Request.Data.SetDataProvider("product",BinaryDataHandler);
        }

        private async void BinaryDataHandler(DataProviderRequest request)
        {
            try
            {
                var product = new Product()
                {
                    Name = "Bread",
                    Count = 2
                };
                var stream = new InMemoryRandomAccessStream();

                using (var writer = new DataWriter(stream))
                {
                    writer.WriteInt32(product.Name.Length);
                    writer.WriteString(product.Name);
                    writer.WriteInt32(product.Count);
                    await writer.StoreAsync();
                    await writer.FlushAsync();
                    writer.DetachStream();
                }
                stream.Seek(0);
                request.SetData(stream);
            }
            finally 
            {
                request.GetDeferral().Complete();
            }
        }

Основное преимущество этого подхода заключается в том что теперь диалоговое окно шаринга появится быстрее, метод BinaryDataHandler будет вызван только ПОСЛЕ выбора конкретного приложения. И если пользователь отменит операцию шаринга метод не будет вызван вообще.

Правильная организация передачи больших объемов данных (музыка, видео)

В тех случаях когда приложение поставляет большой объем данных мы не должны заставлять пользователя ждать пока все данные не будут переданы.

В тех случаях когда мы уже завершили прием данных и если мы в интерфейсе провели все необходимые действия (к примеру нажали кнопку Send в почтовом интефейсе или кнопку Save где явно необходимо подтверждение от пользователя), мы можем вызвать метод:

shareOperation.ReportCompleted();

Который уведомляет систему что все операции по передаче данных завершены и приводит к закрытию интерфейса шаринга приложения приемника.

В тех случаях когда объем передаваемых данных слишком большой мы не должны заставлять пользователя ждать.

Мы можем вызывать метод

shareOperation.ReportStarted();

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

Далее мы должны вызывать метод

    shareOperation.ReportDataRetreived();

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

По завершению передачи можем вызвать метод

shareOperation.ReportCompleted();

или метод

shareOperation.ReportError("error description");

Что бы уведомить систему об успешности или ошибке при передаче данных.

В последнем случае пользователь получит уведомление об ошибке и может посмотреть ошибку если снова откроет шаринг и перейдет по ссылке внизу что бы проверить состояние передачи.

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

     shareOperation.ReportSubmittedBackgroundTask();

Добавление ссылок на панель шаринга.

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

Предположим что в нашем приложении-приемнике нет единого списка продуктов. Есть список из списков продуктов. Если пользователь выбрал приложение и выбрал список куда хочет передать продукт мы можем предоставить быструю ссылку на этот список.

Рассмотрим как можно добавить быструю ссылку в наше приложение-приемнике:

        private async void ButtonSaveLink_Click(object sender, RoutedEventArgs e)
        {
            var quickLinkInfo = new QuickLink
            {
                Id = "homeListId",
                Title = "Add to Home list",

                SupportedFileTypes = { "*" },
                SupportedDataFormats =
                    { 
                        //Оставлено для примера
                        StandardDataFormats.Bitmap,
                        "product" 
                    }
            };

            try
            {
                var iconFile = await Package.Current.InstalledLocation.CreateFileAsync("assets\Logo.png", CreationCollisionOption.OpenIfExists);
                quickLinkInfo.Thumbnail = RandomAccessStreamReference.CreateFromFile(iconFile);

                shareOperation.ReportCompleted(quickLinkInfo);
            }
            catch (Exception)
            {
                shareOperation.ReportCompleted();
                throw;
            }
       }

Соответствующий код в XAML

<Button Content="Save" Click="ButtonSaveLink_Click" />

Теперь если мы в приложении приемнике нажмем кнопку Save мы увидим что в следующий раз при шаринге у нас будет доступна быстрая ссылка.

image

Теперь в следующий раз мы можем упростить однотипное действие, определив по какому из быстрых ссылок щелкнул пользователь.

                shareOperation = (ShareOperation)e.Parameter;
                if(shareOperation.QuickLinkId=="homeListId")
                {
                    //handle selected list
                }

Почему не надо добавлять эту ссылку автоматически? Потому что пользователь возможно не выполнит никакого действия после активации приложения.

API шаринга специально спроектировано таким образом что добавление ссылки сворачивает приложение-приемник. Соответственно добавление ссылки должно быть последней операцией которую можно сделать. Т.е. если мы выбрали приложение приемник и выбрав список мы тут же сохраняемся и добавляем быструю ссылку. Или же выбрав список даем возможность изменить свой выбор и сохраняем в выбранный список только после нажатия кнопки Save.

В случае если ссылка теряет актуальность, мы можем удалить быструю ссылку из нашего приложения. (К примеру больше не существует списка с этим идентификатором).

           shareOperation.RemoveThisQuickLink();

Обрабатываем логические ошибки.

Если страница приложения-источника не поддерживает шаринг данных при попытке шаринга пользователь увидит сообщение “This app can’t share.”

Возможно вы захотите подсказать пользователю что надо перейти в детали определенного продукта для шаринга. Или что он должен отметить несколько продуктов на этой странице для их шаринга (хотя как альтернатива можно было бы слать все данные если их не слишком много).

В любом случае, при необходимости вы можете уведомить пользователя почему вы не можете предоставить данные для поставки и что надо сделать пользователю.

Например:

        async void Share_DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
        {
            args.Request.Data.Properties.Title = "Product sharing";
            args.Request.FailWithDisplayText("please select products for sharing");
        }

Теперь мы можем увидеть следующий результат при попытке «расшаривания» страницы:

image

Улучшаем шаринг данных. Сбор статистики

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

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

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

Узнать в какие приложения наиболее часто поставляются данные достаточно просто. Мы можем подписаться на событие выбора приложения и собирать статистику

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            DataTransferManager.GetForCurrentView().DataRequested += Share_DataRequested;
            DataTransferManager.GetForCurrentView().TargetApplicationChosen += ShareText_TargetApplicationChosen;
        }

        protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
        {
            DataTransferManager.GetForCurrentView().DataRequested -= Share_DataRequested;
            DataTransferManager.GetForCurrentView().TargetApplicationChosen -= ShareText_TargetApplicationChosen;
        }

        void ShareText_TargetApplicationChosen(DataTransferManager sender, TargetApplicationChosenEventArgs args)
        {
            var targetAppName = args.ApplicationName;
            //сбор статистики
        }

Точно так же в приложении приемнике мы можем узнать из какого приложения нам поставляются данные.

        protected async override void OnNavigatedTo(NavigationEventArgs e)
        {
            if (e.Parameter != null)
            {
                
                var shareOperation = (ShareOperation)e.Parameter;

                //Название приложения поставщика
                var sourceAppName = shareOperation.Data.Properties.ApplicationName;
                //Url адрес приложения поставщика в маркете (null если не в маркете)
                var sourceAppMarketUrl = shareOperation.Data.Properties.ApplicationListingUri;
                //сбор статистики				
            }
       }

Общие рекомендации по реализации шаринга. Избегаем ошибки в реализациях.

Не все изложенные здесь рекомендации являются официальными, часть рекомендаций я написал от себя и следует воспринимать критически.

Рекомендации для приложения-поставщика.

Не надо подписываться на событие запроса шаринга в конструкторе. Мы теряем возможность корректно отписаться от шаринга данных при переходе на другую страницу. Это приведет к тому что в многостраничном приложении шаринг будет срабатывать на всех страницах которые подписаны на шаринг, если пользователь побывал на этих страницах.

Наиболее корректное решение это подпись на шаринг при переходе на страницу и отписывание от шаринга при уходе с нее (Методы OnNavigateTo, OnNavigateFrom). Для простоты можно создать базову страницу в которой будет автоматизированы эти действия.

В качестве альтернативы можно подписаться на шаринг при активации приложения, в этом случае в зависимости от того, какая страница сейчас активна и каково его состояние поставлять данные из единого диспетчера шаринга (к примеру что бы не реализовывать подписку и отпуску на шаринг на каждой странице).

Желательно поставлять данные сразу в нескольких форматах. Даже если у вас эксклюзивный формат списка дел, предоставьте возможность поставки данных в Text или Html что бы пользователь мог отправить текущий список дел по почте, к примеру, своим друзьям.

Если ваше приложение передает большие объемы данных, то лучше использовать передачу провайдера данных, что бы максимально быстро запустить операцию шаринга.

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

Следующий код приведет к тому что панель шаринга откроется, рекомендуется его использовать только если пользователь нажимает на кнопку в приложении.

DataTransferManager.ShowShareUI();

Рекомендации для приложения-приемника.

Учитывайте что в ваше приложение данные могут приходить сразу в нескольких форматах. Весь список доступных форматов можно получить из параметра: shareOperation.Data.AvailableFormats

Так же проверить доступность определенного формата можно методом shareOpertaion.Data.Contains();

При передаче больших объемов данных, таких как музыка, видео и т.д. используйте провайдер данных, и возможности shareOperation.Report…(); для того что бы выполнять передачу асинхронно и не заставлять пользователя ждать завершения выполнений операций.

Старайтесь использовать по мере необходимости добавление ссылок на панель шаринга для упрощения однотипных действий.

Если вы работаете со стандартами из scheme.org всегда допускайте что данные придут в неверном формате и проводите соответствующие проверки и обработку ошибок.

Автор: Atreides07

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js