Android Refactor – Part 1
private int mSoundResourceId; private int mImageResourceId; public Meme(int soundResourceId, int imageResourceId){ mSoundResourceId = soundResourceId; mImageResourceId = imageResourceId; }
Although the final layout of the app will be almost identical to what it was before, the way which it is made will be completely different. The hard-coded ScrollView
will be gone, along with all of the hard-coded ImageViews
and LinearLayouts
that go with them. Instead, there will be a single RecyclerView
in the XML layout file, and each item (Image/Sound) will be shown in the RecyclerView
using a separate XML file. Doing it this way will result in some intimidating code for some new Android developers, but it is mostly boilerplate and actually results in a much more maintainable codebase.
Android app Resources
I’ll start with the ID lists for creating the Sound objects, which will be used to generate the views in the RecyclerView
. Rather than hard-coding them in various places, I will create 2 string-arrays in my res/values/strings.xml
file. By doing this, I can access these arrays in code easily, while also making it very easy to add in new images/sounds. To add a new one, all I must do is add an item entry to both arrays, and it will be added to the list of sounds in the RecyclerView
almost like magic. It provides a clean separation between data and logic, where the resource IDs are accessed in a single location.
<resources> <string-array name="meme_audio_source_list"> <item>@raw/et</item> <item>@raw/bradberry</item> <item>@raw/copberry</item> <item>@raw/brrrradberry</item> <item>@raw/hilabradberry</item> <item>@raw/ethanbradberry</item> </string-array> <string-array name="meme_image_source_list"> <item>@drawable/pointyberry</item> <item>@drawable/bradberrypoint</item> <item>@drawable/copberry</item> <item>@drawable/brrradberry</item> <item>@drawable/hila</item> <item>@drawable/ethan</item> </string-array> </resources>
We can see here that the only requirement is that the images and sounds are placed in the same correct order in both string arrays, so that the correct meme clip is played when a user selects from the RecyclerView
To retrieve these IDs, I will use the getResources().obtainTypedArray(int id)
method, which returns a TypedArray
that can be looped through to get all of the IDs within. Before we do that, though, I want to rearrange the setup of the application.
Android Activity/Fragment Construction
Currently it’s just a single Activity
with all of the code lumped into it; I think it can be better. I might want to, at some point in the future, add multiple collections of memes separated by tabs. I’m going to set up my app so that this would be easier to do in the long run as I think it’s a real possibility to do. After reading The Big Nerd Ranch’s Guide (Thoroughly recommended for beginners/intermediate Android developers!) there was a single-activity approach which I really liked, and it made a lot of sense. By putting the unique content into a Fragment, it’s easy to swap content in and out of the Activity
, rather than opening and closing a new Activity
each time. As well as that, if I did implement a TabView
I would need to convert everything to Fragments anyway. My first task here is to create a new Activity
, called SingleFragmentActivity
public abstract class SingleFragmentActivity extends AppCompatActivity { protected abstract Fragment createFragment(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_fragment); FragmentManager fm = getSupportFragmentManager(); Fragment fragment = fm.findFragmentById(R.id.fragment_container); if(fragment == null){ fragment = createFragment(); fm.beginTransaction() .add(R.id.fragment_container, fragment) .commit(); } } }
SingleFragmentActivity
will extend AppCompatActivity
(which in turn extends FragmentActivity
) and will now be the superclass of any Activity
I need to create. The createFragment()
method is abstract and is there to act as a receiver of a Fragment
from a subclass. Now we can have many Activities extending SingleFragmentActivity
, all creating different types of Fragments, which SingleFragmentActivity
accepts and adds it to it’s layout file using a transaction in the onCreate()
method. Simples.
Using this as a superclass means I can reduce my ListActivity (the Activity that will hold the Fragment containing the list of memes) to just a few short lines as shown below.
public class MemeListActivity extends SingleFragmentActivity{ public static final String TAG = "MemeListActivity"; @Override protected Fragment createFragment() { return new MemeListFragment(); } }
I don’t even need to override the onCreate()
method with this Activity, all I need to do is implement the createFragment()
method and return a new instance of the desired Fragment
. Here’s a high level step-by-step process of my app right now:
MemeListActivity
is opened on launch, as specified in theAndroidManifest.xml
- When the
Activity
lifecycle hits theonCreate()
stage, becauseMemeListActivity
does not override it,onCreate()
gets called inSingleFragmentActivity
, the superclass ofMemeListActivity
onCreate()
here callscreateFragment()
, which in this case returns a new instance ofMemeListFragment
(I will go through this now)- This fragment is then inserted into the
Activity
layout, which has a single root element inside – aFrameLayout
that acts as a fragment container - We should then, hopefully see a list of memes that we can listen to as much as we want!

