A buddy of mine is getting into programming. He asked me to create him a project he could use to learn various concepts regarding Firebase realtime database with respect to creating a full project. He didn’t specify a language, so I decided to implement the projects with both Kotlin and Java. By comparing the two projects he is going to not only learn app creation but also both languages.

What My Friend will Learn

From this project, my friend will learn the following:

1. FIREBASE REALTIME DATABASE – How to perform full CRUD in Firebase realtime database.
2. FULL APP CREATION – He will be able to create his first full app. Then later on he will be able to use this as a template for creating more projects.
3. PAGE DESIGNS – He will have several page designs he can customize and re-use for other projects.
4. KOTLIN/JAVA LANGUAGES – As a newbie, he will have two high quality projects but adapted for beginners. From these he will be able to learn Kotlin and Java Languages, and even compare them.
5. MUCH More – Just follow the tutorial below to get all the features he will learn.

 

Here is the gif demo:

Here was what I decided:

STEP 1: DESIGN – Classic App,No Design Pattern

As a beginner, he needs to learn how to create an app before thinking about design patterns. So I decided to make the a project a classic one, removing complications introduced by design patterns like MVVM,MVP and MVI.

This would force him to learn how to actually create an app that people can use instead of getting lost in fancy theoretical concepts. Moreover he would learn how to properly design an app without using already formulated patterns. Then later on when the app is working reliably, he can learn the design patterns and see what advantages they can inject in his app, or even if he needs them in the first place.

I decided to organize the project in three packages:

  1. data – To hold classes dealing directly with our data. The Scientist model class and our adapter class.
  2. helpers – To hold our helper classes. The FilterHelper class will allow us to search filter our data. The FirebaseCRUDHelper will allow us to post to Firebase, update firebase data, delete as well as fetch from firebase. Then the Utils class to contain our static utility methods.
  3. views – To hold our activities. We created 5 activities: SplashActivity, DashboardActivity,CRUDActivity, ScientistsActivity and DetailActivity.

In the main package we had our App class.

STEP 2: DATABASE – Firebase Realtime Database

Firebase Realtime Database is the most popular and pioneer cloud database around. So we decided to use it for creating the project. In FRB data is stored in json nodes and this data is available to all connected clients. Moreover you can enable an offline first capability.

To use Firebase here are the steps you follow.

1 – SPECIFY GOOGLE SERVICES CLASS PATH

Go to your root-level(your project’s root directory) and under the dependecies add google services class path:

        classpath 'com.google.gms:google-services:4.3.2'

Like below:

    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.2'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
        classpath 'com.google.gms:google-services:4.3.2'
    }

2 – ADD FIREBASE AS A DEPENDENCY

First go to your app level build.gradle and add the following dependency:

    implementation 'com.google.firebase:firebase-database:18.0.0' //you can user later version

Make sure to apply google services plugin at the end of dependencies:

dependencies {
    //all dependencies
}

apply plugin: 'com.google.gms.google-services'

 

3 – CONNECT YOUR APP TO FIREBASE

From android studio menu bar, choose Tools –> Firebase

A Firebase Asssitant will be shown like below, so choose Firebase Realtime Database.

Then Click Connect To Firebase button.

A Firebase Connection Dialog will be shown. If you don’t have a project create a new one, otherwise you can use an existing one.

Once you have your project created, go and enable public writing so that your app can write to and read from your database without any authentication. Go to your firebase console in the browser, and under the Firebase realtime database, under the Rules tab, allow read and writes as below:

STEP 3: MODEL CLASS – How to Design our Model Class

The model class is simple yet super important. After all, it defines the basic entity of what our app is about. Our app is about Scientists, so need to programmatically specify what we mean by a Scientist. You may change this entity to a Post or Product or anything. This is the most important class if you are planning to customize the project as your customizations start here.

