2

I was trying to search through the Firebase database from my material search dialog. I was successful in doing that but the problem is that I can search only through titles(my database contains title and descriptions too), although I tried quite a many things nothing worked for me.

At some point, I succeeded by making another search function for searching descriptions but then it made the searched results for titles to misbehave.

I am attaching the code below can, please have a look at it and let me know if there is anything that I can do to search through both titles and descriptions.

Declarations and code(I have skipped the unrequired parts from the activity code)

    materialSearchBar = homeView.findViewById(R.id.searchBar);
        materialSearchBar.setHint("Search Ad");
        loadSuggest();
        materialSearchBar.setLastSuggestions(suggestList);
        materialSearchBar.setCardViewElevation(10);
        materialSearchBar.addTextChangeListener(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) {
                List<String> sugest = new ArrayList<String>();
                for (String search : suggestList) {
                    if (search.toLowerCase().contains(materialSearchBar.getText().toLowerCase()))
                        sugest.add(search);
                }
                materialSearchBar.setLastSuggestions(sugest);

            }

            @Override
            public void afterTextChanged(Editable editable) {

            }
        });
        materialSearchBar.setOnSearchActionListener(new MaterialSearchBar.OnSearchActionListener() {
            @Override
            public void onSearchStateChanged(boolean enabled) {
                if (!enabled)
                    mallUsers.setAdapter(firebaseRecyclerAdapter);
            }

            @Override
            public void onSearchConfirmed(CharSequence text) {

                startSearch(text);

            }

            @Override
            public void onButtonClicked(int buttonCode) {

            }
        });
// start search and load suggestions methods
    private void startSearch(CharSequence text) {
        String searchText = text.toString();
        Query query1 = mDatabase1.orderByChild("title").startAt(searchText).endAt(searchText + "\uf8ff");
        Query query2 = mDatabase1.orderByChild("description").startAt(searchText).endAt(searchText + "\uf8ff");
        FirebaseRecyclerOptions<Ad> firebaseRecyclerOptions2 = new FirebaseRecyclerOptions
                .Builder<Ad>()
                .setQuery(query1, Ad.class)
                .build();

        searchAdapter = new FirebaseRecyclerAdapter<Ad, UsersViewHolder1>(firebaseRecyclerOptions2) {
            @Override
            protected void onBindViewHolder(@NonNull UsersViewHolder1 holder, int position, @NonNull Ad ad1) {
                holder.setTitle(ad1.getTitle());
                holder.setPrice(ad1.getPrice());
                holder.setCategory(ad1.getCategory());
                holder.setImage(ad1.getImage(), getContext());
                holder.setTime(ad1.getTime());


                String user_id = getRef(position).getKey();
                final String kk = user_id.toString();

                holder.mView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Intent mew = new Intent(getActivity(), ExpandActivity.class);
                        mew.putExtra("user_id", kk);
                        startActivity(mew);

                    }
                });

            }

            @NonNull
            @Override
            public UsersViewHolder1 onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
                View view1 = LayoutInflater.from(parent.getContext()).inflate(R.layout
                                .user_ad_layout, parent,
                        false);

                return new UsersViewHolder1(view1);
            }
        };
        searchAdapter.startListening();
        mallUsers.setAdapter(searchAdapter);


    }


    private void loadSuggest() {
        mDatabase1.orderByKey().addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
                for (DataSnapshot postdataSnapshot : dataSnapshot.getChildren()) {
                    Ad item = postdataSnapshot.getValue(Ad.class);
                    if (item != null) {
                        suggestList.add(item.getTitle());
                        suggestList.add(item.getDescription());
                    }
                }
            }

            @Override
            public void onCancelled(@NonNull DatabaseError databaseError) {

            }
        });
    }
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807

2 Answers2

1

According to this post, there is no way in which you can pass two queries to a single adapter. So you can pass either query1 or query2.

Unfortunately, Firebase Realtime database does not support queries on multiple properties (some people say "multiple where clauses" in SQL terms), supports only queries on a single child property. So you'll need to create an extra field to keep both fields. So to achieve this, you need to create a new field which in your database should look like this:

Firebase-root
   |
   --- itemId
          |
          --- title: "valueOfTitle"
          |
          --- description: "valueOfDescription"
          |
          --- title_description: "valueOfTitle_valueOfDescription"

So as you see, the title_description property combines the values that you want to filter on. But Firebase real-time database doesn't support native indexing or search for text fields in objects. Additionally, downloading an entire mode to search for fields client-side isn't practical. However, you can do it for small data sets but as I said before is not practical for large data sets. To enable full text search of your Firebase real-tme database, I recommend you to use a third-party search service like Algolia.

Unlike Firebase Realtime database, Cloud Firestore allows compound queries. You should take a look at this. So a query as the one below is allowed in Cloud Firestore without creating a combined property.

itemIdRef.whereEqualTo("title", "valueOfTitle").whereEqualTo("description", "valueOfDescription");

If you want to use Algolia in Cloud Firestore, I recommend you see my answer from this post. For more information, I also recommend you see this video.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • Thanks a lot of the information but would it be suggested to store ad data in firestore when most of the activities are fetching the data into the recycler view from the realtime database. – Divyansh Tripathi Aug 30 '18 at 04:20
  • According to your app use case, you should check if worth it. See the differences between both realtime databsese [here](https://firebase.googleblog.com/2017/10/cloud-firestore-for-rtdb-developers.html). – Alex Mamo Aug 30 '18 at 06:59
  • Please can you have a look at this too.https://stackoverflow.com/questions/52090995/the-imagebutton-isnt-commiting-to-the-change-in-recyclerview – Divyansh Tripathi Aug 30 '18 at 07:03
  • Ok, I will but do you think that my answer helped you? – Alex Mamo Aug 30 '18 at 07:11
0

You have already experienced the lowest feature of Firebase, querying. However you can query multiple values if you for example upload on firebase both title and description like this:

user:
post:
    title: "cake"
    description: "yummy"
    titledescription: "cake_yummy"

something like this. Another thing you can do is

private boolean hasValue = false;
private boolean hasValue1 = false;

set value is true inside onBindViewHolder and then using handler delay query values one at a time:

Handler myHandler1 = new Handler();
Runnable myRunnable1 = new Runnable() {
   @Override
   public void run() {
       // query1 here
   }
};

Handler myHandler2 = new Handler();
Runnable myRunnable2 = new Runnable() {
   @Override
   public void run() {
       // query2 here
   }
};

then you check them one at a time until it finds a value

   if(!hasValue){
     myHandler1.postDelayed(myRunnable1, 1000);
        if(!hasValue1){
           myHandler2.postDelayed(myRunnable2, 1500); 
        } else {
           myHandler1.removeCallbacks(myRunnable1);
           hasValue1 = false;
     } else {
     myHandler2.removeCallbacks(myRunnable2);
     hasValue2 = false;
     }
 }

Hope it helps.

areyouSEARious
  • 117
  • 1
  • 13
  • I doubt it that using a handler will solve this issue. What is happening if the time to get the data is bigger than 1500 ms? – Alex Mamo Aug 29 '18 at 11:50
  • I use it for smaller data and it works like a charm. For data bigger than 1500 ms you can use a button, click for 1st query, if nothing there then click for 2nd query. Again querying is a pain in the a** with Firebase, knowing full well that finding the stored data easily, is essential. – areyouSEARious Aug 29 '18 at 11:58
  • Hello ace tripping,do I just need to add the single query lines in the query space left or do i need to add something else too – Divyansh Tripathi Aug 30 '18 at 04:50
  • You can change the startSearch to startSearch(CharSequence text, Query querySearch) { FirebaseRecyclerOptions firebaseRecyclerOptions2 = new FirebaseRecyclerOptions .Builder() .setQuery(querySearch, Ad.class) .build(); and then put the handlers inside onSearchConfirmed and on handler 1 startSearch(text, query1) on handler 2 startSearch(text, query2). – areyouSEARious Aug 30 '18 at 06:36