Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to customize the "chips" auto-suggesting mechanism, as used on Gmail's recipients field?

Background

I've searched for a way to have a similar look & feel to the Gmail receipients field, which allows auto-filling of items in a really cool way:

enter image description here

The class that is built into the Android framework and is responsible for this is called "MultiAutoCompleteTextView" .

The problem

the MultiAutoCompleteTextView is quite basic, yet it doesn't hold enough samples, tutorials and libraries to get to know how to customize it like on Gmail and the likes.

I would like to know how to customize it to handle any kind of data, and that I will have full control over it (for example adding, deleting and getting the items that it has auto-completed).

What I've tried

I've found the next possible ways to achieve it:

  1. use a third library like splitwise-TokenAutoComplete. the downside: it's very buggy and doesn't work well on some devices.
  2. create my own way (as shown here). the downside: will take a long time and I will probably need to handle the same problems as of the library.
  3. use the code of Google (found here). The downside: it's really not customizable.

I've decided to use #3 (Google's chips library).

Currently the code for getting the list of contacts used on Google's library:

public List<RecipientEntry> doQuery() {
    final Cursor cursor = mContentResolver.query(mQuery.getContentUri(), mQuery.getProjection(), null, null, null);
    final LinkedHashMap<Long, List<RecipientEntry>> entryMap = new LinkedHashMap<Long, List<RecipientEntry>>();
    final List<RecipientEntry> nonAggregatedEntries = new ArrayList<RecipientEntry>();
    final Set<String> existingDestinations = new HashSet<String>();
    while (cursor.moveToNext())
        putOneEntry(new TemporaryEntry(cursor, false /* isGalContact */), true, entryMap, nonAggregatedEntries,
                existingDestinations);
    cursor.close();
    final List<RecipientEntry> entries = new ArrayList<RecipientEntry>();
    {
        for (final Map.Entry<Long, List<RecipientEntry>> mapEntry : entryMap.entrySet()) {
            final List<RecipientEntry> entryList = mapEntry.getValue();
            for (final RecipientEntry recipientEntry : entryList)
                entries.add(recipientEntry);
        }
        for (final RecipientEntry entry : nonAggregatedEntries)
            entries.add(entry);
    }
    return entries;
}

It works fine, but I'm having difficulties adding items and deleting them.

I think that getting the items is used by calling "getContactIds" , but about modifying the items within the chips, that's very problematic to find.

For example, I've tried to add a similar function to "submitItemAtPosition" , which seems to add a new entity found from the adapter. It does add, but the display-name of the contact isn't shown on the chip itself.

The question

After a lot of thoughts, I decided to use Google's code.

Sadly, as I've written, the view and its classes are very tight to the usage of it.

  1. How can I de-couple the view and make it much more customizable? How can I make it use any type of data instead of just what Google has done?

  2. How do I get which items were entered (that became "chips"), and also be able to remove or add items from outside?

like image 537
android developer Avatar asked Apr 03 '14 11:04

android developer


2 Answers

I've succeeded adding the functionality of adding a recipient. The only thing to remember is to call it only after the view got its size (example of how to do it here) :

/** adds a recipient to the view. note that it should be called when the view has determined its size */
public void addRecipient(final RecipientEntry entry) {
    if (entry == null)
        return;
    clearComposingText();

    final int end = getSelectionEnd();
    final int start = mTokenizer.findTokenStart(getText(), end);

    final Editable editable = getText();
    QwertyKeyListener.markAsReplaced(editable, start, end, "");
    final CharSequence chip = createChip(entry, false);
    if (chip != null && start >= 0 && end >= 0) {
        editable.replace(start, end, chip);
    }
    sanitizeBetween();
}

private void submitItemAtPosition(final int position) {
    final RecipientEntry entry = createValidatedEntry(getAdapter().getItem(position));
    if (entry == null)
        return;
    addRecipient(entry);
}

And, for deletion:

/** removes a chip of a recipient from the view */
public void removeRecipient(final RecipientEntry entry) {
    final DrawableRecipientChip[] chips = getSpannable().getSpans(0, getText().length(),
            DrawableRecipientChip.class);
    final List<DrawableRecipientChip> chipsToRemove = new ArrayList<DrawableRecipientChip>();
    for (final DrawableRecipientChip chip : chips)
        if (chip.getDataId() == entry.getDataId())
            chipsToRemove.add(chip);
    for (final DrawableRecipientChip chip : chipsToRemove)
        removeChip(chip);
}

and as I've written before, for getting the list of contactIds that are currently inside the view, use "getContactIds()" . Another alternative is:

/** returns a collection of all of the chips' items. key is the contact id, and the value is the recipient itself */
public Map<Long, RecipientEntry> getChosenRecipients() {
    final Map<Long, RecipientEntry> result = new HashMap<Long, RecipientEntry>();
    final DrawableRecipientChip[] chips = getSortedRecipients();
    if (chips != null)
        for (final DrawableRecipientChip chip : chips) {
            // if(result.)
            final long contactId = chip.getContactId();
            if (!result.containsKey(contactId))
                result.put(contactId, chip.getEntry());
        }
    return result;
}

Maybe I should post the code on Github.

The only thing I miss now is a good listener to the chips themselves : when a chip is added, removed and replaced. for most of the cases I can detect it, but not when the user presses backspace and removes a chip.


EDIT: also added the listener. now I've found a bug in searching of contacts. it seems to search the normal English letters as if they were phone numbers.


EDIT: I've decided to put a sample and a library on GitHub, here . Hope to update it with more useful features soon.

I would really be happy for any contribution to the code.

like image 83
android developer Avatar answered Nov 02 '22 08:11

android developer


This library seems to allow you to configure what it searches for while also matching the Material Design look. It also seems to be based off of Google's chip library. I happened to find it when investigating a similar problem.

https://github.com/klinker41/android-chips

like image 23
blunden Avatar answered Nov 02 '22 07:11

blunden