However another thing that makes the design of this class is important is especially how we handle null values in Kotlin. If you convert java project’s Scientist class to Kotlin, it will initialize the attributes to null. However we want to change this by assigning empty values. This will save us alot from null pointer exceptions. We can do this easily using the elvis operator:

class Scientist : Serializable {
    var id: String? = ""
    var name: String? = ""
    var description: String? = ""
    var galaxy: String? = ""
    var star: String? = ""
    var dob: String? = ""
    var dod: String? = ""
    @get:Exclude
    @set:Exclude
    var key: String? = ""

Because we are using Firebase we need to specify empty constructor:

    constructor() { //empty constructor needed
    }

Here is the code in Java:

public class Scientist  implements Serializable {

    private String mId;
    private String name;
    private String description;
    private String galaxy;
    private String star;
    private String dob;
    private String dod;
    private String key;

    public Scientist() {
        //empty constructor needed
    }

 

STEP 4 : UTILITIES – Static Utiliy Methods

There are some functionalities that he will need over and over across several classes and activities. So we want to put these methods together in one object class. Then he can access them without having to instantiate the class. These type of method are normally called Utility methods.

In Kotlin:

object Utils {

And in Java:

public class Utils {

In this case we will also hold two variables here:

    const val DATE_FORMAT = "yyyy-MM-dd"
    var DataCache: ArrayList<Scientist> = ArrayList()
    var searchString = ""

In Java:

    public static final String DATE_FORMAT = "yyyy-MM-dd";
    public static List<Scientist> DataCache =new ArrayList<>();

    public static String searchString = "";

The DATE_FORMAT is the format of our date.

The DataCache will hold us the list of scientists we have downloaded. Then we can search/filter that list an easily bind it to recyclerview. The searchString will hold us the search term. Then we can access that search term in our adapter class so that we know portions of text to highlight.

To show a Toast message in Kotlin:

    /**
     * This method allows you easily show a toast message from any activity
     * @param c
     * @param message
     */
    @JvmStatic
    fun show(c: Context?, message: String?) {
        Toast.makeText(c, message, Toast.LENGTH_SHORT).show()
    }

And in Java:

    public static void show(Context c,String message){
        Toast.makeText(c, message, Toast.LENGTH_SHORT).show();
    }

To validate edittexts:

    /**
     * You can pass an arbitrary number of edittexts into this method, then
     * we pick the first three and validate them
     * @param editTexts
     * @return
     */
    fun validate(vararg editTexts: EditText): Boolean {
        val nameTxt = editTexts[0]
        val descriptionTxt = editTexts[1]
        val galaxyTxt = editTexts[2]
        if (nameTxt.text == null || nameTxt.text.toString().isEmpty()) {
            nameTxt.error = "Name is Required Please!"
            return false
        }
        if (descriptionTxt.text == null || descriptionTxt.text.toString().isEmpty()) {
            descriptionTxt.error = "Description is Required Please!"
            return false
        }
        if (galaxyTxt.text == null || galaxyTxt.text.toString().isEmpty()) {
            galaxyTxt.error = "Galaxy is Required Please!"
            return false
        }
        return true
    }

Here is how you open a new activity in Kotlin:


    /**
     * This method will allow us easily open any activity
     * @param c
     * @param clazz
     */
    @JvmStatic
    fun openActivity(c: Context, clazz: Class<*>?) {
        val intent = Intent(c, clazz)
        c.startActivity(intent)
    }

And here is how you do it in Java:

    public static void openActivity(Context c,Class clazz){
        Intent intent = new Intent(c, clazz);
        c.startActivity(intent);
    }

I wanted to give him the ability to show beautiful materail dialogs anywhere in the app. Dialogs are important because they are interactive unlike Toasts. User can click a button after reading the message and invoke a certain action.Moreover they are dismissed manually, this making sure the user has viewed the message.

So I did a search in github and concluded that one of the best and convenient libraries there, among other great ones, was LovelyDialogs. It works well, is easy and reasonably customizable.

It is hosted in jcenter. You install it by adding the following line in the app level build.gradle:

    implementation 'com.yarolegovich:lovely-dialog:1.1.0'

Then here is the method that will allow him show an info dialog from any activity or fragment:

    /**
     * This method will allow us show an Info dialog anywhere in our app.
     */
    @JvmStatic
    fun showInfoDialog(activity: AppCompatActivity, title: String?, message: String?) {
        LovelyStandardDialog(activity, LovelyStandardDialog.ButtonLayout.HORIZONTAL)
            .setTopColorRes(R.color.indigo)
            .setButtonsColorRes(R.color.darkDeepOrange)
            .setIcon(R.drawable.m_info)
            .setTitle(title)
            .setMessage(message)
            .setPositiveButton("Relax") { }
            .setNeutralButton("Go Home") {
                openActivity(activity, DashboardActivity::class.java)
            }
            .setNegativeButton("Go Back") { activity.finish() }
            .show()
    }

Here is the equivalent method in java:

    public static void showInfoDialog(final AppCompatActivity activity, String title,
                                      String message) {
        new LovelyStandardDialog(activity, LovelyStandardDialog.ButtonLayout.HORIZONTAL)
                .setTopColorRes(R.color.indigo)
                .setButtonsColorRes(R.color.darkDeepOrange)
                .setIcon(R.drawable.m_info)
                .setTitle(title)
                .setMessage(message)
                .setPositiveButton("Relax", v -> {})
                .setNeutralButton("Go Home", v -> openActivity(activity, DashboardActivity.class))
                .setNegativeButton("Go Back", v -> activity.finish())
                .show();
    }

 

Our adventures with the LovelyDialogs doesn’t stop there. We need it even more for showing single choice dialogs. These are dialogs that allow user to select an item. In our case we will pass an edittext where the chosen item will be set:

    /**
     * This method will allow us show a single select dialog where we can select and return a
     * star to an edittext.
     */
    fun selectStar(c: Context?, starTxt: EditText) {
        val stars = arrayOf(
            "Rigel", "Aldebaran", "Arcturus", "Betelgeuse", "Antares", "Deneb",
            "Wezen", "VY Canis Majoris", "Sirius", "Alpha Pegasi", "Vega", "Saiph", "Polaris",
            "Canopus", "KY Cygni", "VV Cephei", "Uy Scuti", "Bellatrix", "Naos", "Pollux",
            "Achernar", "Other"
        )
        val adapter = ArrayAdapter(c!!, android.R.layout.simple_list_item_1, stars)
        LovelyChoiceDialog(c)
            .setTopColorRes(R.color.darkGreen)
            .setTitle("Stars Picker")
            .setTitleGravity(Gravity.CENTER_HORIZONTAL)
            .setIcon(R.drawable.m_star)
            .setMessage("Select the Star where the Scientist was born.")
            .setMessageGravity(Gravity.CENTER_HORIZONTAL)
            .setItems(adapter) { _: Int, item: String? ->
                starTxt.setText(item)
            }
            .show()
    }

And here is the equivalent method in Java:

    public static void selectStar(Context c,final EditText starTxt){
        String[] stars ={"Rigel","Aldebaran","Arcturus","Betelgeuse","Antares","Deneb",
                "Wezen","VY Canis Majoris","Sirius","Alpha Pegasi","Vega","Saiph","Polaris",
                "Canopus","KY Cygni","VV Cephei","Uy Scuti","Bellatrix","Naos","Pollux",
                "Achernar","Other"};
        ArrayAdapter<String> adapter = new ArrayAdapter<>(c,
                android.R.layout.simple_list_item_1,
                stars);
        new LovelyChoiceDialog(c)
                .setTopColorRes(R.color.darkGreen)
                .setTitle("Stars Picker")
                .setTitleGravity(Gravity.CENTER_HORIZONTAL)
                .setIcon(R.drawable.m_star)
                .setMessage("Select the Star where the Scientist was born.")
                .setMessageGravity(Gravity.CENTER_HORIZONTAL)
                .setItems(adapter, (position, item) -> starTxt.setText(item))
                .show();
    }

Then we want a method we can use to safely attempt converting a string into a java.util.Date object:

    fun giveMeDate(stringDate: String?): Date? {
        return try {
            val sdf = SimpleDateFormat(DATE_FORMAT)
            sdf.parse(stringDate)
        } catch (e: ParseException) {
            e.printStackTrace()
            null
        }
    }

Then a function to send an object to another activity via Intent:

    fun sendScientistToActivity(c: Context, scientist: Scientist?, clazz: Class<*>?) {
        val i = Intent(c, clazz)
        i.putExtra("SCIENTIST_KEY", scientist)
        c.startActivity(i)
    }

And another method to receive it:

    fun receiveScientist(intent: Intent, c: Context?): Scientist? {
        try {
            return intent.getSerializableExtra("SCIENTIST_KEY") as Scientist
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return null
    }

Then to show or hide progressbars in kotlin:

    @JvmStatic
    fun showProgressBar(pb: ProgressBar) {
        pb.visibility = View.VISIBLE
    }

    @JvmStatic
    fun hideProgressBar(pb: ProgressBar) {
        pb.visibility = View.GONE
    }

How about this, we create a simple function that will be obtaining us value of an edittext safely. The method has to be short as we will invoke many times:

    @JvmStatic
    fun valOf(editText: EditText): String{
        if (editText.text==null){
            return ""
        }
        return editText.text.toString()
    }

Then a property to get us the DatabaseReference:

    val databaseRefence: DatabaseReference
        get() = FirebaseDatabase.getInstance().reference

In java we have it as a method:

    public static DatabaseReference getDatabaseRefence() {
        return FirebaseDatabase.getInstance().getReference();
    }

STEP 5: CRUD – HOW TO PERFORM FULL FIREBASE CRUD

We wanted him to learn the full Firebase realtime database CRUD capability from the app. So to make it easy, we created a seperate re-usable class that contains methods that allow us:

  1. C – Create a renowned scientist.
  2. R – Read renowned scientsist
  3. U – Update renowned scientist
  4. D – Delete a renowned scientist.

We have lifted organized all the logic needed to perform full CRUD in seperate class called FirebaseCrudHelper.

1. CREATING/INSERTING/POSTING

Well here is the method that will be insert or create an item in our firebase database:

    /**
     * This method will allow us post into firebase realtime database
     * @param a
     * @param mDatabaseRef
     * @param pb
     * @param scientist
     */
    fun insert(a: AppCompatActivity?, mDatabaseRef: DatabaseReference, pb: ProgressBar?, scientist: Scientist?) { //check if they have passed us a valid scientist. If so then return false.
        if (scientist == null) {
            showInfoDialog(a!!, "VALIDATION FAILED", "Scientist is null")
            return
        } else { //otherwise try to push data to firebase database.
            showProgressBar(pb!!)
            //push data to FirebaseDatabase. Table or Child called Scientist will be created
            mDatabaseRef.child("Scientists").push().setValue(scientist)
                .addOnCompleteListener { task ->
                    hideProgressBar(pb)
                    if (task.isSuccessful) {
                        openActivity(a!!, ScientistsActivity::class.java)
                        show(a, "Congrats! INSERT SUCCESSFUL")
                    } else {
                        showInfoDialog(a!!, "UNSUCCESSFUL", task.exception!!.message)
                    }
                }
        }
    }

2. READING/SELECTING/FETCHING/RETRIEVING/DOWNLOADING

Well obviously we need more than anything the capability to read existing data in firebase realtime database.

Here is how you read/fetch data from firebase realtime database in kotlin:

    /**
     * This method will allow us retrieve from firebase realtime database
     * @param a
     * @param db
     * @param pb
     * @param rv
     * @param adapter
     */
    fun select( a: AppCompatActivity?, db: DatabaseReference, pb: ProgressBar?, rv: RecyclerView, adapter: MyAdapter) {
        showProgressBar(pb!!)
        db.child("Scientists").addValueEventListener(object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                DataCache.clear()
                if (dataSnapshot.exists() && dataSnapshot.childrenCount > 0) {
                    for (ds in dataSnapshot.children) { //Now get Scientist Objects and populate our arraylist.
                        val scientist: Scientist = ds.getValue(Scientist::class.java)!!
                        scientist.key=ds.key
                        DataCache.add(scientist)
                    }
                    adapter.notifyDataSetChanged()
                    Handler().post {
                        hideProgressBar(pb)
                        rv.smoothScrollToPosition(DataCache.size)
                    }
                } else {
                    show(a, "No more item found")
                }
            }

            override fun onCancelled(databaseError: DatabaseError) {
                Log.d("FIREBASE CRUD", databaseError.message)
                hideProgressBar(pb)
                showInfoDialog(a!!, "CANCELLED", databaseError.message)
            }
        })
    }

As you can see, once we’ve read data, we scroll over to the last added item.

3. UPDATING/EDITING

Well we also need to teach him how to update data that is already existing in the Firebase Realtime database. We will need the key of the node to be updated.

Here is how you update firebase in Kotlin:

    /**
     * This method will allow us update existing data in firebase realtime database
     * @param a - Host activity
     * @param mDatabaseRef - DatabaseReference
     * @param pb - ProgresBar
     * @param updatedScientist - Updated scientist
     */
    fun update(a: AppCompatActivity?, mDatabaseRef: DatabaseReference, pb: ProgressBar?, updatedScientist: Scientist?) {
        if (updatedScientist == null) {
            showInfoDialog(a!!, "VALIDATION FAILED", "Scientist is null")
            return
        }
        showProgressBar(pb!!)
        mDatabaseRef.child("Scientists").child(updatedScientist.key!!).setValue(updatedScientist)
            .addOnCompleteListener { task ->
                hideProgressBar(pb)
                if (task.isSuccessful) {
                    show(a, updatedScientist.name.toString() + " Update Successful.")
                    openActivity(a!!, ScientistsActivity::class.java)
                } else {
                    showInfoDialog(a!!, "UNSUCCESSFUL", task.exception!!.message)
                }
            }
    }

Then when edit menu item is clicked:

            R.id.editMenuItem -> {
                if (receivedScientist != null) {
                    updateData()
                } else {
                    show(this, "EDIT ONLY WORKS IN EDITING MODE")
                }
                return true
            }

4. DELETING/REMOVING

Well we want his app to have the capability to remove un-needed data. This can free up his space which is necessary since space costs money.

Here is how you delete from firebase realtime database in kotlin:

    /**
     * This method will allow us to delete from firebase realtime database
     * @param a
     * @param mDatabaseRef
     * @param pb
     * @param selectedScientist
     */
    fun delete(a: AppCompatActivity?, mDatabaseRef: DatabaseReference, pb: ProgressBar?, selectedScientist: Scientist) {
        showProgressBar(pb!!)
        val selectedScientistKey: String = selectedScientist.key!!
        mDatabaseRef.child("Scientists").child(selectedScientistKey).removeValue()
            .addOnCompleteListener { task ->
                hideProgressBar(pb)
                if (task.isSuccessful) {
                    show(a, selectedScientist.name.toString() + " Successfully Deleted.")
                    openActivity(a!!, ScientistsActivity::class.java)
                } else {
                    showInfoDialog(a!!, "UNSUCCESSFUL", task.exception!!.message)
                }
            }
    }

Then later in our CRUDActivity:

    private fun deleteData() {
        crudHelper.delete(this, db, pb, receivedScientist!!)
    }

Then in the same activity when delete menu item is clicked:

            R.id.deleteMenuItem -> {
                if (receivedScientist != null) {
                    deleteData()
                } else {
                    show(this, "DELETE ONLY WORKS IN EDITING MODE")
                }
                return true
            }

 

STEP 6: How To enable Offline-First Capability

I decided to make the app an offline-first one. User would be able to not only access data while completely offline, but also post,update and delete. In short the app can work even if the user is completely offline. Then when the connection is resumed, the data is synchronized with the cloud.

Making the app offline-first would allow him to create an app that is bandwidth friendly and fast, this making his users happy.

To support this capability, go to your App class, inside the onCreate method:

class App : Application() {
    override fun onCreate() {
        super.onCreate()

First initialize the FirebaseApp:

        FirebaseApp.initializeApp(this)

Then set persistance enable to true:

        //permanently cache data to disk so that we can access data even when offline
        FirebaseDatabase.getInstance().setPersistenceEnabled(true)

STEP 7: FONTS – How To Use Custom Fonts

This friend of mine has his own taste when it comes to awesome UI. I guess he would like to use fonts of his own choosing. So we will allow him to do that by giving the app the capability to use custom fonts application-wide as well as per widget.

First go to your app level build.gradle and add the following:

    implementation 'io.github.inflationx:calligraphy3:3.1.0'
    implementation 'io.github.inflationx:viewpump:1.0.0'

Then sync.

Then create an assets folder. Where? Inside the main folder. Inside it create another folder called fonts. Inside it paste your fonts. You can download them from online.

Then come to the App class, inside the onCreate method and initialize calligraphy as well as setting your default fonts:

        //initialize calligraphy, our fonts library before UI is ready
        ViewPump.init(
            ViewPump.builder()
                .addInterceptor(
                    CalligraphyInterceptor(
                        CalligraphyConfig.Builder()
                            .setDefaultFontPath("fonts/RobotoBold.ttf")
                            .setFontAttrId(R.attr.fontPath)
                            .build()
                    )
                )
                .build()
        )

Then in the activity or fragment where you want the custom font applied, make sure you override the attachBaseContext() method as follows:

    /**
     * Inject custom font to this activity as well
     * @param newBase
     */
    override fun attachBaseContext(newBase: Context) {
        super.attachBaseContext(ViewPumpContextWrapper.wrap(newBase))
    }

 

STEP 8 : UI – Many Pages

Because I wanted him to learn how to create a full app, I decided to include several activities:

  1. SplashActivity – To allow him to portray his brand when the app is starting.
class SplashActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_splash)
    }
}

2. DashboardActivity – To give users a central location to navigate to other pages.

class DashboardActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_dashboard)
    }
}

 

3. CRUDActivity – Intelligently designed to be re-used for Creating, Updating and Deleting.

class CRUDActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_crud)
    }
}

4. ScientistsActivity – To allow him list scientsists in the recyclerview.

class ScientistsActivity : AppCompatActivity(), SearchView.OnQueryTextListener, MenuItem.OnActionExpandListener {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_scientists)
    }
}

5. DetailActivity – To allow him show details of a single scientist.

class DetailActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_detail)
    }
}

STEP 9: Search/Filter Capability

If his users were to have so much data, we wanted him to allow them to filter these data. So I incorporated a toolbar searchview. As user types in the toolbar, the data in the recyclerview is filtered. The search resulsts are beautifully highlighted.

So we have created a class called Filter helper. This class extends the android.widget.Filter class:

class FilterHelper : Filter() {
}

Here is how we will be performing search and filtering:

    /*
    - Perform actual filtering.
     */
    override fun performFiltering(constraint: CharSequence): FilterResults {
        var constraint: CharSequence? = constraint
        val filterResults = FilterResults()
        if (constraint != null && constraint.isNotEmpty()) { //CHANGE TO UPPER
            constraint = constraint.toString().toUpperCase()
            //HOLD FILTERS WE FIND
            val foundFilters: ArrayList<Scientist> = ArrayList()
            var galaxy: String
            var name: String
            var star: String
            var description: String
            //ITERATE CURRENT LIST
            for (i in currentList!!.indices) {
                galaxy = currentList!![i].galaxy!!
                name = currentList!![i].name!!
                //SEARCH
                if (galaxy.toUpperCase().contains(constraint)) {
                    foundFilters.add(currentList!![i])
                } else if (name.toUpperCase().contains(constraint)) {
                    foundFilters.add(currentList!![i])
                }
            }
            //SET RESULTS TO FILTER LIST
            filterResults.count = foundFilters.size
            filterResults.values = foundFilters
        } else { //NO ITEM FOUND.LIST REMAINS INTACT
            filterResults.count = currentList!!.size
            filterResults.values = currentList
        }
        //RETURN RESULTS
        return filterResults
    }

And here is how we will be publishing the results to the adapter:

    override fun publishResults(charSequence: CharSequence, filterResults: FilterResults) {
        adapter!!.scientists = filterResults.values as ArrayList<Scientist>
        adapter!!.notifyDataSetChanged()
    }

And here is how we will be highlighting the search results:

        //get name and galaxy
        val name = s.name!!.toLowerCase(Locale.getDefault())
        val galaxy = s.galaxy!!.toLowerCase(Locale.getDefault())
        //highlight name text while searching
        if (name.contains(searchString) && searchString.isNotEmpty()) {
            val startPos: Int = name.indexOf(searchString)
            val endPos: Int = startPos + searchString.length
            val spanString =
                Spannable.Factory.getInstance().newSpannable(holder.nameTxt.text)
            spanString.setSpan(
                ForegroundColorSpan(Color.RED), startPos, endPos,
                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
            )
            holder.nameTxt.text = spanString
        }
        //highlight galaxy text while searching
        if (galaxy.contains(searchString) && searchString.isNotEmpty()) {
            val startPos: Int = galaxy.indexOf(searchString)
            val endPos: Int = startPos + searchString.length
            val spanString =
                Spannable.Factory.getInstance().newSpannable(holder.galaxyTxt.text)
            spanString.setSpan(
                ForegroundColorSpan(Color.BLUE), startPos, endPos,
                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
            )
            holder.galaxyTxt.text = spanString
        }

In the UI, in our toolbar, as the user searches:

    override fun onQueryTextChange(query: String): Boolean {
        Utils.searchString = query
        val adapter =
            MyAdapter(this, Utils.DataCache)
        adapter.filter.filter(query)
        rv!!.layoutManager = LinearLayoutManager(this)
        rv!!.adapter = adapter
        return false
    }

 

STEP 10: DETAIL PAGE – Receiving and Showing Details

His detail page will be pretty simple in terms of its role, even though it will be quite beautiful.

We mainly receive and show data in the appropriate widgets in Kotlin:

    private fun receiveAndShowData() {
        receivedScientist = receiveScientist(intent, this@DetailActivity)
        if (receivedScientist != null) {
            nameTV.text = receivedScientist!!.name
            descriptionTV.text = receivedScientist!!.description
            galaxyTV.text = receivedScientist!!.galaxy
            starTV.text = receivedScientist!!.star
            dobTV.text = receivedScientist!!.dob
            dodTV.text = receivedScientist!!.dod
            mCollapsingToolbarLayout!!.title = receivedScientist!!.name
        }

    }

And in Java:

    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());
         }

    }

We will show the title color of our collapsing toolbar layout so that the title is visible. Here is how you change it:

        mCollapsingToolbarLayout.setExpandedTitleColor(resources.getColor(R.color.white))

Then when the user clicks the floating action button, we will send the current scientit to editing page:

        editFAB.setOnClickListener {
            sendScientistToActivity(this, receivedScientist, CRUDActivity::class.java)
            finish()
        }

Continous Updates

We will be updating these two projects and adding more features. We will also be giving some bonuses and coupons to purchasers of this and our other projects, as well as sending them the updates via email. To follow the updates, go to the download page by clicking the button below.