Google is committed to advancing racial equity for Black communities. See how.

Stamp Collector App using RecyclerView and SharedPreferences

Who else likes to collect postage stamps? Why not learn a few Android concepts while creating a simple stamp collector app? When you have a large amount of data to display, you need a view that can handle the data efficiently. With the help of the RecyclerView class, you can display stamp-collection data very smoothly. To save information about all the stamps you've collected, you can use Android SharedPreferences.

What you'll build

In this codelab, you will build an app that displays a collection of stamps in a list format and saves data about the stamps. While creating the app, you'll learn how to:

  • Create your own custom RecyclerView object from scratch.
  • Save and load data using shared preferences.
  • Add, update, and delete a stamp.

What you'll learn

  • How to use a RecyclerView object inside an app
  • How to save and retrieve data using shared preferences

What you'll need

  • Latest version of Android Studio
  • An Android device or emulator to run the app
  • The sample code, which you will download in the next step
  • Basic knowledge of Android programming in java.

Download the code

Click the following link to download all the code for this codelab:

DOWNLOAD SOURCE CODE

You can also find it in this github repository.

Unpack the downloaded zip file. The root folder, which is called StampCollectorApp, includes a folder for each step of this codelab:

  • The StampCollector_Starter_App folder contains the starter code.
  • The StampCollector_Recyclerview folder contains the app after the RecyclerView has been added.
  • The StampCollector_SharedPref folder contains the app after SharedPreferences are included.
  • The StampCollector_Complete folder contains the completed app, including the functionality to add, update, and delete a stamp.

App overview

StampCollectorApp is a simple stamp-collecting app that displays a list of stamps in a RecyclerView. You'll use mock data that's included in the starter app, so take a moment to familiarize yourself with the code in the StampCollector_Starter_App app.

  1. Open StampCollector_Starter_App in Android Studio.
  2. If you have any gradle version errors, fix them by updating your gradle version an syncing.
  3. Take a look at the code in java/com.example.stampcollectorapp/StampData.java. This class holds stamp attributes, getter methods, and setter methods.
public class StampData {
    private String mStampTitle;
    private int mStampCounter;
    private int mStampIcon;

    public StampData(){
        mStampIcon = R.drawable.general_stamp;
    }

    public String getStampTitle() {
        return mStampTitle;
    }

    public void setStampTitle(String mStampTitle) {
        this.mStampTitle = mStampTitle;
    }

    public int getStampCounter() {
        return mStampCounter;
    }

    public void setStampCounter(int mStampCounter) {
        this.mStampCounter = mStampCounter;
    }

    public int getStampIcon() {
        return mStampIcon;
    }

    public void setStampIcon(int mStampIcon) {
        this.mStampIcon = mStampIcon;
    }
}

For this codelab, you use three attributes, mStampTitle, mStampCounter, and mStampIcon, to store the name, quantity, and image resource ID of each stamp. If you wanted to save more data about each stamp, you could use more attributes. By default, there will be a generic stamp icon for all stamps initialized in the constructor, but you can override it by using the setStampIcon()method.

  1. The resource file res/values/String.xml contains the mock data required for the app.
<resources>
    <string name="app_name">Stamp Collector</string>

    <string name="stamp_title">Stamp Name</string>
    <string name="stamp_pic_desc">Stamp Picture Details</string>
    <string name="stamp_counter">0</string>
    <string name="add_counter_button">+</string>
    <string name="subtract_counter_button">-</string>
</resources>
  1. The resource file res/values/arrays.xml contains the arrays of stamp title and stamp count as default mock data.
<resources>
    <string-array name="stamp_title_array">
        <item>Indian Post</item>
        <item>Gandhi Post</item>
        <item>Vivekanand Post</item>
        <item>Bismillah Khan Post</item>
        <item>APJ Abdul Kalam Post</item>
        <item>Mother Teresa Post</item>
    </string-array>


    <integer-array name="stamp_counter_array">
        <item>0</item>
        <item>0</item>
        <item>0</item>
        <item>0</item>
        <item>0</item>
        <item>0</item>
    </integer-array>

    <integer-array name="stamp_icon_array">
        <item>@drawable/indian</item>
        <item>@drawable/gandhi</item>
        <item>@drawable/vivek</item>
        <item>@drawable/bismillah</item>
        <item>@drawable/apj</item>
        <item>@drawable/mother</item>
    </integer-array>

</resources>
  1. In java/com.example.stampcollectorapp/StampCollectorActivity.java, the data set is initialized using the setupData method. Examine the following code in the StampCollectorActivity class.
public class StampCollectorActivity extends AppCompatActivity {

    private String mStampTitle[];

    private TypedArray mStampIcon;

    private int mStampCounter[];

    private ArrayList<StampData> mStampData;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_stamp_collector);

        mStampTitle = getResources().
                getStringArray(R.array.stamp_title_array);

        mStampCounter = getResources().
                getIntArray(R.array.stamp_counter_array);

          mStampIcon = getResources().
                obtainTypedArray(R.array.stamp_icon_array);

        setupData(mStampTitle,mStampIcon,mStampCounter);
    }


  public void setupData(String title[],TypedArray icon,int count[]){
        mStampData = new ArrayList<StampData>();

        for (int i=0; i < title.length; i++){
            StampData instance = new StampData();
            instance.setStampTitle(title[i]);
            instance.setStampIcon(icon.getResourceId(i,0));
            instance.setStampCounter(count[i]);
            mStampData.add(instance);
        }
    }
}
  1. Run the app.

You see a Hello World screen for now. In the next step, you will start designing your app.

Set up the activity_stamp_collector.xml layout

  1. In Android Studio, open the activity_stamp_collector.xml layout file and look at it in the Text tab.
  2. Replace the TextView in the template with the following RecyclerView. You'll use the RecyclerView to display your stamps in a vertical scrollable list.
<android.support.v7.widget.RecyclerView
        android:id="@+id/mRecyclerView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

Now switch to the Design tab of the layout. Your UI should look something like the following:

777cfbfa9f16440e.png

The RecyclerView class is a subclass of ViewGroup and is a resource-efficient way to display scrollable lists. Instead of creating a view for each item, whether or not the item is visible, RecyclerView creates a limited number of list items and reuses them to show visible content.

To display your data in a RecyclerView object, you need the following:

  • A data source: You use sample data for this codelab.
  • A RecyclerView object: The scrolling list that contains the list items, which you already created in your activity_stamp_collector.xml layout file.
  • A row layout: Layout for each row in the RecyclerView.
  • A layout manager: RecyclerView requires an explicit layout manager to manage the arrangement of list items contained within it. The layout can be vertical, horizontal, or a grid. You will use a vertical linear layout manager in this codelab.
  • An adapter: The adapter connects your data to the RecyclerView and prepares the data in a view holder. In this codelab you create an adapter that updates the stamp data in your views.
  • A view holder: Inside your adapter, you create a ViewHolder class that contains the view information for displaying one item from the item's layout.

800672a1f57d2048.png

Implement a RecyclerView

To implement a RecyclerView in this codelab, you'll follow these steps:

  • Create a layout XML file for one item (a row layout).
  • Extend the RecyclerView.Adapter class and implement the onCreateViewHolder(), getItemCount(), and onBindViewHolder() methods.
  • Extend the RecyclerView.ViewHolder class to create a view holder for the item layout. You can add click behavior by overriding the onClick method.
  • In your StampCollectorActivity, inside the onCreate() method, create a RecyclerView. Attach the RecyclerView with the adapter and a layout manager.

The steps are described in more detail below.

1. Create the layout for one item (a row layout)

The layout for a RecyclerView item is kept in a separate layout file so that the adapter can change item views without affecting the layout of the activity.

  1. Create a new layout file in your Android Studio project:
  • Select File > New > Android resource file.
  • Name the file recycler_row_layout.
  • Choose Layout as the resource type.
  • Choose android.support.constraint.ConstraintLayout as the root element.
  • Leave the default for the other options. Click OK.
  1. Open the recycler_row_layout.xml file and add following code inside the ConstraintLayout tag.
<ImageView
   android:id="@+id/stamp_pic"
   android:layout_width="100dp"
   android:layout_height="100dp"
   android:layout_marginBottom="16dp"
   android:layout_marginStart="16dp"
   android:layout_marginTop="16dp"
   android:contentDescription="@string/stamp_pic_desc"
   android:scaleType="fitXY"
   app:layout_constraintBottom_toBottomOf="parent"
   app:layout_constraintStart_toStartOf="parent"
   app:layout_constraintTop_toTopOf="parent"
   app:srcCompat="@mipmap/ic_launcher" />

<TextView
   android:id="@+id/stamp_title"
   android:layout_width="0dp"
   android:layout_height="100dp"
   android:layout_marginBottom="16dp"
   android:layout_marginStart="16dp"
   android:layout_marginTop="16dp"
   android:gravity="center_vertical"
   android:maxLines="6"
   android:scrollbars="vertical"
   android:text="@string/stamp_title"
   android:textAlignment="center"
   android:textColor="@android:color/black"
   android:padding="16dp"
   app:layout_constraintBottom_toBottomOf="parent"
   app:layout_constraintStart_toEndOf="@+id/stamp_pic"
   app:layout_constraintEnd_toStartOf="@+id/stamp_count"
   app:layout_constraintTop_toTopOf="parent" />

<TextView
   android:id="@+id/stamp_count"
   android:layout_width="wrap_content"
   android:layout_height="100dp"
   android:layout_marginBottom="16dp"
   android:layout_marginStart="16dp"
   android:layout_marginTop="16dp"
   android:gravity="center_vertical"
   android:text="@string/stamp_counter"
   android:textAlignment="center"
   android:textColor="@android:color/black"
   android:padding="16dp"
   app:layout_constraintBottom_toBottomOf="parent"
   app:layout_constraintEnd_toStartOf="@+id/add_button"
   app:layout_constraintTop_toTopOf="parent" />

<Button
   android:id="@+id/add_button"
   android:layout_width="50dp"
   android:layout_height="50dp"
   android:layout_marginLeft="16dp"
   android:layout_marginRight="16dp"
   android:layout_marginTop="8dp"
   android:text="@string/add_counter_button"
   android:textColor="@color/colorPrimaryDark"
   app:layout_constraintEnd_toEndOf="parent"
   app:layout_constraintTop_toTopOf="parent" />

<Button
   android:id="@+id/sub_button"
   android:layout_width="50dp"
   android:layout_height="50dp"
   android:layout_marginBottom="8dp"
   android:layout_marginLeft="16dp"
   android:layout_marginRight="16dp"
   android:layout_marginTop="8dp"
   android:text="@string/subtract_counter_button"
   android:textColor="@color/colorPrimaryDark"
   app:layout_constraintBottom_toBottomOf="parent"
   app:layout_constraintEnd_toEndOf="parent"
   app:layout_constraintTop_toBottomOf="@+id/add_button" />
  1. In the ConstraintLayout element, change the value for android:layout_height attribute to "wrap_content". The recycler_row_layout.xml layout file in Design tab should look like the following:

8175e5048364daae.png

2. Create an adapter

In the RecyclerView, the adapter acts as a bridge between the data and the views that are displayed. The adapter receives or retrieves the data, makes the data displayable, and places the data in a view.

You need to create your own custom adapter class. A class extending RecyclerView.Adapter implements a view holder, and must override the following callbacks:

  • onCreateViewHolder() inflates an item view and returns a new view holder that contains the item. The RecyclerView calls this method when the RecyclerView needs a new view holder to represent an item.
  • onBindViewHolder() sets the contents of an item at a given position in the RecyclerView. The RecyclerView calls this method when, for example, a new item scrolls into view.
  • getItemCount() returns the total number of items in the data set held by the adapter.

To create your own custom adapter, follow these steps:

  1. Right-click java/com.example.stampcollectorapp and select New > Java Class. Name the new class StampAdapter, leaving the default options selected.
  2. Give the StampAdapter class the following signature.
public class StampAdapter extends
       RecyclerView.Adapter<StampAdapter.StampHolder> {
}

The StampAdapter class extends a generic adapter for RecyclerView to use a view holder that is specific for your app and defined inside StampAdapter.StampHolder. The class shows an error, because you have not defined it. You define it in later steps.

  1. Click on the StampAdapter class declaration, then click the red light bulb on the left side of the pane. Select Implement methods. Select all three methods and click OK.

