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

しばやん雑記

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

Xamarin.Android で Runtime Permissions を実装した時にはまったのでメモ

Xamarin Android

最近は Xamarin.Android も少しだけ弄っていますが、Android 6.0 で追加された Runtime Permissions に対応している時に、よく分からない挙動ではまったのでメモしておきます。

その挙動とは RequestPermissions で複数のパーミッションの確認を行い、結果として全てのパーミッションが取れているのかを LINQ でサクッと確認しようとした時でした。ちなみに以下のようなコードを書きました。

[Activity(Label = "PermissionTest", MainLauncher = true, Icon = "@mipmap/icon")]
public class MainActivity : Activity, ActivityCompat.IOnRequestPermissionsResultCallback
{
    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        // Set our view from the "main" layout resource
        SetContentView(Resource.Layout.Main);

        // Get our button from the layout resource,
        // and attach an event to it
        Button button = FindViewById<Button>(Resource.Id.myButton);

        button.Click += (sender, e) =>
        {
            ActivityCompat.RequestPermissions(this, new[]
            {
                Manifest.Permission.WriteExternalStorage, Manifest.Permission.Camera
            }, 0);
        };
    }

    public void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
    {
        if (grantResults.All(x => x == Permission.Granted))
        {
            new AlertDialog.Builder(this)
                           .SetTitle("Runtime Permission")
                           .SetMessage("All Granted")
                           .SetPositiveButton("OK", (_, __) => {  })
                           .Show();
        }
    }
}

実行すると、iOS のようにパーミッションの確認ダイアログが表示されます。今回の場合は複数のパーミッションを要求しているので、複数回表示されます。

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

そして OnRequestPermissionsResult の中で grantResults が全て Permission.Granted かどうかを確認したつもりですが、実行してみるとアプリケーションが例外で終了してしまいました。

しかも、その例外の内容がとても不思議でした。

[Mono] Assembly Ref addref PermissionTest[0xad36ad80] -> System.Core[0xad36baa0]: 3
[] System.Int32[] doesn't implement interface System.Collections.Generic.IEnumerable<Android.Content.PM.Permission>
[libc] Fatal signal 6 (SIGABRT), code -6 in tid 1807 (.permissiontest)

grantResults は Permission の配列なはずですが、実際には int の配列として渡されてきているようです。デバッガーで止めて確認すると、本当に何故か int が渡されていました。

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

Xamarin.Android の不具合なのかも知れませんが、とりあえず Permission にキャストし直すことにしました。

public void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
{
    if (grantResults.Cast<Permission>().All(x => x == Permission.Granted))
    {
        new AlertDialog.Builder(this)
                       .SetTitle("Runtime Permission")
                       .SetMessage("All Granted")
                       .SetPositiveButton("OK", (_, __) => {  })
                       .Show();
    }
}

これで All がちゃんと動作するので、パーミッションが全て取れていればダイアログが出るようになりました。

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

かなり気持ち悪い挙動ですが、何度も触る部分では無いので妥協しました。Android 6.0 だけではなく、以前のバージョンでも同じ挙動になるみたいなので、微妙に注意が必要な気がします。

Xamarin で Razor Template が使えることを知らなかった

Xamarin

ここ最近はずっと Xamarin.iOS を使ってアプリの開発を少しやっているのですが、その途中で新規作成ダイアログに Preprocessed Razor Template という項目を見つけたので試してみました。

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

Razor 以外には T4 も使えるみたいですが、個人的には Razor で十分な気がしています。

ちゃんと本家にドキュメントもありました。名前の通り Razor で書いたテンプレートを予め C# にコンパイルしておく機能のようです。Razor Engine とは異なり事前にコンパイルされるので、とても高速なはずです。

Building HTML views using Razor Templates - Xamarin

アプリケーション側で WebView を使って動的にページを作成して表示する場合に、Razor でテンプレートとモデルを作成しておけば、MVC アプリケーションと同様にレンダリングした結果を得ることができます。

そして気が付いていなかったのですが、プロジェクトテンプレートにある WebView App を選ぶと、Razor Template を使ってプロジェクトが作成されるみたいです。

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

説明をよく見ると、ちゃんと Razor Template Engine を使うと書いてあります。

実際にプロジェクトを作成すると、MVC アプリケーションのようなフォルダ構成になっていました。

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

cshtml を編集し、保存したタイミングで C# へのコンパイルが行われるので、特にコンパイルを意識することなく書けてとても良い感じです。

プリコンパイル結果はファイルと同名のクラスとして出力されるので、そのクラスをインスタンス化して GenerateString を呼び出すだけという簡単な API です。

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

別に HTML の生成に限らず、Razor Template は文字列の生成に使うことができるので、プリコンパイルされる汎用的なテンプレートエンジンだと考えると応用範囲がかなり広がりそうです。

もっと早く知っていれば、楽を出来た部分がかなり多かったなぁと思いました。

