読者です 読者をやめる 読者になる 読者になる

しばやん雑記

ASP.NET とメイドさんが大好きなフリーランスのプログラマーのブログ

UWP でアプリを作ってから 1 週間でやったことをまとめた

Windows

色々と苦労ばかりしてますが、何だかんだで UWP で作ったアプリの更新を頻繁に行っています。

情報が非常に少ないという点が苦労の原因なので、文句ばかり言わず調べた部分に関してはまとめます。実際にアプリに組み込んで試したことだけ書いてますが、間違っている可能性は高いです。

HockeyApp を有効化

Visual Studio から HockeyApp のクラッシュ分析を有効化すると、簡単にアカウント作ったりアプリの登録が出来ますが、AppId の設定は自分で行う必要があるみたいでした。

using Microsoft.HockeyApp;

public sealed partial class App
{
    public App()
    {
        HockeyClient.Current.Configure("APPID");

        InitializeComponent();

        Suspending += OnSuspending;
    }
}

公式のサンプルが InitializeComponent の前に呼び出していたので、同じようにしました。

f:id:shiba-yan:20160817214734p:plain

正しく設定が行えていると、HockeyApp のダッシュボードに情報が表示され始めます。最低でも Version が表示されていない場合は設定に失敗しているので、AppId を確認する必要があります。

課金処理を StoreContext API に変更

昔から存在している CurrentApp を使った課金処理は、Windows 10 Anniversary Update から新しく追加された StoreContext に置き換えられるようです。

https://msdn.microsoft.com/en-us/windows/uwp/monetize/in-app-purchases-and-trials

API が大きく変わっているのと、サンプルコードがいまいちでわかりにくかったので、実際に自分が書いたコードを参考までに載せておきます。正しいのかはわからないので自己責任で。

アドオンの購入は RequestPurchaseAsync メソッドに StoreId を渡すだけなので、これまでとあまり違いはないです。ProductId ではなく StoreId なので注意が必要です。

var context = StoreContext.GetDefault();

var result = await context.RequestPurchaseAsync(StoreId);

switch (result.Status)
{
    case StorePurchaseStatus.AlreadyPurchased:
        // 既に購入済み
        break;

    case StorePurchaseStatus.Succeeded:
        // 購入完了
        break;

    default:
        // エラー扱いにする
        return;
}

購入後は Status の値によってダイアログを出したりすることになると思います。

そして購入済みライセンスのチェックですが、新しい API では SKU という概念が追加されたみたいなので、これまでより少しめんどくさくなりました。

For a SKU, the Store ID has the format /xxxx, where xxxx is a 4-character alpha-numeric string that identifies a SKU for the product. For example, 9NBLGGH4R315/000N. This ID is returned by the StoreId property of a StoreSku object, and it is sometimes called the SKU Store ID.

In-app purchases and trials

情報自体は GetAppLicenseAsync メソッドで取得できますが、AddOnLicenses のキーは StoreSku から取得できる StoreId なので、単純にインデクサーで引くとエラーになります。

なので、LINQ を使って StartsWith で比較するか、StoreSku を先に調べて引っ張ってくるかの方法が必要になりそうです。挙動が不明だったので、私は後者を選びました。

// Durable なアドオンを取得する
var list = await context.GetAssociatedStoreProductsAsync(new[] { "Durable" });

// Skus は必ず 1 つ存在する
var skuStoreId = list.Products[StoreId].Skus[0].StoreId;

// ライセンス情報を取得
var license = await context.GetAppLicenseAsync();

// AddOnLicenses は KeyNotFoundException を投げる
if (license.AddOnLicenses.ContainsKey(skuStoreId) && license.AddOnLicenses[skuStoreId].IsActive)
{
    // アドオンを購入済み
}

ひとまず、これで StoreContext を使った IAP が動作するようになりました。

AdMediator が削除されたので変更

これまで Microsoft Store Engagement and Monetization SDK としてリリースされていた拡張が、先日新しく Microsoft Store Services SDK に変わりましたが、このタイミングで AdMediator が削除されました。

