Screen Transition with Fragments

This topic explains the implementation of screen transition with fragments of the Android SDK.

The fragment is not a Kii component but an Android component. This topic explains how to apply fragments to your mobile app for Kii Cloud based on the basic usage of fragments. Refer to specialized books or general technical information on the Internet for more information.

Screen transition

Hello Thing-IF has two screens: the login screen and the command screen. When onboarding completes on the login screen, an instance of ThingIFAPI is created and the command screen opens.

ThingIFAPI is a class to call the APIs of the Thing-IF SDK. The class methods includes APIs to communicate with Thing Interaction Framework, such as those to execute commands and get state information.

Screen transition of the mobile app has a close relation with the management of ThingIFAPI instances. Hello Thing-IF transitions the screens as in the below figure. Onboarding in LoginFragment creates and relays a ThingIFAPI instance to CommandFragment. At the same time, Hello Thing-IF manages serialization to retain the instance in preparation for process restart.

The user interface in MainActivity reserves the area for screen transition with fragments as R.id.main. LoginFragment is set as below in onCreate() of MainActivity when the mobile app starts. We will discuss how to implement screen transition with conditions at step 2 in this topic later.

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

The following processes are performed after the above sample code. The list numbers correspond to those in the above figure.

  1. Relay the Instance after Onboarding
  2. Retain the Instance in MainActivity
  3. Relay the Instance to the Command Screen
  4. Use the Instance in CommandFragment

1. Relay the instance after onboarding

This is the first process in the figure, which passes a ThingIFAPI instance after onboarding.

LoginFragment implements the login screen process. When ONBOARD is clicked, onboarding is performed and a ThingIFAPI instance is created as a result of onboarding. This instance is relayed via MainActivity.

To relay the instance, LoginFragment has the OnFragmentIntentListener interface that has the onThingIFInitialized() method, which passes the ThingIFAPI instance after onboarding. MainActivity implements this interface.

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 uses OnFragmentInteractionListener as in the below code, which is based on the template applied in Android Studio when a Fragment class is newly created.

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

onAttach() in LoginFragment is the event which occurs when MainActivity and LoginFragment are associated. This event stores the OnFragmentInteractionListener interface of MainActivity in mListener. This creates a route to pass ThingIFAPI to MainActivity from LoginFragment.

When ONBOARD is clicked, Butter Knife calls the onOnboardClicked() method, which performs onboarding with the api.initializeThingIFAPI() method. A promise calls onDone() at success. Finally, mListener.onThingIFInitialized(api); passes the ThingIFAPI instance api, which was obtained in onboarding, to MainActivity.

2. Retain the instance in MainActivity

This is the second process in the figure, which retains and serializes the ThingIFAPI instance.

MainActivity retains the obtained ThingIFAPI instance in the mApi field. The class serializes the instance so that the instance remains available after the mobile app or the process is restarted.

When the mobile app starts, the screen to open is determined by the result of deserialization of the ThingIFAPI instance.

These processes in MainActivity are extracted as below.

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

ThingIFAPI is restored in onCreate() in the following two steps.

  1. Restore the instance when the process is restarted

    Android uses a bundle to save and restore the ThingIFAPI instance when the mobile app goes to the background or its language setting is changed.

    ThingIFAPI implements the Parcelable interface and can be saved in a bundle.

    When the activity is destroyed, the ThingIFAPI instance is saved in the parameter BUNDLE_KEY_THING_IF_API in onSaveInstanceState(). It is restored in onCreate() and retained in mApi.

  2. Deserialize the instance when the mobile app is started

    If savedInstanceState.getParcelable() returns null for mApi, it means that the instance could not be restored from the bundle. In this case, the following procceses are performed.

    If a new process is started from the home screen while the mobile app of Hello Thing-IF is not in the background, savedInstanceState is null and the mobile app executes the logic of if (mApi == null) {.

    At this time, the mobile app attempts to restore the previous ThingIFAPI instance from the shared preferences. The ThingIFAPI.loadFromStoredInstance() method restores the serialized ThingIFAPI instance. The Thing-IF SDK automatically saves ThingIFAPI in the shared preferences when the instance is created or updated. It is restored to mApi in onCreate().

    The instance has not been saved just after the mobile app is installed. In this case, the API throws the StoredThingIFAPIInstanceNotFoundException exception and mApi remains null.

Next, the screen to open is determined.

If savedInstanceState is not null in onCreate() of the activity, the view inside the activity is automatically recreated by the OS and you do not need to explicitly create the view. An an example, this happens when the screen is rotated.

Otherwise, you need to recreate the view. The sample code determines the screen to open based on the status of restored mApi. If mApi is null, LoginFragment is created and the login screen opens. If mApi is restored, CommandFragment is created and the command screen opens because the thing has been onboarded.

In this implementation, once ThingIFAPI is saved in the shared preferences, the command screen always opens unless you delete the mobile app data. You can open LoginFragment from the menu to reset the account. See onCreateOptionsMenu() of MainActivity for more information.

3. Relay the instance to the command screen

This is the third process in the figure, which passes the ThingIFAPI instance as the parameter of fragment initialization. Pay attention to the process of parameter passing to the fragment.

As seen already, if ThingIFAPI is valid in MainActivity, onCreate() opens the command screen. The sample code passes mApi to CommandFragment to create and set a fragment in the view.

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  ......
  FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
  transaction.replace(R.id.main, CommandFragment.newInstance(mApi));
  transaction.commit();
  ......
}

Similarly, CommandFragment is created when onThingIFInitialized is called in MainActivity in Process 1: Relay the Instance after Onboarding.

@Override
public void onThingIFInitialized(ThingIFAPI api) {
  mApi = api;
  FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
  transaction.replace(R.id.main, CommandFragment.newInstance(mApi));
  transaction.commit();
  ......
}

CommandFragment takes ThingIFAPI as the argument for initialization. However, you cannot call the fragment as a constractor with the argument like new CommandFragment(mApi); because Android automatically recreates the Fragment class at any timing.

To work around it, the parameter is passed via a bundle as in the below code, which is based on the template applied in Android Studio when a Fragment class is newly created.

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

The newInstance method stores the ThingIFAPI instance in the form of the ARG_THING_IF_API parameter when an instance of CommandFragment is created.

onCreate() restores the ThingIFAPI instance from the provided ARG_THING_IF_API parameter and retains it in the class field.

By implementing code like the above, you can restore the ThingIFAPI instance passed as an argument when the OS automatically recreates CommandFragment because the instance is managed by the OS.

4. Use the instance in CommandFragment

This is the fourth process in the figure, which retains and uses the ThingIFAPI instance.

As seen already, CommandFragment retains the ThingIFAPI instance in the class field.

public class CommandFragment extends Fragment {
  private ThingIFAPI mApi;
  ......
}

The handlers of the SEND COMMAND button and the REFRESH STATE button in the command screen call the APIs of this ThingIFAPI instance to make use of the features of the Thing-IF SDK.

See Command Screen Implementation for the detail of those processes.

Using ProgressDialogFragment

Hello Thing-IF uses the custom ProgressDialogFragment class derived from DialogFragment. ProgressDialogFragment opens a dialog which indicates a process is in progress.

This class is used for processes which take time for network access, like API calls to Kii Cloud. Such processes can be implemented as below.

// Open ProgressDialogFragment
ProgressDialogFragment.show(getActivity(), getFragmentManager(), R.string.add_user);

// Do something.
......

// Close ProgressDialogFragment
ProgressDialogFragment.close(getFragmentManager());

The show method receives the string resource used for the dialog title and message. The static methods show and close can display and destroy ProcessDialogFragment because only one occurrence of ProcessDialogFragment is expected to appear on the screen.

If the screen is rotated while ProgressDialog is displayed on the screen for an ongoing asynchronous process, the activity will be recreated, making ProcessDialogFragment inaccessible. To prevent this problem, ProcessDialogFragment is closed when the fragment starts.

@Override
public void onStart(){
  super.onStart();
  ProgressDialogFragment.close(getFragmentManager());
}

The sample code in the subsequent topics omits lines related to ProgressDialogFragment for simplicity.


What's next?

Let us walk through the process of the login screen including how to use the API.

Go to Login Screen Implementation.

If you want to learn more...