androidfirebasefirebase-realtime-databasemultiautocompletetextview

MultiAutoCompleteTextView and Firebase


I have a list of Tags stored in Firebase. In one of my Fragments, a MultiAutoCompleteTextView (MACTV) lets the User, to select the relevant Tags.

The objective,

Here's how I tried implementing.

Defining the ArrayAdapter for MACTV:

ArrayAdapter<String> adapterMultiAutoComplete = new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1);

Using the AddValueEventListener to populate the ArrayAdapter

if (fbUser != null) {
  dbTags.addValueEventListener(new ValueEventListener() {
      @Override
      public void onDataChange(DataSnapshot dataSnapshot) {
      //Basically, this says "For each DataSnapshot *Data* in dataSnapshot, do what's inside the method.
          for (DataSnapshot tagNameSnapshot : dataSnapshot.getChildren()) {
          //Get the suggestion by childing the key of the string you want to get.
              String ValueTagName = tagNameSnapshot.child("tagName")).getValue(String.class);
          //Add ValueTagName to ArrayAdapter
              adapterMultiAutoComplete.add(ValueTagName);
          }
       }

       @Override
       public void onCancelled(DatabaseError databaseError) {/*Do Nothing*/}
   });
}

Code for MACTV

MultiAutoCompleteTextView articleTags = (MultiAutoCompleteTextView) findViewById(R.id.mactv_tags);
articleTags.requestFocus();
articleTags.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
articleTags.setAdapter(adapterMultiAutoComplete);

Saving the selected tags to Firebase

List<String> ArticleTags = new ArrayList<>(Arrays.asList(articleTags.getText().toString().split(", ")));
DatabaseReference db = FirebaseDatabase.getInstance().getReference().child("tags").setValue(ArticleTags);

The Threshold value is defined as 2. Unfortunately, no relevant tags appear as pop-up when I start typing in MACTV.

Where did I go wrong?


Solution

  • BTW, great question! The most interesting I've seen this month.

    First of all, with this code you loading whole tags node to device. Imagine, you have 2M of tags (does SO has?), will this code still work? ;)

    The second thing, code inside onDataChange (as well as code in all firebase callbacks) is called on worker thread, but adapter methods should be called in UI thread.

    And the last, here is how I see the solution:

    1. You need to use Queries (you can read more in docs)
    2. First of all, we need to sort tags by name. So this is base query:

      DatabaseReference baseRef = FirebaseDatabase.getInstance().getReference().child("tags").orderByChild("tagName");
      
    3. Next - you must attach TextWatcher to your MultiAutoCompleteTextView with following code

      articleTags.addTextChangedListener(new TextWatcher() {
      
          @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
      
          @Override
          public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
      
              if (charSequence.length <3) return;
      
              String searchTarget = charSequence.toString().toLowerCase();
              //Here magic happens)
              baseRef.startAt(searchTarget).endAt(searchTarget + "\uf8ff").limitToFirst(20).addValueEventListener(new ValueEventListener() {
                  @Override
                  public void onDataChange(DataSnapshot dataSnapshot) {
                      //Handle executing this code in main thread yourself, answer will be too long with it
                      adapterMultiAutoComplete.removeAll();
                      for (DataSnapshot data: dataSnapshot.getChildren()) {
                           adapterMultiAutoComplete.add(data.getValue(String.class))
                      }
                      adapterMultiAutoComplete.notifyDatasetChanged();
                  }
      
                  @Override
                  public void onCancelled(DatabaseError databaseError) {
                     Log.wtf("What a terrible failure!", databaseError.toException());
                  }
              });
      
          }
      
          @Override public void afterTextChanged(Editable editable) {}
      
      });
      

    P.S.: Code never tested and probably not working. But you got the idea ;)