今後はサーバーサイドに切り替わっていくみたいですが、SDK がリリースされてもサーバー側がリリースされていないので、現状は Microsoft Advertising のみ利用可能です。

  • AdMediator is no longer being maintained as we shift to server side mediation.
    • For Windows 10 apps, an MSDN topic is coming out this week with prescriptive steps to refactor.
Microsoft Store Services SDK extension

英語版のドキュメントは既に Store Services SDK 向けに更新されています。

https://msdn.microsoft.com/windows/uwp/monetize/microsoft-store-services-sdk

おそらく近日中にサーバー側がリリースされるんでしょう、足並み揃えろという感は拭えませんが。

Feedback Hub の起動コードを変更

Insider 向け機能として提供されている Feedback Hub を使ったフィードバック機能ですが、Store Services SDK の更新に従って微妙に API が更新されています。

まず StoreServicesFeedbackLauncher.IsSupported メソッドを使って、利用可能かチェックします。

if (StoreServicesFeedbackLauncher.IsSupported())
{
    feedbackButton.Visibility = Visibility.Visible;
}

何故か IsSupported はメソッドに変更されています。このあたりイマイチですね。

実際に Feedback Hub を開くためには StoreServicesFeedbackLauncher.GetDefault メソッドでインスタンスを取得した後、LaunchAsync メソッドを呼び出します。

private async void FeedbackButton_Click(object sender, RoutedEventArgs e)
{
    await StoreServicesFeedbackLauncher.GetDefault().LaunchAsync();
}

この修正はシンプルなのですぐ対応できます。

実際にフィードバックが送られるとどうなるのかはよくわかっていないですが、めんどくさい部分を Windows Store が受け持ってくれるのは便利ですね。

京都合宿に参加して URL Rewrite Visualizer を作った話

イベント・セミナー

4 月からお手伝いしている一休.comの開発メンバーが、京都で 2 泊 3 日の開発合宿するという話を聞いたので、密かに紛れ込んできました。

f:id:shiba-yan:20160811125121j:plain:w450

事前に予習が必要だったのですが、数話だけ見た状態で合宿に突入しました。

f:id:shiba-yan:20160815001335p:plain

ちはやふるに関しては他の人がまとめてくれると信じているので割愛します。競技かるたをすると 1 日で 3kg 痩せると聞いてダイエットクラスタが騒めきました。

作ったもの

Visual Studio には Directed Graph Markup Language (DGML) というファイルを操作する機能が用意されているので、それを利用して URL Rewrite のルール依存関係を表示するツールを作りました。

例えば以下のような URL Rewrite のルールを用意して、DGML を書き出します。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="Redirect from blog">
          <match url="^blog/([_0-9a-z-]+)/([0-9]+)" />
          <action type="Redirect" url="article/{R:2}/{R:1}" redirectType="Found" />
        </rule>
        <rule name="Redirect from page">
          <match url="^page/([_0-9a-z-]+)/([0-9]+)" />
          <action type="Redirect" url="article/{R:2}/{R:1}" redirectType="Found" />
        </rule>
        <rule name="Redirect from top">
          <match url="^top$" />
          <action type="Redirect" url="article/12345/12345" redirectType="Found" />
        </rule>
        <rule name="Redirect from root">
          <match url="^$" />
          <action type="Redirect" url="top" redirectType="Found" />
        </rule>
        <rule name="Error response">
          <match url="^error$" />
          <action type="AbortRequest" />
        </rule>
        <rule name="Rewrite to article.aspx">
          <match url="^article/([0-9]+)/([_0-9a-z-]+)" />
          <action type="Rewrite" url="article.aspx?id={R:1}&amp;title={R:2}" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

書き出したファイルを Visual Studio で開くと、有向グラフとして表示されます。特に説明は要らないと思いますが、ルール間の依存関係が視覚化されていることが分かりますね。

f:id:shiba-yan:20160814234431p:plain

DGML の操作に必要な GraphModel ライブラリは Visual Studio のインストールディレクトリにありますが、場所がちょっとわかりにくいのでスクリーンショットだけ貼っておきます。

f:id:shiba-yan:20160815000836p:plain

実際の Rewrite ルールなどで試していないので、多分微妙な動作とか普通にあると思いますが、今回の実装のメインは正規表現で定義された match url をどうやって作るか、という部分でした。

正規表現にマッチする入力文字列を作るために、今回は Fare というライブラリを使いました。元々の Java 実装を C# に移植したライブラリになります。

Fare.Xeger クラスを使うと、簡単に正規表現にマッチするランダムな文字列を生成してくれます。使い方も以下のようにとてもシンプルです。

var xeger = new Fare.Xeger("^blog/([_0-9a-z-]+)/([0-9]+)");

var str = xeger.Generate();

これで入力文字列が生成されるので、あとは上から順に Rewrite ルールを辿って行って GraphModel の表現にマッピングしているだけです。

GraphModel は Node と Link を追加していくだけで、いい感じにグラフを作ってくれるので良い感じでした。

京都での食事

食事は基本的に全て naoya さんが案内してくれました。お昼はラーメンとつけ麺で迷いつつも、みんながつけ麺だったのでラーメン選ぶぐらいひねくれてるのが私です。

f:id:shiba-yan:20160811142307j:plain

某はてな社のすぐ近くにある店でしたが、インターンしてた時には毎日が忙しすぎて事務所から一歩も外に出なかったなと思い出しました。大学生の頃なのでとても懐かしい話です。

夜も最高の店で、ここも烏丸御池の近くでした。みんなで進捗を気にせず飲む酒は最高という感じです。

f:id:shiba-yan:20160812210328j:plain

f:id:shiba-yan:20160812211753j:plain

f:id:shiba-yan:20160812212556j:plain

京都って安くて美味しい店多いんだと思いなおしました。また個人的にも食べに行きたいお店でした。

合宿の準備などは全て任せっきりで、本当に乗っかっただけで申し訳ない気持ちでいっぱいですが、集中してコーディング出来たのと楽しい時間を過ごすことが出来て最高でした。

Windows Phone 8.1 向けに作ったアプリを Windows 10 Mobile 向けに作り直してみた

Windows

最後に更新してから 1 年ほど放置していたアプリが、実はダウンロード数が順調に伸びていたのと MADOSMA Q601 を買ったので Windows 10 Mobile に作り直してみました。

作り直したと言っても、WinRT から UWP への変更点としてはプロジェクトファイルぐらいみたいですが、project.json 周りで嫌な思いしたくなかったので新規にプロジェクトを作りました。

UWP プロジェクトに既存のコードをコピーして、ビルドすると大体移植は完了といった感じです。しかし、アイコンサイズが大きく変わっているため、UWP Logo Maker で作り直しました。

基本的には透過画像が好まれますが、StoreLogo 系だけは背景色を付けておかないと、ダッシュボードでの表示が残念なことになります。リリースするともっと悲惨になったかもしれません。

あと、今回は Mobile 向けなので Package.appxmanifest の TargetDeviceFamily を Windows.Mobile に変えておきました。これで Mobile をターゲットにしていることになるらしいです。

<Dependencies>
  <TargetDeviceFamily Name="Windows.Mobile" MinVersion="10.0.10240.0" MaxVersionTested="10.0.10586.0" />
</Dependencies>

ストアに出すときに嵌ったのが、デフォルトテンプレートでは MinVersion と MaxVersionTested が 10.0.0.0 になっていて、審査前にリジェクトされたことでした。

UWP なので Windows 10 Mobile 以外でも動作しますが、ストア側で Mobile のみ配布するようにしました。

f:id:shiba-yan:20160802234551p:plain

運悪く土日を挟んでいたため審査には時間がかかり、さらにストアへの情報反映までにはもっと時間がかかりましたが、何とか UWP 版のリリースまで行えました。

今後も Feedback Hub への対応や、IAP の実装などいい感じの実験アプリとしてリリースしていきます。

Windows Phone 8.1 への対応を打ち切って、UWP 版だけのリリースにしてしまいましたが、世界的に見ると Windows 10 Mobile のシェアは Windows Phone 8.1 の半分以下らしいので、かなり早まった感があります。

このスライドは AdDuplex が毎月公開しているものですが、今回は何故か日本のシェアについて特集されているので読んでみると面白いと思います。特に Lumia がシェア 1 位の部分とか突っ込みどころだと思います。