MVVM + Messenger パターンとは、

らへんを参照。

勉強がてら、Android(Java) で、Messenger を実装してみようとした。
要は、View 側で regist された Action を溜めておき、ViewModel 側から send(new Message()) した時に invoke させればいいんでしょ?と。

とりあえずこんな感じになると思われる。

```java common_interfaces
public interface Message {
}

public interface Action1 { public void invoke(T arg); }


```java Messenger.java
public final class Messenger {
	
	private Map<String, Action1<? extends Message>> _actions = 
			new HashMap<String, Action1<? extends Message>>();

	public <T extends Message> void register(Action1<T> action) {
		String nameOfT = /* TODO action から T を取得 */
		_actions.put(nameOfT, action);
	}

	public void send(Message message) {
		/* TODO message の型をキーにして _actions から取り出して invoke */
	}
}

これの実装中、いくつか問題にハマった。

##問題1:X<T> の T が取れない

Action を溜める時に、Message をキーにして Map に入れておけばいいでしょ、と思ったのだが、できない。 ``Action`` の ``T`` が取り出せない。

よく調べてみると

だそうです。ふむーなるほど、実行時には T は消えてしまっていると。

しかしいろいろ試していたら、こんな方法で文字列としては取り出すことができました。

```java Messenger.java
public final class Messenger {

private Map<String, Action1<? extends Message>> _actions = 
		new HashMap<String, Action1<? extends Message>>();

public <T extends Message> void register(Action1<T> action) {
	// action が使ってる Generics な型を取り出す(という意味?)。
	// action.getClass().getInterfaces(); でもいけるかと思ったら、Action1 までしか取り出せなかった。
	Type[] types = action.getClass().getGenericInterfaces();
	// 文字列化したら Action1<T> の T の部分も実際の型名が得られた。
	// ex: "hoge.mvvm.Action1<com.piyo.MyMessage>"
	String typeString = types[0].toString();

	// < > 内だけ取り出す		
	int start = typeString.indexOf("<");
	int end = typeString.lastIndexOf(">");
	String nameOfT = typeString.subSequence(start + 1, end).toString();

	_actions.put(nameOfT, action);
}

public void send(Message message) {
	/* TODO message の型をキーにして _actions から取り出して invoke */
} } ```

##問題2:<? extend T> or <? super T> ?

次の問題、今度は send の方。
_actions.get(nameOfMessage) で取り出した Action1<? extend Message> は、invoke メソッドの型が null になってて、使えませんでした。
なので仕方なく、Generics パラメータなしの Action1 で受けることに。

```java Messenger.java
public final class Messenger {

private Map<String, Action1<? extends Message>> _actions = 
		new HashMap<String, Action1<? extends Message>>();

public <T extends Message> void register(Action1<T> action) {
	// action が使ってる Generics な型を取り出す(という意味?)。
	// action.getClass().getInterfaces(); でもいけるかと思ったら、Action1 までしか取り出せなかった。
	Type[] types = action.getClass().getGenericInterfaces();
	// 文字列化したら Action1<T> の T の部分も実際の型名が得られた。
	// ex: "hoge.mvvm.Action1<com.piyo.MyMessage>"
	String typeString = types[0].toString();

	// < > 内だけ取り出す		
	int start = typeString.indexOf("<");
	int end = typeString.lastIndexOf(">");
	String nameOfT = typeString.subSequence(start + 1, end).toString();

	_actions.put(nameOfT, action);
}

@SuppressWarnings("unchecked")
public void send(Message message) {
	final String messengerTypeName = message.getClass().getName();
	
	if (!_actions.containsKey(messengerTypeName)) {
		return;
	}

	// Action1<? extends Message> だと、invoke の型が null になってしまう。
	// が、_actions は追加/取得を兼ねているので Action1<? super Message> にすることもできず…		
	// 仕方なく Generics 未使用で。
	@SuppressWarnings("rawtypes")
	Action1 action = _actions.get(messengerTypeName);
	action.invoke(message);
} } ```

とりあえず動くけど、なんかスッキリしない。。。

##ここまで実装しておいて…

を発見。
あれ、Map じゃなくて List でしたか。1回の Send で複数の Callback が走るのね。ま、いいや Android で使うだけだし。
.NET はいいなあ typeof(T) が使えて。

##使い方
だいたいこんな感じで使える。

```java usage
/** ダイアログを表示させるメッセージ */
public class DialogMessage implements Message {
public String message;
public Action1 callback;

public DialogMessage(String message, Action1<Boolean> callback) {
	this.message = message;
	this.callback = callback;
} }

/** View側 */
public class MyActivity extends Activity {

private MyViewModel _vm = new MyViewModel();

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_my);
	
	// Messenger に Message に対応する Action を登録する。
	Messenger messenger = _vm.getMessenger();
	messenger.register(new Action1<DialogMessage>() {
		@Override
		public void invoke(DialogMessage dlgMsg) {
			boolean isOk = showDialog(dlgMsg.message); // ホントは非同期なのでもう少し複雑
			if (isOk) {
				finish();
			}
			dlgMsg.callback.invoke(isOk); // VM に結果を通知する
		}
	});
}

// 終了ボタンが押されたら Finish コマンドを実行。
public void exitButton_Click(View view) {
	_vm.commandExit.execute();
} }

/** ViewModel ※ Command インターフェースの定義とかは省略 */
public class MyViewModel {
private final Messenger _messenger = new Messenger();

public Messenger getMessenger() {
	return _messenger;
}

public final Command commandExit = new Command() {
	@Override
	public void execute() {
		// ダイアログを表示する Message を送る
		_messenger.send(new DialogMessage("終了します", new Action1<Boolean>() {
			@Override
			public void invoke(Boolean pushOk) {
				// ダイアログの表示結果を受ける
				Log.d(TAG, "Pushed button is OK? -> " + pushOk);
			}
		}));
	}
}; } ```

##その他

Java
Android

published

Ads

Read more!

amay077

Microsoft MVP(Xamarin). フルリモートワーカー. Geospatial Mobile app developer. Love C#.

amay077 amay077