Android L: RecyclerView Tutorial

Hi,  in today’s tutorial I will show you how to create a list with the newest widget presented at Google I/O 2014, RecyclerView. Also I will show how to add click and long click events within adapter.

WHAT’S DIFFERENT?

  • is a more advanced and flexible version of ListView
  • it forces the implementation of ViewHolde pattern
  • the recycling process is more efficient
  • before you had to create only the ListView and the Adapter, now you have to create the ListView, a LayoutManager and the Adapter.
  • the LayoutManager avoids the calling of findViewById for too many times

 

1. Create a new project and name your main activity MyActivity. (At this step do not write anything in this class).

NOTE: Create your project to support Android L Preview. If you do not have the Android L sdk you can download it from SDK manager.  Your build.gradle file should look like this:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 'android-L'
    buildToolsVersion "20.0.0"

    defaultConfig {
        applicationId "com.example.myapplication"
        minSdkVersion 'L'
        targetSdkVersion 'L'
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            runProguard false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:recyclerview-v7:+'
}

2.  Create an xml (or rename the generated one) and name it “my_activity_layout.xml”.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MyActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/my_recycler_view"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>

3. Create a new xml and name it “item_row.xml”

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:gravity="center_vertical"
    android:id="@+id/layout">

    <TextView
        android:id="@+id/rowNumberTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <TextView
        android:id="@+id/nameTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout>

4. Create a new java class for the adapter and name it “MyCustomAdapter”

import android.content.Context;
import android.graphics.Color;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;

public class MyCustomAdapter extends RecyclerView.Adapter<MyCustomAdapter.ViewHolder> implements View.OnClickListener,
        View.OnLongClickListener{

    private ArrayList<String> mDataset;
    private static Context sContext;

    // Adapter's Constructor
    public MyCustomAdapter(Context context, ArrayList<String> myDataset) {
        mDataset = myDataset;
        sContext = context;
    }

    // Create new views. This is invoked by the layout manager.
    @Override
    public MyCustomAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                   int viewType) {
        // Create a new view by inflating the row item xml.
        View v = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_row, parent, false);

        // Set the view to the ViewHolder
        ViewHolder holder = new ViewHolder(v);
        holder.mNameTextView.setOnClickListener(MyCustomAdapter.this);
        holder.mNameTextView.setOnLongClickListener(MyCustomAdapter.this);

        holder.mNameTextView.setTag(holder);

        return holder;
    }

    // Replace the contents of a view. This is invoked by the layout manager.
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {

        holder.mNumberRowTextView.setText(String.valueOf(position) + ". ");
        // Get element from your dataset at this position and set the text for the specified element
        holder.mNameTextView.setText(mDataset.get(position));

        // Set the color to red if row is even, or to green if row is odd.
        if (position % 2 == 0) {
            holder.mNumberRowTextView.setTextColor(Color.RED);
        } else {
            holder.mNumberRowTextView.setTextColor(Color.GREEN);
        }
    }

    // Return the size of your dataset (invoked by the layout manager)
    @Override
    public int getItemCount() {
        return mDataset.size();
    }

    // Implement OnClick listener. The clicked item text is displayed in a Toast message.
    @Override
    public void onClick(View view) {
        ViewHolder holder = (ViewHolder) view.getTag();
        if (view.getId() == holder.mNameTextView.getId()) {
            Toast.makeText(sContext, holder.mNameTextView.getText(), Toast.LENGTH_SHORT).show();
        }
    }

    // Implement OnLongClick listener. Long Clicked items is removed from list.
    @Override
    public boolean onLongClick(View view) {
        ViewHolder holder = (ViewHolder) view.getTag();
        if (view.getId() == holder.mNameTextView.getId()) {
            mDataset.remove(holder.getPosition());

            // Call this method to refresh the list and display the "updated" list
            notifyDataSetChanged();

            Toast.makeText(sContext, "Item " + holder.mNameTextView.getText() + " has been removed from list",
                    Toast.LENGTH_SHORT).show();
        }
        return false;
    }

    // Create the ViewHolder class to keep references to your views
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView mNumberRowTextView;
        public TextView mNameTextView;

        /**
         * Constructor
         * @param v The container view which holds the elements from the row item xml
         */
        public ViewHolder(View v) {
            super(v);

            mNumberRowTextView = (TextView) v.findViewById(R.id.rowNumberTextView);
            mNameTextView = (TextView) v.findViewById(R.id.nameTextView);
        }
    }
}

5. Go to your “MyActivity” class and add the following code:

import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import java.util.ArrayList;

public class MyActivity extends Activity {

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

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);

        // improve performance if you know that changes in content
        // do not change the size of the RecyclerView
        recyclerView.setHasFixedSize(true);

        // use a linear layout manager
        RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(mLayoutManager);

        // Data set used by the adapter. This data will be displayed.
        ArrayList<String> myDataset = new ArrayList<String>();
        for (int i= 0; i < 70; i++){
            myDataset.add("Diana " + i);
        }

        // Create the adapter
        RecyclerView.Adapter adapter = new MyCustomAdapter(MyActivity.this, myDataset);
        recyclerView.setAdapter(adapter);
    }
}

Create an emulator which can run Android L (Api Level 20) and run the app.

RecyclerView

Errors after update to Android Studio 0.8

I have just updated my Android Studio to Android Studio 0.8.2. But, after the update I kept getting these 3 errors while I was trying to run my project:

1.  First error

Error:Execution failed for task 
> java.io.FileNotFoundException: C:\Program Files (x86)\Android\android-studio\sdk\tools\proguard\proguard-android-optimize.txt (The system cannot find the path specified)

2. Second error (this happened only after the 1st error had been fixed)

Error:Execution failed for task 
> Manifest merger failed : uses-sdk:minSdkVersion 9 cannot be smaller than version L declared in library com.android.support:appcompat-v7:21.0.0-rc1

3. Third Error (when running the app for Android L)

Error:compileSdkVersion android-L requires compiling with JDK 7

1. First error solution As a workaround I did the following steps:

  • downloaded the proguard txt file (you can download it from here:proguard-android.txt).
  •  renamed it from proguard-android to proguard-android-optimize.txt
  • created a new folder and named it proguard under
 C:\Program Files (x86)\Android\android-studio\sdk\tools\

I don’t know exactly why I got this error as on my the other PC I did the update and everything was ok, but I found on the internet that there are other developers who had this problem (here). 2. Second error solution  So after some research I found out that in one of my modules I had this in the dependencies from build.gradle:

 compile 'com.android.support:appcompat-v7:+'

which I suppose represents the latest version which is 21.0.0.  The support libraries of the preview of Android L work only if the minSDK is Android L. So, as in my case as I didn’t want to run the app for Android L, I had to set specifically the appcompat-v7 to 20 (the latest one before Android L). Below is the code:

compile 'com.android.support:appcompat-v7:20.+'

appcompat-v7.20

 

3.  Third Error Solution

This means that Android L is working only with jdk 7. So in order to fix this error you have to do the following steps:

3.1. Install jdk 7. You can download the .dmg and .exe files from here.

MAC

jdk 7 download

Windows

jdk windows

3.2. After the dmg is downloded click on it to install the jdk.

3.3. After the jdk is installed open Project Structure from here:

project structure

or from File – Project Structure.

3.4.  Now JDK location should be changed from :

MAC OS

/System/Library/Java/JavaVirtualMachines/jdk1.6.0.jdk/Contents/Home

JDK location

to:

/Library/Java/JavaVirtualMachines/jdk1.7.0_55.jdk/Contents/Home

New JDK Location

 Notice that on Mac OS they changed the path from /System/Library to /Library!

Windows 

Change JDK location from:

C:\Program Files\Java\jdk1.6.0_45

jdk location windows

to

C:\Program Files\Java\jdk1.7.0_55

new jdk location windows

Share image Intent

Whenever you need to share an image from the external storage you can use the startActivity method and an Intent to do that.

Doing so, the user will be able to chose whatever app he likes to share the photo.

Intent share = new Intent(Intent.ACTION_SEND);
share.setType("image/png");
share.putExtra(Intent.EXTRA_STREAM, Uri.parse("file:///"+imagePath));
startActivity(Intent.createChooser(share, "Share Image"));

If you don’t append the “file:///” prefix to the image path, the apps won’t load your image so that is important too.

And if you need a ready to go method, here you go:

/**
     * Opens a picker dialog displaying all the apps that support image sharing
     *
     * @param context     activity that triggers this method
     * @param image       path to the image that should be uploaded
     * @param dialogTitle the title of the sharing dialog
     */
    public void shareImageIntent(Context context, String image, String dialogTitle) {
        Intent share = new Intent(Intent.ACTION_SEND);
        share.setType("image/png");
        share.putExtra(Intent.EXTRA_STREAM, Uri.parse("file:///" + image));
        context.startActivity(Intent.createChooser(share, dialogTitle));
    }