In this Guide, You will learn how to create an advanced mobile app, The application to be developed is an online Lab Tests Booking System which will allow patients book for specific Lab Tests form different Laboratories, it includes Authentication module, Services Listing, Shopping Cart Module, Maps and GPS Module, Fragments, Payment, APIs etc, This app is inspired by Online Booking Apps such as Glovo, Uber Eats, Booking.com, Yum, Jumia Foods etc.
In order to develop this app, you must have an understanding of Kotlin Language and have Android Application Development Fundamentals.
This mobile application consumes an API in the backend, If you have not created the API, Please follow below link
https://github.com/modcomlearning/medilab
In this step we understand the app structure. Create a New Android App. Use Empty Views Activity.
NB: Please use a different unique Package.
In your app Package under Kotlin + Java, create four subpackages namely;
adapters - To be used to store our Adapters used by Recycler Views
constants - To be used in storing Constants variables that are used across the application
helpers - To be used to hold helpers that help us with different functionalities in our App i.e APIHelper
models - This package will store models that define the data we will be working on.
in res Folder, create a subfolder named 'font' and place sample fonts inside.
Download Fonts from here https://www.fontsquirrel.com/fonts/list/popular
Right click your main package, Create a New Empty Views activity named Screen1 and another one named Screen2.
In drawable Folder place 2 images to be shown in screen1 and screen2
In activity_screen1.xml Write below code.
In below code, we have used an image stored in res/drawable and a font stored in res/fonts
<?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:orientation="vertical"
android:layout_height="match_parent"
tools:context=".Screen1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginRight="15dp"
android:layout_marginTop="15dp"
>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/skip1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Skip"
android:fontFamily="@font/montserrat"
android:textAlignment="textEnd"
android:textColor="#9F9797"
android:textStyle="bold"
android:textSize="15sp"/>
</LinearLayout>
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Book Your Lab"
android:textAlignment="textStart"
android:layout_marginTop="100dp"
android:fontFamily="@font/montserrat"
android:layout_marginLeft="15dp"
android:textColor="#353232"
android:textStyle="bold"
android:textSize="34sp"/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This is professional mobile onboarding screens.this screens useing on food delivery app."
android:textAlignment="textStart"
android:layout_marginTop="10dp"
android:layout_marginLeft="15dp"
android:fontFamily="@font/montserrat"
android:textColor="#706E6E"
android:textStyle="normal"
android:lineHeight="20dp"
android:textSize="14sp"/>
<ImageView
android:layout_width="400dp"
android:layout_height="300dp"
android:src="@drawable/screen11"
android:padding="20dp"/>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="10dp"
app:backgroundTint="#3F51B5" />
<TextView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="Next"
android:elevation="6dp"
android:textSize="18dp"
android:fontFamily="@font/montserrat"
android:textColor="#fff"
app:layout_anchor="@id/fab"
app:layout_anchorGravity="center"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout>
In Screen1.kt Write below code
package com.modcom.medilabsapp
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.textview.MaterialTextView
class Screen1 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_screen1)
//Find Views by ID
val skip1 = findViewById<MaterialTextView>(R.id.skip1)
//Button to Go Next to Screen 2
skip1.setOnClickListener {
startActivity(Intent(applicationContext, MainActivity::class.java))
}// End
//Button to Skip to Main Activity
val fab = findViewById<FloatingActionButton>(R.id.fab)
fab.setOnClickListener {
startActivity(Intent(applicationContext, Screen2::class.java))
}// End
}
}
In above code, when a user clicks Skip button it directs to MainActivity, when a user clicks Next Button it Goes to Screen2
In activity_screen2.xml, write below code In below code, we have used an image stored in res/drawable and a font stored in res/fonts
<?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:orientation="vertical"
android:layout_height="match_parent"
tools:context=".Screen1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginRight="15dp"
android:layout_marginTop="15dp"
>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/skip2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Skip"
android:textAlignment="textEnd"
android:textColor="#9F9797"
android:textStyle="bold"
android:textSize="15sp"/>
</LinearLayout>
<ImageView
android:layout_width="400dp"
android:layout_height="400dp"
android:src="@drawable/screen1"
android:padding="20dp"/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Quality Service"
android:textAlignment="textStart"
android:layout_marginTop="10dp"
android:layout_marginLeft="15dp"
android:textColor="#353232"
android:fontFamily="@font/montserrat"
android:textStyle="bold"
android:textSize="34sp"/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This is professional mobile onboarding screens.this screens useing on food delivery app."
android:textAlignment="textStart"
android:layout_marginTop="10dp"
android:layout_marginLeft="15dp"
android:textColor="#706E6E"
android:textStyle="normal"
android:fontFamily="@font/montserrat"
android:lineHeight="20dp"
android:textSize="14sp"/>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="10dp"
app:backgroundTint="#3F51B5" />
<TextView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="Next"
android:elevation="6dp"
android:textSize="18dp"
android:textColor="#fff"
android:fontFamily="@font/montserrat"
app:layout_anchor="@id/fab2"
app:layout_anchorGravity="center"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout>
Then in Screen2.kt, Write the Kotlin code.
package com.modcom.medilabsapp
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.textview.MaterialTextView
class Screen2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_screen2)
//When user skips goes to MainActivity
val skip2 = findViewById<MaterialTextView>(R.id.skip2)
skip2.setOnClickListener {
startActivity(Intent(applicationContext, MainActivity::class.java))
}//end
//When user click Next goes Still to MainActivity
val fab2 = findViewById<FloatingActionButton>(R.id.fab2)
fab2.setOnClickListener {
startActivity(Intent(applicationContext, MainActivity::class.java))
}// End
}
}
Finally, In Manifest File, Make Screen1 the Launcher Activity
Also note we used android:theme="@style/Theme.AppCompat.Light.NoActionBar" theme in our Screen1 and Screen2 in Manifest below
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.MediLabsApp"
tools:targetApi="31">
<activity
android:name=".Screen2"
android:exported="false"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
<activity
android:name=".MainActivity"
android:exported="false" />
<activity
android:name=".Screen1"
android:exported="true"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
In this step we will work on MainActivity to load all Laboratories from API to and display in Android Recycler View
In build.gradle(Module App), under dependencies add below lines and sync Project.
//LoopJ for API Connections
implementation 'com.loopj.android:android-async-http:1.4.9'
// For JSON Conversions, From JSONArray to ArrayList
implementation 'com.google.code.gson:gson:2.8.7'
//For Swipe Refresh in Recycler View
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
//For making rounded circular imageView
implementation 'de.hdodenhof:circleimageview:3.0.1'
Sync Your Project
In helpers Package, Add API Helper from https://justpaste.it/bewc2
Under models Package, create a Kotlin Class Lab.kt and write below code.
Below code creates/defines our variables as per the Laboratories API.
This model will be used in Our Recycler View Adapter to define our data
NB: The variable names MUST be the same as keys in your API.
Lab.kt
//Package ...
class Lab (
val email: String = "",
val lab_id:String = "",
val lab_name: String = "",
val permit_id: String = "",
val phone: String = "",
val reg_date: String = ""
)
Next, in res/layout, create a file named single_lab.xml and place below code, This file will be used by our LabAdapter
in below code, we use de.hdodenhof.circleimageview.CircleImageView and place screen11 image on It.
single_lab.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="10dp"
android:layout_margin="8dp"
app:cardCornerRadius="5dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="100dp">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/labimage"
android:layout_width="55dp"
android:layout_height="55dp"
android:src="@drawable/screen1"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"/>
<LinearLayout
android:id="@+id/linear1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/labimage"
android:orientation="vertical"
android:layout_marginLeft="10dp">
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lancet Pathologists"
android:textStyle="bold"
android:textSize="16sp"
android:fontFamily="@font/montserrat"
android:textColor="@color/black"
android:id="@+id/lab_name"/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Permit ID: 457878"
android:textStyle="normal"
android:textSize="12sp"
android:fontFamily="@font/montserrat"
android:textColor="#787474"
android:layout_marginTop="6dp"
android:id="@+id/permit_id"/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/linear1"
android:layout_marginTop="5dp"
android:layout_marginLeft="20dp"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="lancet@gmail.com"
android:textStyle="bold"
android:textSize="15sp"
android:fontFamily="@font/montserrat"
android:textColor="#FF5722"
android:layout_marginTop="6dp"
android:id="@+id/email"/>
</LinearLayout>
</RelativeLayout>
</androidx.cardview.widget.CardView>
The layout above shows how a single item is displayed on RecyclerView
In adapters Package, Create a Class File named LabAdapter.kt and write below code
LabAdapter.kt
package com.modcom.medilabsapp.adapters
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.textview.MaterialTextView
import com.modcom.medilabsapp.R
import com.modcom.medilabsapp.models.Lab
//We provide context in below class to make it be like an activity
class LabAdapter(var context: Context):
RecyclerView.Adapter<LabAdapter.ViewHolder>() {
//Create a List and connect it with our model
var itemList : List<Lab> = listOf() //Its empty
//Create a Class here, will hold our views in single_lab xml
inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
//Access single_lab XML
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LabAdapter.ViewHolder {
//access/inflate the single lab xml
val view = LayoutInflater.from(parent.context).inflate(R.layout.single_lab,
parent, false)
return ViewHolder(view) //pass the single lab to ViewHolder
}
// Bind data to Views from single_lab XML
override fun onBindViewHolder(holder: LabAdapter.ViewHolder, position: Int) {
//Find your 3 text views
val lab_name = holder.itemView.findViewById<MaterialTextView>(R.id.lab_name)
val permit_id = holder.itemView.findViewById<MaterialTextView>(R.id.permit_id)
val email = holder.itemView.findViewById<MaterialTextView>(R.id.email)
//Assume one Lab, and bind data, it will loop other Labs
val lab = itemList[position]
lab_name.text = lab.lab_name
permit_id.text = "Permit ID: "+lab.permit_id
email.text = lab.email
//When one Lab is clicked, Move to Lab tests Activity
holder.itemView.setOnClickListener {
//To Navigate to LabTests on each Lab Click
}
}
//Count Number of items
override fun getItemCount(): Int {
return itemList.size //Count how may Items in the List
}
//This is for filtering data
fun filterList(filterList: List<Lab>){
itemList = filterList
notifyDataSetChanged()
}
//Earlier we mentioned item List is empty!
//We will get data from our APi, then bring it to below function
//The data you bring here must follow the Lab model
fun setListItems(data: List<Lab>){
itemList = data //map/link the data to itemlist
notifyDataSetChanged()
//Tell this adapter class that now itemList is loaded with data
}
//justpaste.it/cgaym
}
Go to activity_main.xml and Place this code which has a ProgressBar , SwipeRefresh and a RecyclerView
<?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=".MainActivity">
<ProgressBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="3dp"
android:id="@+id/progress"/>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/swipeRefreshLayout">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/single_lab"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>
In MainActivity.kt Write below code.
package com.modcom.medilabsapp
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.ProgressBar
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.gson.GsonBuilder
import com.modcom.medilabsapp.adapters.LabAdapter
import com.modcom.medilabsapp.constants.Constants
import com.modcom.medilabsapp.helpers.ApiHelper
import com.modcom.medilabsapp.models.Lab
import org.json.JSONArray
import org.json.JSONObject
class MainActivity : AppCompatActivity() {
//Global Declaration - they can be accessed all over this class.
// Declare variables as lateinit means they are guaranteed to be initialized in the future
lateinit var itemList: List<Lab>
lateinit var labAdapter: LabAdapter
lateinit var recyclerView: RecyclerView
lateinit var progress: ProgressBar
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//Find Views
progress = findViewById(R.id.progress)
recyclerView = findViewById(R.id.recycler)
//Link to adapter
labAdapter = LabAdapter(applicationContext)
//Set recycler adapter layout manager
recyclerView.layoutManager = LinearLayoutManager(applicationContext)
recyclerView.setHasFixedSize(true)
//Call the function to fetch data
fetchData()
}//end Oncreate
// FUnctions to Fetch Laboratories from API and Bind then in Recycler View
fun fetchData(){
//Go to the PAi get the dataapplicationContextthis
val api = Constants.BASE_URL+"/laboratories"
val helper = ApiHelper(this@MainActivity)
helper.get(api, object: ApiHelper.CallBack{
override fun onSuccess(result: JSONArray?) {
//Take above result to adapter
//Convert Above result from JSON array to LIST<Lab>
val gson = GsonBuilder().create()
itemList = gson.fromJson(result.toString(),
Array<Lab>::class.java).toList()
//Finally, our adapter has the data
labAdapter.setListItems(itemList)
//For the sake of recycling/Looping items, add the adapter to recycler
recyclerView.adapter = labAdapter
progress.visibility = View.GONE
}//end
override fun onSuccess(result: JSONObject?) {
}
override fun onFailure(result: String?) {
Toast.makeText(applicationContext, "Error:"+result.toString(),
Toast.LENGTH_SHORT).show()
Log.d("failureerrors", result.toString())
progress.visibility = View.GONE
}
})
}//end fetch data
//End
}
Run your App
Adding Swipe Refresh, We already have a SwipeRefresh View in activity_main.xml, in MainActivity.kt we just need to find it and apply a swipe refresh listener.
Below is updated code with SwipeRefresh
package com.modcom.medilabsapp
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.ProgressBar
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.google.gson.GsonBuilder
import com.modcom.medilabsapp.adapters.LabAdapter
import com.modcom.medilabsapp.constants.Constants
import com.modcom.medilabsapp.helpers.ApiHelper
import com.modcom.medilabsapp.models.Lab
import org.json.JSONArray
import org.json.JSONObject
class MainActivity : AppCompatActivity() {
//Global Declaration - they can be accessed all over this class
lateinit var itemList: List<Lab>
lateinit var labAdapter: LabAdapter
lateinit var recyclerView: RecyclerView
lateinit var progress: ProgressBar
//add Swipe refresh declaration
lateinit var swiperefresh: SwipeRefreshLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//Find Views
progress = findViewById(R.id.progress)
recyclerView = findViewById(R.id.recycler)
//Link to adapter
labAdapter = LabAdapter(applicationContext)
//Set recycler adapter layout manager
recyclerView.layoutManager = LinearLayoutManager(applicationContext)
recyclerView.setHasFixedSize(true)
//Call the function to fetch data
fetchData()
//FInd swipe refresh and set refresh Listener
swiperefresh = findViewById<SwipeRefreshLayout>(R.id.swipeRefreshLayout)
swiperefresh.setOnRefreshListener {
fetchData()// fetch data again
}//end refresh
}//end Oncreate
fun fetchData(){
//Go to the PAi get the dataapplicationContextthis
val api = Constants.BASE_URL+"/laboratories"
val helper = ApiHelper(this@MainActivity)
helper.get(api, object: ApiHelper.CallBack{
override fun onSuccess(result: JSONArray?) {
//Take above result to adapter
//Convert Above result from JSON array to LIST<Lab>
val gson = GsonBuilder().create()
itemList = gson.fromJson(result.toString(),
Array<Lab>::class.java).toList()
//Finally, our adapter has the data
labAdapter.setListItems(itemList)
//For the sake of recycling/Looping items, add the adapter to recycler
recyclerView.adapter = labAdapter
//set is progress to gone when records are loaded
progress.visibility = View.GONE
//set is refreshing to false when records are loaded
swiperefresh.isRefreshing = false
}//end
override fun onSuccess(result: JSONObject?) {
}
override fun onFailure(result: String?) {
Toast.makeText(applicationContext, "Error:"+result.toString(),
Toast.LENGTH_SHORT).show()
Log.d("failureerrors", result.toString())
progress.visibility = View.GONE
}
})
}//end fetch data
}
Run your App, Observe the Pull Refresh
Adding a Search Filter.
In activity_main.xml and an EditText for search
Below is updated activity_main.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=".MainActivity">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Search Lab name.."
android:drawableStart="@android:drawable/ic_menu_search"
android:layout_margin="5dp"
android:id="@+id/etsearch"/>
<ProgressBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="3dp"
android:id="@+id/progress"/>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/swipeRefreshLayout">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/single_lab"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>
In LabAdapter.kt, add below function This function will be used by filter function to filter the Array list of Labs
fun filterList(filterList: List<LabTests>){
itemList = filterList
notifyDataSetChanged()
}
Next in MainActivity.kt add below function code from https://justpaste.it/9j21s This filter function filters data in the recycler view using the Adapter
private fun filter(text: String) {
// creating a new array list to filter our data.
val filteredlist: ArrayList<Lab> = ArrayList()
// running a for loop to compare elements.
for (item in itemList) {
// checking if the entered string matched with any item of our recycler view.
if (item.lab_name.lowercase().contains(text.lowercase())) {
// if the item is matched we are
// adding it to our filtered list.
filteredlist.add(item)
}
}
if (filteredlist.isEmpty()) {
// if no item is added in filtered list we are
// displaying a toast message as no data found.
//Toast.makeText(this, "No Data Found..", Toast.LENGTH_SHORT).show()
labAdapter.filterList(filteredlist)
} else {
// at last we are passing that filtered
// list to our adapter class.
labAdapter.filterList(filteredlist)
}
}
Then in MainActivity.kt inside oncreate function write below code. Below code adds a Text Changed Listener to EditText, onTextChanged it calls filter function
//Filter Labs
val etsearch = findViewById<EditText>(R.id.etsearch)
etsearch.addTextChangedListener(object: TextWatcher{
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(texttyped: CharSequence?, p1: Int, p2: Int, p3: Int) {
filter(texttyped.toString())
}
override fun afterTextChanged(p0: Editable?) {
}
})
Final Code for MainActivity.kt
package com.modcom.medilabsapp
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.View
import android.widget.EditText
import android.widget.ProgressBar
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.google.gson.GsonBuilder
import com.modcom.medilabsapp.adapters.LabAdapter
import com.modcom.medilabsapp.constants.Constants
import com.modcom.medilabsapp.helpers.ApiHelper
import com.modcom.medilabsapp.models.Lab
import org.json.JSONArray
import org.json.JSONObject
class MainActivity : AppCompatActivity() {
//Global Declaration - they can be accessed all over this class
lateinit var itemList: List<Lab>
lateinit var labAdapter: LabAdapter
lateinit var recyclerView: RecyclerView
lateinit var progress: ProgressBar
lateinit var swiperefresh: SwipeRefreshLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
progress = findViewById(R.id.progress)
recyclerView = findViewById(R.id.recycler)
labAdapter = LabAdapter(applicationContext)
recyclerView.layoutManager = LinearLayoutManager(applicationContext)
recyclerView.setHasFixedSize(true)
//Call the function
fetchData()
swiperefresh = findViewById<SwipeRefreshLayout>(R.id.swipeRefreshLayout)
swiperefresh.setOnRefreshListener {
fetchData()// fetch data again
}//end refresh
//Filter Labs
val etsearch = findViewById<EditText>(R.id.etsearch)
etsearch.addTextChangedListener(object: TextWatcher{
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(texttyped: CharSequence?, p1: Int, p2: Int, p3: Int) {
filter(texttyped.toString())
}
override fun afterTextChanged(p0: Editable?) {
}
})
}//end Oncreate
fun fetchData(){
//Go to the PAi get the dataapplicationContextthis
val api = Constants.BASE_URL+"/laboratories"
val helper = ApiHelper(this@MainActivity)
helper.get(api, object: ApiHelper.CallBack{
override fun onSuccess(result: JSONArray?) {
//Take above result to adapter
//Convert Above result from JSON array to LIST<Lab>
val gson = GsonBuilder().create()
itemList = gson.fromJson(result.toString(),
Array<Lab>::class.java).toList()
//Finally, our adapter has the data
labAdapter.setListItems(itemList)
//For the sake of recycling/Looping items, add the adapter to recycler
recyclerView.adapter = labAdapter
progress.visibility = View.GONE
swiperefresh.isRefreshing = false
}//end
override fun onSuccess(result: JSONObject?) {
}
override fun onFailure(result: String?) {
Toast.makeText(applicationContext, "Error:"+result.toString(),
Toast.LENGTH_SHORT).show()
Log.d("failureerrors", result.toString())
progress.visibility = View.GONE
}
})
}//end fetch data
//Filter function
//justpaste.it/9j21s
//Filter
private fun filter(text: String) {
// creating a new array list to filter our data.
val filteredlist: ArrayList<Lab> = ArrayList()
// running a for loop to compare elements.
for (item in itemList) {
// checking if the entered string matched with any item of our recycler view.
if (item.lab_name.lowercase().contains(text.lowercase())) {
// if the item is matched we are
// adding it to our filtered list.
filteredlist.add(item)
}
}
if (filteredlist.isEmpty()) {
// if no item is added in filtered list we are
// displaying a toast message as no data found.
//Toast.makeText(this, "No Data Found..", Toast.LENGTH_SHORT).show()
labAdapter.filterList(filteredlist)
} else {
// at last we are passing that filtered
// list to our adapter class.
labAdapter.filterList(filteredlist)
}
}
}
Creating a LabTestActivity which will show LabTest when one Lab is clicked from above screenshot
In models Package create a Class File named LabTests.kt and write below code
LabTests.kt
class LabTests (
val availability: String = "",
var lab_id: String = "",
val more_info: String = "",
val reg_date: String = "",
var test_description: String = "",
val test_discount: String = "",
var test_cost: String = "",
var test_id: String = "",
var test_name: String = "",
)
Above the variable names Must be same as your API keys
The Recycler View that will display the Lab Tests is more similar with the one displaying the Lab, so the process is same
in res/layout create a file named single_labtests.xml and write below code. single_labtests.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="10dp"
android:layout_margin="8dp"
app:cardCornerRadius="5dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="120dp">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/labimage"
android:layout_width="55dp"
android:layout_height="55dp"
android:src="@drawable/screen1"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"/>
<LinearLayout
android:id="@+id/linear1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/labimage"
android:orientation="vertical"
android:layout_marginLeft="10dp">
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lipid Profile"
android:textStyle="bold"
android:textSize="16sp"
android:fontFamily="@font/montserrat"
android:textColor="@color/black"
android:id="@+id/test_name"/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is a nice Lab tests for keeping Fit, This is a nice test keeping Fit"
android:textStyle="normal"
android:textSize="12sp"
android:maxLines="2"
android:fontFamily="@font/montserrat"
android:textColor="#787474"
android:layout_marginTop="6dp"
android:id="@+id/test_description"/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/linear1"
android:layout_marginTop="5dp"
android:layout_marginLeft="10dp"
android:layout_marginBottom="5dp"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/test_cost"
android:layout_width="315dp"
android:layout_height="wrap_content"
android:fontFamily="@font/montserrat"
android:text="KES 1,200"
android:textAlignment="textEnd"
android:textColor="#FF5722"
android:textSize="15sp"
android:textStyle="bold" />
</LinearLayout>
</RelativeLayout>
</androidx.cardview.widget.CardView>
in adapters package, Create a Class File named LabTestsAdapter.kt and write below code
package com.modcom.medilabsapp.adapters
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.textview.MaterialTextView
import com.modcom.medilabsapp.R
import com.modcom.medilabsapp.models.LabTests
class LabTestsAdapter(var context: Context):
RecyclerView.Adapter<LabTestsAdapter.ViewHolder>() {
//Create a List and connect it with our model
var itemList : List<LabTests> = listOf() //Its empty
//Create a Class here, will hold our views in single_lab xml
inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LabTestsAdapter.ViewHolder {
//access/inflate the single labtests xml
val view = LayoutInflater.from(parent.context).inflate(R.layout.single_labtests,
parent, false)
return ViewHolder(view) //pass the single lab to ViewHolder
}
override fun onBindViewHolder(holder: LabTestsAdapter.ViewHolder, position: Int) {
//Find your 3 text views
val test_name = holder.itemView.findViewById<MaterialTextView>(R.id.test_name)
val test_description = holder.itemView.findViewById<MaterialTextView>(R.id.test_description)
val test_cost = holder.itemView.findViewById<MaterialTextView>(R.id.test_cost)
//Assume one Lab, bind data,
val item = itemList[position]
test_name.text = item.test_name
test_description.text = item.test_description
test_cost.text = item.test_cost+" KES"
holder.itemView.setOnClickListener {
//To Navigate to single item Activity
}//end
// Toast.makeText(context, "yyy"+item.test_cost, Toast.LENGTH_SHORT).show()
}
//count number of items
override fun getItemCount(): Int {
return itemList.size //Count how may Items in the List
}
//This is for filtering data
fun filterList(filterList: List<LabTests>){
itemList = filterList
notifyDataSetChanged()
}
//Earlier we mentioned item List is empty!
//We will get data from our APi, then bring it to below function
//The data you bring here must follow the Lab model
fun setListItems(data: List<LabTests>){
itemList = data //map/link the data to itemlist
notifyDataSetChanged()
//Tell this adapter class that now itemList is loaded with data
}
//justpaste.it/cgaym
}
In your main Package create an Empty Views Activity named LabTestsActivity and write below code.
NB: Below code is 90% same as MainActivity we did for Laboratories.
package com.modcom.medilabsapp
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.View
import android.widget.EditText
import android.widget.ProgressBar
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.google.gson.GsonBuilder
import com.modcom.medilabsapp.adapters.LabAdapter
import com.modcom.medilabsapp.adapters.LabTestsAdapter
import com.modcom.medilabsapp.constants.Constants
import com.modcom.medilabsapp.helpers.ApiHelper
import com.modcom.medilabsapp.models.Lab
import com.modcom.medilabsapp.models.LabTests
import org.json.JSONArray
import org.json.JSONObject
class LabTestsActivity : AppCompatActivity() {
lateinit var itemList: List<LabTests>
lateinit var labtestAdapter: LabTestsAdapter
lateinit var recyclerView: RecyclerView
lateinit var progress: ProgressBar
lateinit var swiperefresh: SwipeRefreshLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_lab_tests)
progress = findViewById(R.id.progress)
recyclerView = findViewById(R.id.recycler)
labtestAdapter = LabTestsAdapter(applicationContext)
recyclerView.layoutManager = LinearLayoutManager(applicationContext)
recyclerView.setHasFixedSize(true)
post_fetch()
swiperefresh = findViewById<SwipeRefreshLayout>(R.id.swipeRefreshLayout)
swiperefresh.setOnRefreshListener {
post_fetch()// fetch data again
}//end refresh
//Filter labs
val etsearch = findViewById<EditText>(R.id.etsearch)
etsearch.addTextChangedListener(object: TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(texttyped: CharSequence?, p1: Int, p2: Int, p3: Int) {
filter(texttyped.toString())
}
override fun afterTextChanged(p0: Editable?) {
}
})
}//end oncreate
fun post_fetch(){
val api = Constants.BASE_URL+"/lab_tests"
//Above APi needs a Body, So we have to build it
val body = JSONObject()
//Retrieve the id from Intent Extras - To Add this ID to bundles extras Later
val id = intent.extras?.getString("key1")
//Toast.makeText(applicationContext, "ID $id", Toast.LENGTH_SHORT).show()
//provide the ID to the API
body.put("lab_id", id) //NB: 4 is static
val helper = ApiHelper(applicationContext)
helper.post(api, body, object : ApiHelper.CallBack{
override fun onSuccess(result: JSONArray?) {
val gson = GsonBuilder().create()
itemList = gson.fromJson(result.toString(),
Array<LabTests>::class.java).toList()
//Finally, our adapter has the data
labtestAdapter.setListItems(itemList)
//For the sake of recycling/Looping items, add the adapter to recycler
recyclerView.adapter = labtestAdapter
progress.visibility = View.GONE
swiperefresh.isRefreshing = false
}
override fun onSuccess(result: JSONObject?) {
Toast.makeText(applicationContext, result.toString(),
Toast.LENGTH_SHORT).show()
progress.visibility = View.GONE
}
override fun onFailure(result: String?) {
Toast.makeText(applicationContext, "Error:"+result.toString(),
Toast.LENGTH_SHORT).show()
Log.d("failureerrors", result.toString())
}
})
}//end fetch
private fun filter(text: String) {
// creating a new array list to filter our data.
val filteredlist: ArrayList<LabTests> = ArrayList()
// running a for loop to compare elements.
for (item in itemList) {
// checking if the entered string matched with any item of our recycler view.
if (item.test_name.lowercase().contains(text.lowercase())) {
// if the item is matched we are
// adding it to our filtered list.
filteredlist.add(item)
}
}
if (filteredlist.isEmpty()) {
// if no item is added in filtered list we are
// displaying a toast message as no data found.
//Toast.makeText(this, "No Data Found..", Toast.LENGTH_SHORT).show()
labtestAdapter.filterList(filteredlist)
} else {
// at last we are passing that filtered
// list to our adapter class.
labtestAdapter.filterList(filteredlist)
}
}
}
In above code we have a function named Post fetch, see it below
fun post_fetch(){
val api = Constants.BASE_URL+"/lab_tests"
//Above APi needs a Body to parse the lab_id, So we have to build the body
val body = JSONObject()
//Retrieve the id from Intent Extras
val id = intent.extras?.getString("key1")
//Toast.makeText(applicationContext, "ID $id", Toast.LENGTH_SHORT).show()
//provide the ID to the API
body.put("lab_id", id) //NB: 4 is static
val helper = ApiHelper(applicationContext)
helper.post(api, body, object : ApiHelper.CallBack{
override fun onSuccess(result: JSONArray?) {
val gson = GsonBuilder().create()
itemList = gson.fromJson(result.toString(),
Array<LabTests>::class.java).toList()
//Finally, our adapter has the data
labtestAdapter.setListItems(itemList)
//For the sake of recycling/Looping items, add the adapter to recycler
recyclerView.adapter = labtestAdapter
progress.visibility = View.GONE
swiperefresh.isRefreshing = false
}
override fun onSuccess(result: JSONObject?) {
Toast.makeText(applicationContext, result.toString(),
Toast.LENGTH_SHORT).show()
progress.visibility = View.GONE
}
override fun onFailure(result: String?) {
Toast.makeText(applicationContext, "Error:"+result.toString(),
Toast.LENGTH_SHORT).show()
Log.d("failureerrors", result.toString())
}
})
}//end fetch
Since the Lab Test API requires a lab_id to return specific lab tests, we have retrieved the lab_id from extras with code below
We use key1 to retrieve it.
//Retrieve the id from Intent Extras
val id = intent.extras?.getString("key1")
Before we run our code we need to store the lab_id of the Clicked Lab from MainActivity - LabAdapter This will help above code retrieve that lab_id.
Open your LabAdapter.kt
On onBindViewHolder function update the holder set OnclickListener , Update with below code
holder.itemView.setOnClickListener {
//carry/cpature the Lab_id of what you clicked.
//carry it with Bundles
val id = lab.lab_id //lab id
//pass this ID along with intent
val i = Intent(context, LabTestsActivity::class.java)
//We use key1 to save it
i.putExtra("key1", id)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(i)
}
Now run your App Click on one Lab and it will open its Lab Tests
In this section, when one lab test is clicked from above screen, You will learn how to display it on a Single Screen
In your Main Package, Create an Empty Views Activity named SingleLabTest
In LabTestAdapter, Update the onBindViewHolder() function, Update code for holder setonclick listener
In below code, after a Lab test is clicked from recycler view, we get all its details and add them to Bundle extras, then we navigate to SingleLabTest Activity
holder.itemView.setOnClickListener {
val i = Intent(context, SingleLabTest::class.java)
i.putExtra("lab_id", item.lab_id)
i.putExtra("test_id", item.test_id)
i.putExtra("test_discount", item.test_discount)
i.putExtra("test_cost", item.test_cost)
i.putExtra("test_name", item.test_name)
i.putExtra("test_description", item.test_description)
i.putExtra("availability", item.availability)
i.putExtra("more_info", item.more_info)
i.putExtra("reg_date", item.reg_date)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(i)
}//end
In res/layout open activity_single_lab_test.xml and write below code
This code has TextViews to be used in displaying data for specific lab test.
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="10dp"
android:layout_marginTop="10dp"
android:layout_margin="8dp"
app:cardCornerRadius="5dp">
<RelativeLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SingleLabTest">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/linear1"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/labimage"
android:layout_width="55dp"
android:layout_height="55dp"
android:src="@drawable/screen1"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Test Y"
android:fontFamily="@font/montserrat"
android:id="@+id/test_name"
android:textStyle="bold"
android:textSize="18dp"
android:layout_marginTop="10dp"
android:layout_marginStart="10dp"
android:layout_marginBottom="5dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="15dp"
android:layout_below="@+id/linear1"
android:id="@+id/linear2">
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Dummy Desc Dummy DescDummy scDummy DescDummy DescDummy DescDummy DescDummy DescDummy DescDummy Desc"
android:fontFamily="@font/montserrat"
android:id="@+id/test_description"
android:textStyle="normal"
android:textSize="13dp"
android:layout_marginBottom="5dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/linear3"
android:layout_marginTop="15dp"
android:orientation="horizontal"
android:layout_below="@id/linear2">
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="5% OFF"
android:id="@+id/test_discount"
android:fontFamily="@font/montserrat"
android:textStyle="bold"
android:textSize="40dp"
android:padding="10dp"
android:textColor="#FF9800"
android:layout_marginBottom="5dp"/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="KES 2,300"
android:id="@+id/test_cost"
android:fontFamily="@font/montserrat"
android:textStyle="bold"
android:textSize="25dp"
android:textColor="@color/black"
android:layout_marginBottom="5dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/linear4"
android:layout_marginTop="10dp"
android:orientation="vertical"
android:layout_below="@id/linear3">
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Dummy Avalability"
android:fontFamily="@font/montserrat"
android:id="@+id/availability"
android:textStyle="bold"
android:textSize="14dp"
android:layout_marginBottom="5dp"/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Dummy Info"
android:fontFamily="@font/montserrat"
android:id="@+id/more_info"
android:textSize="12dp"
android:layout_marginBottom="5dp"/>
<com.google.android.material.button.MaterialButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text= "Add to Cart"
android:fontFamily="@font/montserrat"
android:layout_margin="10dp"
android:id="@+id/addcart"/>
</LinearLayout>
</RelativeLayout>
</androidx.cardview.widget.CardView>
In SingleLabTest Activity, write below code. In this code we get data from Bundle extras and place in Text Views
package com.modcom.medilabsapp
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.google.android.material.button.MaterialButton
import com.google.android.material.textview.MaterialTextView
class SingleLabTest : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_single_lab_test)
//find the textViews, get data from extras and place it in the textview
val tvtest_name = findViewById<MaterialTextView>(R.id.test_name)
val test_name = intent.extras?.getString("test_name")
tvtest_name.text = test_name
//==============
val tvtest_cost = findViewById<MaterialTextView>(R.id.test_cost)
val test_cost = intent.extras?.getString("test_cost")
tvtest_cost.text = "$test_cost KES"
//==============
val tvtest_discount = findViewById<MaterialTextView>(R.id.test_discount)
val test_discount = intent.extras?.getString("test_discount")
tvtest_discount.text = "$test_discount % OFF"
//==============
val tvtest_description = findViewById<MaterialTextView>(R.id.test_description)
val test_description = intent.extras?.getString("test_description")
tvtest_description.text = test_description
//==============
val tvtest_availability = findViewById<MaterialTextView>(R.id.availability)
val availability = intent.extras?.getString("availability")
tvtest_availability.text = availability
//=================
val tvtest_info = findViewById<MaterialTextView>(R.id.more_info)
val more_info = intent.extras?.getString("more_info")
tvtest_info.text = more_info
val addcart = findViewById<MaterialButton>(R.id.addcart)
//To DO add to cart
}//end oncreate
//on Back Pressed go back to main activity
override fun onBackPressed() {
val i = Intent(applicationContext, MainActivity::class.java)
startActivity(i)
finishAffinity()
}
}//end class
Run your, Click on one Lab, then Click on Lab Test, and see the Lab Test Details.
Next, we add a product to the shopping cart, The cart will use SQlite Database, For more on Sqlite check https://developer.android.com/kotlin/multiplatform/sqlite and https://www.geeksforgeeks.org/android-sqlite-database-in-kotlin/
In your project helpers package, create a kotlin class file named SQLiteCartHelper.kt
In this file create an Sqlite Helper as below
package com.modcom.medilabsapp.helpers
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
class SQLiteCartHelper(context: Context): SQLiteOpenHelper(context, "cart.db", null, 1) {
override fun onCreate(sql: SQLiteDatabase?) {
TODO("Not yet implemented")
}
override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) {
TODO("Not yet implemented")
}
}
Then create a table under onCreate function and upgrade table under onUpgrade function.
package com.modcom.medilabsapp.helpers
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
class SQLiteCartHelper(context: Context): SQLiteOpenHelper(context, "cart.db", null, 1) {
override fun onCreate(sql: SQLiteDatabase?) {
sql?.execSQL("CREATE TABLE IF NOT EXISTS cart(test_id Integer primary key, test_name varchar, test_cost Integer, lab_id Integer, test_description text)")
}
override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) {
sql?.execSQL("DROP TABLE IF EXISTS cart")
}
}
Next function is to add data to the cart.db under cart table created in onCreate(), Below is the function., the function accepts 5 parameters. below is the add function.
fun insert(test_id: String, test_name: String, test_cost: String,
test_description: String, lab_id: String){
//Get permissions to write the database
val db = this.writableDatabase
//Select before insert see if ID already exists
val values = ContentValues()
values.put("test_id", test_id)
values.put("test_name", test_name)
values.put("lab_id", lab_id)
values.put("test_cost", test_cost)
values.put("test_description", test_description)
//save/insert to cart table
val result:Long = db.insert("cart", null, values)
if (result < 1){
println("Failed to Add")
Toast.makeText(context, "Item Already in Cart", Toast.LENGTH_SHORT).show()
}
else {
println("Item Added To Cart")
Toast.makeText(context, "Item Added to cart", Toast.LENGTH_SHORT).show()
}
}//end
Testing the Insert Go to your SingleLabTest Activity, when the add cart button is clicked, add below code to save a lab test to Cart - SQLite
val addcart = findViewById<MaterialButton>(R.id.addcart)
addcart.setOnClickListener {
val helper = SQLiteCartHelper(applicationContext)
val lab_id = intent.extras?.getString("lab_id")
try {
helper.insert(test_id!!, test_name!!, test_cost!!,
test_description!!, lab_id!!)
}
catch (e: Exception){
Toast.makeText(applicationContext, "An Error Occurred.",
Toast.LENGTH_SHORT).show()
}
//Done
}//end onclick
The next function, check the number of items saved in our table
fun getNumItems(): Int {
//Get permissions to read the database
val db = this.readableDatabase
val result: Cursor = db.rawQuery("select * from cart", null)
//return result count
return result.count
} //end
Next, we select all records from the cart, we retrieve all records and loop them in an ArrayList
fun getAllItems(): ArrayList<LabTests> {
//Get permissions to write the database
val db = this.writableDatabase
val items = ArrayList<LabTests>()
val result: Cursor = db.rawQuery("select * from cart", null)
//Lets Add all rows into the items arrayList
while (result.moveToNext())
{
// access the LabTests Model
val model = LabTests()
//Map rows to the model
model.test_id = result.getString(0) //Assume one row, test_id
model.test_name = result.getString(1) //Assume one row, test_name
model.test_cost = result.getString(2) //Assume one row, test_cost
model.lab_id = result.getString(3) //Assume one row, test_description
model.test_description = result.getString(4) //Assume one row, test_description
items.add(model)//add model to ArrayList
}//end while
return items
}//end function
After fetching all records, we can now create a function to clear all lab tests from the cart.
fun clearCart(){
//Get permissions to write the database
val db = this.writableDatabase
db.delete("cart", null, null)
println("Cart Cleared")
Toast.makeText(context, "Cart Cleared", Toast.LENGTH_SHORT).show()
//After data is cleared , Navigate MyCart Intent,TODO This later once we create MyCart Class
val i = Intent(context, MyCart::class.java)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(i)
} //end
The Next function deletes a record by ID. This deletes one record at a time.
//Remove One Item
fun clearCartById(test_id: String){
val db = this.writableDatabase
//Provide the test_id when deleting
db.delete("cart", "test_id=?", arrayOf(test_id))
println("Item Id $test_id Removed")
Toast.makeText(context, "Item Id $test_id Removed", Toast.LENGTH_SHORT).show()
//After data is cleared , Navigate MyCart Intent,TODO This later once we create MyCart Class
val i = Intent(context, MyCart::class.java)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(i)
}//end
Below function will be used to find the Total of all items in the Cart
//Find Total Cost
fun totalCost(): Double {
val db = this.readableDatabase
val result: Cursor = db.rawQuery("select test_cost from cart",
null)
//Set total to 0.0
var total: Double = 0.0
while (result.moveToNext()){
//the cursor result returns a Lists of test_cost.
//Loop through as you add them to total
total += result.getDouble(0)
}//end while
//Return the updated total
return total
}//End
Below is the Full Code for SQLiteCartHelper.
package com.modcom.medilabsapp.helpers
import android.annotation.SuppressLint
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.widget.Toast
import com.modcom.medilabsapp.MainActivity
import com.modcom.medilabsapp.MyCart
import com.modcom.medilabsapp.SingleLabTest
import com.modcom.medilabsapp.models.LabTests
import java.util.zip.DeflaterOutputStream
class SQLiteCartHelper(context: Context):
SQLiteOpenHelper(context, "cart.db", null, 1) {
//Make context visible to other functions
val context = context
//SQLite helps store data Locally on your phone - You can CRUD
override fun onCreate(sql: SQLiteDatabase?) {
sql?.execSQL("CREATE TABLE IF NOT EXISTS cart(test_id Integer primary key, test_name varchar, test_cost Integer, lab_id Integer, test_description text)")
}
override fun onUpgrade(sql: SQLiteDatabase?, p1: Int, p2: Int) {
sql?.execSQL("DROP TABLE IF EXISTS cart")
}
//INSERT/Save to cart
fun insert(test_id: String, test_name: String, test_cost: String,
test_description: String, lab_id: String){
val db = this.writableDatabase
//Select before insert see if ID already exsists
val values = ContentValues()
values.put("test_id", test_id)
values.put("test_name", test_name)
values.put("lab_id", lab_id)
values.put("test_cost", test_cost)
values.put("test_description", test_description)
//save
val result:Long = db.insert("cart", null, values)
if (result < 1){
println("Failed to Add")
Toast.makeText(context, "Item Already in Cart", Toast.LENGTH_SHORT).show()
}
else {
println("Item Added To Cart")
` // val i = Intent(context, MyCart::class.java)
// i.flags=Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
// context.startActivity(i)
Toast.makeText(context, "Item Added to cart", Toast.LENGTH_SHORT).show()
}
}//end
//Count How may items are there in the cart table
fun getNumItems(): Int {
val db = this.readableDatabase
val result: Cursor = db.rawQuery("select * from cart", null)
//return result count
return result.count
} //end
//Clear all records
fun clearCart(){
val db = this.writableDatabase
db.delete("cart", null, null)
println("Cart Cleared")
Toast.makeText(context, "Cart Cleared", Toast.LENGTH_SHORT).show()
// val i = Intent(context, MyCart::class.java)
// i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
// context.startActivity(i)
} //end
//Remove One Item
fun clearCartById(test_id: String){
val db = this.writableDatabase
//Provide the test_id when deleting
db.delete("cart", "test_id=?", arrayOf(test_id))
println("Item Id $test_id Removed")
Toast.makeText(context, "Item Id $test_id Removed", Toast.LENGTH_SHORT).show()
// val i = Intent(context, MyCart::class.java)
// i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
// context.startActivity(i)
}//end
//Find Total Cost
fun totalCost(): Double {
val db = this.readableDatabase
val result: Cursor = db.rawQuery("select test_cost from cart",
null)
//Set total to 0.0
var total: Double = 0.0
while (result.moveToNext()){
//the cursor result returns a Lists of test_cost.
//Loop through as you add them to total
total += result.getDouble(0)
}//end while
//Return the updated total
return total
}//End
//Get all items from the Cart
fun getAllItems(): ArrayList<LabTests> {
val db = this.writableDatabase
val items = ArrayList<LabTests>()
val result: Cursor = db.rawQuery("select * from cart", null)
//Lets Add all rows into the items arrayList
while (result.moveToNext())
{
val model = LabTests()
//Map rows to the model
model.test_id = result.getString(0) //Assume one row, test_id
model.test_name = result.getString(1) //Assume one row, test_name
model.test_cost = result.getString(2) //Assume one row, test_cost
model.lab_id = result.getString(3) //Assume one row, test_description
model.test_description = result.getString(4) //Assume one row, test_description
items.add(model)//add model to ArrayList
}//end while
return items
}//end function
}
In this section, we will create MyCart activity, this activity will display the shopping Cart in a recycler view. in res/layout, Create a file named single_labtests_cart.xml, In this File write below code, NB: This file is similar to single_labtests created earlier.
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="10dp"
android:layout_margin="8dp"
app:cardCornerRadius="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/labimage"
android:layout_width="55dp"
android:layout_height="55dp"
android:src="@drawable/screen1"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"/>
<LinearLayout
android:id="@+id/linear1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/labimage"
android:orientation="vertical"
android:layout_marginLeft="10dp">
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lipid Profile"
android:textStyle="bold"
android:textSize="16sp"
android:fontFamily="@font/montserrat"
android:textColor="@color/black"
android:id="@+id/test_name"/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is a nice Lab tests for keeping Fit, This is a nice test keeping Fit"
android:textStyle="normal"
android:textSize="12sp"
android:maxLines="2"
android:fontFamily="@font/montserrat"
android:textColor="#787474"
android:layout_marginTop="6dp"
android:id="@+id/test_description"/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/linear1"
android:layout_marginTop="10dp"
android:id="@+id/linear2"
android:layout_marginLeft="10dp"
android:layout_marginBottom="5dp"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/test_cost"
android:layout_width="315dp"
android:layout_height="wrap_content"
android:fontFamily="@font/montserrat"
android:text="KES 1,200"
android:textAlignment="textEnd"
android:textColor="#FF5722"
android:textSize="15sp"
android:textStyle="bold" />
</LinearLayout>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp">
<com.google.android.material.button.MaterialButton
android:layout_width="100dp"
android:layout_height="40dp"
android:text="Remove"
android:textSize="12sp"
android:layout_gravity="right"
android:id="@+id/remove"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
Then in adapters package, Create a Kotlin class file named LabTestsCartAdapter.kt and write below code. NB: This code is similar to LabTestsAdapter.
package com.modcom.medilabsapp.adapters
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton
import com.google.android.material.textview.MaterialTextView
import com.modcom.medilabsapp.R
import com.modcom.medilabsapp.models.LabTests
class LabTestsCartAdapter(var context: Context):
RecyclerView.Adapter<LabTestsCartAdapter.ViewHolder>() {
//Create a List and connect it with our model
var itemList : List<LabTests> = listOf() //Its empty
//Create a Class here, will hold our views in single_lab xml
inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LabTestsCartAdapter.ViewHolder {
//access/inflate the single lab xml
val view = LayoutInflater.from(parent.context).inflate(R.layout.single_labtests_cart,
parent, false)
return ViewHolder(view) //pass the single lab to ViewHolder
}
override fun onBindViewHolder(holder: LabTestsCartAdapter.ViewHolder, position: Int) {
//Find your 3 text views
val test_name = holder.itemView.findViewById<MaterialTextView>(R.id.test_name)
val test_description = holder.itemView.findViewById<MaterialTextView>(R.id.test_description)
val test_cost = holder.itemView.findViewById<MaterialTextView>(R.id.test_cost)
//Assume one Lab
val item = itemList[position]
test_name.text = item.test_name
test_description.text = item.test_description
test_cost.text = item.test_cost+" KES"
//Find remove button and set Listener
val remove = holder.itemView.findViewById<MaterialButton>(R.id.remove)
remove.setOnClickListener {
//TODO it later, to remove an item from cart
}
// Toast.makeText(context, "yyy"+item.test_cost, Toast.LENGTH_SHORT).show()
}
override fun getItemCount(): Int {
return itemList.size //Count how may Items in the List
}
//Earlier we mentioned item List is empty!
//We will get data from our APi, then bring it to below function
//The data you bring here must follow the Lab model
fun setListItems(data: List<LabTests>){
itemList = data //map/link the data to itemlist
notifyDataSetChanged()
//Tell this adapter class that now itemList is loaded with data
}
}
In the Main Package, create a new Empty Views Activity named MyCart and write below code
<?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:orientation="vertical"
android:layout_margin="10dp"
android:layout_height="match_parent"
tools:context=".MyCart">
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Total: 2300 KES"
android:textSize="30sp"
android:textAlignment="textEnd"
android:textColor="#FF9800"
android:layout_margin="10dp"
android:id="@+id/total"/>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:listitem="@layout/single_labtests_cart"
android:id="@+id/recycler"/>
<com.google.android.material.button.MaterialButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text = "Checkout"
android:padding="10dp"
android:textStyle="bold"
android:layout_margin="10dp"
android:id="@+id/checkout"/>
</LinearLayout>
Above XML will be used to display the shopping Cart and the Total.
In MyCart.kt, Write below code, step by step.
package com.modcom.medilabsapp
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton
import com.google.android.material.textview.MaterialTextView
import com.modcom.medilabsapp.adapters.LabTestsCartAdapter
import com.modcom.medilabsapp.helpers.SQLiteCartHelper
class MyCart : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my_cart)
//put total cost in a textview
val helper = SQLiteCartHelper(applicationContext)
val checkout = findViewById<MaterialButton>(R.id.checkout)
//Access SQLite helper, to check the totalCOst, Its zero remove the checkout button
if (helper.totalCost() == 0.0){
checkout.visibility = View.GONE
}//end
val total = findViewById<MaterialTextView>(R.id.total)
//Put the total in its text View
total.text = "Total: "+helper.totalCost()
//Find recycler
val recycler = findViewById<RecyclerView>(R.id.recycler)
//Set Layout
recycler.layoutManager = LinearLayoutManager(applicationContext)
recycler.setHasFixedSize(true)
//If total items from SQLite helper is zero, show cart is empty
if(helper.getNumItems() == 0){
Toast.makeText(applicationContext, "Your Cart is Empty",
Toast.LENGTH_SHORT).show()
}
else {
//Access adapter and provide it with data using getAllItems
val adapter = LabTestsCartAdapter(applicationContext)
adapter.setListItems(helper.getAllItems())//pass data
recycler.adapter = adapter //link adapter to recycler
}
}
//To Add More Functions Here
}//end
At this point we can test the MyCart Activity if its displaying.
Next we need to create a cart icon at the Options Menu.
Android Option Menus are the primary menus of android. They can be used for settings, searching, deleting items, etc. When and how this item should appear as an action item in the app bar is decided by the Show Action attribute
Options menu show in the Top/Tool Bar, see below image
Next we create an Options Menu, In res/layout create a file named design and write below code.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:clipToPadding="false"
android:focusable="true">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/cart"
android:id="@+id/image"
android:layout_gravity="center"
/>
<TextView
android:layout_width="18dp"
android:text="3"
android:id="@+id/badge"
android:textStyle="bold"
android:layout_gravity="top|end"
android:textColor="#2196F3"
android:background="@drawable/shape1"
android:layout_marginTop="-4dp"
android:layout_marginEnd="-4dp"
android:gravity="center"
android:textSize="11sp"
android:layout_height="18dp"/>
</FrameLayout>
Then is res/menu, Create a file named main.xml and write below code, this code code Put the design.xml on the Options Menu.
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/mycart"
app:showAsAction="always"
android:title="My Cart"
android:actionLayout="@layout/design"
/>
</menu>
This is how it Looks Like
We need to make the shopping Cart icon on the options menu, Clickable and Link to MyCart Activity. in MainActivity.kt add this function.
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.main, menu) // Access main.xml
val item: MenuItem = menu!!.findItem(R.id.mycart) //find my cart item
item.setActionView(R.layout.design) //load the design
val actionView: View? = item.actionView
//Access the views in design XML
val number = actionView?.findViewById<TextView>(R.id.badge)
val image = actionView?.findViewById<ImageView>(R.id.image)
//If image is clicked, Link to MyCart
image?.setOnClickListener {
startActivity(Intent(applicationContext, MyCart::class.java))
}
//load the number of items in Cart icon
val helper = SQLiteCartHelper(applicationContext)
number?.text = ""+helper.getNumItems()
return super.onCreateOptionsMenu(menu) //show options menu
}
Run Your Application and Observe the shopping Cart icon on Options menu. Click on it and it Takes you to MyCart
Next Steps is to Make sure that when a user is in MyCart Activity and Clicks on the Back button , they are directed back to MainActivity. Add below function in MyCart Activity.
override fun onBackPressed() {
val i = Intent(applicationContext, MainActivity::class.java)
startActivity(i)
finishAffinity()
}
finishAffinity() - This Clears the running/Open Activities(including MyCart) and Goes back to MainActivity.
Now your Cart is Ready. To finalize the Cart, We need to add Clear All Items and remove One Item from the cart.
We will do these functionalities in our MyCart Activity - Options Menu
In res/menu, Create a File named cart.xml, and write below code.
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/clearcart"
app:showAsAction="always"
android:title="Clear Cart"
android:icon="@android:drawable/ic_delete"/>
<item
android:id="@+id/backtoLabs"
app:showAsAction="never"
android:title="Back"
/>
<item
android:id="@+id/login"
app:showAsAction="never"
android:title="Login"
/>
</menu>
Above code creates an Options Menu Like below
In MyCart.kt add below function that Loads the Options Menu in the Tool Bar.
//Activate Options menu
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
//Below code Loads the cart XML
menuInflater.inflate(R.menu.cart, menu)
return super.onCreateOptionsMenu(menu) //Creates an Options Menu
}
Then add below functions to make the Options Menu Clickable.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
//Below code makes id clear cart clickablea and clears the cart using helper.clearCart()
if (item.itemId == R.id.clearcart){
val helper = SQLiteCartHelper(applicationContext)
helper.clearCart()
}
//backtoLabs takes us back to MainActivity
if (item.itemId == R.id.backtoLabs){
startActivity(Intent(applicationContext, MainActivity::class.java))
}
return super.onOptionsItemSelected(item)
}
Go to SQLiteHelper and reload the MyCart Activity in clearCart() fun.
//Clear all records
fun clearCart(){
val db = this.writableDatabase
db.delete("cart", null, null)
println("Cart Cleared")
Toast.makeText(context, "Cart Cleared", Toast.LENGTH_SHORT).show()
//Reload the MyCart Activity
val i = Intent(context, MyCart::class.java)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(i)
} //end
Run Your Project. Clear the Cart Using the the Options menu with icon (X).
Finally, We do the remove individual items from the Cart. In adapters Package, Open LabTestsCartAdapter.kt, in onBindViewHolder Update the remove Button Click Listener as below
val remove = holder.itemView.findViewById<MaterialButton>(R.id.remove)
remove.setOnClickListener {
//Get the clicked item ID
val test_id = item.test_id
//Access helper and provide the id to clearCartById() function
val helper = SQLiteCartHelper(context)
helper.clearCartById(test_id)
//The Item is Removed.
//Go to Helper and reload the MyCart Activity in clearCartById fun
}
Go to SQLiteHelper and reload the MyCart Activity in clearCartById() fun.
//Remove One Item
fun clearCartById(test_id: String){
val db = this.writableDatabase
//Provide the test_id when deleting
db.delete("cart", "test_id=?", arrayOf(test_id))
println("Item Id $test_id Removed")
Toast.makeText(context, "Item Id $test_id Removed", Toast.LENGTH_SHORT).show()
//Reload the MyCart Activity
val i = Intent(context, MyCart::class.java)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(i)
}//end
Now you can run the App and Observe the Complete shopping Cart!
In the Previous part, we have seen how the shopping Cart works, In this Part, we will do a Registration Page to Sign Up user of our application
The sign up is necessary in this app because users need to create an account before they Checkout.
In Main Package, Create an activity named SignUpActivity.
In res/layout/activity_sign_up.xml write below code
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
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">
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_margin="10dp"
android:layout_height="match_parent"
tools:context=".SignUpActivity">
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Create an Account"
android:textStyle="bold"
android:textSize="30sp"
android:fontFamily="@font/montserrat"
android:textColor="@color/black"
/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="Your Surname">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPersonName"
android:id="@+id/surname"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="Your Others">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPersonName"
android:id="@+id/others"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="Your Email">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:id="@+id/email"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="Your Phone">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="phone"
android:id="@+id/phone"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="Your Password">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:id="@+id/password"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="Confirm Password">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:id="@+id/confirm"/>
</com.google.android.material.textfield.TextInputLayout>
<RadioGroup
android:id="@+id/radioGroupGender"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/radioMale"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Male"
/>
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/radioFemale"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Female"
/>
</RadioGroup>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="DOB"
android:id="@+id/buttonDatePicker"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Date here"
android:enabled="false"
android:id="@+id/editTextDate"/>
</LinearLayout>
<!--spinner-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<Spinner
android:id="@+id/spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/selectedItemText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Selected Item: " />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Create Account"
android:id="@+id/create"/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Already have an Account, Login"
android:padding="20dp"
android:textStyle="bold"
android:textColor="#3F51B5"
android:id="@+id/linktologin"/>
</LinearLayout>
</ScrollView>
In SignUpActivity.kt, write below code.
package com.modcom.medilabsapp
import android.app.DatePickerDialog
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.*
import com.google.android.material.button.MaterialButton
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textview.MaterialTextView
import com.google.gson.GsonBuilder
import com.modcom.medilabsapp.constants.Constants
import com.modcom.medilabsapp.helpers.ApiHelper
import com.modcom.medilabsapp.models.Locations
import org.json.JSONArray
import org.json.JSONObject
import java.text.SimpleDateFormat
import java.util.*
class SignUpActivity : AppCompatActivity() {
private lateinit var buttonDatePicker: Button
private lateinit var editTextDate: EditText
private lateinit var spinner: Spinner
private lateinit var selectedItemText: TextView
private lateinit var locations: List<Locations>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sign_up)
//link to Login while in Register.
val linktologin = findViewById<MaterialTextView>(R.id.linktologin)
linktologin.setOnClickListener {
startActivity(Intent(applicationContext, SignInActivity::class.java))
}
buttonDatePicker = findViewById(R.id.buttonDatePicker)
editTextDate = findViewById(R.id.editTextDate)
buttonDatePicker.setOnClickListener {
//First create below function
showDatePickerDialog()
}//end onclick
//Spinner
spinner = findViewById(R.id.spinner)
selectedItemText = findViewById(R.id.selectedItemText)
// Sample data for the spinner
//Fetch Locations and Bring them here
val helper = ApiHelper(applicationContext)
val body = JSONObject()
//Access the /locations End point
val api = Constants.BASE_URL+"/locations"
helper.post(api, body,object: ApiHelper.CallBack{
override fun onSuccess(result: JSONArray?) {
//JSON Array Is Returned - Locations
// Convert JSONArray to ArrayList<Locations>
val gson = GsonBuilder().create()
locations = gson.fromJson(result.toString(),
Array<Locations>::class.java).toList()
val locationNames = locations.map { it.location }
val adapter = ArrayAdapter(applicationContext,
android.R.layout.simple_spinner_item, locationNames)
// Specify the layout to use when the list of choices appears
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
// Set the adapter to the spinner
spinner.adapter = adapter
}
override fun onSuccess(result: JSONObject?) {
//JSON Object for No Locations
}
override fun onFailure(result: String?) {
}
})//end
//val data: List<String> = listOf("1", "2", "3", "4", "5")// pending
// Create an ArrayAdapter using the sample data
//Check the selected location
var location_id = ""
spinner.onItemSelectedListener = object :AdapterView.OnItemSelectedListener{
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, position: Int, p3: Long) {
val selectedLocation = locations[position]
location_id = selectedLocation.location_id
}
override fun onNothingSelected(p0: AdapterView<*>?) {
Toast.makeText(applicationContext,
"Please Select a location", Toast.LENGTH_SHORT).show()
}
}//end
val create = findViewById<MaterialButton>(R.id.create)
create.setOnClickListener { //where do we close it?
//Push/Post data to APi.
val surname = findViewById<TextInputEditText>(R.id.surname)
val others = findViewById<TextInputEditText>(R.id.others)
val email = findViewById<TextInputEditText>(R.id.email)
val phone = findViewById<TextInputEditText>(R.id.phone)
val password = findViewById<TextInputEditText>(R.id.password)
val confirm = findViewById<TextInputEditText>(R.id.confirm)
val female = findViewById<RadioButton>(R.id.radioFemale)
val male = findViewById<RadioButton>(R.id.radioMale)
//handle radio buttons
var gender = "N/A"
if (female.isChecked) {
gender = "Female"
}
if (male.isChecked) {
gender = "Male"
}
//check if passwords are matching
if (password.text.toString() != confirm.text.toString()) {
Toast.makeText(
applicationContext, "Password Not Matching",
Toast.LENGTH_SHORT
).show()
} else {
//Post data to /member_signup API
val api = Constants.BASE_URL+"/member_signup"
val helper = ApiHelper(applicationContext)
val body = JSONObject()
body.put("surname", surname.text.toString())
body.put("others", others.text.toString())
body.put("email", email.text.toString())
body.put("phone", phone.text.toString())
body.put("dob", editTextDate.text.toString())
body.put("password", password.text.toString())
body.put("gender", gender)
body.put("location_id", location_id)
body.put("status", "TRUE")
helper.post(api, body, object : ApiHelper.CallBack {
override fun onSuccess(result: JSONArray?) {
}
override fun onSuccess(result: JSONObject?) {
//Posted successfully
Toast.makeText(applicationContext, result.toString(),
Toast.LENGTH_SHORT).show()
}
override fun onFailure(result: String?) {
//Failed to post
Toast.makeText(applicationContext, result.toString(),
Toast.LENGTH_SHORT).show()
}
});
}//end
}//here inside oncreate, closes on click
}//end oncreate
//other functions- Date Picker function
private fun showDatePickerDialog() {
val calendar = Calendar.getInstance()
// Create a date picker dialog and set the current date as the default selection
val datePickerDialog = DatePickerDialog(
this,
{ _: DatePicker, year: Int, month: Int, day: Int ->
val selectedDate = formatDate(year, month, day)
editTextDate.setText(selectedDate)
},
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)
)
// Show the date picker dialog, user cannot pick tomorrow
datePickerDialog.datePicker.maxDate = System.currentTimeMillis() - 568080000000;
datePickerDialog.show()
}
//Date conversion
private fun formatDate(year: Int, month: Int, day: Int): String {
val calendar = Calendar.getInstance()
calendar.set(year, month, day)
val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
return dateFormat.format(calendar.time)
}
}//end class
Test Your Sign Up Activity.
Before doing the SIgnInActivity, we will need to use shared prefferences to store our user data.
In helpers package, Create a Kotlin Class File named PrefsHelper.kt and write below code
package com.modcom.medilabsapp.helpers
import android.content.Context
import android.content.SharedPreferences
import android.content.SharedPreferences.Editor
//Shared preferences are used to values in a Key - value Approach
class PrefsHelper {
companion object{
//Save to Preferences
fun savePrefs(context: Context, key: String, value:String){
val pref: SharedPreferences = context.getSharedPreferences("store",
Context.MODE_PRIVATE)
val editor = pref.edit()
editor.putString(key, value)
editor.apply()
}//end save
//Get from Preferences
fun getPrefs(context: Context, key: String) : String{
val pref: SharedPreferences = context.getSharedPreferences("store",
Context.MODE_PRIVATE)
val value = pref.getString(key, "")//key is empty or not exist return empty
return value.toString()
}//end get
//Remove an Item from Preferences
fun clearPrefsByKey(context: Context, key: String){
val pref: SharedPreferences = context.getSharedPreferences("store",
Context.MODE_PRIVATE)
val editor = pref.edit()
editor.remove(key)
editor.apply()
}//end
//Clear All from Preferences
fun clearPrefs(context: Context){
val pref: SharedPreferences = context.getSharedPreferences("store",
Context.MODE_PRIVATE)
val editor = pref.edit()
editor.clear()
editor.apply()
}
}//end companion
}//end class
Above Helper will be used to Save, Get and Delete data from Shared Preferences
Next we do the sign in Activity. In Main Package, Create a New Empty Views Activity named SignInActivity.kt
in res/layout/activity_sign_in.xml write below code.
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
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">
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_margin="10dp"
android:layout_height="match_parent"
tools:context=".SignInActivity">
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Login Account"
android:textStyle="bold"
android:textSize="30sp"
android:fontFamily="@font/montserrat"
android:textColor="@color/black"
/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="Your Surname">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPersonName"
android:id="@+id/surname"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="Your Password">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:id="@+id/password"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Login Account"
android:id="@+id/login"/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Don't have an Account, Create"
android:padding="20dp"
android:textStyle="bold"
android:textColor="#3F51B5"
android:id="@+id/linktoregister"/>
</LinearLayout>
</ScrollView>
In SignInActivity.kt write below login code.
package com.modcom.medilabsapp
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import com.google.android.material.button.MaterialButton
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textview.MaterialTextView
import com.modcom.medilabsapp.constants.Constants
import com.modcom.medilabsapp.helpers.ApiHelper
import com.modcom.medilabsapp.helpers.PrefsHelper
import org.json.JSONArray
import org.json.JSONObject
class SignInActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sign_in)
//link to register while in Login.
val linktoregister = findViewById<MaterialTextView>(R.id.linktoregister)
linktoregister.setOnClickListener {
startActivity(Intent(applicationContext, SignUpActivity::class.java))
}
//Find Views
val surname = findViewById<TextInputEditText>(R.id.surname)
val password = findViewById<TextInputEditText>(R.id.password)
val login = findViewById<MaterialButton>(R.id.login)
login.setOnClickListener {
//Specify the /member_signin" Endpoint
val api = Constants.BASE_URL+"/member_signin"
val helper = ApiHelper(applicationContext)
//Create a JSON Object of email and Password
val body = JSONObject()
//Use Email Edit Text
body.put("email", surname.text.toString())
body.put("password", password.text.toString())
helper.post(api, body, object : ApiHelper.CallBack {
override fun onSuccess(result: JSONArray?) {
}
override fun onSuccess(result: JSONObject?) {
//Consume the JSON - access keys
//Check if access_token exist in response
if (result!!.has("access_token")){
//Access token Found, Login Success
//access the access token and member from the JSOn returned
val access_token = result.getString("access_token")
val member = result.getString("member")// {} Object user details
//Toast a success message
Toast.makeText(applicationContext, "Success",
Toast.LENGTH_SHORT).show()
//Save access Token to Shared Prefs
PrefsHelper.savePrefs(applicationContext,
"access_token", access_token)
//convert member to an Object
val memberObject = JSONObject(member)
val member_id = memberObject.getString("member_id")
val email = memberObject.getString("email")
val surname = memberObject.getString("surname")
//save member, member_id, email, surname to Shared Prefs
PrefsHelper.savePrefs(applicationContext,
"userObject", member)
PrefsHelper.savePrefs(applicationContext,
"member_id", member_id)
PrefsHelper.savePrefs(applicationContext,
"email", email)
PrefsHelper.savePrefs(applicationContext,
"surname", surname)
//redirect to MainActivity upon successful Login
startActivity(Intent(applicationContext, MainActivity::class.java))
finishAffinity()
}
else {
//No access token Found , Login Failed
Toast.makeText(applicationContext, result.toString(),
Toast.LENGTH_SHORT).show()
}
}
override fun onFailure(result: String?) {
//Fails to Connect
Toast.makeText(applicationContext, result.toString(),
Toast.LENGTH_SHORT).show()
}
});
}//end listener
}//end oncreate
}//end class
To test the SignInActivity, Open MyCart Activity and handle The checkout button Listener Like Below
checkout.setOnClickListener {
//Using Prefs check if token exists
val token = PrefsHelper.getPrefs(applicationContext, "access_token")
if (token.isEmpty()){
//Token does not exist, meaning Not Logged In
Toast.makeText(applicationContext, "Not Logged In",
Toast.LENGTH_SHORT).show()
startActivity(Intent(applicationContext, SignInActivity::class.java))
finish()
}
else {
//Token Exists, meaning Logged In we Go to Next step
startActivity(Intent(applicationContext, CheckoutStep1::class.java))
Toast.makeText(applicationContext, "Logged In", Toast.LENGTH_SHORT).show()
}
}//end
Test Your SignInActivity. Add Items to shopping cart and press the Checkout Button
Finally, we need to update the MainActivity to detect if user is logged or Not and Update the UI accordingly. in activity_main.xml update by adding 3 more buttons namely sign in, signout, profile and a user TextView as shown below
<?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=".MainActivity">
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Guest"
android:padding="5dp"
android:id="@+id/user"
android:textStyle="bold"/>
<com.google.android.material.button.MaterialButton
android:layout_width="wrap_content"
android:layout_height="40dp"
android:textSize="11sp"
android:text="Sign In"
android:layout_marginStart="5dp"
android:id="@+id/signin"/>
<com.google.android.material.button.MaterialButton
android:layout_width="wrap_content"
android:layout_height="40dp"
android:textSize="11sp"
android:text="Sign Out"
android:layout_marginStart="5dp"
android:id="@+id/signout"/>
<com.google.android.material.button.MaterialButton
android:layout_width="wrap_content"
android:layout_height="40dp"
android:textSize="11sp"
android:text="Profile"
android:layout_marginStart="5dp"
android:id="@+id/profile"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Search Lab name.."
android:drawableStart="@android:drawable/ic_menu_search"
android:layout_margin="5dp"
android:id="@+id/etsearch"/>
<ProgressBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="3dp"
android:id="@+id/progress"/>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/swipeRefreshLayout">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/single_lab"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>
Then in MainActivity.kt add the following function
fun update(){
//Find Views By ID
val user = findViewById<MaterialTextView>(R.id.user)
val signin = findViewById<MaterialButton>(R.id.signin)
val signout = findViewById<MaterialButton>(R.id.signout)
val profile = findViewById<MaterialButton>(R.id.profile)
//Set below 3 Views to GONE/Disappear
signin.visibility = View.GONE
signout.visibility = View.GONE
profile.visibility = View.GONE
//Access user access token from Prefs
val token = PrefsHelper.getPrefs(applicationContext, "access_token")
if (token.isEmpty()){
//If user Token does not exist, Update user TextView with Not Logged In
user.text = "Not Logged In"
//Make sign in button visible
signin.visibility = View.VISIBLE
signin.setOnClickListener {
//Link to Sign in Activity
startActivity(Intent(applicationContext, SignInActivity::class.java))
}
}
else{
//If user Token exist,
//Make Profile Button visible
profile.visibility = View.VISIBLE
profile.setOnClickListener {
//Link to Member Profile TODO Later
startActivity(Intent(applicationContext, MemberProfile::class.java))
}//end
//Access username from Prefs
val surname = PrefsHelper.getPrefs(applicationContext, "surname")
//Update user textView with Logged in User
user.text = "Welcome $surname"
//Make signout button visble
signout.visibility = View.VISIBLE
//Link to PrefHelper and Clear Prefs
signout.setOnClickListener{
PrefsHelper.clearPrefs(applicationContext)
startActivity(intent)
finishAffinity()
}
}
}//end
In the same file MainActivity.kt, under onCreate() Function, Call the Update Function like below.
.....
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
update()
.....
Run your application, Signin to the App and Observe the MainActivity.
Screenshot before Login
Screenshot after Login.
In this Part, we will create a MemberProfile section, This will include Viewing Logged in Member Details, Add Dependants and View Dependants.
In your main package, create a New Botton Navigation Activity, below is activity_member_profile.xml file
<?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"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?attr/actionBarSize">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/bottom_nav_menu" />
<fragment
android:id="@+id/nav_host_fragment_activity_member_profile"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
Below is the corresponding MemberProfile.kt code. Below code have 2 Bottom navigations represented by their IDs. R.id.navigation_home, R.id.navigation_dependants
package com.modcom.medilabsapp
import android.os.Bundle
import com.google.android.material.bottomnavigation.BottomNavigationView
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import com.modcom.medilabsapp.databinding.ActivityMemberProfileBinding
class MemberProfile : AppCompatActivity() {
private lateinit var binding: ActivityMemberProfileBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMemberProfileBinding.inflate(layoutInflater)
setContentView(binding.root)
val navView: BottomNavigationView = binding.navView
val navController = findNavController(R.id.nav_host_fragment_activity_member_profile)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.navigation_home, R.id.navigation_dependants
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
}
}
in res/values/strings.xml you can update the Bottom Navigation titles linke below
<resources>
<string name="app_name">MediLabsApp</string>
<string name="title_activity_member_profile">MemberProfile</string>
<!-- Botton Navigation Titles-->
<string name="title_home">Home</string>
<string name="title_dependants">Dependants</string>
<string name="title_notifications">Notifications</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
</resources>
Under ui package, Open HomeFragment.kt, here we will create the member profile, already we have the member details in the SharedPrefferences, we can retrieve them with below code and bind them in TextViews.
val userObject = PrefsHelper.getPrefs(requireContext(), "userObject")
Below is the code for home fragment 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:orientation="vertical"
android:layout_height="match_parent"
tools:context=".ui.home.HomeFragment">
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="My Profile"
android:textSize="30sp"
android:textAlignment="center"
android:textStyle="bold"/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Jane"
android:textSize="14sp"
android:textAlignment="center"
android:id="@+id/surname"
android:textStyle="normal"/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Jane"
android:textSize="14sp"
android:textAlignment="center"
android:id="@+id/others"
android:textStyle="normal"/>
<!-- TODO others-->
</LinearLayout>
Below is the code for HomeFragment.kt.
package com.modcom.medilabsapp.ui.home
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.google.android.material.textview.MaterialTextView
import com.modcom.medilabsapp.databinding.FragmentHomeBinding
import com.modcom.medilabsapp.helpers.PrefsHelper
import org.json.JSONObject
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
val root: View = binding.root
//Code here
val userObject = PrefsHelper.getPrefs(requireContext(), "userObject")
val user = JSONObject(userObject) //convert to JSON Object
//Text View 1
val surname = _binding!!.surname //find view
surname.text = "Surname: "+ user.getString("surname")
//Text View 2
val others = _binding!!.others //find view
others.text = "Others: "+user.getString("others")
//gender, dob, reg_date, email
return root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
NB: Above code is a Fragment not an Activity.
Read more https://developer.android.com/guide/fragments
Below code accesses the XML
_binding = FragmentHomeBinding.inflate(inflater, container, false)
val root: View = binding.root
We also use requireContext(), as this is a Fragment. in Activities, we used getApplicationContext().
To Link to MemberProfile from MainActivity, go to MainActivity under update() function, Put a listener to Profile Button and Intent to MemberProfile
Check code commented with // *************************
fun update(){
//Find Views By ID
val user = findViewById<MaterialTextView>(R.id.user)
val signin = findViewById<MaterialButton>(R.id.signin)
val signout = findViewById<MaterialButton>(R.id.signout)
val profile = findViewById<MaterialButton>(R.id.profile)
//Set below 3 Views to GONE/Disappear
signin.visibility = View.GONE
signout.visibility = View.GONE
profile.visibility = View.GONE
//Access user access token from Prefs
val token = PrefsHelper.getPrefs(applicationContext, "access_token")
if (token.isEmpty()){
//If user Token does not exist, Update user TextView with Not Logged In
user.text = "Not Logged In"
//Make sign in button visible
signin.visibility = View.VISIBLE
signin.setOnClickListener {
//Link to Sign in Activity
startActivity(Intent(applicationContext, SignInActivity::class.java))
}
}
else{
//If user Token exist,
//Make Profile Button visible
profile.visibility = View.VISIBLE
// *************************
profile.setOnClickListener {
//Link to Member Profile TODO Later
startActivity(Intent(applicationContext, MemberProfile::class.java))
}//end
//Access username from Prefs
val surname = PrefsHelper.getPrefs(applicationContext, "surname")
//Update user textView with Logged in User
user.text = "Welcome $surname"
//Make signout button visble
signout.visibility = View.VISIBLE
//Link to PrefHelper and Clear Prefs
signout.setOnClickListener{
PrefsHelper.clearPrefs(applicationContext)
startActivity(intent)
finishAffinity()
}
}
}//end
Above we did Profile in home fragment. Next we do Dependants(Add and View Dependants).
In res/layout create a file named fragment_dependant.xml and write below form inputs, they include TextViews, EditTexts, RadioButton, Buttons, ScrollView etc.
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
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">
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_margin="10dp"
android:layout_height="match_parent"
tools:context=".SignUpActivity">
<com.google.android.material.button.MaterialButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="My Dependants"
android:padding="10dp"
android:id="@+id/mydependants"/>
<com.google.android.material.button.MaterialButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="My Bookings"
android:padding="10dp"
android:background="@drawable/bg1"
android:id="@+id/mybookings"/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add Dependant"
android:textStyle="bold"
android:textSize="30sp"
android:fontFamily="@font/montserrat"
android:textColor="@color/black"
/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="Your Surname">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPersonName"
android:id="@+id/surname"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="Your Others">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPersonName"
android:id="@+id/others"/>
</com.google.android.material.textfield.TextInputLayout>
<RadioGroup
android:id="@+id/radioGroupGender"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/radioMale"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Male"
/>
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/radioFemale"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Female"
/>
</RadioGroup>
<!--
ChatGPT - click button and popup a calender put the
date in Edittext android kotlin xml
-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="DOB"
android:id="@+id/buttonDatePicker"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Date here"
android:enabled="false"
android:id="@+id/editTextDate"/>
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add Dependant"
android:background="@drawable/bg1"
android:id="@+id/adddependant"/>
</LinearLayout>
</ScrollView>
Under ui package, create a subpackage named dependants, Inside this subpackage, create a Kotlin Class File and write below code to get data from XML inputs and Post to our add_dependants API.
package com.modcom.medilabsapp.ui.dependants
import android.app.DatePickerDialog
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.core.app.ActivityCompat.finishAffinity
import com.google.android.material.button.MaterialButton
import com.google.android.material.textfield.TextInputEditText
import com.modcom.medilabsapp.*
import com.modcom.medilabsapp.constants.Constants
import com.modcom.medilabsapp.helpers.ApiHelper
import com.modcom.medilabsapp.helpers.PrefsHelper
import org.json.JSONArray
import org.json.JSONObject
import java.text.SimpleDateFormat
import java.util.*
class DependantFragment : Fragment() {
private lateinit var buttonDatePicker: Button
private lateinit var editTextDate: EditText
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val root = inflater.inflate(R.layout.fragment_dependant, container, false)
//Code here
buttonDatePicker = root.findViewById(R.id.buttonDatePicker)
editTextDate = root.findViewById(R.id.editTextDate)
//Button Listener and call showDatePickerDialog() - see this function at the end of this File.
buttonDatePicker.setOnClickListener {
showDatePickerDialog()
}//end onclick
val mydependants = root.findViewById<MaterialButton>(R.id.mydependants)
mydependants.setOnClickListener {
// below Intent, to be activated once ViewDependants is created
// startActivity(Intent(requireContext(), ViewDependants::class.java))
}
val mybookings = root.findViewById<MaterialButton>(R.id.mybookings)
mybookings.setOnClickListener {
startActivity(Intent(requireContext(), MyBookings::class.java))
}
//post to API
val adddependant = root.findViewById<MaterialButton>(R.id.adddependant)
adddependant.setOnClickListener {
//Push/Post data to APi.
val surname = root.findViewById<TextInputEditText>(R.id.surname)
val others = root.findViewById<TextInputEditText>(R.id.others)
val female = root.findViewById<RadioButton>(R.id.radioFemale)
val male = root.findViewById<RadioButton>(R.id.radioMale)
//handle Radio Buttons
var gender = "N/A"
if (female.isSelected) {
gender = "Female"
}
if (male.isSelected) {
gender = "Male"
}
//Get member ID from Prefs
val member_id = PrefsHelper.getPrefs(requireContext(), "member_id")
//Access base URL
val api = Constants.BASE_URL+"/add_dependant"
val helper = ApiHelper(requireContext())
val body = JSONObject()
body.put("surname", surname.text.toString())
body.put("others", others.text.toString())
body.put("dob", editTextDate.text.toString())
body.put("gender", gender)
body.put("member_id", member_id)
helper.post(api, body, object : ApiHelper.CallBack {
override fun onSuccess(result: JSONArray?) {
}
override fun onSuccess(result: JSONObject?) {
//Dependants Saved
Toast.makeText(requireContext(), result.toString(),
Toast.LENGTH_SHORT).show()
}
override fun onFailure(result: String?) {
Toast.makeText(requireContext(),
result.toString(), Toast.LENGTH_SHORT).show()
val json = JSONObject(result.toString())
val msg = json.opt("msg")
//TODO
//If token is Expired redrect to SignIn and Clear Prefs
if (msg == "Token has Expired"){
PrefsHelper.clearPrefs(requireContext())
startActivity(Intent(requireContext(), SignInActivity::class.java))
finishAffinity(requireActivity())
}
}
});
}//end listener
return root
}//end oncreate view
//This is for launching the DatePicker.
private fun showDatePickerDialog() {
val calendar = Calendar.getInstance()
// Create a date picker dialog and set the current date as the default selection
val datePickerDialog = DatePickerDialog(
requireContext(),
{ _: DatePicker, year: Int, month: Int, day: Int ->
val selectedDate = formatDate(year, month, day)
editTextDate.setText(selectedDate)
},
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)
)
// Show the date picker dialog
datePickerDialog.show()
}
private fun formatDate(year: Int, month: Int, day: Int): String {
val calendar = Calendar.getInstance()
calendar.set(year, month, day)
val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
return dateFormat.format(calendar.time)
}
}//last brace, End Class
Above will help a member add a dependant.
This Step We Create a File to View dependants. In res/layout create a file named single_dependant.xml and write below code.
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="10dp"
android:layout_margin="8dp"
app:cardCornerRadius="5dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="120dp">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/labimage"
android:layout_width="55dp"
android:layout_height="55dp"
android:src="@drawable/screen1"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"/>
<LinearLayout
android:id="@+id/linear1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/labimage"
android:orientation="vertical"
android:layout_marginLeft="10dp">
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ann"
android:textStyle="bold"
android:textSize="16sp"
android:fontFamily="@font/montserrat"
android:textColor="@color/black"
android:id="@+id/dep_name"/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="James"
android:textStyle="normal"
android:textSize="12sp"
android:maxLines="2"
android:fontFamily="@font/montserrat"
android:textColor="#787474"
android:layout_marginTop="6dp"
android:id="@+id/dep_others"/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/linear1"
android:layout_marginTop="5dp"
android:layout_marginLeft="10dp"
android:layout_marginBottom="5dp"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/dep_dob"
android:layout_width="315dp"
android:layout_height="wrap_content"
android:fontFamily="@font/montserrat"
android:text="2022-02-08"
android:textAlignment="textEnd"
android:textColor="#FF5722"
android:textSize="15sp"
android:textStyle="bold" />
</LinearLayout>
</RelativeLayout>
</androidx.cardview.widget.CardView>
in adapters, Create an adapter named DependantAdapter.kt and write below adapter.
package com.modcom.medilabsapp.adapters
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.textview.MaterialTextView
import com.modcom.medilabsapp.CheckoutStep2GPS
import com.modcom.medilabsapp.LabTestsActivity
import com.modcom.medilabsapp.R
import com.modcom.medilabsapp.SingleLabTest
import com.modcom.medilabsapp.helpers.PrefsHelper
import com.modcom.medilabsapp.models.Dependant
import com.modcom.medilabsapp.models.Lab
import com.modcom.medilabsapp.models.LabTests
class DependantAdapter(var context: Context):
RecyclerView.Adapter<DependantAdapter.ViewHolder>() {
//Create a List and connect it with our model
var itemList : List<Dependant> = listOf() //Its empty
//Create a Class here, will hold our views in single_lab xml
inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DependantAdapter.ViewHolder {
//access/inflate the single lab xml
val view = LayoutInflater.from(parent.context).inflate(R.layout.single_dependant,
parent, false)
return ViewHolder(view) //pass the single lab to ViewHolder
}
override fun onBindViewHolder(holder: DependantAdapter.ViewHolder, position: Int) {
//Find your 3 text views
val dep_name = holder.itemView.findViewById<MaterialTextView>(R.id.dep_name)
val dep_others = holder.itemView.findViewById<MaterialTextView>(R.id.dep_others)
val dep_dob = holder.itemView.findViewById<MaterialTextView>(R.id.dep_dob)
//Assume one Lab
val item = itemList[position]
dep_name.text = item.surname
dep_others.text = item.others
dep_dob.text = item.dob
holder.itemView.setOnClickListener {
}//end Listner
}//end bind
override fun getItemCount(): Int {
return itemList.size //Count how may Items in the List
}
//This is for filtering data
fun filterList(filterList: List<Dependant>){
itemList = filterList
notifyDataSetChanged()
}
//Earlier we mentioned item List is empty!
//We will get data from our APi, then bring it to below function
//The data you bring here must follow the Lab model
fun setListItems(data: List<Dependant>){
itemList = data //map/link the data to itemlist
notifyDataSetChanged()
//Tell this adapter class that now itemList is loaded with data
}
}
Next, in main package, create a new Empty Views Activity named ViewDependants. in activity_view_dependants.xml write below code for the Recycler View.
<?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=".ViewDependants">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Search dependant.."
android:drawableStart="@android:drawable/ic_menu_search"
android:layout_margin="5dp"
android:id="@+id/etsearch"/>
<ProgressBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="3dp"
android:id="@+id/progress"/>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/swipeRefreshLayout">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/single_dependant"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>
Then in ViewDependants.kt write below code to load the data from the Adpater to our Recycler View
package com.modcom.medilabsapp
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.View
import android.widget.EditText
import android.widget.ProgressBar
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.google.gson.GsonBuilder
import com.modcom.medilabsapp.adapters.DependantAdapter
import com.modcom.medilabsapp.adapters.LabTestsAdapter
import com.modcom.medilabsapp.constants.Constants
import com.modcom.medilabsapp.helpers.ApiHelper
import com.modcom.medilabsapp.helpers.PrefsHelper
import com.modcom.medilabsapp.models.Dependant
import com.modcom.medilabsapp.models.LabTests
import org.json.JSONArray
import org.json.JSONObject
class ViewDependants : AppCompatActivity() {
lateinit var itemList: List<Dependant>
lateinit var depAdapter: DependantAdapter
lateinit var recyclerView: RecyclerView
lateinit var progress: ProgressBar
lateinit var swiperefresh: SwipeRefreshLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_view_dependants)
progress = findViewById(R.id.progress)
recyclerView = findViewById(R.id.recycler)
depAdapter = DependantAdapter(applicationContext)
recyclerView.layoutManager = LinearLayoutManager(applicationContext)
recyclerView.setHasFixedSize(true)
post_fetch()
swiperefresh = findViewById<SwipeRefreshLayout>(R.id.swipeRefreshLayout)
swiperefresh.setOnRefreshListener {
post_fetch()// fetch data again
}//end refresh
//Filter labs
val etsearch = findViewById<EditText>(R.id.etsearch)
etsearch.addTextChangedListener(object: TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(texttyped: CharSequence?, p1: Int, p2: Int, p3: Int) {
filter(texttyped.toString())
}
override fun afterTextChanged(p0: Editable?) {
}
})
}//end oncreate
fun post_fetch(){
val api = Constants.BASE_URL+"/view_dependants"
//Above APi needs a Body, So we have to build it
val body = JSONObject()
//Retrieve the id from Prefs
val member_id = PrefsHelper.getPrefs(applicationContext,"member_id")
//provide the ID to the API
body.put("member_id", member_id) //NB: 4 is static
val helper = ApiHelper(applicationContext)
helper.post(api, body, object : ApiHelper.CallBack{
override fun onSuccess(result: JSONArray?) {
val gson = GsonBuilder().create()
itemList = gson.fromJson(result.toString(),
Array<Dependant>::class.java).toList()
//Finally, our adapter has the data
depAdapter.setListItems(itemList)
//For the sake of recycling/Looping items, add the adapter to recycler
recyclerView.adapter = depAdapter
progress.visibility = View.GONE
swiperefresh.isRefreshing = false
}
override fun onSuccess(result: JSONObject?) {
Toast.makeText(applicationContext, result.toString(),
Toast.LENGTH_SHORT).show()
progress.visibility = View.GONE
}
override fun onFailure(result: String?) {
Toast.makeText(applicationContext, "Error:"+result.toString(),
Toast.LENGTH_SHORT).show()
//It returns a msg : Token Expired
val json = JSONObject(result.toString())
val msg = json.opt("msg")
//TODO
if (msg == "Token has Expired"){
PrefsHelper.clearPrefs(applicationContext)
startActivity(Intent(applicationContext, SignInActivity::class.java))
finishAffinity()
}
Log.d("failureerrors", result.toString())
}
})
}//end fetch
private fun filter(text: String) {
// creating a new array list to filter our data.
val filteredlist: ArrayList<Dependant> = ArrayList()
// running a for loop to compare elements.
for (item in itemList) {
// checking if the entered string matched with any item of our recycler view.
if (item.surname.lowercase().contains(text.lowercase())) {
// if the item is matched we are
// adding it to our filtered list.
filteredlist.add(item)
}
}
if (filteredlist.isEmpty()) {
// if no item is added in filtered list we are
// displaying a toast message as no data found.
//Toast.makeText(this, "No Data Found..", Toast.LENGTH_SHORT).show()
depAdapter.filterList(filteredlist)
} else {
// at last we are passing that filtered
// list to our adapter class.
depAdapter.filterList(filteredlist)
}
}
}//end class
To Link to ViewDependants.kt from AddDependants, Recall in AddDependants XML there was a button for View Dependants, In AddDependants.kt Activity, Update with this Code
val mydependants = root.findViewById<MaterialButton>(R.id.mydependants)
mydependants.setOnClickListener {
// Below Intent, Link to ViewDependants::class.java
startActivity(Intent(requireContext(), ViewDependants::class.java))
}
Now Run your App,
From the MainActivity, Click on Profile Button(You must be logged in).
Then Click on AddDependant Bottom Nav.
Test Add Dependants and View Dependants.
In this Part, we will proceed with Checkout, Now that we have Laboratories, LabTests, Add to Cart, Signin, SignUp, Profile, Add Dependants and ViewDependants
During checkout, User must have items in the Cart, They must be logged in. Confirm in your App that above mentioned features are working.
Open MyCart.kt and Add Logic to checkout Button(At this Point revisit the Shopping Cart and confirm it works correctly). Add this code in MyCart.kt
val checkout = findViewById<MaterialButton>(R.id.checkout)
if (helper.totalCost() == 0.0){
checkout.visibility = View.GONE
}//end
//This one
checkout.setOnClickListener {
//Using Prefs check if token exists
val token = PrefsHelper.getPrefs(applicationContext, "access_token")
if (token.isEmpty()){
//Token does not exist, meaning Not Signed In, Redirect to Sign up
Toast.makeText(applicationContext, "Not Logged In",
Toast.LENGTH_SHORT).show()
startActivity(Intent(applicationContext, SignInActivity::class.java))
finish()
}
else {
//Token Exists, meaning Signed In and we Go to Next step
//startActivity(Intent(applicationContext, CheckoutStep1::class.java))
Toast.makeText(applicationContext, "Logged In", Toast.LENGTH_SHORT).show()
}
}//end
In your MainPackage, create an Empty Views Activity named CheckoutStep1.
In below Code;
- There are Radio Buttons for user to Pick either the Booking is for Self or Other (dependant)
- There are Radio Buttons for user to Pick either the Booking is done at Home or Hosptal (Away)
- There is a DatePicker(We will use a Button and Edittext) for user to Pick Booking Date - yyyy/mm/dd
- There is a TimePicker(We will use a Button and Edittext) for user to Pick Booking Time - hh:mm
- There is a Button to Proceed to Next Step - GPS
In activity_checkout_step1.xml write below code .
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
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">
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_margin="10dp"
android:layout_height="match_parent"
tools:context=".CheckoutStep1">
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Step 1"
android:textStyle="bold"
android:textSize="30sp"
android:fontFamily="@font/montserrat"
android:textColor="@color/black"
/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Who is this test For?"/>
<RadioGroup
android:id="@+id/radioGroupGender"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/radioSelf"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Self"
/>
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/radioOther"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Other"
/>
</RadioGroup>
<!-- Radio buttons for Away or Home-->
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Where is Test Done?"/>
<RadioGroup
android:id="@+id/radioGroupDone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/radioAway"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Away"
/>
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/radioHome"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Home"
/>
</RadioGroup>
<!--
ChatGPT - click button and popup a calender put the
date in Edittext android kotlin xml
-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Appointment Date"
android:id="@+id/buttonDatePicker"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Date here"
android:enabled="false"
android:id="@+id/editTextDate"/>
</LinearLayout>
<!-- Time-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:orientation="horizontal">
<Button
android:id="@+id/btnTimePicker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Appointment Time" />
<EditText
android:id="@+id/editTextTime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Selected Time"
android:inputType="none"
android:enabled="false" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Proceed to Step 2"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:background="@drawable/bg1"
android:textColor="#000000"
android:drawableEnd="@android:drawable/ic_media_next"
android:id="@+id/proceedstep2"/>
</LinearLayout>
<!--github.com/modcomlearning/MediLabApp-->
</ScrollView>
In CheckoutStep1.kt, Write Below code, Start by Creating functions for TimePicker and DatePicker, DO buttons to trigger TimePicker and DatePicker. Then handle the Proceed button Listener, followed by Radio buttons etc.
package com.modcom.medilabsapp
import android.app.DatePickerDialog
import android.app.TimePickerDialog
import android.content.Context
import android.content.Intent
import android.location.Geocoder
import android.location.LocationManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.widget.*
import com.google.android.gms.maps.model.LatLng
import com.google.android.material.button.MaterialButton
import com.modcom.medilabsapp.helpers.PrefsHelper
import java.text.SimpleDateFormat
import java.util.*
class CheckoutStep1 : AppCompatActivity() {
private lateinit var buttonDatePicker: Button
private lateinit var editTextDate: EditText
private lateinit var btnTimePicker: Button
private lateinit var editTextTime: EditText
//This will help show the TimePicker
fun showTimePicker(){
val calender = Calendar.getInstance()
val timePickerDialog = TimePickerDialog(
this,
timeSetListener,
calender.get(Calendar.HOUR_OF_DAY),
calender.get(Calendar.MINUTE),
false)
timePickerDialog.show()
}//end
//https://justpaste.it/cmht0
//This code Listens for time selections and place the time in editTextTime
private val timeSetListener = TimePickerDialog.OnTimeSetListener{
_, hourOfDay, minute ->
val calendar = Calendar.getInstance() //***********
calendar.set(Calendar.HOUR_OF_DAY, hourOfDay)
calendar.set(Calendar.MINUTE, minute)
val sdf = SimpleDateFormat("hh:mm", Locale.getDefault())
val selectedTime = sdf.format(calendar.time)
editTextTime.setText(selectedTime)
}//end
private fun showDatePickerDialog() {
val calendar = Calendar.getInstance()
// Create a date picker dialog and set the current date as the default selection
val datePickerDialog = DatePickerDialog(
this,
{ _: DatePicker, year: Int, month: Int, day: Int ->
val selectedDate = formatDate(year, month, day)
editTextDate.setText(selectedDate)
},
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)
)
// Show the date picker dialog
datePickerDialog.datePicker.minDate = System.currentTimeMillis() - 1000;
datePickerDialog.show()
}
private fun formatDate(year: Int, month: Int, day: Int): String {
val calendar = Calendar.getInstance()
calendar.set(year, month, day)
val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
return dateFormat.format(calendar.time)
}
//justpaste.it/6l88f
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_checkout_step1)
//Click button to trigger - showTimePicker()
btnTimePicker = findViewById(R.id.btnTimePicker)
editTextTime = findViewById(R.id.editTextTime)
btnTimePicker.setOnClickListener {
showTimePicker()
}//end
//Click button to trigger - showDatePickerDialog()
buttonDatePicker = findViewById(R.id.buttonDatePicker)
editTextDate = findViewById(R.id.editTextDate)
buttonDatePicker.setOnClickListener {
showDatePickerDialog()
}//end onclick
//Find proceed button and set Listener
val proceed = findViewById<MaterialButton>(R.id.proceedstep2)
proceed.setOnClickListener {
val date = editTextDate.text.toString()
val time = editTextTime.text.toString()
//Radio Button Place - Home/Away
val home = findViewById<RadioButton>(R.id.radioHome)
val away = findViewById<RadioButton>(R.id.radioAway)
var where_taken = ""
if (home.isChecked){
where_taken = "Home"
}//end dif
if (away.isChecked){
where_taken = "Away"
}//end if
//Radio Button Self/Other
val self = findViewById<RadioButton>(R.id.radioSelf)
val other = findViewById<RadioButton>(R.id.radioOther)
var booked_for = ""
if (self.isChecked){
booked_for = "Self"
}//end
if (other.isChecked){
booked_for = "Other"
}//end
//check empties
if (date.isEmpty() || time.isEmpty() || where_taken.isEmpty()
|| booked_for.isEmpty()){
Toast.makeText(applicationContext, "Empty Fields",
Toast.LENGTH_SHORT).show()
}
else {
//Save values collected from Radio button to Preferences
PrefsHelper.savePrefs(applicationContext, "date", date)
PrefsHelper.savePrefs(applicationContext, "time", time)
PrefsHelper.savePrefs(applicationContext, "where_taken", where_taken)
PrefsHelper.savePrefs(applicationContext, "booked_for", booked_for)
//Check if user Checked Self/Other
if (booked_for=="Self"){
//For Self - Save an Empty dependant_id to Prefs, Since no dependant
PrefsHelper.savePrefs(applicationContext, "dependant_id", "")
//Below to be done once the CheckoutStep2GPS is done, we leave it for now.
//startActivity(Intent(applicationContext, CheckoutStep2GPS::class.java))
}
else {
//For Other - Direct the user to pick a dependant in View Dependants
startActivity(Intent(applicationContext, ViewDependants::class.java))
}//end else
}//end else
}//end listener
}//End onCreate
//justpaste.it/arfi6
}//end class
In this section, we will handle the CheckoutStep2GPS and ViewDependants. Note that the user is directed to these two depending on which radio they picked either Self or Other.
To start, assume the user Picked Self, so we need to create the CheckoutStep2GPS activity.
In your main package, Create an Empty Views Activity named CheckoutStep2GPS.
in activity_checkout_step2_gps.xml write below code. It contains some TextViews, EditTexts(to place GPS coordinates) and a Button
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
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">
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_margin="10dp"
android:layout_height="match_parent"
tools:context=".CheckoutStep2GPS">
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Step 2"
android:textStyle="bold"
android:layout_marginTop="25dp"
android:textSize="30sp"
android:fontFamily="@font/montserrat"
android:textColor="@color/black"
/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="Latitude here">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPersonName"
android:id="@+id/editLatitude"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="Longitude Here">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPersonName"
android:id="@+id/editLongitude"/>
</com.google.android.material.textfield.TextInputLayout>
<ProgressBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/progress"/>
<com.google.android.material.button.MaterialButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Get Location"
android:background="@drawable/bg2"
android:backgroundTint="#000000"
android:layout_gravity="center"
android:id="@+id/getlocation"/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""
android:padding="20dp"
android:textStyle="bold"
android:textColor="#3F51B5"
android:id="@+id/skip"/>
<com.google.android.material.button.MaterialButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Complete"
android:background="@drawable/bg1"
android:layout_gravity="center"
android:id="@+id/complete"/>
</LinearLayout>
</ScrollView>
In CheckoutStep2GPS.kt write below code to retrieve phones GPS and place in EditTexts
package com.modcom.medilabsapp
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Geocoder
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Looper
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.ProgressBar
import android.widget.Toast
import androidx.core.app.ActivityCompat
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
import com.google.android.gms.maps.model.LatLng
import com.google.android.material.button.MaterialButton
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textview.MaterialTextView
import com.modcom.medilabsapp.helpers.PrefsHelper
class CheckoutStep2GPS : AppCompatActivity() {
private lateinit var editLatitude: TextInputEditText
private lateinit var editLongitude: TextInputEditText
private lateinit var getlocation: MaterialButton
private lateinit var progress: ProgressBar
private lateinit var skip: MaterialTextView
private lateinit var fusedLocationClient: FusedLocationProviderClient
//Convert cordinates to Address
fun getAddress(latlng: LatLng) : String{
val geoCoder = Geocoder(this)
val list = geoCoder.getFromLocation(latlng.latitude, latlng.longitude,
1)
return list!![0].getAddressLine(0)
}//end
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_checkout_step2_gps)
editLatitude = findViewById(R.id.editLatitude)
editLongitude = findViewById(R.id.editLongitude)
progress = findViewById(R.id.progress)
getlocation = findViewById(R.id.getlocation)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
progress.visibility = View.GONE
getlocation.setOnClickListener {
//TODO
progress.visibility = View.VISIBLE
requestLocation()
}//end
//Handle Complete Button Here in Step 6
}//end onCreate
//Function to check if user accepted permission or not
//If user has not accepted permissions, Give them dialog to decide
fun requestLocation(){
if(ActivityCompat.checkSelfPermission(
this,
android.Manifest.permission.ACCESS_FINE_LOCATION) !=
PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(
this,
arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION),
123)
}//end if
else {
getLocation() //get Lat and Lon
}
}//end function
@SuppressLint("MissingPermission")
fun getLocation(){
fusedLocationClient.lastLocation
.addOnSuccessListener {
location ->
location?.let {
editLatitude.setText(it.latitude.toString())
editLongitude.setText(it.longitude.toString())
progress.visibility = View.GONE
val place = getAddress(LatLng(it.latitude, it.longitude));
Toast.makeText(applicationContext, "here $place",
Toast.LENGTH_SHORT).show()
//Put the place ia TextView
val skip = findViewById<MaterialTextView>(R.id.skip)
skip.text = "Current Location \n $place"
requestNewLocation()
//Put button when I click on that button.
//It intents to Maps and shows that Location.
//...................
//Interfaces.
//JS - Advanced
} ?: run {
Toast.makeText(applicationContext, "Searching Location",
Toast.LENGTH_SHORT).show()
progress.visibility = View.GONE
requestNewLocation()
} //end run
}//end success
.addOnFailureListener { e ->
Toast.makeText(applicationContext, "Error $e", Toast.LENGTH_SHORT).show()
progress.visibility = View.GONE
requestNewLocation()
}//end Failure
}//end function
lateinit var mLocationCallback: LocationCallback
@SuppressLint("MissingPermission")
fun requestNewLocation(){
progress.visibility = View.VISIBLE
Log.d("hhhhhh", "Requesting New Location")
val mLocationRequest = LocationRequest.create()
mLocationRequest.interval = 10000
mLocationRequest.fastestInterval = 10000
mLocationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
mLocationCallback = object : LocationCallback(){
override fun onLocationResult(result: LocationResult) {
for(location in result.locations){
if (location!=null){
editLatitude.setText(location.latitude.toString())
editLongitude.setText(location.longitude.toString())
progress.visibility = View.GONE
PrefsHelper.savePrefs(applicationContext, "latitude", editLatitude.text.toString())
PrefsHelper.savePrefs(applicationContext, "longitude", editLongitude.text.toString())
}//end if
else {
Toast.makeText(applicationContext, "Check GPS",
Toast.LENGTH_SHORT).show()
}//end else
}//end for
}//end result
}//end call back
fusedLocationClient.requestLocationUpdates(mLocationRequest,
mLocationCallback, Looper.getMainLooper())
}//end function
}//end class
In AndroidManifest.xml add below GPS permissions.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
.....
Now having done the GPS Part for Step 2, Go back to CheckoutStep1 and locate below code(Found under proceed button Listener) and update this code snippet.
//Save values collected from Radio button to Preferences
PrefsHelper.savePrefs(applicationContext, "date", date)
PrefsHelper.savePrefs(applicationContext, "time", time)
PrefsHelper.savePrefs(applicationContext, "where_taken", where_taken)
PrefsHelper.savePrefs(applicationContext, "booked_for", booked_for)
//Check if user Checked Self/Other
if (booked_for=="Self"){
//For Self - Save an Empty depemdant_id to Prefs, Since no dependant
PrefsHelper.savePrefs(applicationContext, "dependant_id", "")
//Proceed to CheckoutStep2GPS
startActivity(Intent(applicationContext, CheckoutStep2GPS::class.java))
}
else {
//For Other - Direct the user to pick a dependant in View Dependants
startActivity(Intent(applicationContext, ViewDependants::class.java))
}//end else
Run Your App,
Select booked_for=="Self". Test the GPS.
After running you notice that you may have challenges when the user did not set the GPS to On earlier before landing to CheckoutStep2GPS.
To avoid that its important to check if GPS is on before opening CheckoutStep2GPS.
In CheckoutStep1, Add this function.
//justpaste.it/arfi6
private fun isLocationEnabled(): Boolean {
var locationManager: LocationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(
LocationManager.NETWORK_PROVIDER
)
}//end
Then Update this code done earlier
PrefsHelper.savePrefs(applicationContext, "date", date)
PrefsHelper.savePrefs(applicationContext, "time", time)
PrefsHelper.savePrefs(applicationContext, "where_taken", where_taken)
PrefsHelper.savePrefs(applicationContext, "booked_for", booked_for)
//Check if GPS is enabled
if (isLocationEnabled()) {
//If enabled , Check Status of Booked for is it Self/Away
if (booked_for=="Self"){
//If Self Save Empty Dependant id and proceed to CheckoutStep2GPS
PrefsHelper.savePrefs(applicationContext, "dependant_id", "")
startActivity(Intent(applicationContext, CheckoutStep2GPS::class.java))
}
else {
//Direct the user to pick a dependant in ViewDependants
startActivity(Intent(applicationContext, ViewDependants::class.java))
}//end else
}//end if Location enabled
else {
// if location not enabled, Take user to Settings to Activate GPS
Toast.makeText(applicationContext, "GPS Is OFF",
Toast.LENGTH_SHORT).show()
startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
}
Below is the Full CheckoutStep1.kt
package com.modcom.medilabsapp
import android.app.DatePickerDialog
import android.app.TimePickerDialog
import android.content.Context
import android.content.Intent
import android.location.Geocoder
import android.location.LocationManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.widget.*
import com.google.android.gms.maps.model.LatLng
import com.google.android.material.button.MaterialButton
import com.modcom.medilabsapp.helpers.PrefsHelper
import java.text.SimpleDateFormat
import java.util.*
class CheckoutStep1 : AppCompatActivity() {
private lateinit var buttonDatePicker: Button
private lateinit var editTextDate: EditText
private lateinit var btnTimePicker: Button
private lateinit var editTextTime: EditText
//This will help show the TimePicker
fun showTimePicker(){
val calender = Calendar.getInstance()
val timePickerDialog = TimePickerDialog(
this,
timeSetListener,
calender.get(Calendar.HOUR_OF_DAY),
calender.get(Calendar.MINUTE),
false)
timePickerDialog.show()
}//end
//https://justpaste.it/cmht0
//This code Listens for time selections and place the time in editTextTime
private val timeSetListener = TimePickerDialog.OnTimeSetListener{
_, hourOfDay, minute ->
val calendar = Calendar.getInstance() //***********
calendar.set(Calendar.HOUR_OF_DAY, hourOfDay)
calendar.set(Calendar.MINUTE, minute)
val sdf = SimpleDateFormat("hh:mm", Locale.getDefault())
val selectedTime = sdf.format(calendar.time)
editTextTime.setText(selectedTime)
}//end
private fun showDatePickerDialog() {
val calendar = Calendar.getInstance()
// Create a date picker dialog and set the current date as the default selection
val datePickerDialog = DatePickerDialog(
this,
{ _: DatePicker, year: Int, month: Int, day: Int ->
val selectedDate = formatDate(year, month, day)
editTextDate.setText(selectedDate)
},
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)
)
// Show the date picker dialog
datePickerDialog.datePicker.minDate = System.currentTimeMillis() - 1000;
datePickerDialog.show()
}
private fun formatDate(year: Int, month: Int, day: Int): String {
val calendar = Calendar.getInstance()
calendar.set(year, month, day)
val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
return dateFormat.format(calendar.time)
}
//justpaste.it/6l88f
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_checkout_step1)
//Click buttonto trigger - showTimePicker()
btnTimePicker = findViewById(R.id.btnTimePicker)
editTextTime = findViewById(R.id.editTextTime)
btnTimePicker.setOnClickListener {
showTimePicker()
}//end
//Click buttonto trigger - showDatePickerDialog()
buttonDatePicker = findViewById(R.id.buttonDatePicker)
editTextDate = findViewById(R.id.editTextDate)
buttonDatePicker.setOnClickListener {
showDatePickerDialog()
}//end onclick
//Find proceed button and set Listener
val proceed = findViewById<MaterialButton>(R.id.proceedstep2)
proceed.setOnClickListener {
val date = editTextDate.text.toString()
val time = editTextTime.text.toString()
//Radio Button Place - Home/Away
val home = findViewById<RadioButton>(R.id.radioHome)
val away = findViewById<RadioButton>(R.id.radioAway)
var where_taken = ""
if (home.isChecked){
where_taken = "Home"
}//end dif
if (away.isChecked){
where_taken = "Away"
}//end if
//Radio Button Self/Other
val self = findViewById<RadioButton>(R.id.radioSelf)
val other = findViewById<RadioButton>(R.id.radioOther)
var booked_for = ""
if (self.isChecked){
booked_for = "Self"
}//end
if (other.isChecked){
booked_for = "Other"
}//end
//check empties
if (date.isEmpty() || time.isEmpty() || where_taken.isEmpty()
|| booked_for.isEmpty()){
Toast.makeText(applicationContext, "Empty Fields",
Toast.LENGTH_SHORT).show()
}
else {
//Save values collected from Radio button to Preferences
PrefsHelper.savePrefs(applicationContext, "date", date)
PrefsHelper.savePrefs(applicationContext, "time", time)
PrefsHelper.savePrefs(applicationContext, "where_taken", where_taken)
PrefsHelper.savePrefs(applicationContext, "booked_for", booked_for)
//Check if GPS is enabled
if (isLocationEnabled()) {
//Check if user Checked Self/Other
if (booked_for=="Self"){
//For Self - Save an Empty dependant_id to Prefs, Since no dependant
PrefsHelper.savePrefs(applicationContext, "dependant_id", "")
startActivity(Intent(applicationContext, CheckoutStep2GPS::class.java))
}
else {
//Direct the user to pick a dependant in ViewDependants
startActivity(Intent(applicationContext, ViewDependants::class.java))
}//end else
}//end if Location enabled
else {
// if location not enabled, Take user to Settings to Activate GPS
Toast.makeText(applicationContext, "GPS Is OFF",
Toast.LENGTH_SHORT).show()
startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
}
}//end else
}//end listener
}//End onCreate
//justpaste.it/arfi6
//This function checks if GPS is ON or OFF in your phone
private fun isLocationEnabled(): Boolean {
var locationManager: LocationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(
LocationManager.NETWORK_PROVIDER
)
}//end
}//end class
Run and Test GPS.
In this Step, we do where a user picked "Other" on the Radio Button.
Open the DependantAdapter.kt under onBindViewHolder() function. and update the holder.itemView.setOnClickListener with below code.
holder.itemView.setOnClickListener {
//Pick the item.dependant_id of the item selected from recycler View
//Save that dependant id in preferences
PrefsHelper.savePrefs(context, "dependant_id", item.dependant_id)
//Redirect to GPS Activity done in Previous Step.
val i = Intent(context, CheckoutStep2GPS::class.java)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(i)
}//end Listener
Create a New Empty Views Activity named CompleteActivity.kt and its XML In activity_complete.xml write below code
<?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"
android:background="@color/teal_700"
tools:context=".CompleteActivity">
<ProgressBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="30dp"/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Proccessing...Please Wait."
android:textSize="40dp"
android:textAlignment="center"
android:textColor="@color/white"/>
</LinearLayout>
In CompleteActivity.kt write below code
package com.modcom.medilabsapp
import android.content.Intent
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import com.modcom.medilabsapp.constants.Constants
import com.modcom.medilabsapp.helpers.ApiHelper
import com.modcom.medilabsapp.helpers.PrefsHelper
import com.modcom.medilabsapp.helpers.SQLiteCartHelper
import org.json.JSONArray
import org.json.JSONObject
import java.text.SimpleDateFormat
import java.util.*
class CompleteActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_complete)
//Access SQLIte Helper
val sqllitehelper = SQLiteCartHelper(applicationContext)
val items = sqllitehelper.getAllItems() //Get All items
//Generate Invoice No. Check this Function at the bottom of this File
val invoice_no = generateInvoiceNumber()
//Save Invoice No to Prefs
PrefsHelper.savePrefs(applicationContext, "invoice_no", invoice_no)
val numItems = items.size // Get all items size,
//Loop each item
items.forEachIndexed { index, labTests ->
//How many Items do you have?
// Capture test ID/Lab ID at a given Loop
//Each item has a test_id and lab_id
val test_id = labTests.test_id
val lab_id = labTests.lab_id
//Capture other details from Preferences, These were saved in different part in this projects
//NB: You might need to confirm that below were saved in preferences.
//Retrieve all details from prefs
val member_id = PrefsHelper.getPrefs(this, "member_id")
val date = PrefsHelper.getPrefs(this, "date")
val time = PrefsHelper.getPrefs(this, "time")
val booked_for = PrefsHelper.getPrefs(this, "booked_for")
val where_taken = PrefsHelper.getPrefs(this, "where_taken")
val latitude = PrefsHelper.getPrefs(this, "latitude")
val longitude = PrefsHelper.getPrefs(this, "longitude")
val dependant_id = PrefsHelper.getPrefs(this, "dependant_id")
//Access API helper and Post your variables to API
val helper = ApiHelper(this)
val api = Constants.BASE_URL + "/make_booking"
val body = JSONObject()
body.put("member_id", member_id)
body.put("appointment_date", date)
body.put("appointment_time", time)
body.put("booked_for", booked_for)
body.put("where_taken", where_taken)
body.put("latitude", latitude)
body.put("longitude", longitude)
body.put("dependant_id", dependant_id)
body.put("test_id", test_id)
body.put("lab_id", lab_id)
body.put("invoice_no", invoice_no)
helper.post(api, body, object : ApiHelper.CallBack {
override fun onSuccess(result: JSONObject?) {
//Success Posted
Toast.makeText(applicationContext, result.toString(), Toast.LENGTH_SHORT).show()
}
override fun onFailure(result: String?) {
//Failed to POST
Toast.makeText(applicationContext,
result.toString(), Toast.LENGTH_SHORT).show()
// val json = JSONObject(result.toString())
// val msg = json.opt("msg")
// //TODO
// if (msg == "Token has Expired"){
// PrefsHelper.clearPrefs(applicationContext)
// startActivity(Intent(applicationContext, SignInActivity::class.java))
// finishAffinity()
// }
}
override fun onSuccess(result: JSONArray?) {
}
})//end post
//Index counts from zero, Means all lab tests have been posted to API we can now Proceed to Payment
if (index == (numItems-1)){
Toast.makeText(applicationContext, "Processing Done", Toast.LENGTH_SHORT).show()
//To activate the Intent Later once the Payment.kt is available/created
//startActivity(Intent(applicationContext, Payment::class.java))
//finish()
}//end
}//end for each
}//end oncreate
//Generate Invoice Number Function
fun generateInvoiceNumber(): String {
val dateFormat = SimpleDateFormat("yyyyMMddHHmmss")
val currentTime = Date()
val timestamp = dateFormat.format(currentTime)
// You can use a random number or a sequential number to add uniqueness to the invoice number
// For example, using a random number:
val random = Random()
val randomNumber = random.nextInt(1000) // Change the upper bound as needed
// Combine the timestamp and random number to create the invoice number
return "INV-$timestamp-$randomNumber" //Unique
}
//github.com/modcomlearning/MediLabApp
}//end class
In Your CheckoutStep2GPS.kt Add below code in onCreate Function, Note that there was a complete Button in CheckoutStep2GPS XML.
val complete = findViewById<Button>(R.id.complete)
complete.setOnClickListener {
PrefsHelper.savePrefs(applicationContext, "latitude",
editLatitude.text.toString())
PrefsHelper.savePrefs(applicationContext, "longitude",
editLongitude.text.toString())
val intent = Intent(applicationContext, CompleteActivity::class.java)
startActivity(intent)
}
Run and test your Application
Test Booking is Working
In your main package, Create an Empty Views Activity named Payment.
in activity_payment.xml write below code.
<?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=".Payment">
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Please Pay 1,500"
android:padding="20dp"
android:textSize="30sp"
android:textAlignment="center"
android:id="@+id/textpay"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:padding="20dp"
android:hint="Your Phone">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="phone"
android:id="@+id/phone"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Pay Now"
android:layout_margin="20dp"
android:id="@+id/btnpay"/>
</LinearLayout>
in Payment.kt, write below code
package com.modcom.medilabsapp
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import com.google.android.material.button.MaterialButton
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textview.MaterialTextView
import com.modcom.medilabsapp.constants.Constants
import com.modcom.medilabsapp.helpers.ApiHelper
import com.modcom.medilabsapp.helpers.PrefsHelper
import com.modcom.medilabsapp.helpers.SQLiteCartHelper
import org.json.JSONArray
import org.json.JSONObject
class Payment : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_payment)
//Find TextView with id textpay
val textpay = findViewById<MaterialTextView>(R.id.textpay)
//Access helper
val helper = SQLiteCartHelper(applicationContext)
textpay.text = "Please Pay KES "+helper.totalCost() //Set Total Cost in TextView
//Find Button and set Listener
val btnpay = findViewById<MaterialButton>(R.id.btnpay)
btnpay.setOnClickListener {
//Find the phone EditText
val phone = findViewById<TextInputEditText>(R.id.phone)
//Access base URL
val api = Constants.BASE_URL+"/make_payment"
//Create JSON
val body = JSONObject()
//Put variables phone and amount
body.put("phone", phone.text.toString())//254
body.put("amount", helper.totalCost())
//body.put("amount", "1") //For Testing
//Get from invoice_no from prefs
val invoice_no = PrefsHelper.getPrefs(this, "invoice_no")
//put invoice in body
body.put("invoice_no", invoice_no)// get from prefs
//Access Helper
val apihelper = ApiHelper(applicationContext)
//Post body to API
apihelper.post2(api, body, object : ApiHelper.CallBack{
override fun onSuccess(result: JSONArray?) {
}
override fun onSuccess(result: JSONObject?) {
//SUccess
Toast.makeText(applicationContext, result.toString(),
Toast.LENGTH_SHORT).show()
}
override fun onFailure(result: String?) {
//Failure
Toast.makeText(applicationContext,
result.toString(), Toast.LENGTH_SHORT).show()
// val json = JSONObject(result.toString())
// val msg = json.opt("msg")
// //TODO
// if (msg == "Token has Expired"){
// PrefsHelper.clearPrefs(applicationContext)
// startActivity(Intent(applicationContext, SignInActivity::class.java))
// finishAffinity()
// }
}
})
}//end listener
}//end oncreate
}//end class
In CompleteActivity.kt, enable below Intent
if (index == (numItems-1)){
Toast.makeText(applicationContext, "Processing Done", Toast.LENGTH_SHORT).show()
//To activate the Intent Later once the Payment.kt is available/created
// ADD/ENABLE BELOW INTENT TO TAKE US TO PAYMENT ACTIVITY
startActivity(Intent(applicationContext, Payment::class.java))
finish()
}//end
Run and test your Application
Test Payment is Working
In this Application, we needed data connection in many activities such as SignIn, SignUp, Fetching Laboratories, Lab Tests, Making Payment etc,
Its important to have a function to check whether a user has internet or not in their devices
In this section we create a function to whether a user has internet or not in their devices
in helpers pckage create a Kotlin class file named NetworkHelper.kt and write below code
package com.modcom.medilabsapp.helpers
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
class NetworkHelper {
companion object {
//justpaste.it/6l88f
fun checkForInternet(context: Context): Boolean {
// register activity with the connectivity manager service
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
//This will work for Android M and above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val network = connectivityManager.activeNetwork ?: return false
// Representation of the capabilities of an active network.
val activeNetwork = connectivityManager.getNetworkCapabilities(network) ?: return false
return when {
activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
// Indicates this network uses a Cellular transport. or
// Cellular has network connectivity
activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
// else return false
else -> false
}
} else {
// if the android version is below M
@Suppress("DEPRECATION") val networkInfo =
connectivityManager.activeNetworkInfo ?: return false
@Suppress("DEPRECATION")
return networkInfo.isConnected
}//end else
}//end check net function
}//end companion
}//end class
To use above function, we can put it in MainActivity in Screen1.
Below is an updated Scrren1.kt with check internet functionality
package com.modcom.medilabsapp
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.textview.MaterialTextView
import com.modcom.medilabsapp.helpers.NetworkHelper
import com.modcom.medilabsapp.helpers.SQLiteCartHelper
class Screen1 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_screen1)
//Check For Internet Connection
if(!NetworkHelper.checkForInternet(applicationContext)){
Toast.makeText(applicationContext, "Not Internet", Toast.LENGTH_SHORT).show()
startActivity(Intent(applicationContext, NoNetActivity::class.java))
finish()
}
else {
//There is Internet Connection
//Find Views by ID
val skip1 = findViewById<MaterialTextView>(R.id.skip1)
//Button to Go Next to Screen 2
skip1.setOnClickListener {
startActivity(Intent(applicationContext, MainActivity::class.java))
}// End
//Button to Skip to Main Activity
val fab = findViewById<FloatingActionButton>(R.id.fab)
fab.setOnClickListener {
startActivity(Intent(applicationContext, Screen2::class.java))
}// End
}
}
}
NB: Create an Empty Views Activity named NoNetActivity.kt to be called if No Internet.