Android Firebase File Storage - Part 2: Downloading Files

Android Firebase File Storage - Part 2: Downloading Files

    In Part 1, you've learned about integrating Firebase in your Android Studio project and  upload files from your app to Firebase Storage then.  Of course, in this part 2, I will talk about the remaining feature: downloading file with two options: as a byte array or a temporary file.

    Firstly, go to Firebase Console page and select your project, in the File Storage entry, you'll see all uploaded files like this:
    Now, we will dig to the the solution that download these files to your Android app.

Download file as a byte array

    As noted at part 1, in order to access your Firebase Storage files, you'll need to first get a reference to the FirebaseStorage object, and then create a StorageReference to your project's URL and the file that you want to download. You can find your project's URL at the top of the Files section of Storage in the Firebase Console.
FirebaseStorage storage = FirebaseStorage.getInstance();
StorageReference storageRef = storage.getReferenceFromUrl("gs://filestorage-d5afb.appspot.com").child("nougat.jpg");
    If you only need to download the file as a byte[] and don't need it as a file, which is the more likely case when loading an image into an ImageView, then you can retrieve the bytes in a similar style:
                final long ONE_MEGABYTE = 1024 * 1024;

                //download file as a byte array
                storageRef.getBytes(ONE_MEGABYTE).addOnSuccessListener(new OnSuccessListener<byte[]>() {
                    @Override
                    public void onSuccess(byte[] bytes) {
                        Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
                        imageView.setImageBitmap(bitmap);
                        showToast("Download successful!");
                    }
                });
    In my sample project, this is the output for this process:

Download as a temporary file

    Let create a File object and attempt to load the file you want by calling getFile() method on your StorageReference with the new File object passed as a parameter. Since this operation happens asynchronously, you can also add OnSuccessListener and OnFailureListener interface to your call in order to handle result:
                try {
                    showProgressDialog("Download File", "Downloading File...");
                    final File localFile = File.createTempFile("images", "jpg");
                    storageRef.getFile(localFile).addOnSuccessListener(new OnSuccessListener() {
                        @Override
                        public void onSuccess(FileDownloadTask.TaskSnapshot taskSnapshot) {
                            Bitmap bitmap = BitmapFactory.decodeFile(localFile.getAbsolutePath());
                            imageView.setImageBitmap(bitmap);
                            dismissProgressDialog();
                            showToast("Download successful!");
                        }
                    }).addOnFailureListener(new OnFailureListener() {
                        @Override
                        public void onFailure(@NonNull Exception exception) {
                            dismissProgressDialog();
                            showToast("Download Failed!");
                        }
                    });
                } catch (IOException e ) {
                    e.printStackTrace();
                    Log.e("Main", "IOE Exception");
                }
    In my case, this downloaded file is an image so I set it to the ImageView. Moreover, you can do other works with the downloaded file depend on your own aim. This is output of my code:

Getting a file's URL

    Sometimes, if you wouldn't like to download the file, you only need it's URL, you can use getDownloadUrl() method on your StorageReference, which will give you a Uri pointing to the file's location:
storageRef.getDownloadUrl().addOnSuccessListener(new OnSuccessListener() {
                    @Override
                    public void onSuccess(Uri uri) {
                        Log.i("Main", "File uri: " + uri.toString());
                    }
                });
    And this is the output on Log Cat:

Some advanced options with uploading/downloading process

    As any Android developer can attest, sometimes the Android activity lifecycle can cause unexpected issues. Fortunately, Firebase SDK has provided the way to keep all active download/upload tasks and you can restore them when your Activity recreated (by override onSaveInstanceState() and onRestoreInstanceState(). For the details, please read at handling Activity lifecycle change at File Storage official document, I won't talk about this on this simple tutorial.
    In particular about uploading process, there are some methods to control the UploadTask state: pause(), resume(), cancel(). In addition, you can use an OnPauseListener and OnProgressListener to keep track of upload progress and pause states. You can read more at the upload process document.

Conclusions

    Through 2 parts of Firebase File Storage tutorial, I hope that you've learned one more feature of Firebase back-end service and able to apply to your own work. You now understand the way to upload and download files, control data transfers,...when these tasks are occurring. With more tutorials about Firebase, please visit this link.
    References:
Android Firebase File Storage - Part 1: Uploading Files

Android Firebase File Storage - Part 1: Uploading Files

    In this blog, I had presented some Firebase features like account authentication, real-time database, put notifications. Because of being a major tool for providing quick back-end support for web pages and mobile applications now, Firebase has been added a lot of new features. Today, with this tutorial, I will introduce you to the file storage and retrieval functionality available for your Android apps.

Setting up Firebase to Android Studio project

    Firebase now has been integrated to Android Studio as a tool, so in the menu bar, let select Tool -> Firebase, you will see this panel on the right:
    Choose Storage entry and click "Upload and download file with Firebase Storage", this panel will appear:
    Choose "Connect to Firebase", Android Studio will launch your default browser and you must be logged in by Google account to continue, let follow these simple steps on Google developer console and when you return to Android Studio, you have this dialog:
    Choose an existed project on your Firebase console (or create a new project) and click "Connect to Firebase". After this process completed, click at "Add Firebase Storage to your app" (entry (2)), you'll have this dialog:
    Click "Accept Changes" to add these dependencies and google-services.json file to your project. You now have completed setting up environment work!
    As you can see at the right panel, at the entry (3), (4) and (5), these are tutorials about initializing StorageReference and download/upload files from/to Firebase Storage:

    Open Firebase console page and select your project, choose Storage in the left navigation column and select Rules tab, you'll see this code:
    In this tutorial, I would like to allow unauthenticated users to access and upload files to keep things simple. So on the line allow read, write: if request.auth != null;, change != to == and click the PUBLISH button:

    Now any user of your app should be able to upload or download files from your Firebase back-end. Please remember: this is not ideal for a production environment, but within the scope of a tutorial, it will make learning about Firebase Storage a lot easier without having to dig into authentication code.

Testing Upload/Download File on Firebase console

    Of course, you can manually upload files from the Firebase Console. Switch to tab File, you'll see a blue button titled Upload File:Click it and upload a file from your computer, it will appear in your Firebase Storage:
    Similar with upload, you can select a file from this page, "Download" button will appear and when click it, your file will be downloaded to your computer:

Upload a Byte array from Android application

    Now, turn to your Android project to write upload code. First of all, put an image (PNG) file to assets folder and a text file to raw folder like this:

    In order to access your Firebase Storage files, you'll need to first get a reference to the FirebaseStorage object, and then create a StorageReference to your project's URL and the file that you want to upload. You can find your project's URL at the top of the Files section of Storage in the Firebase Console. In onCreate() of your Activity, provide this code:
FirebaseStorage storage = FirebaseStorage.getInstance();
StorageReference storageReference = storage.getReferenceFromUrl("gs://filestorage-d5afb.appspot.com").child("firebase.png");
    Next, we will need to get a byte array from the image file located in assets folder. We will retrieve it as a Bitmap, compressing it into a ByteArrayOutputStream, and then turning that into a byte[]. Then, you can create an UploadTask by calling putBytes(byte[]) to upload your image to Firebase. This UploadTask can also have an OnSuccessListener and OnFailureListener to handling the upload process result:
AssetManager assetManager = MainActivity.this.getAssets();
                InputStream istr;
                Bitmap bitmap;
                try {
                    //get bitmap from PNG file in assets folder
                    istr = assetManager.open("firebase.png");
                    bitmap = BitmapFactory.decodeStream(istr);

                    //decode to byte output stream
                    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                    bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
                    byte[] data = outputStream.toByteArray();

                    //Upload to firebase
                    showProgressDialog("Upload Bitmap", "Uploading...");
                    UploadTask uploadTask = storageReference.putBytes(data);
                    uploadTask.addOnFailureListener(new OnFailureListener() {
                        @Override
                        public void onFailure(@NonNull Exception exception) {
                            exception.printStackTrace();
                            dismissProgressDialog();
                            Toast.makeText(MainActivity.this, "Upload Failed!", Toast.LENGTH_SHORT).show();
                        }
                    }).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
                        @Override
                        public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
                            dismissProgressDialog();
                            Toast.makeText(MainActivity.this, "Upload successful!", Toast.LENGTH_SHORT).show();
                        }
                    });

                } catch (IOException e) {
                    e.printStackTrace();
                }
    In this sample project, upload process will be invoked after click a Button, this is output:
    Go to Firebase Console page, you will see the new uploaded file(firebase.png):

Uploading From an InputStream

    Now that you know how to upload a byte array, the other two types of uploads should be fairly intuitive. Let's say we have a text file named test.txt in our raw resources folder. We can read this into an InputStream and then upload it by using putStream(InputStream) method of StorageReference. Like the case above, we use an UploadTask to upload and adding addOnSuccessListener and addOnFailureListener to it:
                storageReference = storage.getReferenceFromUrl("gs://filestorage-d5afb.appspot.com").child("test_upload.txt");

                //Upload input stream to Firebase
                showProgressDialog("Upload File", "Uploading text file...");
                InputStream stream = getResources().openRawResource(R.raw.test);
                UploadTask uploadTask = storageReference.putStream(stream);
                uploadTask.addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception exception) {
                        exception.printStackTrace();
                        dismissProgressDialog();
                        Toast.makeText(MainActivity.this, "Upload Failed!", Toast.LENGTH_SHORT).show();
                    }
                }).addOnSuccessListener(new OnSuccessListener() {
                    @Override
                    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
                        dismissProgressDialog();
                        Toast.makeText(MainActivity.this, "Upload successful!", Toast.LENGTH_SHORT).show();
                    }
                });
    And this is output of my sample project:
    Go to Firebase Console page, you will see new uploaded file named test_upload.txt:

    NOTE: You also have another upload option is File. Uploading an existing file is just as easy: simply get a reference to the file and call putFile(Uri) with a URI pointing to your file.

Conclusions

    In this post, I have presented the way to upload a file to Firebase. Based on the file type, we can upload it as a byte array or InputStream or File. Up to Part 2, I will talk about downloading file from Firebase Storage, coming soon!
    References:
Android Material Design component: Chip - Part 2: AutoCompleteTextView with chips (like Gmail)

Android Material Design component: Chip - Part 2: AutoCompleteTextView with chips (like Gmail)

    In Part 1, you've learned how to make a chip layout by customizing in XML resources. The fact that the most popular apply of this component is putting into an AutoCompleteTextview to perform hint/selected information. For example, we can see this design in Gmail application:
    In this post, I will present the way to make this design by using a third-pary library!

Adding library dependencies

    As noted above, because this design is very popular so there are a lot of libraries are available on Github which able to help us to do this work easily. I will use TokenAutoComplete, in my opinion, this is good library and has a clearly document. So, in order to use it in your project, please add it's dependency to your application level build.gradle first:
compile "com.splitwise:tokenautocomplete:2.0.8@aar"

Custom an AutoCompleteTextView

    First of all, suppose we have a POJO class (contact data) simple like this:
SimpleContact.java
package info.devexchanges.chipedittext;

public class SimpleContact {
    private int drawableId;
    private String name;
    private String email;

    public SimpleContact(int drawableId, String name, String email) {
        this.drawableId = drawableId;
        this.name = name;
        this.email = email;
    }

    public int getDrawableId() {
        return drawableId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    @Override
    public String toString() {
        return getName() + "|" + getEmail();
    }
}
    We now must create a subclass of TokenCompleteTextView<T> to make a layout for "chips item" inside the EditText. For simplicity, just make a class look like ContactsCompletionView.java in the library sample module. In this project, T is SimpleContact:
ContactsCompletionView.java
package info.devexchanges.chipedittext;

import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import com.tokenautocomplete.TokenCompleteTextView;

/**
 * Sample token completion view for basic contact info
 * <p>
 * Created on 9/12/13.
 *
 * @author mgod
 */
public class ContactsCompletionView extends TokenCompleteTextView<SimpleContact> {

    public ContactsCompletionView(Context context) {
        super(context);
    }

    public ContactsCompletionView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ContactsCompletionView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected View getViewForObject(SimpleContact contact) {
        LayoutInflater l = (LayoutInflater) getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
        View tokenView = l.inflate(R.layout.item_autocomplete_contact, (ViewGroup) getParent(), false);
        TokenTextView textView = (TokenTextView) tokenView.findViewById(R.id.token_text);
        ImageView icon = (ImageView) tokenView.findViewById(R.id.icon);
        textView.setText(contact.getName());
        icon.setImageResource(contact.getDrawableId());

        return tokenView;
    }

    @Override
    protected SimpleContact defaultObject(String completionText) {
        //Stupid simple example of guessing if we have an email or not
        int index = completionText.indexOf('@');
        if (index == -1) {
            return new SimpleContact(R.drawable.male, completionText, completionText.replace(" ", "") + "@example.com");
        } else {
            return new SimpleContact(R.drawable.female, completionText.substring(0, index), completionText);
        }
    }
}
    And this is each chip layout (XML) file:
item_autocomplete_contact.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/chip_drawable"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:contentDescription="@null" />

    <info.devexchanges.chipedittext.TokenTextView
        android:id="@+id/token_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@android:color/white"
        android:layout_centerVertical="true"
        android:layout_toRightOf="@id/icon"
        android:paddingLeft="8dp"
        android:paddingRight="8dp"
        android:textStyle="bold" />

</RelativeLayout>
    As you can see, there is an class named TokenTextView, this is a subclass of TextView which onSelected() method was overridden to set it's state when user selected/clicked:
TokenTextView.java
package info.devexchanges.chipedittext;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.TextView;

/**
 * Created by mgod on 5/27/15.
 *
 * Simple custom view example to show how to get selected events from the token
 * view. See ContactsCompletionView and contact_token.xml for usage
 */
public class TokenTextView extends TextView {

    public TokenTextView(Context context) {
        super(context);
    }

    public TokenTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void setSelected(boolean selected) {
        super.setSelected(selected);
        setCompoundDrawablesWithIntrinsicBounds(0, 0, selected ? R.drawable.ic_clear_white_18dp : 0, 0);
    }
}
    I took this class from original file in the library sample module. This is the background drawable for the root view of item_autocomplete_contact.xml file:
res/drawable/chip_drawable.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <solid android:color="@android:color/darker_gray" />
    <corners android:radius="30dp" />
</shape>

Putting the AutoCompleteTextView to activity layout

    Up to now, we created a auto completed view object named ContactsCompletionView, so put an instance to the main activity layout file like this:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="info.devexchanges.chipedittext.MainActivity">

    <info.devexchanges.chipedittext.ContactsCompletionView
        android:id="@+id/autocomplete_textview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:focusableInTouchMode="true"
        android:imeOptions="actionDone"
        android:inputType="text|textNoSuggestions|textMultiLine"
        android:nextFocusDown="@+id/editText"
        android:textColor="@android:color/darker_gray"
        android:textSize="19sp" />

    <Button
        android:id="@+id/btn_get"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/activity_horizontal_margin"
        android:text="Get Input Data" />

    <TextView
        android:id="@+id/input_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/activity_horizontal_margin" />
</LinearLayout>

Create adapter class for AutoCompleteTextView

    The next work is making a filter adapter for the ContactsCompletionView by making a subclass of FilteredArrayAdapter, I'll create a my own adapter class by overriding getView() and keepObject() methods:
FilterAdapter.java
package info.devexchanges.chipedittext;

import android.app.Activity;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.tokenautocomplete.FilteredArrayAdapter;

import java.util.List;

public class FilterAdapter extends FilteredArrayAdapter<SimpleContact> {

    public FilterAdapter(Context context, int resource, List<SimpleContact> objects) {
        super(context, resource,  objects);
    }

    @NonNull
    @Override
    public View getView(int position, View convertView, @NonNull ViewGroup parent) {
        if (convertView == null) {

            LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
            convertView = layoutInflater.inflate(R.layout.item_contact, parent, false);
        }

        SimpleContact contact = getItem(position);
        ((TextView) convertView.findViewById(R.id.name)).setText(contact != null ? contact.getName() : null);
        ((TextView) convertView.findViewById(R.id.email)).setText(contact != null ? contact.getEmail() : null);
        assert contact != null;
        ((ImageView) convertView.findViewById(R.id.icon)).setImageResource(contact.getDrawableId());

        return convertView;
    }

    @Override
    protected boolean keepObject(SimpleContact person, String mask) {
        mask = mask.toLowerCase();
        return person.getName().toLowerCase().startsWith(mask) || person.getEmail().toLowerCase().startsWith(mask);
    }
}
    As you can see at the adapter class code, each "hint row" of auto completion view layout was inflated from item_contact (XML) file. This is it's code:
item_contact.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="5dp">

    <ImageView
        android:id="@+id/icon"
        android:src="@drawable/male"
        android:layout_centerVertical="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/icon"
        android:text="Large Text"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <TextView
        android:id="@+id/email"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/name"
        android:layout_toRightOf="@id/icon"
        android:text="Small Text"
        android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>

Activity programmatically code configuration

    In order to make a responding to user selections in the auto completion view items (chip object), your Activity must implements TokenListener interface. By this, there are 2 methods you must override:
  • onTokenAdded(): called when a chip item added
  • onTokenRemoved: called when user remove a chip item from auto completion view
    This is simple source code the main activity:
MainActivity.java
package info.devexchanges.chipedittext;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import com.tokenautocomplete.TokenCompleteTextView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity implements TokenCompleteTextView.TokenListener<SimpleContact> {

    private ArrayList<SimpleContact> contacts;
    private FilterAdapter filterAdapter;
    private ContactsCompletionView autoCompleteTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);
        setSampleContact();

        autoCompleteTextView = (ContactsCompletionView) findViewById(R.id.autocomplete_textview);

        //Initializing and attaching adapter for AutocompleteTextView
        filterAdapter = new FilterAdapter(this, R.layout.item_contact, contacts);
        autoCompleteTextView.setAdapter(filterAdapter);

        //Set the listener that will be notified of changes in the Tokenlist
        autoCompleteTextView.setTokenListener(this);

        //Set the action to be taken when a Token is clicked
        autoCompleteTextView.setTokenClickStyle(TokenCompleteTextView.TokenClickStyle.Select);

        final TextView inputContent = (TextView) findViewById(R.id.input_content);
        View btnGet = findViewById(R.id.btn_get);
        btnGet.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                List<SimpleContact> tokens = autoCompleteTextView.getObjects();
                StringBuilder content = new StringBuilder();
                for (int i = 0; i < tokens.size(); i++) {
                    content.append(tokens.get(i)).append("; ");
                }
                inputContent.setText(String.format("You choose: %s", content.toString()));
            }
        });
    }

    private void setSampleContact() {
        contacts = new ArrayList<>();
        contacts.add(new SimpleContact(R.drawable.female, "Thanh Ngan", "ngan@gmail.com"));
        contacts.add(new SimpleContact(R.drawable.male, "Quang Minh", "minh@gmail.com"));
        contacts.add(new SimpleContact(R.drawable.male, "Tran Tinh", "thanh_67@gmail.com"));
        contacts.add(new SimpleContact(R.drawable.female, "Phan Hoa", "hoa@gmail.com"));
        contacts.add(new SimpleContact(R.drawable.female, "Pham Trang", "trang@gmail.com"));
        contacts.add(new SimpleContact(R.drawable.male, "Dinh Tuan", "dtuan@gmail.com"));
        contacts.add(new SimpleContact(R.drawable.female, "Kim Chi", "kimchi@gmail.com"));
        contacts.add(new SimpleContact(R.drawable.male, "Quoc Cuong", "cuong@gmail.com"));
        contacts.add(new SimpleContact(R.drawable.female, "Hai Yen", "hai_yen@gmail.com"));
    }

    @Override
    public void onTokenAdded(SimpleContact token) {
        Log.d("Main", "A Token added");
    }

    @Override
    public void onTokenRemoved(SimpleContact token) {
        Log.d("Main", "A Token removed");
    }
}

Running the application - some screen shots

    A chip in the auto completion view is simple like this:
    When you are typing in the auto completion view, suggestion results will be displayed:
    When user select and delete a chip item:
    After click "Get Input Data" button:

Conclusions

    By using a third-party library, we now can creating an auto completion view with chip, a good design that you can see at Gmail Android app. For more details, you can read at this library document. By searching on the Internet, you can find out that there are a lot of other libraries which able to resolved this problem easily you can try. For examples:
Android Material Design component: Chip - Part 1: Creating chips layout

Android Material Design component: Chip - Part 1: Creating chips layout

    Chip is a Material Design component which presented by Google developers. It represents complex entities in small blocks, such as a contact. From guideline on Google design, a chip may:
  • contain entities such as a photo, text, rules, an icon, or a contact.
  • represent contact information in a compact way.
    For a popular example, we can see this component on Google Play:

    In Android SDK and Design Support library, there is no official widget to make a chip layout, so we must custom it ourselves. Moreover, there are tons of third-party libraries can help us to deal with this problem.
    Now, in this post, I will present a solution to make some chip styles by custom in xml files. For making a Chip EditText, please read Part 2.

Declaring some dimension resources

    Just take a glance at specs entry of the chip guideline, you will noticed some required dimensions when making a chip layout. So, before starting, I provide some dimension resources first:
dimens.xml
<resources>
    <!-- Default screen margins, per the Android Design guidelines. -->
    <dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">16dp</dimen>
    <dimen name="chip_padding">12dp</dimen>
    <dimen name="icon_height">36dp</dimen>
    <dimen name="remove_icon_dimen">24dp</dimen>
    <dimen name="remove_icon_margin">4dp</dimen>
    <dimen name="deletable_chip_padding">12dp</dimen>
    <dimen name="contact_chip_left_padding">8dp</dimen>
    <dimen name="contact_chip_right_padding">12dp</dimen>

    <dimen name="padding_top">8dp</dimen>
</resources>

Creating a simple chip layout (only text label)

    There are nothing unfamiliar here at all, we just only make a background with round corner for TextView to make a chip. Firstly, defining a new drawable resource file with shape as the root attribute:
res\drawable\shape_chip_simple_drawable.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <solid android:color="@color/colorAccent" />

    <padding
        android:bottom="@dimen/chip_padding"
        android:left="@dimen/chip_padding"
        android:right="@dimen/chip_padding"
        android:top="@dimen/chip_padding" />

    <corners android:radius="30dp" />
</shape>
    In your activity layout file, just define this drawable resource file as the TextView background like this:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="info.devexchanges.chiplayout.SimpleChipActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:background="@drawable/shape_chip_simple_drawable"
        android:text="Hello, I'm a simple Chip!"
        android:textColor="@android:color/white"
        android:textStyle="bold" />
</RelativeLayout>
    Running this activity, we'll have this simple chip layout:

Creating a deletable chip

    A deleteable chip contains a delete icon on the right side. When click at this icon, the chip will be deleted. In order to design this layout, wrapping a TextView and an ImageView in a RelativeLayout like this:
activity_cross_chips.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:id="@+id/chip_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:background="@drawable/shape_chip_drawable"
        android:paddingBottom="@dimen/padding_top"
        android:paddingTop="@dimen/padding_top">

        <TextView
            android:id="@+id/text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:paddingLeft="@dimen/chip_padding"
            android:text="Example chip"
            android:textStyle="bold" />

        <ImageView
            android:id="@+id/delete"
            android:layout_width="@dimen/remove_icon_dimen"
            android:layout_height="@dimen/remove_icon_dimen"
            android:layout_centerVertical="true"
            android:layout_toRightOf="@id/text"
            android:contentDescription="@null"
            android:padding="@dimen/remove_icon_margin"
            android:layout_marginRight="@dimen/remove_icon_margin"
            android:src="@drawable/delete" />
    </RelativeLayout>

</RelativeLayout>
    And this is drawable resource file to set as TextView background:
shape_chip_drawable.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <solid android:color="@color/gray_light" />

    <corners android:radius="30dp" />
</shape>
    NOTE: You should use this design instead of defining a TextView with a drawableRight because you must handle delete icon click event in programmatically code:
DeleteChipActivity.java
package info.devexchanges.chiplayout;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

public class DeleteChipActivity extends AppCompatActivity {

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

        View deleteIcon = findViewById(R.id.delete);
        final View chipLayout = findViewById(R.id.chip_layout);
        deleteIcon.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //gone chip layout
                //if it a list or an adapter, please remove it!
                chipLayout.setVisibility(View.GONE);
            }
        });
    }
}
    Running this activity, we'll have this output:
    When click the delete icon:

Chip with text and icon (image)

    It may be called contact chip, which have an icon on the left side of label. Moreover, it may have a remove icon on the right. The icon is always a round ImageView, wrapping them in a RelativeLayout like this:
activity_icon_chip.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_icon_chip"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="info.devexchanges.chiplayout.IconChipActivity">

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:background="@drawable/shape_chip_icon_drawable">

        <de.hdodenhof.circleimageview.CircleImageView
            android:id="@+id/icon"
            android:layout_width="@dimen/icon_height"
            android:layout_height="@dimen/icon_height"
            android:src="@drawable/midu"
            app:civ_border_color="#FF000000" />

        <TextView
            android:id="@+id/text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_toRightOf="@id/icon"
            android:paddingTop="@dimen/padding_top"
            android:paddingLeft="@dimen/padding_top"
            android:paddingBottom="@dimen/padding_top"
            android:text="Đặng Thị Mỹ Dung"
            android:textStyle="bold" />

        <ImageView
            android:id="@+id/delete"
            android:layout_width="@dimen/remove_icon_dimen"
            android:layout_height="@dimen/remove_icon_dimen"
            android:layout_centerVertical="true"
            android:layout_toRightOf="@id/text"
            android:padding="@dimen/remove_icon_margin"
            android:layout_marginLeft="@dimen/remove_icon_margin"
            android:layout_marginRight="@dimen/remove_icon_margin"
            android:contentDescription="@null"
            android:src="@drawable/delete" />
    </RelativeLayout>

</RelativeLayout>
    The round ImageView can be created by a third-party library. In this example, I use CircleImageView, so please add this dependency to dependencies scope in your application level build.gradle:
compile 'de.hdodenhof:circleimageview:2.1.0'
    The hardest work is fixing sizes for each widget, I use dimension resources like noted above. Running this activity, we'll have this output:

Conclusions

    Chip component can be created easily with some configuration in the resources and layout files. From now, you can use HorizontalScrollView to display a row of chips (like GooglePlay application) or use StaggeredGridLayoutManager to build a staggered grid of chips layout (named tag layout),...
    Up to Part 2, I will talk about putting chip into EditText (AutoCompleteTextView with chips inside) like Gmail app does, this is popular design that you can see on many applications (coming soon!).
    References:
  • Chip component guideline on Google Design
  • Round ImageView library page on Github
  • Read more: my post about using RecyclerView with StaggeredGridLayoutManager to build a staggered grid layout.
Populating multiple list views on a single Activity by using RecyclerView in Android

Populating multiple list views on a single Activity by using RecyclerView in Android

    With RecyclerView, building list view is now more simple, especially with new "scrolling mechanism", we now can put multiple RecyclerViews into a single screen (Activity or Fragment) without customizing in code.
    In this tip, I will present this solution through combining NestedScrollView and RecyclerView in the activity layout file. Of course, if you use ListView, please read my previous post to find out the way to expand it's height to maximum to display all list items.

Configuration in the layout (XML) file

    Make a NestedScrollView work as the root view and put 2 RecyclerView objects as it's children views to build this layout:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="info.devexchanges.multiplerecyclerview.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:paddingBottom="10dp"
            android:text="Asia"
            android:textStyle="bold" />

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/activity_horizontal_margin"
            android:gravity="center_vertical"
            android:paddingBottom="10dp"
            android:text="Europe"
            android:textStyle="bold" />

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </LinearLayout>
</android.support.v4.widget.NestedScrollView>
    And this is layout for each RecyclerView item:
item_recycler_view.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="8dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/image"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:src="@drawable/world"
            android:contentDescription="@null" />

        <TextView
            android:id="@+id/text"
            android:textColor="@color/colorPrimary"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textStyle="bold"
            android:layout_centerVertical="true"
            android:layout_toRightOf="@+id/image"
            android:layout_toEndOf="@+id/image"
            android:padding="10dp"
            android:text="@string/app_name" />
    </RelativeLayout>
</android.support.v7.widget.CardView>
    NOTE: In order to use RecyclerView, CardView and NestedScrollView from Design Support Library, you must add these dependencies to your app level build.gradle:
compile 'com.android.support:design:25.1.0'
compile 'com.android.support:recyclerview-v7:25.1.0'
compile 'com.android.support:cardview-v7:25.1.0'

Set LayoutManager and data in programmatically code

    Back to your main activity programmatically code, there is no special thing here, please set LayoutManager for each RecyclerView (here is LinearLayoutManager) and initializing data/"adapter" for them:
MainActivity.java
package info.devexchanges.multiplerecyclerview;

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

public class MainActivity extends AppCompatActivity {

    private String[] asiaCountries = {"Vietnam", "China", "Japan", "Korea", "India", "Singapore", "Thailand", "Malaysia"};
    private String[] europeCountries = {"France", "Germany", "Sweden", "Denmark", "England", "Spain", "Portugal", "Norway"};

    private RecyclerView firstRecyclerView;
    private RecyclerView secondRecyclerView;

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

        firstRecyclerView = (RecyclerView)findViewById(R.id.recycler);
        secondRecyclerView = (RecyclerView)findViewById(R.id.recycler_1);

        //create and set layout manager for each RecyclerView
        RecyclerView.LayoutManager firstLayoutManager = new LinearLayoutManager(this);
        RecyclerView.LayoutManager secondLayoutManager = new LinearLayoutManager(this);

        firstRecyclerView.setLayoutManager(firstLayoutManager);
        secondRecyclerView.setLayoutManager(secondLayoutManager);

        //Initializing and set adapter for each RecyclerView
        RecyclerViewAdapter firstAdapter = new RecyclerViewAdapter(this, asiaCountries);
        RecyclerViewAdapter secondAdapter = new RecyclerViewAdapter(this, europeCountries);

        firstRecyclerView.setAdapter(firstAdapter);
        secondRecyclerView.setAdapter(secondAdapter);
    }
}
    And this is the sample "adapter" class for these RecyclerViews:
RecyclerViewAdapter.java
package info.devexchanges.multiplerecyclerview;

import android.app.Activity;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
    private Activity activity;
    private String[] strings;

    public RecyclerViewAdapter(Activity activity, String[] strings) {
        this.activity = activity;
        this.strings = strings;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = activity.getLayoutInflater();
        View view = inflater.inflate(R.layout.item_recycler_view, parent, false);

        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, final int position) {
        viewHolder.textView.setText(strings[position]);
    }

    @Override
    public int getItemCount() {
        return strings.length;
    }

    class ViewHolder extends RecyclerView.ViewHolder {
        private TextView textView;

        public ViewHolder(View view) {
            super(view);
            textView = (TextView) view.findViewById(R.id.text);
        }
    }
}

Running the project

    This is output of this sample project, you can see that I've build 2 list views into a single screen successful:

Final thought

    As you can see, with simple configuration in the activity layout file, you now can put make a screen which contains multiple list views by using RecyclerViews. Further, please check other posts about this widget on my blog: