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
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.
activity_main.xml
<? xml version = "1.0" encoding = "utf-8" ?>
< androidx.constraintlayout.widget.ConstraintLayout 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
New Activity to Add Medicines
<? xml version = "1.0" encoding = "utf-8" ?>
< LinearLayout 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
Layout File for RecyclerView
<? xml version = "1.0" encoding = "utf-8" ?>
< LinearLayout 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.
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
data class User (
val medicineName : String ,
val time : String
)
|
Step 7: Create UserAdapterClass for recycler View
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.
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.
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
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
)
}
|
Output: