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



So inside the default 'layout' folder we have the one size fits all 'activity_recipe_detail.xml' layout that contains the single fragment_container, while inside the 'layout-sw600dp-land' folder we have  the specially crafted 'activity_recipe_detail.xml' layout that contains both fragment_container and fragment_container2.sw600 stands for smallest width and 600dp denotes the Tablet's dimensions.

image12

 

So while the layouts differ, RecipeDetailFragment's and RecipeStepDetailFragment's underlying code remains unaltered, but should be developed to cater for each case.This can be done at runtime by checking which kind of layout is currently loaded.So when on a Phone, the layouts under the default 'layout' folder get loaded, while on a Tablet and in landscape mode those in 'layout-sw600dp-land' are used.

A good and efficient way to identify which is which is by tagging them. So 'activity_recipe_detail.xml' of 'layout-sw600dp-land' is tagged with android:tag="tablet-land" something that allows for the following check at runtime:


/****
RecipeDetailActivity
****/

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

 

So if the layout currently loaded is tagged as "tablet-land" then I know that that's 'activity_recipe_detail.xml' of  'layout-sw600dp-land' and therefore I can follow the multi pane branch of code. Note the use of the FragmentManager which is the class that provides the methods that allow you to add, remove, and replace fragments in an activity at runtime.

Fragments have their own lifecycle too, although connected to their host's activity lifecycle so that when the hosting activity goes through its own callbacks, onCreate, onStart, onPause, onDestroy, onSaveInstanceState and so on, so do the fragments. However they also come with extra callbacks in onAttach, onDetach and onDestroyView.

As a demonstration, check the onCreateView and onSaveInstanceState callbacks of the fragment RecipeDetailFragment


/****
RecipeDetailFragment callbacks
****/

public class RecipeDetailFragment extends Fragment  {

    ArrayList<Recipe> recipe;
    String recipeName;

    public RecipeDetailFragment() {

    }


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

        View rootView = inflater.inflate(
                                  R.layout.recipe_detail_fragment_body_part,
                                          container, false);

        textView = (TextView)rootView.findViewById(
                                                                 R.id.recipe_detail_text);

        ArrayList<String> recipeIngredientsForWidgets=
                                                                         new ArrayList<>();

        ingredients.forEach((a) ->
            {
                textView.append("\u2022 "+ a.getIngredient()+"\n");
                textView.append("\t\t\t Quantity: "
                                              +a.getQuantity().toString()+"\n");
                textView.append("\t\t\t Measure: "
                                              +a.getMeasure()+"\n\n");
                recipeIngredientsForWidgets.add(a.getIngredient()+"\n"+
                        "Quantity: "+a.getQuantity().toString()+"\n"+
                        "Measure: "+a.getMeasure()+"\n");
            });

        recyclerView=(RecyclerView)rootView.findViewById(
                                                             R.id.recipe_detail_recycler);

        LinearLayoutManager mLayoutManager=
                        new  LinearLayoutManager(getContext());

        recyclerView.setLayoutManager(mLayoutManager);

        RecipeDetailAdapter mRecipeDetailAdapter =
           new RecipeDetailAdapter((RecipeDetailActivity)getActivity());
       recyclerView.setAdapter(mRecipeDetailAdapter);
       mRecipeDetailAdapter.setMasterRecipeData(recipe,getContext());
       UpdateBakingService.startBakingService(getContext(),
                                                            recipeIngredientsForWidgets);

        return rootView;
    }

    @Override
    public void onSaveInstanceState(Bundle currentState) {
        super.onSaveInstanceState(currentState);
        currentState.putParcelableArrayList(
                                             SELECTED_RECIPES, recipe);
        currentState.putString("Title",recipeName);
    }
}
 

Yet, another property of fragments is their ability to push onto the fragment stack, which I'm going to use for implementing the Back Arrow's functionality.

The instances of the FragmentManger we saw earlier use the addToBackStack method in 

addToBackStack(STACK_RECIPE_DETAIL)

and

addToBackStack(STACK_RECIPE_STEP_DETAIL)

pushing the fragment instances onto the stack one after another and in LIFO fashion.

Now when pressing the toolbar's back arrow we pop them off the stack in the following order. When the user stares at the screen with the Video instructions, which means that the RecipeStepDetailFragment is loaded, pressing the back arrow takes him back to the screen with the list of the recipe's steps, popping RecipeStepDetailFragment off the stack and replacing it with RecipeDetailFragment. At the RecipeDetailFragment screen, pressing the back arrow again ends host RecipeDetailActivity by calling finish() and goes back to the launcher RecipeActivity so that the user can check another recipe.


/****
RecipeDetailActivity Back Arrow
****/

 myToolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentManager fm = getSupportFragmentManager();
                if (findViewById(R.id.fragment_container2)==null) {
                    //called when fragment_container2 exists,
                    //that is both fragments are side by side
                    if (fm.getBackStackEntryCount() > 1) {
                        //go back to "Recipe Detail" screen
                        fm.popBackStack(STACK_RECIPE_DETAIL, 0);
                    } else if (fm.getBackStackEntryCount() > 0) {
                        //go back to "Recipe" screen
                        finish();
                    }
                }
                else {
                    //go back to "Recipe" screen
                    finish();
                }
            }
        });
    }


Surprisingly the Backstack and its functionality are not covered in the lesson, therefore you have to somehow suspect that something like that first of all exists and then try to figure out how it works by relying on external resources.



Last Updated ( Tuesday, 04 July 2017 )
 
 

   
Banner
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.