Empty placeholders are created for the methods that you must implement. Note how the onCreateViewHolder and onBindViewHolder methods both reference the StampHolder object, which hasn't been implemented yet.

  1. Define member variables to hold the LayoutInflater object and the data set inside StampAdapter. Initialize them using a constructor.
private ArrayList<StampData> mStampDataArray;
private LayoutInflater mLayoutInflater;

public StampAdapter(Context context,
                   ArrayList<StampData> data){

   mLayoutInflater = LayoutInflater.from(context);
   mStampDataArray = data;
}

The mStampDataArray object holds the data set. The mLayoutInflator object is used for inflating recycler_row_layout with the adapter class in next step.

  1. In onCreateViewHolder(), use the mLayoutInflator object to inflate the recycler_row_layout.xml layout file. Return a StampHolder object.
@Override
public StampAdapter.StampHolder onCreateViewHolder
       (ViewGroup parent, int viewType) {

   View view = mLayoutInflater.
           inflate(R.layout.recycler_row_layout,
                   parent,false);

   return new StampHolder(view);
}

The inflate() method let's you create a view object by attaching your layout XML file (recycler_row_layout.xml) . Once you have the view object, you can pass it as a parameter to the constructor of the StampHolder class.

3. Create a view holder

  1. Inside the StampAdapter class, add a new StampHolder inner class with the following signature.
public class StampHolder extends RecyclerView.ViewHolder {
}

Click the StampHolder class declaration, and the red light bulb pops up on the left. Click the light bulb and select Create constructor matching super to create a default constructor.

Your code should no longer show errors.

  1. In the StampHolder class, define all the view objects that were added in the recycler_row_layout.xml layout file. Also define an integer counter variable and a StampData class object as member variables. Initialize them in the constructor of the StampHolder class.
TextView mStampTitleHolder;
TextView mStampCounterHolder;
ImageView mStampIconHolder;

Button mAddButton;
Button mSubButton;

private int mCounter;

private StampData mEditStampData;

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

   mStampTitleHolder = itemView.
           findViewById(R.id.stamp_title);

   mStampIconHolder = itemView.
           findViewById(R.id.stamp_pic);

   mStampCounterHolder = itemView.
           findViewById(R.id.stamp_count);

   mAddButton = itemView.
           findViewById(R.id.add_button);

   mSubButton = itemView.
           findViewById(R.id.sub_button);
  
}
  1. In the StampAdapter class, inside onBindViewHolder(), bind the data set to the views that are already defined in the StampHolder class.
@Override
public void onBindViewHolder
       (StampAdapter.StampHolder holder, int position) {

StampData currentStampData = mStampDataArray.get(position);

holder.mStampTitleHolder.setText(
       currentStampData.getStampTitle());
holder.mStampIconHolder.setImageResource(
       currentStampData.getStampIcon());
holder.mStampCounterHolder.setText(
       String.valueOf(currentStampData.getStampCounter()));
}
  1. Implement the getItemCount()method in StampAdapter to return the total number of items in the data set.
@Override
public int getItemCount() {
   return mStampDataArray.size();
}

4. Attach the adapter to the RecyclerView in the StampCollectorActivity

Now that you've defined your own custom RecyclerView adapter, you can create an instance of the adapter in the StampCollectorActivity. To add the RecyclerView, follow these steps:

  1. Inside StampCollectorActivity, define a RecyclerView object as a member variable.
private RecyclerView mRecyclerView;
  1. Inside StampCollectorActivity, define a StampAdapter object as a member variable.
private StampAdapter mAdapter;
  1. Create a setupRecyclerView() method to connect the RecyclerView with your StampCollectorActivity. In setUpRecyclerView(), initialize the RecyclerView object. Set a LinearLayoutManager object, and attach the RecyclerView object to the StampAdapter object that will populate the data set.
private void setUpRecyclerView(){

   //Initialize RecyclerView object
   mRecyclerView = findViewById(R.id.mRecyclerView);

   //Set up a line after each row, so it looks like a list
   mRecyclerView.addItemDecoration(new DividerItemDecoration(
           this,DividerItemDecoration.VERTICAL));

   //Set up the LayoutManager for RecyclerView
   mRecyclerView.setLayoutManager(new
           LinearLayoutManager(this));

   //Attach adapter object with RecyclerView
   mRecyclerView.setAdapter(mAdapter);
}
  1. In onCreate(), initialize the adapter object. Call the setupRecyclerView() method at the end.
mAdapter = new StampAdapter(this,mStampData);

setUpRecyclerView();

Now run your app. You see all the stamps loaded in the RecyclerView:

5c7445b403f0fdab.png

5. Handle user interaction inside the RecyclerView

  1. To increase and decrease the stamp count, add click handlers to the "+" and "-" buttons. In StampAdapter.java, add the following code to the StampHolder constructor.
  2. Add the method changeStampCount()inside the StampHolder class.

Inside changeStampCount(), you will first get the current instance of the StampData class in the ArrayList mStampDataArray, then either increment or decrement the count value of mCounter depending on the increaseCount boolean argument. After changing the count values you need to update the instance in the mStampDataArray ArrayList object, and notify the adapter.

  1. Once again, run the app. Now you can interact with the +/- buttons to increase and decrease the stamp counter for each stamp.

When you open the app again, the count resets to 0. In the next part of this codelab you will learn how to save data locally using the SharedPreferences interface.

When you increase or decrease the counter, the counter value is reflected on the screen, but when you close the activity, the counter values are lost. Use the SharedPreferences interface to save the counter value of each stamp in the form of a key/value pair inside an XML file.

Implementing SharedPreferences

1. Initialize the SharedPreferences object

  1. Add a member variable to the StampCollectorActivity class to hold the name of the key you use to save the data. Also add a member variable to hold a reference to a SharedPreferences object.
private static final String STAMP_KEY = "save_key";
private SharedPreferences mSharedPref;
  1. In the onCreate() method of StampCollectorActivity, initialize mSharedPref before the setupData() call.
mSharedPref = getPreferences(MODE_PRIVATE);

The getPreferences(MODE_PRIVATE) method returns a reference to the default SharedPreferences file inside the StampCollectorActivity . The MODE_PRIVATE parameter makes the file inaccessible outside the app.

2. Save preferences in the onPause() method

  1. To save or update any value inside the SharedPreferences object, you need a SharedPreferences.Editor object. Create a saveStamps() method inside the StampCollectorActivity class to save the data in a SharedPreferences.Editor object.
private void saveStamps(){
        //Editor object to save or update data
        SharedPreferences.Editor editor = mSharedPref.edit();
        //Convert data in to Json String
        Gson gson = new Gson();
        String jsonStamps = gson.toJson(mStampData);

        //Save Data inside the SharedPreferences using putString
        editor.putString(STAMP_KEY,jsonStamps);

        //Confirm the changes
        editor.apply();
}

With the Editor object, you can save data of all primitive data types. You have multiple stamps, you want to save values for multiple instances of the StampData class, which are inside an ArrayList object mStampData.

The Gson library is used to convert the mStampData object to String objects. To save data in the form of key/value pairs, use the putString() method inside the SharedPreferences.Editor class.

  1. Override onPause() lifecycle method of the StampCollectorActivity to call the saveStamps() method:
@Override
protected void onPause() {
   super.onPause();
   saveStamps();
}

This code saves your stamp data every time the app is closed, when screen is rotated or even if the app is moved into the background.

3. Load shared preferences

  1. Create a loadStamps() method that returns an array of StampData objects. In loadStamps(), retrieve data using a SharedPreferences object with the same key that was used when the data was saved.
private ArrayList<StampData> loadStamps(){
        //Fetch the data from the SharedPreferences object
        String jsonStampString = mSharedPref.getString(STAMP_KEY,"");

        Gson gson = new Gson();
        Type type = new TypeToken<List<StampData>>(){}.getType();
        ArrayList<StampData> loadData = 
                gson.fromJson(jsonStampString, type);
        if(loadData == null) {
            loadData = new ArrayList<>();
        }
        return loadData;
}

With the Gson library, we again convert our Json String Object back to an ArrayList of StampData and return an array of StampData objects.

  1. Inside the onCreate() method, call loadStamps() to initialize the mStampData, right after the mSharedPref object initialization statement.
