Insider's Guide To Udacity Android Developer Nanodegree Part 3 - Making the Baking App
Insider's Guide To Udacity Android Developer Nanodegree Part 3 - Making the Baking App
Written by Nikos Vaggalis   
Monday, 03 July 2017
Article Index
Insider's Guide To Udacity Android Developer Nanodegree Part 3 - Making the Baking App
Step 1 - Fragments
Fragments In Action
Layouts
Fragments
Step 2 - Libraries & Networking
POJO To Parcelables
Step 3 - Adding Exoplayer
Step 4 - Widgets
Step 5 - The Widget Provider
Step 6 - UI Testing
Step 7 - Testing Intents

Fragment to Fragment and Fragment to host Activity Communication

It's important to note that all communication, be it Fragment to Fragment or Fragment to Activity, is governed by the Control and Command center of the host Activity.That is so because our fragments should be as self contained as possible in order to be modular and reusable. As such letting fragments call into each other would defy this purpose and unnecessarily raise the application's complexity. Going with the flow, the action of choosing a recipe to check from within the RecipeActivity packs the associated Recipe object, selectedRecipe, with its associated Ingredients and Steps lists in a ParcelableArrayList and forwards it to the RecipeDetailActivity:


/****
RecipeActivity-sending the bundle with the Recipe
****/

    selectedRecipeBundle.putParcelableArrayList(
                          SELECTED_RECIPES,selectedRecipe);

    final Intent intent = new Intent(this, RecipeDetailActivity.class);
    intent.putExtras(selectedRecipeBundle);
    startActivity(intent);
       

which in turn forwards to its fragment(s):


/****
RecipeDetailActivity receiving the bundle with the Recipe
****/

 if (savedInstanceState == null) {

            //Get Recipe info from RecipeActivity
            Bundle selectedRecipeBundle = getIntent().getExtras();
            recipe = new ArrayList<>();

            recipe = selectedRecipeBundle
                      .getParcelableArrayList(SELECTED_RECIPES);

            recipeName = recipe.get(0).getName();

            //Pass it down to the fragments
             final RecipeDetailFragment fragment =
                                               new RecipeDetailFragment();
            fragment.setArguments(selectedRecipeBundle);

            FragmentManager fragmentManager =
                                           getSupportFragmentManager();

            fragmentManager.beginTransaction()
                    .replace(R.id.fragment_container,
                          fragment).addToBackStack(
                                     STACK_RECIPE_DETAIL)
                    .commit();

            if (findViewById(R.id.recipe_linear_layout).getTag()!=
               null && findViewById(R.id.recipe_linear_layout)
                         .getTag().equals("tablet-land")) {

                final RecipeStepDetailFragment fragment2 =
                                          new RecipeStepDetailFragment();

                fragment2.setArguments(selectedRecipeBundle);
                fragmentManager.beginTransaction()
                        .replace(R.id.fragment_container2, fragment2)
                        .addToBackStack(STACK_RECIPE_STEP_DETAIL)
                        .commit();
            }
           

 

Fragments then receive the bundle inside their own onCreatView callback:


/****
RecipeDetailFragment-receiving the bundle
with the Recipe from its host activity
****/

@Override
    public View onCreateView(LayoutInflater inflater,
              ViewGroup container, Bundle savedInstanceState) {
        RecyclerView recyclerView;
        TextView textView;

        recipe = new ArrayList<>();

        if(savedInstanceState != null) {
            recipe = savedInstanceState.getParcelableArrayList(
                                                              SELECTED_RECIPES);
        }
        else {
            recipe= getArguments()
                       .getParcelableArrayList(SELECTED_RECIPES);
        }

        List<Ingredient> ingredients = recipe.get(0).getIngredients();
        recipeName=recipe.get(0).getName();


But communication is bi-directional as well, not just from Activity to Fragment but also from Fragment to Activity.This part is coded deep into each fragment's RecyclerView Adapters.For example RecipeDetailFragment's RecipeDetailAdapter which holds the list of the steps associated with the chosen recipe, has an Interface declaration, ListItemClickListener with just one method, onListItemClick.


/****
RecipeDetailAdapter-ListItemClickListener interface
****/

 public interface ListItemClickListener {
        void onListItemClick(List<Step> stepsOut,
                  int clickedItemIndex,String recipeName);
    }
   
    @Override
        public void onClick(View v) {
            int clickedPosition = getAdapterPosition();
            lOnClickListener.onListItemClick(lSteps,
                                                 clickedPosition,recipeName);
        }
       


So upon clicking on a recipe step through the associated RecyclerViewer UI, we trigger the RecyclerViewer's Adapter's onClick method, which in turn reads the whole list of the steps (lSteps) of the given recipe as well as the chosen step's index in the List of steps, lSteps:


/****
RecipeDetailAdapter
****/

public class RecipeDetailAdapter  extends
 RecyclerView.Adapter<RecipeDetailAdapter.RecyclerViewHolder> {

    List<Step> lSteps;
    private  String recipeName;

    final private ListItemClickListener lOnClickListener;

    public interface ListItemClickListener {
        void onListItemClick(List<Step> stepsOut,
              int clickedItemIndex,String  recipeName);
    }

    public RecipeDetailAdapter(ListItemClickListener listener) {
        lOnClickListener =listener;
    }


    public void setMasterRecipeData(List<Recipe> recipesIn,
                                                                    Context context) {
        //lSteps = recipesIn;
        lSteps= recipesIn.get(0).getSteps();
        recipeName=recipesIn.get(0).getName();
        notifyDataSetChanged();
    }

    @Override
    public RecyclerViewHolder onCreateViewHolder(ViewGroup
                                        viewGroup, int viewType) {
        Context context = viewGroup.getContext();

        int layoutIdForListItem = R.layout.recipe_detail_cardview_items;
        LayoutInflater inflater = LayoutInflater.from(context);
        View view = inflater.inflate(layoutIdForListItem, viewGroup, false);

        RecyclerViewHolder viewHolder =
                                  new RecyclerViewHolder(view);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(RecyclerViewHolder holder,
                                                                              int position) {
       holder.textRecyclerView.setText(lSteps.get(position).getId()
                +". "+  lSteps.get(position).getShortDescription());
    }

    @Override
    public int getItemCount() {
        return lSteps !=null ? lSteps.size():0 ;
    }

    class RecyclerViewHolder extends RecyclerView.ViewHolder
                           implements View.OnClickListener {
        TextView textRecyclerView;

        public RecyclerViewHolder(View itemView) {
            super(itemView);

            textRecyclerView =
                (TextView) itemView.findViewById(R.id.shortDescription);
            itemView.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {
            int clickedPosition = getAdapterPosition();
            lOnClickListener.onListItemClick(
                                  lSteps,clickedPosition,recipeName);
        }

    }
}


This means that any interested listener given both of these pieces of information can locate the step within the accompanying list and extract it. And who might that listener be? The fragment's host activity, RecipeDetailActivity, of course, since RecipeDetailActivity  
implements the RecipeDetailAdapter.ListItemClickListener interface: 

  
 public class RecipeDetailActivity extends AppCompatActivity
             implements RecipeDetailAdapter.ListItemClickListener{}


So, with its own onListItemClick method:

   
public void onListItemClick(List<Step> stepsOut,
                         int selectedItemIndex,String recipeName)


is able to capture the list of step objects 'stepsOut' and the associated index in 'selectedItemIndex', sent over from the RecipeDetailAdapter of the RecipeDetailFragment. Acting as the controller of information flow, it turns the very same info:      

  
 fragment.setArguments(stepBundle)


over to RecipeStepDetailFragment with:   


replace(R.id.fragment_container,fragment)
          .addToBackStack(STACK_RECIPE_STEP_DETAIL) 

in order to show the video of Miriam going over the chosen step's instructions.


/****
RecipeDetailActivity as controller of information flow
 ****/

 @Override
    public void onListItemClick(List<Step> stepsOut,
                            int selectedItemIndex,String recipeName) {
        final RecipeStepDetailFragment fragment =
                                         new  RecipeStepDetailFragment();

        FragmentManager fragmentManager =
                                               getSupportFragmentManager();

        getSupportActionBar().setTitle(recipeName);

        Bundle stepBundle = new Bundle();
        stepBundle.putParcelableArrayList (SELECTED_STEPS,
                                                        (ArrayList<Step>) stepsOut);
        stepBundle.putInt(SELECTED_INDEX,selectedItemIndex);
        stepBundle.putString("Title",recipeName);
        fragment.setArguments(stepBundle);

        if (findViewById(R.id.recipe_linear_layout).getTag()!=
                          null && findViewById(R.id.recipe_linear_layout)
                           .getTag().equals("tablet-land")) {
            fragmentManager.beginTransaction()
                    .replace(R.id.fragment_container2,fragment)
                    .addToBackStack(STACK_RECIPE_STEP_DETAIL)
                    .commit();

        }
        else {
            fragmentManager.beginTransaction()
                    .replace(R.id.fragment_container, fragment)
                    .addToBackStack(STACK_RECIPE_STEP_DETAIL)
                    .commit();
        }

    }
   


The following diagram will make the flow easier to understand:

image13

Information flow


With this final step I fulfilled the specification requirements 'App should allow navigation between individual recipes and recipe steps' and 'Application uses Master Detail Flow to display recipe steps and navigation between them' .


 



Last Updated ( Tuesday, 04 July 2017 )
 
 

   
Banner
RSS feed of all content
I Programmer - full contents
Copyright © 2017 i-programmer.info. All Rights Reserved.
Joomla! is Free Software released under the GNU/GPL License.