Login Screen Implementation

This topic explains the implementation of the login screen.

The login screen is defined in the layout file fragment_login.xml. The elements in the XML file are associated with the class implementation as below.

As seen in Using Butter Knife, those are associated through annotations.

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() {
    ......
  }
}

This topic explains the processes of user registration with the ADD USER button and onboarding with the ONBOARD button.

User Registration

The final purpose of the login screen is to get a ThingIFAPI instance by onboarding the thing. To do so, you need to register the user who is the thing owner before onboarding.

See the below code for registering the user.

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());
    }
  });
}

First, username username and passowrd userPassword are obtained as strings.

Next, addUser() is called with PromiseAPIWrapper. As seen in Using Promises through JDeferred, the APIs of the Kii Cloud SDK are called in a worker thread through JDeferred. onDone() is called at success and onFail() is called at failure. Both use the toast to show the success or failure message.

As seen in Using Promises through JDeferred, most of the lines just make up a template. Only the method specified in when() and the processes in onDone() and onFail() are application-specific.

See below for addUser() implementation in PromiseAPIWrapper.

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;
    }
  });
}

It receives username username and password password as arguments and relays the values to the doInBackgroundSafe() method. Both arguments are declared final so that the method can receive them and run in a worker thread.

KiiUser.Builder creates a KiiUser and the register() method creates a user on Kii Cloud. The above code omits unnecessary parts from the implementation shown in Signing up.

The KiiUser is returned as the return value of the promise. However, as seen in the implementation of onDone in LoginFragment, the returned KiiUser is not used.

The code to show the toast is as below. It checks if the context is null or not so that crash does not occur if the activity is destroyed before the asynchronous process completes. Similar checks are implemented in CommandFragment as well.

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

Onboarding

Onboarding associates a user with a thing. Enter the username and password of the user, and the vendorThingID and password of the thing, and click ONBOARD to perform onboarding.

Clicking ONBOARD triggers the process below.

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());
    }
  });
}

First, the user and thing information is captured in username, userPassword, vendorThingID, and thingPassword as strings.

Next, the captured information is passed to the initializeThingIFAPI() method to onboard the thing.

If onboarding succeeds, an instance of ThingIFAPI is passed to the onDone() method as the return value of the promise. As seen in the sample code in Screen Transition with Fragments, the ThingIFAPI instance is relayed to MainActivity after onboarding completes. mListener.onThingIFInitialized(api); in the onDone() method relays the ThingIFAPI instance.

If onboarding fails, the onFail() method displays the toast.

See the below code for initializeThingIFAPI() implementation in PromiseAPIWrapper.

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;
      }
    });
  }
}

Constants in the code are defined as below.

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;
  ......
}

In the above code, KiiUser.logIn() of the Kii Cloud SDK and onboard() of the Thing-IF SDK are the blocking APIs which access network. doInBackgroundSafe() runs in a worker thread and does not affect the user interface.

The following processes are performed in order.

  1. Log in KiiUser

    First, log in to Kii Cloud by calling KiiUser.logIn() with the specified username and password. As a result, the user ID and access token of the logged in user are returned. The access token represents the privilege to access Kii Cloud. You can access Kii Cloud and Thing Interaction Framework with the privilege of the logged in user if you have their access token.

  2. Define the owner

    Create the thing owner information by using the user ID and access token of the logged in user. This owner can operate the thing. An Owner instance is created from the user ID and the access token.

  3. Define the schema

    As seen in Designing Commands and State Information, you need to define a data schema (a set of data types) in order to send and receive commands and state information using Thing Interaction Framework. Prepare such a data schema in a schema instance.

    First, for schema definition, specify the thing type, schema name, schema version, and the name of the class which handles state information. The thing type is for future enhancement with which you will be able to define available functions per thing type. We will define the LEDState class which handles state information in this topic later.

    Moreover, add classes which handle actions and action results with addActionClass().

    Lastly, get a schema instance with the build() method.

    The current version of the schema is only referenced by the Thing-IF SDK on the local device and not uploaded to the server. The schema is treated as design information from the viewpoint of the entire system.

  4. Get ThingIFAPI

    Create a ThingIFAPI instance with the information obtained until now.

    Create a KiiApp instance from the AppID, AppKey, and server location as well as a ThingIFAPI instance from the defined owner and schema.

  5. Onboard the thing

    Finally, onboard the thing with the created ThingIFAPI instance.

    Onboarding saves the target in this ThingIFAPI instance. The target is the thing that the mobile app sends commands to and receives state information from. You can operate the target by calling the APIs of the ThingIFAPI instance.

Once the above processes are performed, the created ThingIFAPI instance is returned as the return value of the promise.

These procedures are almost common among mobile apps which use the Thing-IF SDK. According to the specification of your IoT solution, you will need to modify classes to register with a schema and/or the way to define the owner. Still, you can easily implement the initialization flow based on this sample code.

In this tutorial, the entire process of onboarding is defined as one promise to simplify the sample code. In a normal situation, you should create a separate promise for each function and consecutively execute promises by chaining them for code flexibility. See Implementation Guidelines of the Thing-IF SDK to learn more about implementation with promises.

Defining State Information and Actions

A schema needs classes of state information, actions, and action results. These classes are defined as below based on the schema designed in Designing Commands and State Information.

The class which handles the state information is as below. This is defined as a subclass of TargetState and has parameters defined in the schema in public fields.

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

An action and its action result are defined in a set. Actions are subclasses of Action and have parameters defined in the schema in public fields. Action results are subclasses of 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";
  }
}

Action and action result classes return the action name with the getActionName() method. When you define a schema, implement the same name as the return value in action and action result classes in a set. Otherwise, you will get an error.

The thing program uses the defined action name as a string to identify the action.

Note that MAX_BRIGHTNESS_VALUE is a constant to be used in a process in the command screen and has nothing to do with action definition.


What's Next?

Let us walk through the process of the command screen. The commands and state information introduced and defined in this topic will be communicated with Thing Interaction Framework accordingly.

Go to Command Screen Implementation.

If you want to learn more...

  • See Onboarding for more information about the owner and onboarding and Initializing and Onboarding to learn more about how to implement them.

  • This tutorial specifies the normal user as the owner but you can also use the pseudo user feature which allows you to automatically create a user to associate with the mobile device. The detail will be described in the last topic of this tutorial, Hints for the Next Step.