フラグメントによる画面遷移
このページでは、Android SDK のフラグメントを使って画面遷移を行う実装を説明します。
フラグメントは Kii Cloud の範囲外の機能です。このページではフラグメントの Kii Cloud への応用を説明していますが、実装方法はフラグメントの基本に基づいています。詳細は、専門書籍をご覧になるか、Web 上の一般的な技術情報などを検索してください。
画面遷移
Hello Thing-IF はログイン画面とコマンド画面の 2 つの画面を持っています。ログイン画面で初期登録(onboard)が完了すると、ThingIFAPI
のインスタンスを生成し、コマンド画面に遷移します。
ThingIFAPI
は Thing-IF SDK の API を実行するためのクラスです。メソッドには、コマンドの実行や、ステートの取得など、Thing Interaction Framework に対する API が用意されています。
モバイルアプリの画面遷移は、ThingIFAPI
インスタンスの管理と密接な関係があります。Hello Thing-IF では画面遷移を以下の図のように実現します。LoginFragment の初期登録で ThingIFAPI
インスタンスを生成し、得られた ThingIFAPI
を CommandFragment まで中継します。同時に、プロセス再起動後も保持し続けるようにシリアライズなどの管理を行います。
MainActivity のユーザーインターフェイスでは、R.id.main
として、フラグメントを使った画面切り替え用の領域を確保しています。モバイルアプリの起動時、MainActivity の onCreate()
では、以下のように LoginFragment を設定します(条件による画面遷移の実装方法は後述の 手順 2 で示します)。
public class MainActivity extends AppCompatActivity implements LoginFragment.OnFragmentInteractionListener {
protected void onCreate(Bundle savedInstanceState) {
......
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.main, LoginFragment.newInstance());
transaction.commit();
......
}
}
この後、図の 1~4 に従って、以下のような処理を行います。
1. 初期登録完了時の中継処理
これは、図の「1. 初期登録完了時 ThingIFAPI を渡す」の処理です。
LoginFragment ではログイン画面の処理を実装します。ONBOARD ボタンがクリックされると、初期登録の処理を行い、結果として ThingIFAPI
のインスタンスが生成されます。これを MainActivity 経由で中継します。
中継処理を実現するため、LoginFragment では OnFragmentIntentListener
インターフェイスを定義します。ここでは初期登録完了時に ThingIFAPI
インスタンスを渡すためのメソッド onThingIFInitialized()
を定義しています。このインターフェイスは MainActivity が実装します。
public class LoginFragment extends Fragment {
......
public interface OnFragmentInteractionListener {
void onThingIFInitialized(ThingIFAPI api);
}
}
public class MainActivity extends AppCompatActivity implements LoginFragment.OnFragmentInteractionListener {
private ThingIFAPI mApi;
@Override
public void onThingIFInitialized(ThingIFAPI api) {
mApi = api;
......
}
}
LoginFragment では OnFragmentInteractionListener を以下のように利用します。これは、Android Studio で Fragment クラスを新規作成すると作成されるテンプレートのとおりです。
public class LoginFragment extends Fragment {
private OnFragmentInteractionListener mListener;
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnFragmentInteractionListener) {
mListener = (OnFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener.");
}
}
@OnClick(R.id.buttonOnboard)
void onOnboardClicked() {
......
mAdm.when(api.initializeThingIFApi(getContext(), username, userPassword, vendorThingID, thingPassword)
).then(new DoneCallback<ThingIFAPI>() {
@Override
public void onDone(ThingIFAPI api) {
if (mListener != null) {
mListener.onThingIFInitialized(api);
}
......
}
});
......
}
}
LoginFragment の onAttach()
は MainActivity と LoginFragment が紐付いた際のイベントです。ここで、mListener
に MainActivity の OnFragmentInteractionListener インターフェイスを保持しておきます。これによって、LoinFragment から MainActivity に ThingIFAPI
を引き渡す経路が形成されます。
ONBOARD ボタンがクリックされると、Butter Knife によって onOnboardClicked()
メソッドが呼び出されます。ここでは、api.initializeThingIFAPI()
メソッドによって初期登録を実行します。成功時には Promise によって onDone()
が実行されます。最終的に、初期登録で得られた ThingIFAPI インスタンス api
を、mListener.onThingIFInitialized(api);
によって、MainActivity に渡します。
2. MainActivity での保持
これは、図の「2. 保持/シリアライズ管理」の処理です。
MainActivity では、得られた ThingIFAPI
インスタンスを mApi
フィールドで保持します。また、シリアライズ機能を実装して、再起動時にもインスタンスが保持され続けるように管理します。
モバイルアプリの起動時には、シリアライズの結果から ThingIFAPI
インスタンスを復元できたかどうかによって、画面遷移を決定します。
これらの処理を MainActivity から抜粋すると、以下のようになります。
public class MainActivity ... {
private static final String BUNDLE_KEY_THING_IF_API = "ThingIFAPI";
private ThingIFAPI mApi;
@Override
protected void onCreate(Bundle savedInstanceState) {
......
if (savedInstanceState != null) {
// restore ThingIFAPI from the Bundle
mApi = savedInstanceState.getParcelable(BUNDLE_KEY_THING_IF_API);
}
if (mApi == null) {
// restore ThingIFAPI from the storage
try {
mApi = ThingIFAPI.loadFromStoredInstance(getApplicationContext());
} catch (StoredThingIFAPIInstanceNotFoundException e) {
mApi = null;
}
}
if (savedInstanceState == null) {
// create ui elements
if (mApi == null) {
// if mApi has not been set, restart the login page
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.main, LoginFragment.newInstance());
transaction.commit();
} else {
// if mApi has already been set, skip the login page
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.main, CommandFragment.newInstance(mApi));
transaction.commit();
......
}
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(BUNDLE_KEY_THING_IF_API, mApi);
}
}
onCreate()
での ThingIFAPI
の復元は、以下の 2 段階で行っています。
プロセス再起動時の復元
Android では、モバイルアプリがバックグラウンドに遷移したり言語設定が変更されたりした場合に Bundle を使って
ThingIFAPI
インスタンスを保存/復元する処理を行います。ThingIFAPI
は、Parcelable インターフェイスを実装しているため、Bundle に保存できます。アクティビティが破棄されるとき、
onSaveInstanceState()
の処理でThingIFAPI
インスタンスをパラメーターBUNDLE_KEY_THING_IF_API
で保存します。onCreate()
ではこれを復元します。復元した結果はmApi
に保持します。新規起動時のデシリアライズ
savedInstanceState.getParcelable()
を行ってもmApi
が null の場合、Bundle からの復元に失敗しています。その場合は、ここでの処理を行います。バックグラウンドに Hello Thing-IF モバイルアプリがない状態で、ホーム画面からプロセスが新規に起動されると、
savedInstanceState
は null の状態で起動してif (mApi == null) {
のロジックに入ります。このとき、前回の ThingIFAPI インスタンスを SharedPreferences から復元する処理を試みます。
ThingIFAPI.loadFromStoredInstance()
メソッドを実行するとシリアライズされたThingIFAPI
インスタンスを復元できます。Thing-IF SDK の内部では、ThingIFAPI
が作成または更新されたタイミングで、自動的に SharedPreferences に保存されています。onCreate()
では、これをmApi
に復元します。モバイルアプリのインストール直後などは SharedPreferences に保存されていないため、API は StoredThingIFAPIInstanceNotFoundException 例外をスローします。この場合、
mApi
は null の状態を保持します。
次に、遷移先画面を決めます。
Activity の onCreate()
では、savedInstanceState != null
のとき、内部のビューは OS によって自動的に再生成されます。この場合、ビューの作成処理は不要です。たとえば、画面を回転した場合が該当します。
savedInstanceState
が null の場合、ビューの再作成が必要です。ここでは、復元した mApi
の状態によって遷移先の画面を決めます。mApi
が null の場合は LoginFragment を作成してログイン画面に遷移します。mApi
が復元できた場合は初期登録済みのため、CommandFragment を作成してコマンド画面に遷移します。
この実装では、 ThingIFAPI
が SharedPreferences に保存された後は、モバイルアプリのデータを削除しない限り、必ずコマンド画面が開いてしまいます。アカウントの再設定ができないと不便であるため、メニューから LoginFragment を表示できる処理も用意しています。詳細は、MainActivity の onCreateOptionsMenu()
を参照してください。
3.コマンド画面への中継
これは、図の「3. フラグメント初期化パラメーターとして ThingIFAPI を渡す」の処理です。フラグメントではパラメーターの渡し方に注意が必要です。
すでに見たように、MainActivity が作成されて ThingIFAPI
インスタンスが有効な場合、onCreate()
ではコマンド画面へ遷移します。ここでは以下のように CommandFragment に mApi
を渡してフラグメントを作成し、ビューに設定しています。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
......
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.main, CommandFragment.newInstance(mApi));
transaction.commit();
......
}
また、1. 初期登録完了時の中継処理 で、MainActivity の onThingIFInitialized
が呼び出された場合も、同様に CommandFramgent を作成します。
@Override
public void onThingIFInitialized(ThingIFAPI api) {
mApi = api;
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.main, CommandFragment.newInstance(mApi));
transaction.commit();
......
}
CommandFragment は、ThingIFAPI
を初期化時の引数に取ります。ただし、Fragment クラスは Android によって任意のタイミングで自動的に再作成されるため、new CommandFragment(mApi);
のようにコンストラクタで引数を渡すことはできません。
これを解決するため、以下のように Bundle 経由でパラメーターを渡すように実装します。これは、Android Studio で Fragment クラスを新規作成したときのテンプレートに記載されている実装方法と同じです。
public class CommandFragment extends Fragment {
private static final String ARG_THING_IF_API = "ThingIFAPI";
private ThingIFAPI mApi;
public CommandFragment() {
}
public static CommandFragment newInstance(ThingIFAPI api) {
CommandFragment fragment = new CommandFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_THING_IF_API, api);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mApi = getArguments().getParcelable(ARG_THING_IF_API);
}
}
}
newInstance
メソッドでは、CommandFragment のインスタンス生成時に、ARG_THING_IF_API
パラメーターとして引数の ThingIFAPI
インスタンスを格納します。
onCreate()
では、設定した ARG_THING_IF_API
パラメーターから ThingIFAPI
インスタンスを復元して、クラスのフィールドで保持します。
このように実装することで、CommandFragment が OS によって自動的に再作成されたときにも、引数で渡された ThingIFAPI
インスタンスは OS が管理しているため、復元することができます。
4. CommandFragment での利用
これは、図中の「4. 保持/利用」に相当する処理です。
すでに見たように、CommandFragment では、ThingIFAPI インスタンスがクラスのフィールドとして保持されています。
public class CommandFragment extends Fragment {
private ThingIFAPI mApi;
......
}
コマンド画面でのコマンドの送信ボタンやステートの取得ボタンのハンドラーでは、この ThingIFAPI
インスタンスの API を呼び出すことで Thing-IF SDK の機能を利用することができます。
処理の詳細は、コマンド画面の実装 で説明します。
ProgressDialogFragment の利用
Hello Thing-IF では、処理中を表すダイアログとして、独自のクラス ProgressDialogFragment を使用しています。このクラスは DialogFragment の派生クラスとして実装しています。
Kii Cloud の API 呼び出しのように、ネットワークアクセスなどで時間がかかる処理で使用します。時間がかかる処理は、以下のように実装します。
// Open ProgressDialogFragment
ProgressDialogFragment.show(getActivity(), getFragmentManager(), R.string.add_user);
// Do something.
......
// Close ProgressDialogFragment
ProgressDialogFragment.close(getFragmentManager());
show
のメソッドには、ダイアログのタイトルとメッセージに使用される文字列リソースを渡します。ProcessDialogFragment は画面中に 1 つしか表示されない想定のため、スタティックメソッドの show
と close
で表示と破棄を行うことができます。
非同期処理で ProcessDialog を表示中に画面を回転させると、アクティビティが再作成されるため、非同期処理完了時に ProcessDialogFragment にアクセスできなくなります。これを防止するため、フラグメントの開始時に ProcessDialogFragment を閉じる処理を実行しています。
@Override
public void onStart(){
super.onStart();
ProgressDialogFragment.close(getFragmentManager());
}
次のページ以降に示すサンプルコードでは、単純化のため、ProgressDialogFragment 関連のコードは省略しています。
次は...
ログイン画面の処理について説明します。API の使用方法などを見ていきます。
ログイン画面の実装 に移動してください。
より詳しく学びたい方へ
フラグメントを使ったモバイルアプリの実装方法は、Kii Balance チュートリアルの 実装技術の紹介(フラグメント) でも詳細を説明しています。
初期化済みの
ThingIFAPI
を保存または復元する実装の詳細は 初期化済みの情報の保存と復元 を参照してください。複数の
ThingIFAPI
インスタンスを使って複数の Thing または複数のスキーマを扱うことができます。これには、ThingIFAPI
インスタンスのタグ機能を利用します。実装方法は、初期化済みの情報の保存と復元(ゲートウェイ) を参考にしてください。Kii Cloud SDK の機能を併用する場合、
ThingIFAPI
に加えて、ログイン中のユーザーのアクセストークンも保存する必要があります。実装方法は Android での実装における注意点 および ログインとアクセストークン を参照してください。