少し複雑な操作が要求される画面を作る機会があったので、こんな感じに状態を表現できるかなということで試してみました。
作るもの
- お試しなので単純にAPIを読んでリストに表示するだけの画面です。APIは何でもよかったのですが、ATNDさんのイベントを引っ張っています。
状態遷移
- こんな感じ
- 初期状態から始まって、表示するデータを持ってなければ、準備中に状態遷移してAPIを呼んで取ってきたデータを表示して、入力待ちに状態遷移。初期状態時にすでにデータを持っていればそのまま表示して入力待ちに状態遷移、というイメージ。
登場するクラス
- MainFragment ・・・ stateを管理するだけのクラス。ライフサイクルやイベントに応じて保持しているstateの対応するメソッドを呼ぶ。
- MainFragmentLogic ・・・ Viewの操作やアプリの挙動などを定義する。
-
MainFragmentState ・・・ Fragmentの状態を表現するenum。各状態は必要に応じてメソッドをオーバライドする。
- INIT : 初期状態
- GETTING_READY : 準備中
- IDLE : 入力待ち
- PAUSE : 停止状態
例えばINITの場合
INIT {
@Override
public void onActivityCreated(MainFragment fragment, Bundle savedInstanceState) {
super.onActivityCreated(fragment, savedInstanceState);
fragment.initView();
}
@Override
public void onResume(MainFragment fragment) {
super.onResume(fragment);
if (fragment.hasEventData()) {
// すでにイベントのデータを持っていたら表示して入力待ちに状態遷移
fragment.displayContents();
fragment.next(IDLE);
} else {
// 持っていなかったら準備中に状態遷移
fragment.next(GETTING_READY);
}
}
},
実装
MainFragment.java
package me.rei_m.statepatternsample.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import me.rei_m.statepatternsample.fragment.state.MainFragmentState;
import me.rei_m.statepatternsample.model.AtndModel;
public class MainFragment extends MainFragmentLogic {
public static MainFragment newInstance() {
MainFragment fragment = new MainFragment();
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
}
private MainFragmentState state = MainFragmentState.INIT;
public MainFragment() {
// Required empty public constructor
}
public void next(MainFragmentState nextState) {
state.exit(this);
state = nextState;
state.entry(this);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
state.onCreate(this, savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
state.onCreateView(this, savedInstanceState);
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
state.onActivityCreated(this, savedInstanceState);
}
@Override
public void onStart() {
super.onStart();
state.onStart(this);
}
@Override
public void onResume() {
super.onResume();
state.onResume(this);
}
@Override
public void onPause() {
super.onPause();
state.onPause(this);
}
@Override
public void onStop() {
super.onStop();
state.onStop(this);
}
@Override
public void onDestroyView() {
super.onDestroyView();
state.onDestroyView(this);
}
@Override
public void onDestroy() {
super.onDestroy();
state.onDestroy(this);
}
@Override
public void subscribe(Object o) {
if (o instanceof AtndModel.AtndEventLoadedEvent) {
state.subscribe(this, (AtndModel.AtndEventLoadedEvent) o);
}
}
}
MainFragmentLogic.java
package me.rei_m.statepatternsample.fragment;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import android.widget.TextView;
import butterknife.Bind;
import butterknife.ButterKnife;
import me.rei_m.statepatternsample.R;
import me.rei_m.statepatternsample.event.RxBusProvider;
import me.rei_m.statepatternsample.manager.ModelLocator;
import me.rei_m.statepatternsample.model.AtndModel;
import me.rei_m.statepatternsample.view.adaptor.AtndEventListAdapter;
import rx.android.schedulers.AndroidSchedulers;
import rx.subscriptions.CompositeSubscription;
abstract public class MainFragmentLogic extends Fragment {
@Bind(R.id.list_atnd_event)
ListView listAtndEvent;
@Bind(R.id.text_list_atnd_empty)
TextView emptyView;
@Bind(R.id.text_list_atnd_error)
TextView errorView;
private CompositeSubscription compositeSubscription;
private ProgressDialog progressDialog;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, container, false);
ButterKnife.bind(this, view);
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
ButterKnife.unbind(this);
}
@Override
public void onPause() {
super.onPause();
if (compositeSubscription != null) {
compositeSubscription.unsubscribe();
compositeSubscription = null;
}
}
public void initView() {
AtndEventListAdapter listAdapter = new AtndEventListAdapter(getContext(), R.layout.list_item_atnd_event);
listAtndEvent.setAdapter(listAdapter);
emptyView.setVisibility(View.GONE);
errorView.setVisibility(View.GONE);
}
public void fetchEvent() {
compositeSubscription = new CompositeSubscription();
compositeSubscription.add(RxBusProvider.INSTANCE.get()
.toObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::subscribe));
((AtndModel) ModelLocator.get(ModelLocator.Tag.ATND)).fetch();
}
public void showProgressDialog() {
progressDialog = new ProgressDialog(getActivity());
progressDialog.setCancelable(false);
progressDialog.show();
}
public void hideProgressDialog() {
progressDialog.dismiss();
}
abstract public void subscribe(Object o);
public void displayContents() {
AtndModel atndModel = ((AtndModel) ModelLocator.get(ModelLocator.Tag.ATND));
AtndEventListAdapter listAdapter = (AtndEventListAdapter) listAtndEvent.getAdapter();
listAdapter.clear();
listAdapter.addAll(atndModel.getAtndEventList());
listAdapter.notifyDataSetChanged();
}
public void displayEmptyMessage() {
emptyView.setVisibility(View.VISIBLE);
}
public void displayErrorMessage() {
errorView.setVisibility(View.VISIBLE);
}
public boolean hasEventData() {
AtndModel atndModel = ((AtndModel) ModelLocator.get(ModelLocator.Tag.ATND));
return (0 < atndModel.getEventCount());
}
}
MainFragmentState.java
package me.rei_m.statepatternsample.fragment.state;
import android.os.Bundle;
import android.util.Log;
import me.rei_m.statepatternsample.fragment.MainFragment;
import me.rei_m.statepatternsample.model.AtndModel;
public enum MainFragmentState {
INIT {
@Override
public void onActivityCreated(MainFragment fragment, Bundle savedInstanceState) {
super.onActivityCreated(fragment, savedInstanceState);
fragment.initView();
}
@Override
public void onResume(MainFragment fragment) {
super.onResume(fragment);
if (fragment.hasEventData()) {
fragment.displayContents();
fragment.next(IDLE);
} else {
fragment.next(GETTING_READY);
}
}
},
GETTING_READY {
@Override
public void entry(MainFragment fragment) {
super.entry(fragment);
fragment.showProgressDialog();
fragment.fetchEvent();
}
@Override
public void onPause(MainFragment fragment) {
super.onPause(fragment);
fragment.next(PAUSE);
}
@Override
public void subscribe(MainFragment fragment, AtndModel.AtndEventLoadedEvent event) {
super.subscribe(fragment, event);
fragment.hideProgressDialog();
if (event.isSuccess()) {
if (fragment.hasEventData()) {
fragment.displayContents();
} else {
fragment.displayEmptyMessage();
}
} else {
fragment.displayErrorMessage();
}
fragment.next(IDLE);
}
},
IDLE {
@Override
public void onPause(MainFragment fragment) {
super.onPause(fragment);
fragment.next(PAUSE);
}
},
PAUSE {
@Override
public void onCreateView(MainFragment fragment, Bundle savedInstanceState) {
super.onCreateView(fragment, savedInstanceState);
fragment.next(INIT);
}
};
MainFragmentState() {
}
private String tag = MainFragmentState.class.getSimpleName() + "/" + name();
public void entry(MainFragment fragment) {
Log.d(tag, "entry");
}
public void exit(MainFragment fragment) {
Log.d(tag, "exit");
}
public void onCreate(MainFragment fragment, Bundle savedInstanceState) {
Log.d(tag, "onCreate");
}
public void onCreateView(MainFragment fragment, Bundle savedInstanceState) {
Log.d(tag, "onCreateView");
}
public void onActivityCreated(MainFragment fragment, Bundle savedInstanceState) {
Log.d(tag, "onActivityCreated");
}
public void onStart(MainFragment fragment) {
Log.d(tag, "onStart");
}
public void onResume(MainFragment fragment) {
Log.d(tag, "onResume");
}
public void onPause(MainFragment fragment) {
Log.d(tag, "onPause");
}
public void onStop(MainFragment fragment) {
Log.d(tag, "onStop");
}
public void onDestroyView(MainFragment fragment) {
Log.d(tag, "onDestroyView");
}
public void onDestroy(MainFragment fragment) {
Log.d(tag, "onDestroy");
}
public void subscribe(MainFragment fragment, AtndModel.AtndEventLoadedEvent event) {
Log.d(tag, "subscribe/AtndEventLoadedEvent");
}
}
おまけ
- 上記のコードはこちらに。一気に書いたのでちょっと変なところがあるかも。。。
- 状態はEnumで表現しましたが、親クラスをabstractとしたり、interfaceにする形でも同じようにできると思います。