mStampData = loadStamps();
  1. After calling loadStamps(), when the app is run for first time, loadStamps() does not return any data, because data is still not saved. You need to load default data. Replace the setupData() call inside onCreate() with the following code.
if (mStampData.size() == 0) {
   setupData(mStampTitle, mStampIcon, mStampCounter);
}

Now run the app again. This time, the stamp-count data persists across app relaunch.

You have now implemented the SharedPreferences interface in your app. SharedPreferences data is saved locally in your app and will remain there until the app is uninstalled or its data is cleared.

By now, you have a working app, where you can see some stamps, increase their count, and save and retrieve them. To create a complete basic stamp collector app, let's add a few more features to it, such as adding a new stamp, updating an existing stamp, and deleting a stamp. Let's start by adding and updating a stamp.

Add and Update a Stamp

1. Add additional resources

Going forward you will require more string resources. So open res>values>strings.xml, and add the following resources.

<string name="stamp_update_title">Update Stamp</string>
<string name="stamp_add_title">Add New Stamp</string>
<string name="save">save</string>
<string name="cancel">cancel</string>

2. Create Layout for Dialog box

Your app will display a dialog box to the user in order to add or update a stamp. The layout for a Dialog is kept in a separate layout file, so that you can attach it later to a dialog object.

  1. Create a new layout file in your Android Studio project:
  • Select File > New > Android resource file.
  • Name the file stamp_dialog_layout.
  • Choose Layout as the resource type.
  • Choose android.support.constraint.ConstraintLayout as the root element.
  • Leave the default for the other options. Click OK.
  1. Open the stamp_dialog_layout.xml file in the text tab and add the following code inside the ConstraintLayout tag.
<EditText
   android:id="@+id/edittext_stampname"
   android:layout_width="0dp"
   android:layout_height="wrap_content"
   android:layout_marginEnd="16dp"
   android:layout_marginLeft="16dp"
   android:layout_marginRight="16dp"
   android:layout_marginStart="16dp"
   android:layout_marginTop="8dp"
   android:ems="10"
   android:hint="@string/stamp_title"
   android:inputType="textPersonName"
   app:layout_constraintLeft_toLeftOf="parent"
   app:layout_constraintRight_toRightOf="parent"
   app:layout_constraintTop_toTopOf="parent" />

<EditText
   android:id="@+id/edittext_stampcounter"
   android:layout_width="0dp"
   android:layout_height="wrap_content"
   android:layout_marginLeft="16dp"
   android:layout_marginRight="16dp"
   android:layout_marginTop="8dp"
   android:ems="10"
   android:hint="@string/stamp_counter"
   android:inputType="textPersonName"
   app:layout_constraintLeft_toLeftOf="parent"
   app:layout_constraintRight_toRightOf="parent"
   app:layout_constraintTop_toBottomOf="@+id/edittext_stampname" />
  1. The stamp_dialog_layout.xml layout file in the Design tab should look like the following:

9449b2129a031a2d.png

3. Create the AddAndUpdateStampUtility Class

In this step, you create a class called AddAndUpdateStampUtility to define methods to add and update the stamps.

  1. In Android Studio, in the StampCollector_Starter_App app, right-click java/com.example.stampcollectorapp and select New > Java Class.
  2. Name the new class AddAndUpdateStampUtility, leaving the default options selected.
  3. Add the following code inside the AddAndUpdateStampUtility class.
private Context mContext;
private ArrayList<StampData> mStampData;
private RecyclerView.Adapter mAdapter;

private EditText mStampNameEdit,mStampCountEdit;
private boolean mEntryValid;

public AddAndUpdateStampUtility(Context context,
                    ArrayList<StampData> stampData,
                    StampAdapter adapter){
   mContext = context;
   mStampData = stampData;
   mAdapter = adapter;
}

You will need a Context, as well as ArrayList and RecyclerView.Adapter objects, which you will initialise in the constructor of the class. In addition to that, you will also declare two EditText objects, mStampNameEdit and mStampCountEdit, as member variables which will get the input from the dialog box. At last, you will declare the mEntryValid boolean variable to check for valid inputs in both EditText views.

  1. Next you need to define a method which can add or update a stamp. Define a method named addOrUpdateStamp which will take an integer argument position to identify the position of stamp.
public void addOrUpdateStamp(int position){
}
  1. Inside addOrUpdateStamp method, add following code.
final int stampPosition = position;
View dialogView = LayoutInflater.from(mContext).
       inflate(R.layout.stamp_dialog_layout, null);

mStampNameEdit = dialogView.
       findViewById(R.id.edittext_stampname);
mStampCountEdit = dialogView.
       findViewById(R.id.edittext_stampcounter);

final boolean editing = stampPosition > -1;

String dialogTitle = editing ? mContext.
       getString(R.string.stamp_update_title) :
       mContext.getString(R.string.stamp_add_title);


AlertDialog.Builder builder = new AlertDialog.Builder(mContext)
       .setView(dialogView)
       .setTitle(dialogTitle)
       .setPositiveButton(R.string.save, null)
       .setNegativeButton(R.string.cancel, null);

final AlertDialog dialog = builder.show();

if (editing) {
   StampData editStamp = mStampData.get(stampPosition);
   mStampNameEdit.setText(editStamp.getStampTitle());
   mStampCountEdit.setText(
           String.valueOf(editStamp.getStampCounter()));
}

After receiving the current stamp position inside stampPosition, you will first need to display a dialog box asking the user to enter or edit the stamp title and stamp count. To display the dialog box, you attach the stamp_dialog_layout layout to the dialog object.

  • If the user wants to edit an existing stamp, they will tap one of the stamps displayed, which will call the addOrUpdateStamp() method and pass the position of the clicked stamp. The dialog box will be customised accordingly.
  • If the user wants to add a new stamp, they will click the Add Menu item (explained in the next step). In this case, the stamp position will always be -1. The dialog box will be customised for adding a new stamp.
  1. To handle the click listener inside the dialog box, further add the following code inside addOrUpdateStamp method.
dialog.getButton(AlertDialog.BUTTON_POSITIVE).
       setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {

               boolean stampNameValid = !mStampNameEdit.
                       getText().toString().isEmpty();
               boolean stampCountValid = !mStampCountEdit.
                       getText().toString().isEmpty();

               mEntryValid = stampNameValid && stampCountValid;

               if (mEntryValid) {
                   if (editing) {
                       StampData editStamp =
                               mStampData.get(stampPosition);
                       editStamp.setStampTitle(mStampNameEdit.
                               getText().toString());
                       editStamp.setStampCounter(
                               Integer.parseInt(mStampCountEdit
                                       .getText().toString()));
                       mStampData.set(stampPosition, editStamp);

                       mAdapter.notifyItemChanged(stampPosition);
                   } else {
                       StampData newStamp = new StampData();
                       newStamp.setStampTitle(mStampNameEdit.
                               getText().toString());
                       newStamp.setStampCounter(
                               Integer.parseInt(mStampCountEdit
                                       .getText().toString()));
                       mStampData.add(newStamp);

                       mAdapter.notifyItemInserted(mStampData.size());

                   }
                   dialog.dismiss();
               } else {
                   Toast.makeText(mContext,
                           "Please Enter Valid Data",
                           Toast.LENGTH_SHORT).show();
               }

           }
});

While adding a new stamp or updating an existing stamp, in both cases, you need to update mStampData. You will use the mStampData.set() method to update, and mStampData.add() for adding a new stamp.

4. Add a Menu Item

You will start by adding a menu item, which will provide the feature to add a new stamp. To add a menu item, follow these steps

  1. Select res folder and right-click:
  2. Select New>Android resource directory.
  3. Select Resource type: menu.
  4. Right click on menu and select New>New Resource File
  5. Provide File Name: stamp_menu.
  6. Click on Ok.
  7. Open stamp_menu.xml in text tab and add the following code inside the menu tag.
<item
    android:id="@+id/add_new_stamp"
    android:title="Add Stamp"
    app:showAsAction="never" />
  1. In StampCollectorActivity.java, declare the member variable mAddAndUpdateStampUtility of type AddAndUpdateStampUtility.
private AddAndUpdateStampUtility mAddAndUpdateStampUtility;
  1. Inside the onCreate() method of StampCollectorActivity, initialize the object mAddAndUpdateStampUtility after the mAdapter object has been initialized.
mAddAndUpdateStampUtility = new AddAndUpdateStampUtility
                (this,mStampData,mAdapter);
  1. Inside the StampCollectorActivity class, add following two methods.
@Override
public boolean onCreateOptionsMenu(Menu menu) {
   getMenuInflater().inflate(R.menu.stamp_menu,menu);
   return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
   switch (item.getItemId()){
       case R.id.add_new_stamp :
           mAddAndUpdateStampUtility.
                        addOrUpdateStamp(-1);
           saveStamps();
           break;
   }
   return true;
}

In onCreateOptionsMenu attach the menuItem defined in stamp_menu.xml file. You will use the inflate() method inside the MenuInflater class to attach the menu file. Then, in onOptionsItemSelected(), handle the menu items using switch cases. For adding a new stamp, call the method addOrUpdateStamp(-1) defined in the class AddAndUpdateStampUtility. You will pass -1 as the argument, because you want to add a new stamp here. Any new stamp that is added will be displayed as a generic stamp icon.

After adding a new stamp, you also need to save all the stamps using saveStamps().

4. Update AddAndUpdateStampUtility Class

  1. Now to update an existing stamp. Open the StampAdapter class and declare an object for AddAndUpdateStampUtility as a member variable mAddAndUpdateStampUtilityAd.
private AddAndUpdateStampUtility mAddAndUpdateStampUtilityAd;
  1. Inside the constructor of the StampAdapter class, initialize the mAddAndUpdateStampAd object.
mAddAndUpdateStampUtilityAd = new AddAndUpdateStampUtility
                (context,mStampDataArray,this);
  1. When the user clicks on a stamp displayed in the RecyclerView, you want to display a stamp editing dialog box. The dialog box will contain the latest stamp title and stamp count, which a user can update now. So, you need to implement an onClickListener() on the itemView object inside the constructor of the StampHolder class and call the addOrUpdateStamp() method from the AddAndUpdateStampUtility class. Add the following code inside the constructor of the StampHolder class.
itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mAddAndUpdateStampUtilityAd.
                            addOrUpdateStamp(getAdapterPosition());
                }
});

Now run your application. You should be able to update the title of an existing stamp by clicking it.

599700c0709de891.png cb27457a6e6f35e9.png

Delete a Stamp

In order to delete a stamp, we can use the ItemTouchHelper class which extends the RecyclerView.ItemDecoration class. ItemTouchHelper lets you interact with the list of items using touch in four directions: Left, Right, Up and Down. You can use swipe left or right to delete a stamp.

  1. Open StampCollectorActivity.java and add the following code inside the setUpRecyclerView() method.
ItemTouchHelper helper = new ItemTouchHelper
       (new ItemTouchHelper.SimpleCallback(0,
               (ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT)) {
   @Override
   public boolean onMove(RecyclerView recyclerView,
                         RecyclerView.ViewHolder viewHolder,
                         RecyclerView.ViewHolder target) {
       return false;
   }

   @Override
   public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {

   }
});

You are going to use the SimpleCallback abstract class, which provides the methods onMove() and onSwiped().

  1. Inside the onSwiped() method, add the following code.

This code will display an alert dialog every time the user swipes to delete a stamp, confirming their choice.

  1. Finally, attach the helper object to the current RecyclerView object mRecyclerView with the help of the attachToRecyclerView() method. Add the following code at the end of the setUpRecyclerView() method.
helper.attachToRecyclerView(mRecyclerView);

Now run your application and swipe any stamp to the left or right to delete it. You will be asked to confirm your choice to delete the stamp.

766392d9a0580fdf.png

Congratulations, you're all done!

Test the finished StampCollector app in an emulator and on an actual device, and you see the list of stamps displayed in a RecyclerView. This codelab only introduces you to the concept of using RecyclerView objects and shared preferences. You learned how you can add, update, and even delete a stamp.

You can also add more advanced features, such as letting users snap photos of stamps and add them to the list. You can include a RESTful web service to keep the list of stamps updated as new stamps come on the market.

There are lots of possibilities and ideas that you can incorporate and try. Now use this concept to design something cool of your own.