Friday, October 3, 2014

5c. Android: Fragments

When part of your screen has to change layout based on the application logic it is a good practice to use Fragments. For example, user changes convertor type from "length" to "colors" which has very different UI layout, some of the the screen (Activity) will remain the same, but the conversion input will be a different Fragment.


Step 1: Create "fragment" package.
This step is optional, but it will help you keep the code organized.

Step 2: Create a new Fragment class and name it


  • fragment name: e.g. "LengthInputTypesFragment"
  • default layout name: "fragment_length_input_types.xml"




Step 3: in your Activity layout xml add the fragment container which will allow switching content


   <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/conversion_specific_fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />



Step 4: Change your Activity to extend FragmentActivity with import of android.support.v4

import android.support.v4.app.FragmentActivity;
public class MainActivity extends FragmentActivity {


Step 5: Add method to onCreate where you add your Fragment instantiation

  @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      addConversionTypeFragment(savedInstanceState);


and the method content:

   private void addConversionTypeFragment(Bundle savedInstanceState) {
      // Does your Activity layout include this Fragment?
      if (findViewById(R.id. conversion_specific_fragment_container) != null) {

         // If restoring Activity do nothing to prevent creating existing Fragment.
         if (savedInstanceState != null) {
            return;
         }
         // Create instance of the Fragment
         LengthInputTypes lengthInputTypes = new LengthInputTypes();
         // pass in calling Intent's values
         lengthInputTypes.setArguments(getIntent().getExtras());
         // Add the fragment to the 'fragment_container' FrameLayout
         getSupportFragmentManager().beginTransaction().add(R.id. conversion_specific_fragment_container, lengthInputTypes).commit();

      }
   }

Step 6: If you created the Fragment using AndroidStudio template, then comment out OnFragmentInteractionListener which we will not use at this time

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
//        try {
//            mListener = (OnFragmentInteractionListener) activity;
//        } catch (ClassCastException e) {
//            throw new ClassCastException(activity.toString()
//                    + " must implement OnFragmentInteractionListener");
//        }
    }


Step 7: Run the app and you should see the default "Hello blank fragment"

Step 8: Place proper xml code inside Fragment's layout/fragment_length_input_types.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.chicagoandroid.cit299.calc.fragment.LengthInputTypes">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/conversion_units"
            android:layout_weight="40" />
        <Spinner            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/weightInputTypesSpinner"
            android:layout_weight="60" />
    </LinearLayout>
</FrameLayout>

Run the app to see second EMPTY spinner.



Step 9: Add method to populate spinner in onActivityCreated (review Fragment Lifecycle)

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        createUnitSelectorSpinner();
    }


Step 10: Add method content

 private void createUnitSelectorSpinner() {
      Spinner spinner = (Spinner) getView().findViewById(R.id.weightInputTypesSpinner);

      //refactored the following variables for educational purposes only
      final int SPINNER_ITEM_LAYOUT = android.R.layout.simple_spinner_item;
      final int SPINNER_DROPDOWN_LAYOUT = android.R.layout.simple_spinner_dropdown_item;
      final int ARRAY_INPUT_TYPES = R.array.length_input_types;
      Context activityContext = getActivity().getBaseContext();

      ArrayAdapter<CharSequence> adapter;
      adapter = ArrayAdapter.createFromResource(activityContext, ARRAY_INPUT_TYPES, SPINNER_ITEM_LAYOUT);
      adapter.setDropDownViewResource(SPINNER_DROPDOWN_LAYOUT);
      // set array adapter to populate the spinner
      spinner.setAdapter(adapter);
      final String unitTypes[] = getResources().getStringArray(ARRAY_INPUT_TYPES);
      // add listener that will react when an item was selected
      spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

         @Override
         public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
            // assign selected item to the class member to be used later
            selectedUnit = position;
            // we would not have debug toasts in production version, for edu purposes only
            showToast("onItemSelected in position: " + position + " id: " + id + " selected name: " + unitTypes[position]);
         }

         @Override
         public void onNothingSelected(AdapterView<?> parentView) {
            // onNothingSelected is a Callback method to be invoked when the selection disappears from this view.
            // The selection can disappear for instance when touch is activated or when the adapter becomes empty.
            showToast("onNothingSelected");
         }
      });
   }
   private void showToast(CharSequence text) {
      if (debug) {
         Context context = getActivity().getBaseContext();
         int duration = Toast.LENGTH_LONG;
         Toast toast = Toast.makeText(context, text, duration);
         toast.setGravity(Gravity.CENTER | Gravity.CENTER, 0, 0);
         toast.show();
      }
   }


Step 11: Move appropriate code to the new fragment



Step 12: Create (duplicate) Fragment for different conversions (different layouts)
In Activity switch the Fragment depending on "Conversion Type" 

         @Override
         public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
            // assign selected item to the class member to be used later
            selectedConversion = position;
            replaceUnitSpecificFragment();
            // we would not have debug toasts in production version, for edu purposes only
            showToast("onItemSelected in position: " + position + " id: " + id + " selected name: " + conversionTypeArray[position]);
         }






Read more: