Open In App

How to Create a Medicine Tracker Android App with Firebase?

Last Updated : 20 Aug, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

A medicine tracker app can be a useful tool for individuals who need to take multiple medications on a regular basis. It can help users track when they need to take their medications and provide alerts and reminders to ensure they don't miss a dose. This article will look at how to build a medicine tracker app using Kotlin and Firebase. A sample video is given below to get an idea about what we are going to do in this article.

Step-by-Step Implementation

Step 1: Create a New Project in Android Studio

To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio. Note that select Kotlin as the programming language.

Step 2: Set up the development environment

Add firebase real-time database dependency in build.gradle(app) file

implementation 'com.google.firebase:firebase-database-ktx:20.1.0'

Add View binding inside android{} block in build.gradle(app) file

XML
buildFeatures {
        viewBinding = true
    }


Step 3: Create a new Firebase Project

Have a look at Adding Firebase to Android App also add a Real-time database to your firebase project.


Step 4: Design the app's user interface

Navigate to the app > res > layout > activity_main.xml and add the below code to that file. Below is the code for the activity_main.xml file. Comments are added inside the code to understand the code in more detail.

Note: New Activity to Add Medicines and activity_medicine_add fragment.

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:listitem="@layout/medicine_layout">

    </androidx.recyclerview.widget.RecyclerView>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/floatingActionButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:clickable="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginEnd="15dp"
        android:layout_marginBottom="15dp"
        app:srcCompat="@android:drawable/ic_input_add"
        tools:layout_editor_absoluteX="254dp"
        tools:layout_editor_absoluteY="360dp" />

</androidx.constraintlayout.widget.ConstraintLayout>
activity_medicine_add.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MedicineAddActivity">

    <EditText
        android:id="@+id/etMedicineName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:textSize="20dp"
        android:background="@color/white"
        android:hint="Enter Medicine Name"
        android:textColor="@color/black">
    </EditText>
  
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:weightSum="100"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/etMedicineTime"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:text="Medicine Time"
            android:textSize="20dp"
            android:layout_weight="50"
            android:textAlignment="center"
            android:textColor="@color/black"
            android:padding="10dp"
            android:layout_margin="10dp">
        </TextView>

        <ImageView
            android:id="@+id/selectTime"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_weight="50"
            android:padding="10dp"
            android:layout_margin="10dp"
            android:src="@drawable/ic_baseline_access_time_24">
        </ImageView>

    </LinearLayout>
  
    <Button
        android:id="@+id/save"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Save">
    </Button>
  
</LinearLayout>
medicine_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MedicineAddActivity">
  
    <EditText
        android:id="@+id/etMedicineName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:textSize="20dp"
        android:background="@color/white"
        android:hint="Enter Medicine Name"
        android:textColor="@color/black">
    </EditText>
  
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:weightSum="100"
        android:orientation="horizontal">
        <TextView
            android:id="@+id/etMedicineTime"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:text="Medicine Time"
            android:textSize="20dp"
            android:layout_weight="50"
            android:textAlignment="center"
            android:textColor="@color/black"
            android:padding="10dp"
            android:layout_margin="10dp">
        </TextView>
        <ImageView
            android:id="@+id/selectTime"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_weight="50"
            android:padding="10dp"
            android:layout_margin="10dp"
            android:src="@drawable/ic_baseline_access_time_24">
        </ImageView>
    </LinearLayout>
  
    <Button
        android:id="@+id/save"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Save">
    </Button>

</LinearLayout>



Step 5: Create MedicineAlarmReceiver class for notifications 

This class, named "MedicineAlarmReceiver", is a subclass of the Android class "BroadcastReceiver" and is used to receive broadcast notifications.

Kotlin
class MedicineAlarmReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        // Create the notification channel (required for Android 8.0 and higher)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // unique id for the channel
            val channelId = "medicine_reminders"
          
            // name of the channel
            val channelName = "Medicine Reminders"
          
            // importance level of the channel
            val importance = NotificationManager.IMPORTANCE_HIGH 
          
            // creating channel
            val channel =
                NotificationChannel(channelId, channelName, importance) 
                
            // enabling lights for the channel
            channel.enableLights(true) 
            
            // setting light color for the channel
            channel.lightColor = Color.RED 
          
            // enabling vibration for the channel
            channel.enableVibration(true) 
            val notificationManager = context.getSystemService(NotificationManager::class.java)
            notificationManager?.createNotificationChannel(
                channel
            ) // registering the channel with the system
        }

        // Create the notification
        
        // unique id for the notification
        val notificationId = 1 
      
        // title of the notification
        val title = "Medicine Reminder" 
      
        // text of the notification
        val text = "It's time to take your medicine." 
        val intent =
            Intent(
                context,
                MainActivity::class.java
            ) // intent to launch when the notification is clicked
        val pendingIntent =
            PendingIntent.getActivity(
                context,
                0,
                intent,
                0
            ) // creating a pending intent to wrap the intent
            
        // creating the builder object
        val builder =
            NotificationCompat.Builder(context, "medicine_reminders") 
                .setSmallIcon(
                    R.drawable.ic_baseline_mediation_24
                ) // setting the small icon for the notification
                .setContentTitle(title) // setting the title for the notification
                .setContentText(text) // setting the text for the notification
                .setContentIntent(pendingIntent) // attaching the pending intent to the notification
                .setAutoCancel(
                    true
                ) // setting the notification to be automatically cancelled when clicked
                .setPriority(
                    NotificationCompat.PRIORITY_HIGH
                ) // setting the priority level of the notification

        // Display the notification
        
        // getting the notification manager
        val notificationManager =
            NotificationManagerCompat.from(context) 
            
        // displaying the notification
        notificationManager.notify(notificationId, builder.build()) 
    }
}


Step 6: Create a data class User

Kotlin
data class User (
    val medicineName : String ,
    val time : String
        )


Step 7: Create UserAdapterClass for recycler View

Kotlin
class UserAdapter(private val list: ArrayList<User>) :
    RecyclerView.Adapter<UserAdapter.ViewHolder>() {

    // ViewHolder to hold reference to each item of the recycler view
    class ViewHolder(val binding: MedicineLayoutBinding) : RecyclerView.ViewHolder(binding.root) {}

    // inflating the layout and creating the ViewHolder when needed
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(
            MedicineLayoutBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )
    }

    // binding data to the view in the viewholder
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.binding.name.text = list[position].medicineName
        holder.binding.time.text = list[position].time
    }

    // returning the size of the data set
    override fun getItemCount(): Int {
        return list.size
    }
}


Step 8: Create a new activity MedicineAdd and add these lines of code to it 

We will be using a TimePicker dialog in this activity in order to add medicine time.

Kotlin
class MedicineAddActivity : AppCompatActivity(), TimePickerDialog.OnTimeSetListener {
    // reference to the database 
    private lateinit var database : DatabaseReference
    // reference to the layout binding object
    private lateinit var binding: ActivityMedicineAddBinding
    // variables to store the current time
    var hours = 0
    var minutes = 0
    // variables to store the selected time
    var myHours : Int = 0
    var myMinutes : Int= 0
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // inflating the layout and binding it to the binding object
        binding = ActivityMedicineAddBinding.inflate(layoutInflater)
        setContentView(binding.root)
        // setting a click listener on the select time button 
        binding.selectTime.setOnClickListener{
            val cal = Calendar.getInstance()
            // getting the current time 
            hours = cal.get(Calendar.HOUR)
            minutes = cal.get(Calendar.MINUTE)
            // creating a new time picker dialog 
            val timePickerDialog = TimePickerDialog(this,this,hours,minutes,true)
            timePickerDialog.show()
        }
    }
    // method called when the user sets the time on the time picker dialog
    override fun onTimeSet(view: TimePicker?, hourOfDay: Int, minute: Int) {
        myHours = hourOfDay
        myMinutes = minute
        // updating the text view that displays the selected time
        binding.etMedicineTime.text = "$myHours:$myMinutes"
        // setting a click listener on the save button
        binding.save.setOnClickListener{
            saveData()
        }
    }
}


Step 9: Save Data in firebase real-time database 

The method starts by getting an instance of the FirebaseDatabase and getting a reference to the "Users" node in the database. The method then retrieves the value of the text from the "etMedicineName" TextView, trims it, and stores it in the variable medicineName.

Kotlin
private fun saveData() {
    // Reference to the "Users" node in the Firebase Database
    database = FirebaseDatabase.getInstance().getReference("Users")
    // Retrieve the value of the text from the "etMedicineName" TextView
    val medicineName = binding.etMedicineName.text.toString().trim()
    // Create a string with the selected time in the format "hours:minutes"
    val time = "$myHours:$myMinutes"
    // check if the medicineName is not empty
    if (medicineName.isNotEmpty()){
        // create a new User object with the 
        // entered medicine name and selected time
        val user = User(medicineName,time)
        // save the user object to the Firebase Database
        // under the "medicineName" key
        database.child(medicineName).setValue(user).addOnSuccessListener {
            // Clearing the EditText after saving the data
            binding.etMedicineName.text.clear()
            binding.etMedicineTime.text = "Select Time"
            Toast.makeText(this, "Success: Data saved to Firebase", Toast.LENGTH_SHORT).show()
        }.addOnFailureListener{
            Toast.makeText(this, "Error: Failed to save data to Firebase", Toast.LENGTH_SHORT).show()
        }
    }
    // call the setAlarm method to
    // set the alarm based on the selected time
    setAlarm()
}


Step 10: Set Alarm for medicine

The method starts by getting an instance of the AlarmManager service and creating a new Intent for the MedicineAlarmReceiver class. Then, it creates a PendingIntent that wraps the intent and requests the broadcast to be delivered to the MedicineAlarmReceiver

Kotlin
 private fun setAlarm() {
        val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
        val intent = Intent(this, MedicineAlarmReceiver::class.java)
        val pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0)

        // Set the alarm to trigger at the specified time
        val calendar = Calendar.getInstance()
        calendar.timeInMillis = System.currentTimeMillis()
        calendar.set(Calendar.HOUR_OF_DAY, myHours)
        calendar.set(Calendar.MINUTE, myMinutes)

        alarmManager.setRepeating(
            AlarmManager.RTC_WAKEUP,
            calendar.timeInMillis,
            AlarmManager.INTERVAL_DAY,
            pendingIntent
        )
    }

Step 11: Implement MainActivity

In this step, we’ll implement the MainActivity, which will display the list of medicines and provide an interface for adding new medicines

MainActivity.kt
package com.example.medicinetracker

import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.medicinetracker.databinding.ActivityMainBinding
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.FirebaseDatabase

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private lateinit var database: DatabaseReference
    private lateinit var userAdapter: UserAdapter
    private val medicineList = mutableListOf<User>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // Initialize Firebase Database
        database = FirebaseDatabase.getInstance().reference.child("Users")

        // Set up RecyclerView
        userAdapter = UserAdapter(medicineList)
        binding.recyclerView.layoutManager = LinearLayoutManager(this)
        binding.recyclerView.adapter = userAdapter

        // Load medicines from Firebase
        loadMedicines()

        // Set up "Add Medicine" button
        binding.btnAddMedicine.setOnClickListener {
            val intent = Intent(this, MedicineAddActivity::class.java)
            startActivity(intent)
        }
    }

    private fun loadMedicines() {
        database.addValueEventListener(object : com.google.firebase.database.ValueEventListener {
            override fun onDataChange(snapshot: com.google.firebase.database.DataSnapshot) {
                medicineList.clear()
                for (medicineSnapshot in snapshot.children) {
                    val medicine = medicineSnapshot.getValue(User::class.java)
                    medicine?.let { medicineList.add(it) }
                }
                userAdapter.notifyDataSetChanged()
            }

            override fun onCancelled(error: DatabaseError) {
                // Log the error
                Log.e("MainActivity", "Error loading medicines: ${error.message}")

                // Display a user-friendly message
                Toast.makeText(this@MainActivity, "Failed to load medicines. Please try again later.", Toast.LENGTH_LONG).show()
            }
        })
    }
}


Output:


Next Article

Similar Reads