The Fragment
My MemeListFragment
contains all of the logic associated with the images, sounds, and the RecyclerView
/ViewHolder
/Adapter
.
In total, it’s a little under 100 lines of code including all the import statements which isn’t bad at all. At this first stage of refactoring, I haven’t focused on everything, but it will be a more progressive work-through to the end result. Some of the member variables here can be local, the naming isn’t followed fully, and an array of other improvements can be made in the next few posts.
In the onCreateView()
method, I set up the few member variables, such as the RecyclerView
, the TypedArrays (which are then used to create the List of Memes) and the RecyclerView Adapter
. There’s nothing out of the ordinary here, the for-loop uses one of the TyedArray
‘s length as the counter, as both arrays will be the same length. I use a StaggeredGridLayoutManager
for the RecyclerView
‘s layout, which gives the list a nice look to it, similar to Pinterest/Tumblr/Masonry layouts on websites.
private MemeAdapter mMemeAdapter; private RecyclerView mMemeRecyclerView; private List<Meme> mMemeObjList = new ArrayList<>(); @Override public View onCreateView (LayoutInflater inflater,ViewGroup container, Bundle savedInstanceState){ View v = inflater.inflate(R.layout.fragment_meme_list, container, false); mMemeRecyclerView = (RecyclerView) v.findViewById(R.id.meme_recycler_view); mMemeRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL)); mMemeRecyclerView.setHasFixedSize(true); TypedArray spicyMemes = getResources().obtainTypedArray(R.array.meme_audio_source_list); TypedArray spicyMemesImages = getResources().obtainTypedArray(R.array.meme_image_source_list); for (int i = 0; i < spicyMemes.length(); i++) { int memeSoundResoureceId = spicyMemes.getResourceId(i, -1); int memeImageResourceId = spicyMemesImages.getResourceId(i, -1); Meme meme = new Meme(memeSoundResoureceId, memeImageResourceId); mMemeObjList.add(meme); } if(mMemeAdapter == null){ mMemeAdapter = new MemeAdapter(mMemeObjList); mMemeRecyclerView.setAdapter(mMemeAdapter); } return v; }
The ViewHolder
The ViewHolder
is part and parcel of implementing a RecyclerView
. For every item in the RecyclerView
, there is a ViewHolder
there which stores information about the View
. The MemeHolder
inner class is a bog-standard implementation of RecyclerView.ViewHolder
, with only an ImageView
and a Meme
object as member variables. (I may add a TextView
in the future) In the MemeHolder
constructor, I set an onClickListener
(implemented by the MemeHolder
class) and get a reference to the ImageView
from the itemView
parameter. The onClick
method for the ViewHolder
starts up the MediaPlayer
with the sound ID taken from the Meme
object. The final method in MemeHolder
is bindMeme()
which sets the member variable Meme
to the parameter included in the method, as well as setting the ImageDrawable source on the ImageView
. A pretty basic implementation so far, but that’s what’s needed right now.
private class MemeHolder extends RecyclerView.ViewHolder implements View.OnClickListener{ private ImageView memeImageView; private Meme meme; public MemeHolder(View itemView) { super(itemView); itemView.setOnClickListener(this); memeImageView = (ImageView) itemView.findViewById(R.id.meme_image_view); } @Override public void onClick(View view) { MediaPlayer mp = MediaPlayer.create(getActivity(), meme.getSoundResourceId()); mp.setOnCompletionListener(new android.media.MediaPlayer.OnCompletionListener() { @Override public void onCompletion(android.media.MediaPlayer mp) { mp.release(); } }); mp.start(); } public void bindMeme(Meme meme){ this.meme = meme; memeImageView.setImageDrawable(getResources().getDrawable(meme.getImageResourceId())); } }
The Adapter
The Adapter
is arguably the most important part of a RecyclerView
. It creates the Views/Holders to be used in the RecyclerView
, inflating the correct layout from the res/layout
folder. In my Adapter
I have a member variable of a List of Memes which is initialised in the constructor with the List made in the onCreateView()
method of the Fragment
. This list is used to bind the objects to the views so that the correct image/sound is shown. Other than that, the Adapter
is probably the most basic implementation that can be done. My Android app doesn’t have a very complex setup, but I have already made it much easier to manage in the future.
private class MemeAdapter extends RecyclerView.Adapter<MemeHolder>{ private List<Meme> mMemeList; public MemeAdapter(List<Meme> memes){ mMemeList = memes; } @Override public MemeHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater layoutInflater = LayoutInflater.from(getActivity()); View v = layoutInflater.inflate(R.layout.list_item_meme, parent, false); return new MemeHolder(v); } @Override public void onBindViewHolder(MemeHolder holder, int position) { Meme meme = mMemeList.get(position); holder.bindMeme(meme); } @Override public int getItemCount() { return mMemeList.size(); } }
Recap
I started off with a very large layout file, full of ImageViews, nested LinearLayouts and a ScrollView. It was a very messy setup and hard to maintain. A single Sound object was kept in MainActivity, which was used to play the correct sound. As the Activity implemented OnClickListener, I had a large onClick method with a switch statement full of all the possible cases. Based on these cases, the Sound object resource member variable was updated, and played using MediaPlayer.
I now have an Activity with a single FrameLayout view in the layout file, which is replaced by an instance of a Fragment containing ONLY a RecyclerView and a RelativeLayout root in it’s layout. In this fragment, all of the resources are loaded into an Object List from the strings.xml file and given to the RecyclerView to create and show the Views/ViewHolders. The onClick functionality is handled by each ViewHolder, and uses the Meme getter to play the correct sound resource.
I’ve made my life easier to add new sounds in the future – all that needs to be done is:
- add the .ogg file to the res/raw directory
- add the Image to the res/drawable directory
- add the IDs to the string-arrays in strings.xml (IDs being the name of the files)
And that’s it! A much simpler process than before.
If you want to look over the whole Android project, I have the entire app on my Github profile!
The next post will contain some tricks to use Android Studio to help keep my project nice and clean, and some work on the design of the app in its current form.