Firebase – Search/Filter with Search Highlighting
How to Search/Filter Firebase data.
In this tutorial, you will learn how to perform a search filter against firebase at the Firebase side rather than at the client side. The search will be performed using Firebase standard Query class. This much more efficient as the search is performed at the cloud side rather than in the users device.
Once the search hit occurs, we will highlight the keywords to show the user exactly why the result is among the filtered items.
Why Search Data?
Searching data is one of the fundamental operations that any database be it local or cloud-based has to perform. If you are having hundreds, thousands or even just a few dozens of data, you will need to search filter instead of scrolling through all the items. This saves you the item is also less mentally taxing. Thus being able to apply a search filter mechanism in your app makes your app user friendly to your users.
Video Tutorial
We have a detailed step by step YouTube tutorial for a search example. Please watch it and subscribe to our YouTube channel.
Step 1 : Create Project
In your android studio, go to New Project
, Choose Empty Activity as your template, type your app name and Click finish to generate us a project skeleton.
Step 2 : Add Firebase to Project
The second step is to add Firebase to our project. Use the Firebase assistant from the tools menu item. Click Firebase, then go to Realtime database, then save/retrieve. If you are not sure then watch the video.
Step 3 : Add Dependencies
In your project you have two gradle scripsts:
(a). build.gradle(Project)
The first is the project level build.gradle. Take note we have added the google services:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.2'
classpath 'com.google.gms:google-services:3.2.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
(b). build.gradle(app)
The second gradle script is the app level build.gradle. This is where we add our dependencies.
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "info.camposha.firebasesearch"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
targetCompatibility 1.8
sourceCompatibility 1.8
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'junit:junit:4.12'
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'com.google.firebase:firebase-database:18.0.1'
// Third party libraries
implementation 'com.github.ivbaranov:materiallettericon:0.2.3'
}
apply plugin: 'com.google.gms.google-services'
Take note that we have added our Firebase Realtime database as a dependency:
MaterialLetterIcon will allow us render icons from the names obtained from our Firebase Realtimed database:
Lastly don't forget to apply the google services:
Step 4 : Creating our Model Class
(a). Scientists.java
This is our Firebase model class.
package info.camposha.firebasesearch;
import com.google.firebase.database.Exclude;
import java.io.Serializable;
public class Scientist implements Serializable {
private String mId,name,description,galaxy,star,dob,dod,key;
public Scientist() {
//empty constructor needed
}
public Scientist(String name,String description,String galaxy,String star, String dob,
String dod ) {
if (name.trim().equals("")) {
name = "Scientist NoName";
}
this.name = name;
this.description = description;
this.galaxy = galaxy;
this.star = star;
this.dob = dob;
this.dod = dod;
}
public String getId() {
return mId;
}
public void setId(String id) {
mId = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getGalaxy() {
return galaxy;
}
public void setGalaxy(String galaxy) {
this.galaxy = galaxy;
}
public String getStar() {
return star;
}
public void setStar(String star) {
this.star = star;
}
public String getDob() {
return dob;
}
public void setDob(String dob) {
this.dob = dob;
}
public String getDod() {
return dod;
}
public void setDod(String dod) {
this.dod = dod;
}
@Override
public String toString() {
return getName();
}
@Exclude
public String getKey() {
return key;
}
@Exclude
public void setKey(String key) {
this.key = key;
}
}
//end
You can see it's just Plain Old Java Object class. Just don't forget to provide an empty default constructor. We've also made it serializable to be make it transferable across activities. The key property will be excluded when saving/inserting Firebase data since it is autogenerated by Firebase.
Step 5 : Creating our Utilities Class
(a). Utils.java
This is our Utilities class.
package info.camposha.firebasesearch;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.RecyclerView;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.Query;
import com.google.firebase.database.ValueEventListener;
import java.util.ArrayList;
import java.util.List;
public class Utils {
public static final String DATE_FORMAT = "yyyy-MM-dd";
public static List<Scientist> DataCache =new ArrayList<>();
public static String searchString = "";
public static void show(Context c,String message){
Toast.makeText(c, message, Toast.LENGTH_SHORT).show();
}
public static void openActivity(Context c,Class clazz){
Intent intent = new Intent(c, clazz);
c.startActivity(intent);
}
/**
* This method will allow us send a serialized scientist objec to a specified
* activity
*/
public static void sendScientistToActivity(Context c, Scientist scientist,
Class clazz){
Intent i=new Intent(c,clazz);
i.putExtra("SCIENTIST_KEY",scientist);
c.startActivity(i);
}
/**
* This method will allow us receive a serialized scientist, deserialize it and return it,.
*/
public static Scientist receiveScientist(Intent intent, Context c){
try {
return (Scientist) intent.getSerializableExtra("SCIENTIST_KEY");
}catch (Exception e){
e.printStackTrace();
show(c,"RECEIVING-SCIENTIST ERROR: "+e.getMessage());
}
return null;
}
public static void showProgressBar(ProgressBar pb){
pb.setVisibility(View.VISIBLE);
}
public static void hideProgressBar(ProgressBar pb){
pb.setVisibility(View.GONE);
}
public static DatabaseReference getDatabaseRefence() {
return FirebaseDatabase.getInstance().getReference();
}
public static void search(final AppCompatActivity a, DatabaseReference db,
final ProgressBar pb,
MyAdapter adapter, String searchTerm) {
if(searchTerm != null && searchTerm.length()>0){
char[] letters=searchTerm.toCharArray();
String firstLetter = String.valueOf(letters[0]).toUpperCase();
String remainingLetters = searchTerm.substring(1);
searchTerm=firstLetter+remainingLetters;
}
Utils.showProgressBar(pb);
Query firebaseSearchQuery = db.child("Scientists").orderByChild("name").startAt(searchTerm)
.endAt(searchTerm + "uf8ff");
firebaseSearchQuery.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
DataCache.clear();
if (dataSnapshot.exists() && dataSnapshot.getChildrenCount() > 0) {
for (DataSnapshot ds : dataSnapshot.getChildren()) {
//Now get Scientist Objects and populate our arraylist.
Scientist scientist = ds.getValue(Scientist.class);
scientist.setKey(ds.getKey());
DataCache.add(scientist);
}
adapter.notifyDataSetChanged();
}else {
Utils.show(a,"No item found");
}
Utils.hideProgressBar(pb);
}
@Override
public void onCancelled(DatabaseError databaseError) {
Log.d("FIREBASE CRUD", databaseError.getMessage());
Utils.hideProgressBar(pb);
Utils.show(a,databaseError.getMessage());
}
});
}
public static void select(final AppCompatActivity a, DatabaseReference db,
final ProgressBar pb,
final RecyclerView rv,MyAdapter adapter) {
Utils.showProgressBar(pb);
db.child("Scientists").addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
DataCache.clear();
if (dataSnapshot.exists() && dataSnapshot.getChildrenCount() > 0) {
for (DataSnapshot ds : dataSnapshot.getChildren()) {
//Now get Scientist Objects and populate our arraylist.
Scientist scientist = ds.getValue(Scientist.class);
scientist.setKey(ds.getKey());
DataCache.add(scientist);
}
adapter.notifyDataSetChanged();
}else {
Utils.show(a,"No more item found");
}
Utils.hideProgressBar(pb);
}
@Override
public void onCancelled(DatabaseError databaseError) {
Log.d("FIREBASE CRUD", databaseError.getMessage());
Utils.hideProgressBar(pb);
Utils.show(a,databaseError.getMessage());
}
});
}
}
//end
Our two most important methods above are the search adn select. The select method allows us select all data from our Firebase Realtime database. The search method allows us search and filter our Firebase realtime database.
Step 6 : Creating our Adapter Class
(a). MyAdapter.java
This is our recyclerview adapter class.
package info.camposha.firebasesearch;
import android.content.Context;
import android.graphics.Color;
import android.text.Spannable;
import android.text.style.ForegroundColorSpan;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.github.ivbaranov.mli.MaterialLetterIcon;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import static info.camposha.firebasesearch.Utils.searchString;
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder>{
private Context c;
private int mBackground;
private int[] mMaterialColors;
public List<Scientist> scientists;
interface ItemClickListener {
void onItemClick(int pos);
}
public class ViewHolder extends RecyclerView.ViewHolder implements
View.OnClickListener {
private final TextView nameTxt, mDescriptionTxt,galaxyTxt;
private final MaterialLetterIcon mIcon;
private ItemClickListener itemClickListener;
ViewHolder(View itemView) {
super(itemView);
mIcon = itemView.findViewById(R.id.mMaterialLetterIcon);
nameTxt = itemView.findViewById(R.id.mNameTxt);
mDescriptionTxt = itemView.findViewById(R.id.mDescriptionTxt);
galaxyTxt = itemView.findViewById(R.id.mGalaxyTxt);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
this.itemClickListener.onItemClick(this.getLayoutPosition());
}
void setItemClickListener(ItemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
}
}
public MyAdapter(List<Scientist> scientists) {
this.scientists = scientists;
}
private void prepareLetterIcons(Context c){
TypedValue mTypedValue = new TypedValue();
c.getTheme().resolveAttribute(R.attr.selectableItemBackground,
mTypedValue, true);
mMaterialColors = c.getResources().getIntArray(R.array.colors);
mBackground = mTypedValue.resourceId;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
this.c = parent.getContext();
prepareLetterIcons(c);
View view = LayoutInflater.from(c).inflate(R.layout.model, parent, false);
view.setBackgroundResource(mBackground);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
//get current scientist
final Scientist s = scientists.get(position);
//bind data to widgets
holder.nameTxt.setText(s.getName());
holder.mDescriptionTxt.setText(s.getDescription());
holder.galaxyTxt.setText(s.getGalaxy());
holder.mIcon.setInitials(true);
holder.mIcon.setInitialsNumber(2);
holder.mIcon.setLetterSize(25);
holder.mIcon.setShapeColor(mMaterialColors[new Random().nextInt(
mMaterialColors.length)]);
holder.mIcon.setLetter(s.getName());
if(position % 2 == 0){
holder.itemView.setBackgroundColor(Color.parseColor("#efefef"));
}
//get name and galaxy
String name = s.getName().toLowerCase(Locale.getDefault());
//highlight name text while searching
if (name.contains(searchString) && !(searchString.isEmpty())) {
int startPos = name.indexOf(searchString);
int endPos = startPos + searchString.length();
Spannable spanString = Spannable.Factory.getInstance().
newSpannable(holder.nameTxt.getText());
spanString.setSpan(new ForegroundColorSpan(Color.RED), startPos, endPos,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
holder.nameTxt.setText(spanString);
} else {
//Utils.show(ctx, "Search string empty");
}
//open detailactivity when clicked
holder.setItemClickListener(pos -> Utils.sendScientistToActivity(c, s,
DetailActivity.class));
}
@Override
public int getItemCount() {
return scientists.size();
}
}
//end
Step 7 : Creating our Adapter Class
(a). MainActivity.java
This is our main activity class.
package info.camposha.firebasesearch;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.SearchView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public class MainActivity extends AppCompatActivity implements
SearchView.OnQueryTextListener,MenuItem.OnActionExpandListener {
private RecyclerView rv;
public ProgressBar mProgressBar;
private LinearLayoutManager layoutManager;
MyAdapter adapter;
private void initializeViews(){
mProgressBar = findViewById(R.id.mProgressBarLoad);
mProgressBar.setIndeterminate(true);
mProgressBar.setVisibility(View.VISIBLE);
rv = findViewById(R.id.mRecyclerView);
layoutManager = new LinearLayoutManager(this);
rv.setLayoutManager(layoutManager);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(rv.getContext(),
layoutManager.getOrientation());
rv.addItemDecoration(dividerItemDecoration);
adapter=new MyAdapter(Utils.DataCache);
rv.setAdapter(adapter);
}
private void bindData(){
Utils.select(this,Utils.getDatabaseRefence(),mProgressBar,rv,adapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu options from the res/menu/menu_editor.xml file.
// This adds menu items to the app bar.
getMenuInflater().inflate(R.menu.scientists_page_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setOnQueryTextListener(this);
searchView.setIconified(true);
searchView.setQueryHint("Search");
return true;
}
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
return false;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
return false;
}
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String query) {
Utils.searchString=query;
Utils.search(this,Utils.getDatabaseRefence(),mProgressBar, adapter,query);
return false;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scientists);
initializeViews();
bindData();
}
}
//end
(b). DetailActivity.java
This is our detail activity class.
package info.camposha.firebasesearch;
import android.os.Bundle;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
public class DetailActivity extends AppCompatActivity {
private TextView nameTV,descriptionTV,galaxyTV,starTV,dobTV,diedTV;
private Scientist receivedScientist;
private CollapsingToolbarLayout mCollapsingToolbarLayout;
private void initializeWidgets(){
nameTV= findViewById(R.id.nameTV);
descriptionTV= findViewById(R.id.descriptionTV);
galaxyTV= findViewById(R.id.galaxyTV);
starTV= findViewById(R.id.starTV);
dobTV= findViewById(R.id.dobTV);
diedTV= findViewById(R.id.dodTV);
FloatingActionButton editFAB = findViewById(R.id.editFAB);
mCollapsingToolbarLayout=findViewById(R.id.mCollapsingToolbarLayout);
mCollapsingToolbarLayout.setExpandedTitleColor(getResources().
getColor(R.color.white));
}
private void receiveAndShowData(){
receivedScientist= Utils.receiveScientist(getIntent(),DetailActivity.this);
if(receivedScientist != null){
nameTV.setText(receivedScientist.getName());
descriptionTV.setText(receivedScientist.getDescription());
galaxyTV.setText(receivedScientist.getGalaxy());
starTV.setText(receivedScientist.getStar());
dobTV.setText(receivedScientist.getDob());
diedTV.setText(receivedScientist.getDod());
mCollapsingToolbarLayout.setTitle(receivedScientist.getName());
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
initializeWidgets();
receiveAndShowData();
}
}
//end
Resources
No. | Type | Link |
---|---|---|
1. | Github | Browse |
2. | Github | Download |
3. | YouTube | Watch |