おまけ : Razor のプリコンパイル

ここまで Xamarin の話でしたが、Visual Studio も大昔から拡張機能をインストールすることで、同様に Razor のプリコンパイルを行えます。成り立ちの関係から、こちらは MVC 寄りになります。

Generator として Template を選択すると、Xamarin に近い形でのプリコンパイルが行われます。

Xamarin.iOS でも Application Insights を使ってみる

Xamarin iOS Azure Application Insights

最近は @normalian が Application Insights SDK for Java を弄っていたので、何となく iOS で Application Insights SDK を使ってみようと思いました。

ちゃんと Microsoft から iOS 向けの SDK が公開されています。

Microsoft/ApplicationInsights-iOS · GitHub

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

残念なことに、まだ Xamarin で使えるバインディングが無さそうだったので、自作して試してみました。

追記

Microsoft から Xamarin 向けの Application Insights SDK がリリースされました。

この記事の後半は独自にライブラリへのバインディングを作る際の参考にしてください。

バインディングを作成

Xamarin Studio で iOS 向けのバインディングプロジェクトを作成し、Objective Sharpie で定義をある程度自動生成するという流れです。つまり、伊勢さんの記事と同じことをやります。

いつからなのかは分かりませんが、Objective Sharpie 自体が GUI では無くなったみたいなので、頑張ってコマンドを叩いて定義を作成していきます。

Objective Sharpie - Xamarin

幸いなことに、Application Insights SDK for iOS は Framework として提供されているので、ダウンロードした Framework に対して 1 回コマンドを叩くだけです。

sharpie bind -framework ApplicationInsights.framework -sdk iphoneos8.3

sdk のパラメータはインストール済みの SDK に合わせて指定します。これで自動生成されました。

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

ApiDefinition.cs と StructsAndEnums.cs が同じディレクトリに作成されているので、この中身をバインディングプロジェクトにコピーします。名前空間が付いていないので、ファイルコピーだと面倒です。

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

他には Framework からスタティックライブラリをコピーする必要がありますが、とりあえず省略してビルドを通すように頑張っていきます。

とても面倒なことに、Application Insights SDK for iOS はショートカット用にインスタンスメソッドとスタティックメソッドが、同じ名前でたくさん定義されてます。

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

このあたりの調整は面倒くさそうだったので、ひとまずはスタティックなメソッドがあるものはそれを優先して、インスタンスメソッドはコメントアウトしておくことにします。

作成したバインディングプロジェクトを GitHub で公開したので、適当にビルドしてください。

ビルドが無事に成功すると dll が生成されます。何と無く Release ビルドにしておきました。

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

この dll を Xamarin.iOS アプリケーション側の参照に追加することで、やっと Application Insights が使えるようになります。次は実際にアプリケーションに組み込んでみます。

アプリケーションに組み込む

適当に Xamarin.iOS アプリケーションを作成しておき、参照の編集ダイアログからついさっきビルドした ApplicationInsights.dll を参照に追加しておきます。

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

これで SDK が使えるようになったので、AppDelegate.cs に Application Insights の初期化コードを追加します。

public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
    MSAIApplicationInsights.SetupWithInstrumentationKey("...");
    MSAIApplicationInsights.Start();

    return true;
}

InstrumentationKey は Application Insights を作成すると取得できます。疲れてきたので Application Insights を作る部分は紹介しません。

起動したかどうかだけというのも面白くないので、もう少し AppDelegate.cs にコードを追加します。よく使いそうなトレースメッセージを吐き出すコードを仕込んでみます。

public override void DidEnterBackground(UIApplication application)
{
    MSAITelemetryManager.TrackTraceWithMessage("Enter Background");
}

public override void WillEnterForeground(UIApplication application)
{
    MSAITelemetryManager.TrackTraceWithMessage("WillEnterForeground");
}

これで準備ができたので、実際にアプリケーションをシミュレーターで動かして、本当にプレビューポータルの Application Insights から見れるのか確かめてみます。

プレビューポータルから確認

Azure のプレビューポータルから該当する Application Insights を選んで表示してみました。ちゃんとユーザーとセッション数、そしてクラッシュログも取れていることが確認できますね。

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

ブレードの下の方にある Diagnostic search を開くと、アプリケーションから送信されてきた情報を一覧で表示したり、絞り込んで確認することができます。

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

仕込んでおいたメッセージが送信されていることも確認できました。絞り込みが結構便利そうです。

クラッシュログも取得できていましたが、dSYM のアップロードに対応していないみたいなので、スタックトレースやメッセージに関しては不完全な状態ですね。

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

まだ beta3 ですし今後に期待しておきたいと思います。それよりも PCL 化してくれたら、わざわざバインディングを作る必要もないのに。と少し思いました。*1

*1:エラーハンドリング周りは PCL では難しいとは思いつつも要望したい