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.
- Relay the Instance after Onboarding
- Retain the Instance in MainActivity
- Relay the Instance to the Command Screen
- 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.
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 parameterBUNDLE_KEY_THING_IF_API
inonSaveInstanceState()
. It is restored inonCreate()
and retained inmApi
.Deserialize the instance when the mobile app is started
If
savedInstanceState.getParcelable()
returns null formApi
, 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 ofif (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 serializedThingIFAPI
instance. The Thing-IF SDK automatically savesThingIFAPI
in the shared preferences when the instance is created or updated. It is restored tomApi
inonCreate()
.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...
For more information about implementing a mobile app with fragments, see Implemented Technique: Fragments in the Kii Balance tutorial.
See Saving and Restoring the initialized information to learn how to save and restore initialized
ThingIFAPI
.You can handle multiple things or schemas by using multiple instances of
ThingIFAPI
with the tag feature ofThingIFAPI
. See Saving and Restoring Initialized Information with Gateway to learn how to implement it.If you use the Kii Cloud SDK features, you need to save the access token of the logged-in user in addition to
ThingIFAPI
. See Tips for Implementing with Android and Logging in and Using an Access Token to learn how to implement it.