userNotificationCenter:willPresentNotification:withCompletionHandler:が呼ばれないとき

Firebaseに渡すcontent_availableというパラメータがtrueになっていないかを確認する。

Firebase公式ドキュメント

iOS では、このフィールドを使用して APNs ペイロードで content-available を表します。通知やメッセージの送信時にこのフィールドが true に設定されている場合、アクティブでないクライアント アプリのスリープ状態が解除されます。Android では、既定でデータ メッセージによってアプリのスリープ状態が解除されます。Chrome では現在サポートされていません。

上記の記述からして一見フォアグラウンド通知とは関係なさそうだが、実際の挙動を確認してみると、これがtrueの場合はアプリがフォアグラウンドだろうがバックグラウンドだろうが同じメソッドが呼ばれるということのようだ。

で、なぜかデフォルトのwillPresentNotification…ではなく、iOS10ではバックグラウンド通知時に呼ばれるdidReceivedNotificationResponse…がフォアグラウンドでも呼ばれてしまう。willPresentNotification…は呼ばれない。

content_availableをfalseにしたら無事willPresentNotification…が動いた。

ものすごく分かりにくい。これのせいで丸一日くらい悩んだ。

しかもcontent_availableをfalseにしても普通にスリープ中でも通知が表示されるし。。何のためのパラメータなのかわからない。

expectの使い方メモ

ftpしか使えないサーバーがあって、しかもユーザー名に@が入ってたりとかパスワードに:が入ってたりとかして嫌がらせのように一行のエイリアスが作れなかった。もしかしたら一行で接続する方法あるのかもしれないが知識不足でできなかった。ので、expectを書くことにした。

#!/usr/bin/expect

set NAME "hogehoge@hogehoge"
set PASSWORD "fugafuga:piyopiyo"

set timeout 5

spawn env LANG=C ftp example.com
expect {
    "Name" {
        send ${NAME}\n
        exp_continue
    }
    "Password:" {
        send ${PASSWORD}\n
        exp_continue
    }
}

expect {
    "ftp>" {
        interact
        exit 0
    }
}

シェルでexpectごとスクリプト化して書く方法もあるみたいだけど、複数のプロンプトだと途中で何故かとまる。書き方が悪いのだろう。
結局expectで書いて解決した。

env LANG=C というのはプロンプトをデフォルトの言語(英語)で出させるという意味らしい。

ネタ元: Linuxの対話がめんどくさい?そんな時こそ自動化だ!-expect編-

XamarinでAndroidの位置情報をバックグラウンドのServiceでとります

普通にLocationManager使っている。 世の中的にはFusedLocation使えってことなんだろうけどそうもいかない場合もきっとあるはず。

バックグラウンドで位置情報取り続けるには、 Activityクラスに加えてServiceクラス、BroadcastReceiverクラスが必要っぽい。

まずはActivityから。

public class MyActivity : Activity
{
    private Intent ServiceIntent;
    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        // ...

        ServiceIntent = new Intent(this, typeof(MyLocationService));
        StartService(ServiceIntent);

        var receiver = new MyLocationReceiver();
        RegisterReceiver(receiver, new IntentFilter());
        var handler = new MyHandler(this);
        receiver.RegisterHandler(handler);
    }

    // ...

    public class MyHandler : Handler
    {
        private MyActivity Parent;

        public MyHandler(MyActivity parent)
        {
            Parent = parent;
        }

        public override void HandleMessage(Message msg)
        {
            var bundle = msg.Data;
            var latitude = bundle.GetDouble("latitude");
            var longitude = bundle.GetDouble("longitude");
            var message = string.Format("lat: {0}, lng: {1}, latitude,
longitude");
            System.Diagnostics.Debug.WriteLine(message);

            // 取得したlatitudeとlongitudeを使って好きな処理をする
            var loc = new MyLocation(latitude, longitude);
            // ...
        }
    }
}

C#では何かを継承した匿名クラスを作れないみたいなので内部クラス作って対応してる。

MyLocationは簡単にこんな感じ。jsonシリアライズしてサーバーとか他のActivityとかとやり取りしたいのでDataContract属性つけてる。

[DataCantract]
public class MyLocation
{
    [DataMember(Name = "latitude")]
    public double Latitude;
    [DataMember(Name = "longitude")]
    public double Longitude;

    public MyLocation(double latitude, double longitude)
    {
        Latitude = latitude;
        Longitude = longitude;
    }
}

次はServiceクラス。

[Service]
public class MyLocationService : Service
{
    private static string TAG = "MyLocationService";
    private LocationManager mLocationManager = null;
    private static int INTERVAL = 1000;
    private static float DISTANCE = 0f;
    public PendingIntent mPendingIntent;

    public override Android.OS.IBinder OnBind(Android.Content.Intent intent)
    {
        return null;
    }

    public override StartCommandResult
OnStartCommand(Android.Content.Intent intent, StartCommandFlags flags,
int startId)
    {
        mLocationManager =
(LocationManager)getSystemService(Context.LocationService);
        var receiver = new Intent(this, typeof(MyLocationReceiver));
        mPendingIntent = PendingIntent.GetBroadCast(this, 0, receiver,
PendingintentFlags.UpdateCurrent);

        try
        {
            MLocationManager.RequestLocationUpdates(LocationManager.GpsProvider,
INTERVAL, DISTANCE, mPendingIntent);
        }
        catch (Exception e)
        {
            Log.Debug(TAG, e.Message);
        }

        return StartCommandResult.Sticky;
    }

    public override void OnDestroy()
    {
        mLocationManager.RemoveUpdates(mPendingIntent);
        base.OnDestroy();
    }
}

Serviceに関してはほぼここ のソースを使わせて頂きました。

で、Receiver。

[BroadcastReceiver(Enabled = true, Exported = false)]
public class MyLocationReceiver : BroadcastReceiver
{
    public const string TAG = "MyLocationReceiver";
    public static MyActivity.MyHandler Handler;

    public MyLocationReceiver()
    {}

    public override void OnReceive(Context context, Intent intent)
    {
        string locationKey = LocationManager.KeyLocationChanged;
        if (intent.HasExtra(locationKey))
        {
            Location loc = (Location)intent.Extras.Get(locationKey);
            var location = new MyLocation(loc.Latitude, loc.Longitude);

            var task = Task.Run( async () =>
            {
                // ハンドラーにメッセージ送ることでアクティビティ側で処理可能になる
                var msg = CreateLocationMessage(loc.Latitude, loc.Longitude);
                Handler.SendMessage(msg);
                // なんか非同期な処理をする。locationをjsonシリアライズしたりとかhttpで送ったりとか
            });
        }
    }

    public void RegisterHandler(MyActivity.MyHandler handler)
    {
        Handler = handler;
    }

    private Message CreateLocationMessage(double latitude, double longitude)
    {
        var msg = new Message();
        var data = new Bundle();
        data.PutDouble("latitude", latitude);
        data.PutDouble("longitude", longitude);
        msg.Data = data;
        return msg;
    }
}

ハンドラーに関してはここ を参考にさせていただきました。

正直何がどうなって動いているのかよくわからないが備忘のため書いておく。

XamarinでFragmentがinflateできなくてキレそうになった

XamarinでAndroidのFragment使おうと思って調べながら書いてたわけ。

<fragment
    android:name="com.example.MyFragment"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

当然公式ガイド (古いけど)(古いのが放置されてるというのも問題だけど)にもこんなのが載ってる

<?xml version="1.0" encoding="utf-8"?>
<fragment android:name="com.xamarin.sample.fragments.TitlesFragment"
            android:id="@+id/titles_fragment"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" />

そしたらやっぱり、android:name属性はAndroidのパッケージ名+クラス名を書くんやな、て思うじゃん?

そしたらInflateExceptionが出て全然表示できない。それで半日くらいググるのに費やしたけど解決しない。

最終的にはAndroidのパッケージ名ではなくC#のnamespaceを使ったら解決した。糞が。

<fragment
    android:name="MyApplication.Droid.MyFragment"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

なんなんだよマジで。なんでググってもここでハマった人いないの? 俺のプロジェクト名の付け方がおかしいのか? パッケージ名みたいにプロジェクト名もcom.example…とかやるべきなのか?

Android Studioならこんなことでハマることはありえない。 完全に納得がいかなかった。Xamarin辞めたい。

XamarinでAndroidのServiceクラスが使えないとき

[Android] FusedLocationProviderApi を使って位置情報を取得

を参考にVisualStudio for Mac(Xamarin)でServiceとBroadcastReceiverを使おうとしたら呼ばれない。ブレークポイントすらも止まらない。AndroidManifest.xmlに記述が抜けてるのかと思ったがそれも違う。

なぜなんだと思ってXamarinの公式サンプル見たらわかった。

クラスの定義にカスタム属性つけるだけだった。。

Service
[Service]
public class MyService : Service
{
    /* ... */
}
BroadcastReceiver
[BroadcastReceiver(Enabled = true, Exported = false)]
public class MyReceiver : BroadcastReceiver
{
    /* ... */
}

これ書いとけばAndroidManifest.xmlには何も書かなくても動いた。

Xamarinはこういうくだらないけど知らないとハマるポイントが結構あってムカつく。Xamarinは生産性に寄与しない気がする。

Xamarinの良いところって結局C#で書けるということくらいだろうと思う。

正直C#よりもSwiftとKotlin使えるのであればそっち使えたほうが嬉しい。。

C#のHttpClientでPOSTしてresponse bodyを受け取る

リクエスト送信
async public Task<string> HttpPost(string url)
{
    var client = new HttpClient();
    var content = new FormUrlEncodedContent(new Dictionary<string, string>
    {
        /**
         * POSTするデータを書く
         * { "key", value }
         * のような感じで
         */
    });

    var response = await client.PostAsync(url, content);
    return await response.Content.ReadAsStringAsync();
}

responseにはStatusCodeとかHeadersも入ってる。

レスポンス取得
var url = "http://127.0.0.1:8080";
var task = Task.Run(() => {
    return HttpPost(url);
});
System.Console.WriteLine(task.Result);

ググってみたら他所のサイトのサンプルコードには

var task = await HttpPost(url);

で値が取れるかのように書いてあるところもあったけど、自分の場合は無理だった(アプリが止まった)

.NETのWEBアプリじゃなくてXamarinのスマホアプリだからだろうか。。

neovimで言語ごとのインデントとかの設定をする方法

まあマニュアル読めば書いてある話ではあるんだけど、そこらへんうまいこと日本語で解説してくれてる人がいるかと思ったらあんまりいないみたいだったので自分用にメモっておく。

1. runtimeディレクトリにftpluginディレクトリを作る

Unix系のシステムでは通常これは “~/.config/nvim” になるらしい。

mkdir -p ~/.config/nvim/ftplugin

runtimeディレクトリがわかんない場合は、vim上で

:set runtimepath?

とコマンド打てば表示される。
表示された中で一番最初に出てくるパスを普通は使うものらしい。

2. さっき作ったftpluginディレクトリに “ファイルタイプ名.vim” ファイルを作る

で、そのファイルの中身には普通にinit.vim(.vimrc)みたいにインデントとかの設定を書くわけだけど、:setじゃなくて:setlocalとしておくとそのバッファだけに設定が反映されるので他のファイルタイプに影響がないようにできるらしい。

e.g.
setlocal shiftwidth=2

同様にキーマップについてもbufferローカルな設定にしとくと良いらしい。

ftplugin/javascript.vim
let maplocalleader = ","
map <buffer> <LocalLeader>A o新しい行だよ!<ESC>

上記を設定した後に.jsのファイルを開いて",A(shift+a)“とつづけて入力すると、カーソルの下の行に「新しい行だよ!」と追加されて、ノーマルモードに戻っている。