I am very new to MVVM and I would be really happy to get some help. Its very basic project,just to get how things are done in MVVM.
So as soon as the app starts it will fill the recyclerView with all the employees from the API. I have a floating action button that when I press on it, an alert dialog with edittext is opening,and when I type a number, I suppose to get a specific employee from the API based on his ID and update the recyclerview so it will display only 1 employee.
The problems are:
When I type an ID and press search, the server is getting me a specific employee by it's ID and its working fine,but when I search for a specific employee again I don't get the Employee details in the logcat,its like it didn't search it in the server(I am not getting any errors or Failed Log from the logcat).
I am not sure if I implemeted the observables and the MVVM pattern like it should be, so if you have any feedback I would LOVE to hear it.
I am using this dummy API - https://dummy.restapiexample.com/
This is the Employee model:
data class Employee (
@SerializedName("employee_name")
val employeeName:String,
@SerializedName("employee_salary")
val employeeSalary: String,
@SerializedName("employee_age")
val employeeAge: Int,
@SerializedName("id")
val employeeID: Int
)
data class EmployeesListResult(
@SerializedName("data")
val getEmployeesListResult : List<Employee>,
)
data class SingleEmployeeListResult(
@SerializedName("data")
val getSingleEmployeesListResult : Employee
)
This is the APIRequest Object:
object APIRequest {
val baseUrl : String = "https://dummy.restapiexample.com/api/v1/"
var retrofit: Retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
val retrofitCallGetList : APICallRequest = retrofit.create(APICallRequest::class.java)
}
interface APICallRequest{
@GET("employees")
fun callEmployeeList() : Call<EmployeesListResult>
@GET("employee/{id}")
fun callSpecificEmployee(@Path(value = "id", encoded = false) key: Int, ): Call<SingleEmployeeListResult>
}
This is the MainRepository class:
class MainRepository {
val mutableListLiveData = MutableLiveData<List<Employee>>()
val mutableSingleLiveData = MutableLiveData<Employee>()
fun getEmployeeListFromAPI(): MutableLiveData<List<Employee>> {
val apiRequest: APICallRequest = APIRequest.retrofitCallGetList
apiRequest.callEmployeeList().enqueue(object : Callback<EmployeesListResult?> {
override fun onResponse(
call: Call<EmployeesListResult?>,
response: Response<EmployeesListResult?>
) {
if (response.isSuccessful) {
mutableListLiveData.value = response.body()?.getEmployeesListResult
Log.e("onResponse", "Success!")
Log.e("Response:", "${response.body()}")
}
}
override fun onFailure(call: Call<EmployeesListResult?>, t: Throwable) {
Log.e("onFailure", "Failed getting list: ${t.message}")
}
})
return mutableListLiveData
}
fun getSpecificEmployee(employeeID: Int): MutableLiveData<Employee> {
val apiRequest: APICallRequest = APIRequest.retrofitCallGetList
apiRequest.callSpecificEmployee(employeeID).enqueue(object : Callback<SingleEmployeeListResult?> {
override fun onResponse(
call: Call<SingleEmployeeListResult?>,
response: Response<SingleEmployeeListResult?>
) {
if (response.isSuccessful) {
mutableSingleLiveData.value = response.body()?.getSingleEmployeesListResult
Log.e("Single onResponse", "Success!")
Log.e("Response:", "${response.body()}")
}
}
override fun onFailure(call: Call<SingleEmployeeListResult?>, t: Throwable) {
Log.e("Single onResponse FAIL", "FAIL! ${t.message}")
}
})
return mutableSingleLiveData
}
This is the MainViewModel:
class MainViewModel : ViewModel() {
private var employeeMutableData : MutableLiveData<List<Employee>>? = null
private var specificEmployeeMutableData : MutableLiveData<Employee>? = null
fun getEmployeeListFromRepo() : LiveData<List<Employee>>{
if (employeeMutableData == null){
employeeMutableData = MainRepository().getEmployeeListFromAPI()
}
return employeeMutableData as LiveData<List<Employee>>
}
fun getSpecificEmployee(employeeID : Int) : LiveData<Employee> {
if (specificEmployeeMutableData == null){
specificEmployeeMutableData = MainRepository().getSpecificEmployee(employeeID)
}
return specificEmployeeMutableData as LiveData<Employee>
}
}
The MainActivity class:
class MainActivity : AppCompatActivity() {
private val mainViewModel : MainViewModel by viewModels()
private lateinit var recyclerView: RecyclerView
private lateinit var mainAdapter: MainRecyclerViewAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initRecycler()
val actionButton = findViewById<FloatingActionButton>(R.id.actionButton)
actionButton.setOnClickListener(View.OnClickListener {
searchEmployeeByIdDialog()
})
mainViewModel.getEmployeeListFromRepo().observe(this,object : Observer<List<Employee>> {
override fun onChanged(theListOfEmployees: List<Employee>) {
mainAdapter = MainRecyclerViewAdapter(theListOfEmployees)
recyclerView.adapter = mainAdapter
}
})
} // End of OnCreate
private fun initRecycler() {
recyclerView = findViewById<RecyclerView>(R.id.mainRecyclerView)
recyclerView.setHasFixedSize(true)
recyclerView.layoutManager = GridLayoutManager(this@MainActivity,2)
}
private fun searchEmployeeByIdDialog(){
val editTextForDialog = EditText(this)
editTextForDialog.maxLines = 1
editTextForDialog.setPadding(10)
editTextForDialog.inputType = InputType.TYPE_CLASS_NUMBER
val alertDialog = AlertDialog.Builder(this)
alertDialog.setTitle("Employee Search")
alertDialog.setMessage("What employee ID do you want to look for ?")
alertDialog.setView(editTextForDialog)
.setPositiveButton("Search", DialogInterface.OnClickListener { dialogInterface: DialogInterface?, i: Int ->
if (editTextForDialog.text.isNotEmpty()){
mainViewModel.getSpecificEmployee(editTextForDialog.text.toString().toInt()).observe(this,object : Observer<Employee?> {
override fun onChanged(t: Employee?) {
if (t != null) {
val list = listOf(t)
mainAdapter.updateEmployeeList(list)
}
}
})
}else{
Toast.makeText(this,"Please enter employee ID",Toast.LENGTH_SHORT).show()
}
})
.setNegativeButton("Cancel", DialogInterface.OnClickListener { dialogInterface, i ->
dialogInterface.dismiss()
})
.show()
}
}
And finally, The MainRecyclerViewAdapter class:
class MainRecyclerViewAdapter(var employeeList: List<Employee>) : RecyclerView.Adapter<MainRecyclerViewAdapter.EmployeesHolder>() {
inner class EmployeesHolder(var itemView : View) : RecyclerView.ViewHolder(itemView){
fun bindData(employee : Employee){
val nameTextView = itemView.findViewById<TextView>(R.id.nameTextView)
val ageTextView = itemView.findViewById<TextView>(R.id.ageTextView)
val salaryTextView = itemView.findViewById<TextView>(R.id.salaryTextView)
nameTextView.text = employee.employeeName
ageTextView.text = employee.employeeAge.toString()
salaryTextView.text = employee.employeeSalary
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EmployeesHolder {
return EmployeesHolder(LayoutInflater.from(parent.context).inflate(R.layout.recyclerview_row,parent,false))
}
override fun onBindViewHolder(holder: EmployeesHolder, position: Int) {
holder.bindData(employeeList[position])
}
override fun getItemCount(): Int {
return employeeList.size
}
fun updateEmployeeList(newList: List<Employee>) {
this.employeeList = newList
notifyDataSetChanged()
}
}
Thank you very much!
CodePudding user response:
Something stands out to me in your MainViewModel implementation, regarding how you implemented your getSpecificEmployee(...)
method.
The way it's currently implemented, the first time you search for a specific employee, the specificEmployeeMutableData
live data will be null, hence will call the relevant repository method.
But for the subsequent times you try to fetch a specific employee, the specificEmployeeMutableData
will no longer be null, which means it'll not trigger the API call in your repository.
Thats why in your logs, it seems like nothing is happening... because nothing is.
A quick patch could be to remove the null check and just call the repository every time you need to fetch a specific employee.
fun getSpecificEmployee(employeeID : Int) : LiveData<Employee> {
return MainRepository().getSpecificEmployee(employeeID) as LiveData<Employee>
}