RecyclerViews on Android

Given my background as a web developer, I have a strong bias towards the client does something and then the server does its thing and returns a full page to render.  Sure, there are plenty of exceptions especially now, but I was raised in development in the world of Java Server Pages with the server drawing out the whole page.  The biggest fundamental change as I have been developing the KitchenOS Android app is moving from that whole page write / refresh thinking into thinking along the lines that small things like a touch or a gesture can change just a small piece of content without re-writing the whole screen.  Were I actually building this piece out as a web page, I could have had these three simple screens done in a couple of days, maybe even a day.  Instead, I am trying to expand my horizons and teach myself something new while keeping my skills fresh.

Combining the new paradigm and rules within the Android platform, and you have the struggles I went through with building out a list view that would scroll within the confines of a screen.  Sure, it’s definitely “easy” stuff.  But learning how and why it works with some less than stellar documentation has made things take quite a bit longer.  It has been weeks of guessing and checking and then googling and reading the documentation more. 

But I finally did figure out how to use RecyclerViews within Android to create a scrolling list.  I even think I understand the how and why of the building blocks versus just copying and pasting code from the documentation (or Stack Overflow).

The rest of this post is my attempt to restate what I learned so I can be positive that I actually understand this to repeat the process in the future.

Problem Set: I am displaying the details of an overall recipe that I need to edit.  Part of that recipe’s details include two different lists: the ingredients and then the categories (or tags that someone has added to them).

Looking through the Android developer documentation, this is done leveraging RecyclerViews.  The RecyclerView class is a part of the Android Jet Pack Framework.  The RecyclerView provides the capabilities to load a long list of elements and scroll through them.  It will handle the majority of the memory management determining what part of the list needs to be in memory and what doesn’t.  Sure in something that I am building to only run for myself this might not matter, but I want to learn this right.

So how do I do this?  Just follow five simple steps.

Step 1: Create a new fragment

Step 2: Set up the RecyclerView within the Larger Fragment

Step 3: Set up the RecyclerViewAdapter

Step 4: Connect the RecyclerViewAdapter to the main fragment

Step 5: Load the Content into the Adapter

Five steps to get a scrollable list populated.  The third time I did it, it only took my 30 minutes.  Compare that to the first two which each took over a week.  I may have finally cracked the nut and understand what is happening and why.

Step 1: Create a New Fragment

This step was always pretty simple thanks to Android Studio.  Leveraging what Android Studio provides, I would go to the menu and go to Create a new Layout Resource File.  Android Studio ends up generating the actual resource file.  Then using the Layout Editor, I go in and create the layout for how I want each individual item within the list to look.  The only real tricky piece of this is to make sure that the restraints are set appropriately on the different pieces of the layout.

I always set up the fragment to have a Linear Layout at the root level.  I end up setting my height here to make sure the list item is limited to a certain size as I have tried using match_parent, but then it makes each individual list item the size of the container I want them to scroll within.

For the ingredient and category listings, I have set this to be 50dp.  Underneath the Linear Layout element, I create a Constraint Layout set to have layout_width and layout_height as match_constraint making sure it sits within the height limit I set on the Linear Layout.  Inside the Constraint Layout is where I end up building the overall look and feel.

Step 2: Set up the RecyclerView within the Larger Fragment

This step was always pretty simple just to hook up.  But what took me a while was to figure out how to get the layout I created in Step One to show up in the actual fragment it will be displayed in when the application is run.

The simple part was using the Layout Editor to add in a RecyclerView within the constraints of my layout.  That would immediately add in the default view that just said “Item 0, Item 1, etc.” with each on an individual line.  I didn’t like the way that looked, so I kept looking for ways to get my layout to actually show up, and I finally found it with Sample Data and the Android Studio “tools” namespace within the layout’s XML file.

Sample Data

Creating sample data was pretty simple within Android Studio.  All I needed to do was go through the menu bar and choose New > Sample Data.  From there, the sampledata folder was created at the root level of my project.  Adding new sample data, required me to create the JSON file representing the sample contents I wanted.  Simple once you know the format.  The format is a basic JSON object with a comment and a data object that represents the actual content.  The following snippet is a full sample set of data with ingredients.  This is saved within my sampledata folder  as ingredient_list.json.

{
  "comment" : "My sample list of ingredients for the layout.",
  "data" : [
    { "fullText" : "1/2 cup milk"},
    { "fullText" : "1 teaspoon cinnamon"},
    { "fullText" : "1 pound ground beef"},
    { "fullText" : "2 tablespoons brown sugar"}
  ]
}

The sample data is then applied to the project by adding the following element into the layout resource XML file.

tools:text="@sample/ingredient_list.json/data/fullText"

“List Item Layout” View

I don’t know if this is the official name, but it is my name for this idea.  Basically, making sure that the fragment I created in Step 1 is visible to me when I end up viewing the overall “major” fragment. I did a bunch of different Google searches that took me to the Android Documentation for the tools XML namespace in the layout resource XML file. This namespace allows me to show the look & feel of another fragment into my fragment.  In this instance, I can see my design for the list view within the ingredients and categories section when I am looking at the overall recipe layout.  It’s really simple to do, too. 

Add the following line into the appropriate layout XML file within the RecyclerView tag:

tools:listitem="@layout/list_item_category"

This snippet tells my RecyclerView to pull the layout I have called list_item_category into the view.  Now I see the sample data I set up and the list’s layout itself when I view the recipe_detail_fragment within using the Android Studio Layout Editor.  Sure, this has not impact from a pure functionality standpoint, but it matters from an aesthetic standpoint when developing, which is what I was going for.

Step 3: Set Up the RecyclerView Adapter

According to the Android documentation, the RecyclerView Adapter “creates views for items, and replaces the content of some of the views with new data items when the original item is no longer visible.”

OK, so what does that mean?

Within the RecyclerView context, Adapters store the actual contents of the list I am scrolling through and then provide the system with the element when they need to display it.  Simple, right?  In theory, yes, but when I read through everything you need to do it got muddled in my head.  But it really is pretty simple, you need to override three different functions:

  • onCreateViewHolder

  • onBindViewHolder

  • getItemCount

The getItemCount is pretty self-explanatory, that’s the count of items in the list / array you are wanting to display to users on the screen.  So return the length of the list.

The other two are a bit more complicated.

The onCreateViewHolder function creates the abstract view object that will be populated by each list item.  But what is View Holder?  Simple, it is the overall view that I created in Step 2 for the list item.  To handle this, Android Studio will create an inner class.  I called it IngredientViewHolder because I am working on the ingredient list.  This class takes the View that I created and allows me to create and fill the contents of its a sub-views.  In my ingredient list item view, it is currently just a single TextView displaying the ingredient name.  Within the IngredientViewHolder I created an instance variable ingredientNameTextView which houses my TextView.  The class is then returned to the caller, so now I have a ViewHolder returned to the application.

The onBindViewHolder function takes the current place the system is at drawing the screen and says, “ok, how do I actually put content in here?”.  Enter the View Holder.  In my case the View Holder was the IngredientViewHolder inner class.  The View Holder itself is passed into the function, and then I set its sub view content (my simple TextView) to the value of the current position in my ingredient list.

val item = ingredients[position]
holder.ingredientNameTextView.text = item.fullText

with(holder.lineItemView) {
    tag = item
}

Step 4: Connect the Adpater to the Main Fragment

Connecting a RecyclerView Adapter into the main fragment takes two steps:

  1. Setting up an empty adapter when the View is created

  2. Filling the adapter with the list of items when the content is obtained from the ViewModel

Both are straight forward.  But it took me a while to understand the difference between the two.  Within the onCreateView function of my RecipeDetailFragment (the fragment I use to display the full contents of a recipe), I needed to add the creation of the RecyclerView.

ingredientRecyclerView = myView.findViewById<RecyclerView>(R.id.recyclerViewIngredients).apply {
            setHasFixedSize(true)
            layoutManager = LinearLayoutManager(context)
            adapter = IngredientListRecyclerViewAdapter(emptyList())
}

This is pretty simple code. I end up using the larger view (myView which needs a better name I know) to get the sub-view, my RecyclerView.  From there, I leverage Kotlin to apply my layout manager (Linear in my case) and my adapter which is what I created in Step 3.  Notice here I end up filling this with an emptyList() because I don’t yet have the content I need to display.  I don’t load the content into the RecylerView until the system is ready to do so.

Step 5: Load the Content into the Adapter

The final step is to actually place the content I pulled from my backend service into the Adapter and display it on the screen.  For all other pieces of data, I have been leveraging the Live Data and View Model architecture provided as part of the Android Jetpack framework. 

In my architecture, I created a ViewModel associated with my Recipe Detail fragment containing the actual recipe I want to display.  The model for the Recipe includes all of the different attributes I identified such as title, ingredient list, category list, details, etc.  But how do I load that content into the Adapter?

With the LiveData architecture, this is very straightforward.  In the onActivityCreated function, I have the following code that loads the content into the two RecyclerView Adpaters I created.

recipeListViewModel.recipeList.observe(this, Observer { list ->
            var selectedRecipe = list?.filter{ recipe -> recipe.uuid == uuid }?.single()

            // Other code creating the fragment
            ingredientRecyclerView.adapter = IngredientListRecyclerViewAdapter(selectedRecipe!!.ingredients)
})

After that, the content is written to the screen from my list. 

Now on to manipulating it and performing actions against it like editing and enhancement and probably trying to figure out how to create an overlay within an Android app to perform some content editing.