Subscribeは累積する
Xamarin.Formsにはわりと気軽に画面間でデータ(オブジェクト)のやりとりができるMessagingCenter
というものがあります。
これは静的クラスでデータを受け取りたい側でSubscribe
メソッドで受け取る準備(購読)をし、送りたい側でSend
メソッドでデータを送れば、購読先でデータを受け取ることができるので、とても便利です。
さて、このMessagingCenter.Subscribe
メソッドですが、呼び出すたびに購読
が累積していきます。
例えば、以下のように2回購読したとしましょう。
これはメッセージが届いたらダイアログを表示させるだけのものです。
public MainPage()
{
InitializeComponent();
MessagingCenter.Subscribe<MainPage, string>(this, "Greeting", (sender, msg) => {
DisplayAlert("Message1", msg+"1", "OK");
});
MessagingCenter.Subscribe<MainPage, string>(this, "Greeting", (sender, msg) => {
DisplayAlert("Message2", msg+"2", "OK");
});
}
で、例えば以下のようにボタンが押されたタイミングでSend
メソッドでメッセージを送ると、2回ダイアログが表示されます。
private void Button_Clicked(object sender, EventArgs e)
{
MessagingCenter.Send<MainPage, string>(this, "Greeting", "Hello");
}
それは正しい動作なのですが、Subscribe
を累積したくない場合はUnsubscribe
して購読を解除する必要があります。
購読解除するときの注意点
ざっくりいうと、購読を適切に解除するにはUnsubscribe
メソッドを用いるのですが、ここで注意が必要なことは型引数と引数が解除したい購読と同じであることです。
public static void Unsubscribe<TSender,TArgs> (object subscriber, string message) where TSender : class;
例えば上の例で言えば、以下の購読を解除したい場合、
MessagingCenter.Subscribe<MainPage, string>(this, "Greeting", (sender, msg) => {
DisplayAlert("Message1", msg+"1", "OK");
});
購読解除は以下のようになります。
MessagingCenter.Unsubscribe<MainPage, string>(this, "Greeting");
しかし、ここでもう一つ要注意なのがsubscriber
引数です。
公式のドキュメントではSubscribe
でもUnsubscribe
でも以下のように書かれているため、さしあたりthis
にしていると思います。
パラメーター
subscriber
Object
メッセージをサブスクライブしているオブジェクト。 通常、サブスクライブしているオブジェクト内で使用される this キーワードによって指定されます。
多くの場合これは正しく機能するのですが、プログラムによってはthis
が毎回異なる場合があり、解除したい購読を正しく指定できずにどんどん購読が累積していくことになります。
例えば、毎回new
されるオブジェクトの中で購読や解除をしようとする場合です。
subscriber
引数には、変化しないインスタンスを指定する必要があります。
this
が変化しないインスタンスであればそれを使用すればよいし、そうでなければ変化しないインスタンスを用意(例えばシングルトンなオブジェクト)してそれを使用する必要があるようです。
ちなみに
Xamarin.Forms 4.0
(現時点ではPreview)には新しいコンテナのShell
がありますが、これをプロジェクトテンプレートから作成すると、このテンプレートで作成されるプロジェクトは購読の解除がうまくできずに累積してしまいます。
ItemsViewModel
クラスで以下のように購読と解除を行うように変更するとうまくいきます。
public ItemsViewModel()
{
Title = "Browse";
Items = new ObservableCollection<Item>();
LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());
MessagingCenter.Unsubscribe<NewItemPage,Item>(MessagingCenter.Instance, "AddItem");
MessagingCenter.Subscribe<NewItemPage, Item>(MessagingCenter.Instance, "AddItem", async (obj, item) =>
{
var newItem = item as Item;
Items.Add(newItem);
await DataStore.AddItemAsync(newItem);
});
}