Skip to content

Android Recycler View

Sharina Stubbs edited this page Mar 19, 2020 · 21 revisions

See also my Wiki notes from Java-d6 Lecture, from October and Nov 2019.

The following are notes created while working on GFTastyNoms in March, 2020

Things to Know

Recycler View

Extremely common thing to put in an Android App

For when you have many individual rows, versus a block of text (which is ScrollView).

You can set it up to show, say, 12 things, in the recycler view. Then, when the user reaches near the bottom, you make an API call or a DB call to fetch the next 12 things in the data set.

View Adapter

The View Adapter has the job of telling the Recycler View what to display at each row.

The View Adapter needs to know these things, at each index i, to determine what that row's view should look like:

  • The contents/data at each index
  • The total length
  • How to create a single row.

Fragments

Like in Spring and Thymeleaf, they are for small pieces of UI that you want to reuse in various activities. In android, you can have Java files backing those bits of logic.

Make a Recycler View

This example uses my work from the Android App I'm working on called GFTastyNoms. The Recycler View displays each location. Each location is whimsically named a GFTastyNomPlace, and so there is a class called GFTastyNomPlace.java, which has all the expected instance variables, such as name, address, etc.

Add Recycler View to xml file

Drag a RecyclerView from the Palette over to the screen of the xml file you want it on. Let it add dependencies and sync, if you haven't done that manually already.

Add constraints to the RecyclerView on all four sides of 16dps. In Attributes, give it a layout_height of match_constraint, which makes it as tall as it needs to be to be between whatever you'd like it to be between.

This recycler view does nothing at this point. Next, must set up view adapter and follow the docs.

Set up View Adapter

Open up the Recycler View docs

In the project directory, next to your other classes (like Main Activity), make a new Fragment (List).

  • When prompted - fill in "Object Kind," (ie, GFNomsPlaces) then click finish.
  • It will generate many files, including two classes.

Open Up the New RecyclerViewAdapter file (that was just auto-generated)

Note that this RecyclerViewAdapter is trying to identify what is the list of objects to display. We need to create brand new rows in the RecyclerView, which is what the onCreateViewHolder is doing.

Change the instance value of a List called mValues to take in an object of your choice (ie, GFTastyNomPlace), instead of DummyItem.

GFTastyNomPlace will be red; do an option-return (on a mac) and Create class.

Continue going through RecyclerViewAdapter and replacing DummyItem with GFTastyNomPlace.

Open Up the Class GFTastyNomPlace

Make instance variables and a constructor, and generate getters and setters.

Open up, in layout, your fragment_gfnomplaces.xml

In the component tree, there are two things - item_number and content.

Highlight item_number, and in Attributes, change the id of item_number to nomPlaceName.

In RecyclerViewAdapter...

... Go to public class ViewHolder extends RecyclerView.VIewHolder (approx lines 65 and 66):

  • Change instance variables for TextViews from mIDView and mContentView to something more applicable... like mNomPlaceNameView and mAddressView. This is for mental sanity.
  • Change R.id.___ to appropriate id values for what's in the xml file.

Scroll up to public void onBindViewHolder(...) (approx lines 41-56):

  • Change the angry .id to .getNomplacename, which comes from the getter in GFTastyNomPlace class.
  • Change the angry .content to .getAddress(), also from the GFTastyNomPlace class.
  • Delete the content within the onClick(View v) and just add a Log.i to check if it was clicked.

In activity_all_tasty_noms.xml... or wherever you are putting the recycler view:

Delete the recycler view from the view, and drag in a . Click on the fragment name that appears in the box, and hit OK.

  • Remember to, in Declared Attribures, to put "layout_height" and "layout_width" as match_constraint.

On the upper right, there will be a bright red exclamation mark warning symbol. It'll say something like "unknown fragments." It'll provide an option to choose what xml fragment to use with the recycler view.

Go to GFNomPlacesFragment.java

At about line 74, within the public View onCreateView(...), there will be a squiggly red line under "DummyContent.ITEMS...)

  • It's actually trying to create the view adapter (to display things within the recycler view). It's currently passing in stuff from Dummy Content, to populate the recycler view.
  • Replace DummyContent.ITEMS with listOfGFTastyNomPlaces, and one line above, make a new list.
List<GFTastyNomPlace> listOfGFTastyNomPlaces = new LinkedList<>();

Add a GFTastyNomPlace to the list.

listOfGFTastyNomPlaces.add(newGFTastyNomPlace(...));

Plus, change listener at the end of the recyclerView.setAdapter(new MyGFNomPlacesREcyclerViewAdapter(listOfGFTastyNomPlaces, ____ )); to null.

in public void onAttach(Context context), down below:

comment out the two lines that start with "throw new RuntimeException(...)..."

Run your App!

Cross your fingers... it should show content inside the recycler view...

Add Another Column to the Recycler View

Go to the fragment_gfnomplaces.xml

This is where we define the individual views.

Drag and drop a TextView from the Palette into the Component Tree under Linear Layout. Give it a reasonable ID, and remove the "TextView" that is in the "text" within Declared Attributes.

Go to MyGFNomPlacesRecyclerViewAdapter

Add an instance variable within public class ViewHolder extends RecyclerView.ViewHolder {}

public final TextView mGFFriendlyRange;

Within public ViewHolder(View view) {} grab the id for the data we want to add to the recycler view:

mGFFriendlyRange = (TextView) view.findViewById(R.id.gfFriendlyRange);

Scroll up to public void onBindViewHolder(final ViewHolder holder, int position) {}

When filling in a particular row of the recycler view with data, we have to make sure to set the text of the desired view.

holder.mGFFriendlyRange.setText(mValues.get(position).getGffriendlyrange());

Run the app!

You may need to go to the fragment_gfnomplaces.xml and adjust the Attributes and Layout and such to match the first two components, if the new textview content is floating and light grey....

Display Data in Recycler View from DynamoDB

Go to my Wiki on setting up an API and DynamoDB with AWS if this hasn't been done yet.

Review the docs

Amplify docs discuss how to query](https://aws-amplify.github.io/docs/sdk/android/start) the data - do a search for "Next, query the data" to get taken to the right spot in the page.

Go to GFNomPlacesFragment.java

This is the file that gets the data for displaying.

Add an AWSAppSyncClient

Add the instance variable for the client within the public class.

private AWSAppSyncClient mAWSAppSyncClient;

Within the myAWSAppSyncCLient query within public void onResume():

Add a .responseFetcher(...) and .enqueue with its callback.

Within the public void onREsponse(...), add:

recyclerView.setAdapter(new MyGFNomPlacesREcyclerVIewAdapter(resopnse.data().listGFTastyNomss().items(), null));

It will have squiggly red lines under the response.data().listGFTa... so do option-return (Mac user), and choose:

Change 1st parameter of method 'MyGFNomPlacesRecyclerViewAdapter' from 'List<GFTastyNomPlace>' to 'List<Item>'

This will give us a list of items.

Go to MyGFNomPlacesRecyclerViewAdapter.java

Need to fix the red squiggly lines that appear in various places due to the change immediately above.

Update the instance variable private final List<GFTastyNomPlace> mValues with:

<ListGfTastyNomssQuery.Item>

In public classViewHolder extends RecyclerView.ViewHolder {}, update the instance variable for mItem to:

public ListGfTastyNomssQuery.Item mItem;

In public void onBindVIewHolder(...) {}, update the .getNomplacename() and .getAddress() with:

...get(position).nomplacename());
...get(position).address();

Prevent Issues with Threading after Error is Thrown

https://frontrowviews.com/Home/Event/Play/5e1929feeee6d91a1854408c <----------- 02:56:48

By default, AWS can't make network requests on the UI thread, b/c these requests will stop the UI from displaying anything. When we call .enqueue, it is enqueing that request on a separate thread for us; it's handling the work of the threading for us. However, when our callback occurs, that callback isn't running on the UI thread; it's running on a different thread. We need to post that data to the UI thread in a reasonable way... aka handling asynchronous calls.

Go to GFNomPlacesFragment.java

The part of the code that will break is within public void onResume() {}

recyclerView.setAdapter(new...)

The error...

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

Add a handler

This allows us to query data and send it to the main UI thread.

See Android doc on communicating with the UI thread

Add in a handler within the public void onResponse(@Nonnull Response<...> response {}, just one line above the recyclerView.setAdapter(...).

Resources

Docs and Lecture Notes

Projects

Clone this wiki locally