ログイン画面の実装

次に、ログイン画面の実装を説明します。

ログイン画面は、レイアウトファイル fragment_login.xml で定義されており、クラス内の実装と次のように結びついています。

Butter Knife の利用 で示したように、これらはアノテーションによって関連付けています。

public class LoginFragment extends Fragment {
  @BindView(R.id.editTextUsername) EditText mUsername;
  @BindView(R.id.editTextUserPassword) EditText mUserPassword;
  @BindView(R.id.editTextVendorThingID) EditText mVendorThingID;
  @BindView(R.id.editTextThingPassword) EditText mThingPassword;

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
                           Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_login, container, false);
    mButterknifeUnbunder = ButterKnife.bind(this, view);
    return view;
  }

  @OnClick(R.id.buttonAddUser)
  void onAddUserClicked() {
    ......
  }

  @OnClick(R.id.buttonOnboard)
  void onOnboardClicked() {
    ......
  }
}

このページでは、ADD USER ボタンによるユーザー登録の処理と、ONBOARD ボタンによる初期登録の処理について解説します。

ユーザー登録

ログイン画面では、最終的に初期登録を行って ThingIFAPI インスタンスを得ることが目標ですが、初期登録を行うには、Thing のオーナーとなるユーザーをあらかじめ登録しておく必要があります。ここではそのユーザーを登録します。

ユーザー登録処理の実装は以下のとおりです。

private AndroidDeferredManager mAdm = new AndroidDeferredManager();

@OnClick(R.id.buttonAddUser)
void onAddUserClicked() {
  String username = mUsername.getText().toString();
  String userPassword = mUserPassword.getText().toString();

  PromiseAPIWrapper api = new PromiseAPIWrapper(mAdm, null);
  mAdm.when(api.addUser(username, userPassword)
  ).then(new DoneCallback<Void>() {
    @Override
    public void onDone(KiiUser user) {
      showToast("The user has been added.");
    }
  }).fail(new FailCallback<Throwable>() {
    @Override
    public void onFail(final Throwable tr) {
      showToast("Failed to add the user: " + tr.getLocalizedMessage());
    }
  });
}

初めに、ユーザーインターフェイスからユーザー名 username とパスワード userPassword を文字列で取得します。

次に、PromiseAPIWrapper を用意して addUser() を呼び出します。JDeferred による Promise の利用 に示したように、JDeferred を使って作業スレッドで Kii Cloud SDK の API を実行します。成功時には onDone() が、失敗時には onFail() が呼び出されます。それぞれトーストを使って成功、失敗のメッセージを表示します。

JDeferred による Promise の利用 に示したように、これらのコードの大半は定型的な記述です。when() に指定しているメソッド、onDone()onFail() の処理のみがアプリケーション固有のコードです。

PromiseAPIWrapper での addUser() の実装は次のとおりです。

public Promise<KiiUser, Throwable, Void> addUser(final String username, final String password) {
  return mAdm.when(new DeferredAsyncTask<Void, Void, KiiUser>() {
    @Override
    protected KiiUser doInBackgroundSafe(Void... voids) throws Exception {
      KiiUser.Builder builder = KiiUser.builderWithName(username);
      KiiUser user = builder.build();
      user.register(password);
      return user;
    }
  });
}

引数として、ユーザー名 username とパスワード password を受け取ります。それぞれ、final 宣言されているため、作業スレッドで動作する doInBackgroundSafe() メソッドに値を中継できます。

作業スレッド内では、KiiUser.Builder によって KiiUser を作成し、register() メソッドで Kii Cloud にユーザーを作成します。上記の処理は ユーザー作成 に示している実装から不要な部分を取り除いたものです。

Promise の戻り値としては KiiUser を返しますが、LoginFragment の onDone の実装のとおり、返された KiiUser は未使用です。

なお、トーストを表示する部分のコードは以下のとおりです。非同期処理の途中にアクティビティが破棄された場合でもクラッシュしないように null チェックを行います。同様の実装は CommandFragment でも用意しています。

private void showToast(String message) {
  if (getContext() == null) {
    return;
  }
  Toast.makeText(getContext(), message, Toast.LENGTH_LONG).show();
}

初期登録

初期登録は、ユーザーと Thing を紐付ける処理です。初期登録を行うため、ユーザーの名前とパスワード、操作先の Thing の vendorThingID とパスワードを入力して ONBOARD ボタンをクリックします。

ONBOARD ボタンがクリックされたときの処理は以下のとおりです。

private AndroidDeferredManager mAdm = new AndroidDeferredManager();

@OnClick(R.id.buttonOnboard)
void onOnboardClicked() {
  String username = mUsername.getText().toString();
  String userPassword = mUserPassword.getText().toString();
  String vendorThingID = mVendorThingID.getText().toString();
  String thingPassword = mThingPassword.getText().toString();

  PromiseAPIWrapper api = new PromiseAPIWrapper(mAdm, null);
  mAdm.when(api.initializeThingIFAPI(getContext(), username, userPassword, vendorThingID, thingPassword)
  ).then(new DoneCallback<ThingIFAPI>() {
    @Override
    public void onDone(ThingIFAPI api) {
      if (getActivity() == null) {
        return;
      }
      if (mListener != null) {
        mListener.onThingIFInitialized(api);
      }
    }
  }).fail(new FailCallback<Throwable>() {
    @Override
    public void onFail(final Throwable tr) {
      showToast("Failed to onboard the thing: " + tr.getLocalizedMessage());
    }
  });
}

初めに、ユーザーと Thing の情報を、usernameuserPasswordvendorThingIDthingPassword に文字列で取得します。

次に、取得したこれらの情報を、initializeThingIFAPI() メソッドに渡して初期登録を行います。

初期登録に成功すると、Promise の戻り値として ThingIFAPI のインスタンスが onDone() メソッドに渡されます。フラグメントによる画面遷移 のサンプルコードで示したとおり、初期登録完了時の ThingIFAPI インスタンスは MainActivity に中継されます。この中継処理が onDone() メソッドにある mListener.onThingIFInitialized(api); です。

初期登録に失敗すると、onFail() メソッドでトーストを表示します。

PromiseAPIWrapper での initializeThingIFAPI() の実装は次のとおりです。

public class PromiseAPIWrapper {
  public Promise<ThingIFAPI, Throwable, Void> initializeThingIFAPI(final Context context, final String username, final String userPassword, final String vendorThingID, final String thingPassword) {
    return mAdm.when(new DeferredAsyncTask<Void, Void, ThingIFAPI>() {
      @Override
      protected ThingIFAPI doInBackgroundSafe(Void... voids) throws Exception {
        KiiUser ownerUser = KiiUser.logIn(username, userPassword);
        String userID = ownerUser.getID();
        String accessToken = ownerUser.getAccessToken();

        assert userID != null;
        TypedID typedUserID = new TypedID(TypedID.Types.USER, userID);
        assert accessToken != null;
        Owner owner = new Owner(typedUserID, accessToken);

        SchemaBuilder sb = SchemaBuilder.newSchemaBuilder(HelloThingIF.THING_TYPE, HelloThingIF.SCHEMA_NAME, HelloThingIF.SCHEMA_VERSION, LEDState.class);
        sb.addActionClass(TurnPower.class, TurnPowerResult.class).
                addActionClass(SetBrightness.class, SetBrightnessResult.class);
        Schema schema = sb.build();

        KiiApp app = new KiiApp(HelloThingIF.APP_ID, HelloThingIF.APP_KEY, HelloThingIF.APP_SITE_THING_IF);
        ThingIFAPIBuilder ib = ThingIFAPIBuilder.newBuilder(context.getApplicationContext(), app, owner);
        ib.addSchema(schema);
        mApi = ib.build();

        JSONObject properties = new JSONObject();
        mApi.onboard(vendorThingID, thingPassword, HelloThingIF.THING_TYPE, properties);

        return mApi;
      }
    });
  }
}

コード中の定数は以下のように定められています。

public class HelloThingIF extends Application {
  ......
  public static final String THING_TYPE = "HelloThingIF-SmartLED";
  public static final String SCHEMA_NAME = "HelloThingIF-Schema";
  public static final int SCHEMA_VERSION = 1;
  ......
}

ここでは、Kii Cloud SDK の KiiUser.logIn() と、Thing-IF SDK の onboard() がネットワークにアクセスする API で、どちらもブロッキング API です。doInBackgroundSafe() は作業スレッドで動作するため、ユーザーインターフェイスへの影響はありません。

ここでは、順に以下の処理を行います。

  1. KiiUser のログイン

    まずは、指定されたユーザー名とパスワードを使って KiiUser.logIn() で Kii Cloud にログインします。結果として、ログインできたユーザーのユーザー ID とアクセストークンを入手できます。アクセストークンとは、Kii Cloud にアクセスするための権限を表す文字列で、このトークンがあればログイン済みのユーザーの権限で Kii Cloud/Thing Interaction Framework にアクセスできます。

  2. オーナーの定義

    ログインしたユーザーのユーザー ID とアクセストークンを使って、Thing のオーナーの情報を作成します。このオーナーが Thing の操作ユーザーです。ユーザー ID とアクセストークンから、Owner インスタンスを作成します。

  3. スキーマの定義

    コマンドとステートの設計 に示したように、Thing Interaction Framework を使ってコマンドやステートをやりとりするには、やりとりする情報の型の定義が必要です。ここでは、それらを Schema インスタンス schema に用意します。

    スキーマの定義のため、まずは Thing タイプ、スキーマ名、スキーマバージョン、ステートを扱うクラス名を指定します。Thing タイプは、将来の拡張用で Thing タイプごとに利用できる機能を定義できるようになる予定です。ステートを扱うクラス LEDState の定義はこの後で示します。

    さらにアクションとアクションリザルトを扱うクラスを addActionClass() で追加します。

    最後に build() メソッドでスキーマのインスタンス schema を取得できます。

    現在のバージョンでは、schema はローカルの Thing-IF SDK だけで参照され、サーバーへのアップロード行われません。システム全体で見ると、スキーマは設計情報という扱いです。

  4. ThingIFAPI の取得

    ここまでの情報を組み合わせて、ThingIFAPI のインスタンスを生成します。

    アプリケーションの AppID、AppKey、サーバー設置場所が設定された KiiApp インスタンスと、定義済みのオーナー owner、スキーマ schema から ThingIFAPI インスタンスを生成します。

  5. 初期登録の実行

    最後に、生成済みの ThingIFAPI インスタンスを使って初期登録を実行します。

    初期登録を実行すると、この ThingIFAPI インスタンスによって操作される ターゲット(コマンドの送信先やステートの取得元)が ThingIFAPI インスタンスの内部に記憶されます。以降、この ThingIFAPI インスタンスの API を呼び出すと、ターゲットとなっている Thing を操作できます。

以上の処理を実行後、生成した ThingIFAPI インスタンスを Promise の戻り値として返します。

これらの手続きは、Thing-IF SDK を使用するモバイルアプリでほぼ共通です。スキーマで登録するクラスや、オーナーの定義方法は対象となる IoT ソリューションの仕様によって変更が必要ですが、それ以外はこのサンプルコードを基本とすれば、容易に初期化の流れを実装することができます。

今回はサンプルコードをシンプルにするため、初期登録の処理は、処理全体を 1 つの Promise として定義しました。本来は、各機能ごとに Promise を定義してそれを Promise チェーンによって連続実行することでより汎用的な使い方ができます。実装方法は Thing-IF SDK の 実装上の注意点 を参考にしてください。

ステートとアクションの定義

スキーマでは、ステート、アクション、アクションリザルトのクラスを用意する必要があります。コマンドとステートの設計 で設計したスキーマを元に、これらのクラスを以下のように定義しています。

ステートを扱うクラスは以下のとおりです。TargetState のサブクラスとし、スキーマで設計したパラメーターを public フィールドで定義します。

public class LEDState extends TargetState {
  public boolean power;
  public int brightness;
  public int motion;
}

アクションとアクションリザルトは、それぞれをセットで扱います。アクションは Action のサブクラスとし、スキーマで設計したパラメーターを public フィールドで定義します。アクションリザルトは ActionResult のサブクラスとして定義します。

public class TurnPower extends Action {
  public boolean power;

  @Override
  public String getActionName() {
    return "turnPower";
  }
}
public class TurnPowerResult extends ActionResult {

  @Override
  public String getActionName() {
      return "turnPower";
  }
}
public class SetBrightness  extends Action {
  public static final int MAX_BRIGHTNESS_VALUE = 100;

  public int brightness;

  @Override
  public String getActionName() {
      return "setBrightness";
  }
}
public class SetBrightnessResult extends ActionResult {

  @Override
  public String getActionName() {
      return "setBrightness";
  }
}

アクションとアクションリザルトのクラス定義では、それぞれ getActionName() メソッドによってアクション名を返します。スキーマ定義の際、アクションとアクションリザルトは同じ名前を返すように実装しないとエラーになります。

Thing 側の実装では、定義したアクション名はアクションを識別するための文字列として利用します。

なお、MAX_BRIGHTNESS_VALUE はコマンド画面のプログラムから利用するための定数で、アクション定義とは特に関係ありません。


次は...

コマンド画面の処理について説明します。このページで紹介したステートとアクションの定義に基づいて実際に Thing Interaction Framework との間でコマンドやステートのやりとりを行います。

コマンド画面の実装 に移動してください。

より詳しく学びたい方へ

  • オーナーと初期登録の機能の詳細は 初期登録 を、実装方法の詳細は 初期化コードの実装 を参照してください。

  • ここでは、オーナーユーザーを明示的に指定する方法を挙げましたが、モバイルデバイスに紐付けてユーザーを自動的に作成する仮ユーザー(Pseudo User)の機能も用意しています。詳細はこのチュートリアルの最後にある 次のステップへのヒント で紹介します。