Dynamic RecyclerView content with Firebase Realtime DB - android

I'm developing a simple activity where I have to show a dynamic list of reservations. The list has to be synchronized with the Firebase Realtime DB.
What I did
I created a Reservation class with the relative ReservationAdapter and the dedicated layout item_customer_reservation.xml. Up to now, I populate every item with the same informations, just for testing purposes.
class ReservationAdapter(private val reservationList: Array<Reservation>) : RecyclerView.Adapter<ReservationAdapter.ReservationViewHolder>()
{
inner class ReservationViewHolder(private val binding: ItemCustomerReservationBinding) : RecyclerView.ViewHolder(binding.root)
{
fun bind(reservation: Reservation)
{
Log.w(Constants.CUTITAPP, "ReservationViewHolder:onBind")
binding.apply {
// TODO dummy informations
tvSalonName.text = "test salon"
tvServiceName.text = "test name"
tvServiceDate.text = "test date"
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReservationViewHolder
{
val binding = ItemCustomerReservationBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ReservationViewHolder(binding)
}
override fun onBindViewHolder(reservationViewHolder: ReservationViewHolder, position: Int)
{
reservationViewHolder.bind(reservationList[position])
}
override fun getItemCount() = reservationList.size
}
This is the Fragment that hosts the RecyclerView:
class CustomerHomeFragment : Fragment()
{
private lateinit var db: DatabaseReference
private var uid: String? = null
private var reservationList: Array<Reservation>? = null
private var reservationAdapter: ReservationAdapter? = null
// view binding
private var _binding: FragmentCustomerHomeBinding? = null
private val binding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
arguments?.let {
uid = it.getString(Constants.UID) // get the UID from the passed parameter
}
db = FirebaseDatabase.getInstance().reference
reservationList = arrayOf()
reservationAdapter = ReservationAdapter(reservationList!!)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View
{
// inflate the layout for this fragment
_binding = FragmentCustomerHomeBinding.inflate(inflater, container, false)
addCustomerReservationsEventListener()
binding.apply {
rvReservations.adapter = reservationAdapter
}
return binding.root
}
override fun onDestroyView()
{
super.onDestroyView()
_binding = null // needed to destroy the binding when the fragment is destroyed
}
/**
* Adds a [ValueEventListener] for the reservations of the customer.
* Populates the reservationList with the [Reservation] of the customer and notifies the adapter.
*/
private fun addCustomerReservationsEventListener()
{
val reservationsReference = db.child(Constants.DB_CUSTOMERS).child(uid!!).child(Constants.DB_CUSTOMER_RESERVATIONS)
val customerReservationsListener = object : ValueEventListener {
override fun onDataChange(reservationUidListSnapshot: DataSnapshot)
{
// get reservationUidList
val reservationUidList = reservationUidListSnapshot.getValue<HashMap<String, String>>()
Log.d(Constants.CUTITAPP, "reservationUidList: $reservationUidList")
if (!reservationUidList.isNullOrEmpty())
{
// get a Reservation for each Uid to populate the list
for (reservationUid in reservationUidList.values)
{
if (reservationUid.isNotEmpty()) // TODO temporary fix
{
db.child(Constants.DB_RESERVATIONS).child(reservationUid).get().addOnSuccessListener { reservationSnapshot: DataSnapshot ->
val reservation = reservationSnapshot.getValue<Reservation>()
reservationList = reservationList?.plus(reservation!!)
reservationAdapter?.notifyDataSetChanged()
Log.d(Constants.CUTITAPP, "reservationList size: ${reservationList?.size}")
Log.d(Constants.CUTITAPP, "reservationAdapter size: ${reservationAdapter?.itemCount}")
}
}
}
}
}
override fun onCancelled(databaseError: DatabaseError)
{
Log.w(getString(R.string.app_name), "CustomerHomeFragment:onCancelled", databaseError.toException())
}
}
reservationsReference.addValueEventListener(customerReservationsListener)
}
companion object
{
#JvmStatic
fun newInstance(uid: String) =
CustomerHomeFragment().apply {
arguments = Bundle().apply {
putString(Constants.UID, uid) // pass the parameters to the created Fragment
}
}
}
}
Problem
The reservationList collects the Reservation of the user (currently in the DB I added one reservation for testing), its size is 1; it correctly contains a Reservation object with the correct data that i have in the DB.
The problem is that the reservationAdapter has 0 items inside, like the notifyDataSetChanged() is not working. The result is that the RecyclerView is empty.
How can I fix that? Thank you!

I manage to get a solution.
reservationList = reservationList?.plus(reservation!!) creates a new Array object, that is not linked to the Adapter (the old empty array is still attached to it).
The solution is to use ArrayList instead of Array in the ReservationAdapter, then change the onDataChange() like this:
override fun onDataChange(reservationUidMapSnapshot: DataSnapshot)
{
reservationList?.clear()
reservationAdapter?.notifyDataSetChanged()
// Get reservationUidMap
val reservationUidMap = reservationUidMapSnapshot.getValue<HashMap<String, String>>()
if (!reservationUidMap.isNullOrEmpty())
{
// Get a Reservation for each UID to populate the list
for (reservationUid in reservationUidMap.values)
{
db.child(Constants.DB_RESERVATIONS).child(reservationUid).get().addOnSuccessListener { reservationSnapshot: DataSnapshot ->
val reservation = reservationSnapshot.getValue<Reservation>()
reservationList?.add(reservation!!)
// Notify the adapter of the changes
reservationAdapter?.notifyDataSetChanged()
}
}
}
}
I hope it will be helpful.
Cheers!

Related

null cannot be cast to non-null type RecyclerView with stateFlow

I got some categories from an api and trying to show them on a recycler view but it doesn't work for some reason.
Although the data appears correctly in the logcat, it is sent as null to the Category adapter.
This is the Main Activity (where I'm trying to show the data):
`
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val TAG = "MEALZ"
private lateinit var binding: ActivityMainBinding
private val viewModel:MealsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val adapter = CategoryAdapter(this)
binding.categoriesRv.adapter = adapter
viewModel.getMeals()
lifecycleScope.launch {
viewModel.categories.collect {
adapter.setData(it?.categories as List<Category>)
Log.d(TAG, "onCreate: ${it?.categories}")
}
}
}
}
`
This is Recycler Category Adapter :
`
class CategoryAdapter(private val context: Context?) :
RecyclerView.Adapter<CategoryAdapter.CategoryViewHolder>() {
private var categoryList: MutableList<Category?> = mutableListOf<Category?>()
inner class CategoryViewHolder(itemView: CategoryLayoutBinding) :
RecyclerView.ViewHolder(itemView.root) {
val name = itemView.categoryNameTv
val img = itemView.categoryIv
val des = itemView.categoryDesTv
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryViewHolder {
val binding = CategoryLayoutBinding.inflate(LayoutInflater.from(context), parent, false)
return CategoryViewHolder(binding)
}
override fun onBindViewHolder(holder: CategoryViewHolder, position: Int) {
var category = categoryList[position]
holder.name.text = category?.strCategory
holder.des.text = category?.strCategoryDescription
Glide.with(context as Context).load(category?.strCategoryThumb).into(holder.img)
}
override fun getItemCount(): Int {
return categoryList.size
}
fun setData(CategoryList: List<Category>) {
this.categoryList.addAll(CategoryList)
notifyDataSetChanged() //to notify adapter that new data change has been happened to adapt it
}
}
`
This is the View Model class:
#HiltViewModel
class MealsViewModel #Inject constructor(private val getMealsUseCase: GetMeals): ViewModel() {
private val TAG = "MealsViewModel"
private val _categories: MutableStateFlow<CategoryResponse?> = MutableStateFlow(null)
val categories: StateFlow<CategoryResponse?> = _categories
fun getMeals() = viewModelScope.launch {
try {
_categories.value = getMealsUseCase()
} catch (e: Exception) {
Log.d(TAG, "getMeals: ${e.message.toString()}")
}
}
}
you create your _categories with null as initial value, so first value of categories flow will be null and only second one will contain fetched data. As a workaround, you can check that data is not null:
viewModel.categories.collect {
if (it != null) {
adapter.setData(it?.categories as List<Category>)
Log.d(TAG, "onCreate: ${it?.categories}")
}
}
or introduce some kind of "loading" state

RecyclerView only displaying correct data when using breakpoints

I am trying to make my RecylerView display data from a database, however the recyclerView displays nothing, unless i walk through the code with breakpoints, in which case it displays as expected. In the EssayPlanDialogFragment i inisialised an empty arrayList to store the essay lists which then has essayParagrpahs added to it either if the dialogFragemnt is called from the previous essayFragment, in which case the fillIn() function is called to turn database data into paragraphs or if the add paragraph is pressed to add a new empty paragraph. The breakpoint that seem to make it work is on the var paragraphList = ArrayList() and triggers 8 times before displaying the data
RecyclerView Adapter
class EssayPlanAdapter(): RecyclerView.Adapter<EssayPlanAdapter.ViewHolder>() {
var paragraphList = ArrayList<essayParagraph>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
var view = LayoutInflater.from(parent.context).inflate(R.layout.essay_paragraph_layout, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
///code
}
override fun getItemCount(): Int {
return paragraphList.size
}
fun setData(paragraph: ArrayList<essayParagraph>) {
this.paragraphList = paragraph
notifyDataSetChanged()
}
//more code ViewHolder Class and code help with adding essayPlan to databse
}
EssayPlanDialogFragment
class essayPlayDialogFragment(questionId:Int,fromEssay:Int): DialogFragment() {
var paragraphs = ArrayList<essayParagraph>()
var fromEssay = fromEssay
lateinit var introduction: LinearLayout
lateinit var title:EditText
var questionId = questionId
override fun onCreateView(){
//code
}
override fun onViewCreated(view: View, #Nullable savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val adapter = EssayPlanAdapter()
title = view.findViewById(R.id.essayTitle)
introduction = view.findViewById(R.id.introduction)
val viewModle = ViewModelProvider(this).get(essayViewModle::class.java)
val paragraphRecycler: RecyclerView = view.findViewById(R.id.essayPlanRecycler)
val closeBtn: ImageButton = view.findViewById(R.id.closeButton)
val finishBtn:ImageButton = view.findViewById(R.id.finnishButton)
//if Dialog fragment opened from previousEssays prafment fromEssay == 1, if from makeNewEssayPragment == 0
if (fromEssay == 1){
val essay = viewModle.checkId(questionId) //returns essay with aprriate essayId
paragraphRecycler.adapter = adapter
paragraphRecycler.layoutManager = LinearLayoutManager(requireContext())
var tempEssays = fillIn(essay,introduction)
adapter.setData(tempEssay)
}
else{
paragraphRecycler.adapter = adapter
paragraphRecycler.layoutManager = LinearLayoutManager(requireContext())
adapter.setData(paragraphs)
}
val button: Button = view.findViewById(R.id.addParagraph)
button.setOnClickListener(){
paragraphs.add(essayParagraph("","","",""))
adapter.setData(paragraphs)
}
finishBtn.setOnClickListener {
//add essay to database
}
fun constructEssayPlanData(adapter: EssayPlanAdapter,intro:LinearLayout,ID:Int):EssayPlan{
//make essayPlan to be added to database
}
fun fillIn(essay:EssayPlan,intro: LinearLayout): ArrayList<essayParagraph>{
intro.lineOfThought.setText(essay.LOT.toString())
intro.relaventPlot.setText(essay.intro.toString())
title.setText("TestTest")
var topicSentences = essay.topicSentences.split("+").toMutableList()
topicSentences.removeAt(0)
var firstQuotes = essay.firstQuotes.split("+").toMutableList()
firstQuotes.removeAt(0)
var secondQuotes = essay.SecondQuotes.split("+").toMutableList()
secondQuotes.removeAt(0)
var thirdQuotes = essay.ThirdQuotes.split("+").toMutableList()
thirdQuotes.removeAt(0)
for(i in 0..(essay.numParagraph -1)){
var essayTemp = essayParagraph(topicSentences[i],firstQuotes[i],secondQuotes[i],thirdQuotes[i])
paragraphs.add(essayTemp)
}
return paragraphs
}
}
ViewModel
fun checkId(id:Int):EssayPlan{
var essay = EssayPlan(id,"","","","","","","",0)
viewModelScope.launch(Dispatchers.IO) {
essay = repository.check(id)
}
return essay
}
repository
fun check(id:Int):EssayPlan{
var essayPlans: EssayPlan = essayPlanDao.checkExist(id)
return essayPlans
}
DAO
#Query("SELECT * FROM plans WHERE id == :id LIMIT 1")
fun checkExist(id:Int):EssayPlan

Not Able to get the usersList [duplicate]

This question already has answers here:
How to return DataSnapshot value as a result of a method?
(6 answers)
getContactsFromFirebase() method return an empty list
(1 answer)
Setting Singleton property value in Firebase Listener
(3 answers)
Closed 1 year ago.
Hello I am trying to implement a RecyclerView of users that are in my RealtimeDatabase, but this code dosen't work and I dont know why. Here is the code for Activity.
class NewMessageActivity : AppCompatActivity() {
private val TAG = "NewMessageActivityTag/////////////////////////////"
private lateinit var binding : ActivityNewMessageBinding
private lateinit var realtimeDatabase : FirebaseDatabase
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityNewMessageBinding.inflate(layoutInflater)
setContentView(binding.root)
supportActionBar?.title = "New Message"
realtimeDatabase = FirebaseDatabase.getInstance("https://firechat-931d2-default-rtdb.asia-southeast1.firebasedatabase.app/")
val users = fetchUsers()
Log.d(TAG,"vkgjhbiuglkjhglioiuhghubgDone2 $users")
binding.rvNewMessageActivity.adapter = UsersAdpater(this,users)
binding.rvNewMessageActivity.layoutManager = LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false)
}
private fun fetchUsers() : ArrayList<UserInfo> {
val ref = realtimeDatabase.getReference("/users")
val userList = ArrayList<UserInfo>()
Log.d(TAG,"pidsnigfngadsnfpoin")
ref.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
if(snapshot.exists()) {
Log.d(TAG,"$snapshot")
for(child in snapshot.children){
val tempchild = child.getValue(UserInfo::class.java)
Log.d(TAG,"$tempchild")
userList.add(tempchild ?: UserInfo("default_UID","default_email","default_name","default_picurl"))
}
}
}
override fun onCancelled(error: DatabaseError) {
Log.d(TAG,"$error ${error.message}")
}
})
Log.d(TAG,"$userList")
// if(userList.isEmpty()) {
// for(i in 1..10) {
// userList.add(UserInfo("default_UID$i","default_email$i","default_name$i","https://firebasestorage.googleapis.com/v0/b/firechat-931d2.appspot.com/o/ProfileImages%2FL5yOgELQtmXdUcnxf7S6Iqs1OsN2.profileImage?alt=media&token=3573195b-a5bb-499f-a79b-7191d5ad2655"))
// }
// }
return userList
}
}
Now I'll add the data class code
1
Now I'll add the adapter
class UsersAdpater(val context : Context, val gotData : ArrayList<UserInfo>) : RecyclerView.Adapter<UsersAdpater.UserViewHolder>() {
private var data : ArrayList<UserInfo> = gotData
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_user_newmessasgesactivity,parent,false)
return UserViewHolder(view)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.username.text = data[position].name
holder.useremail.text = data[position].email
Glide.with(context).load(data[position].profilePicUrl).into(holder.userImage).onLoadFailed(ContextCompat.getDrawable(context,R.drawable.profilepicnormall))
holder.entireLayout.setOnClickListener {
//TODO("IMPLEMENT OPENING A NEW CHAT WITH THIS USER")
Log.d("NewMessagesActivity","${data[position].toString()}")
}
}
override fun getItemCount(): Int {
return data.size
}
private fun updateList(userList : ArrayList<UserInfo>) {
data = userList
}
inner class UserViewHolder(private val itemView : View) : RecyclerView.ViewHolder(itemView) {
val username = itemView.findViewById<TextView>(R.id.item_rvnma_username)
val useremail = itemView.findViewById<TextView>(R.id.item_rvnma_email)
val userImage = itemView.findViewById<ImageView>(R.id.item_rvnma_image)
val entireLayout = itemView
}
}
// In the Log
2 As you can see the the function fetchUsers() has already returned an empty list and the data is logged after that. Why so ? And also why is it returning an empty list when it logs data for every user?
Is it because the fetching of users takes time? Do I need to use Coroutines?

Room Data doesn't show in the RecyclerList

It's first time using Room Data while also using MVVM pattern. The aim is that I want my data to appeard on the RecyclerList but it's doesn't shut down nor shows me any error it's just appears empty.
Here is my Database class:
#Database(entities = [Plant::class, Plant_Category::class], version = 1)
abstract class PlantDatabase:RoomDatabase() {
abstract fun plantDao(): PlantOperations
abstract fun plantCategoryDao(): PlantCategoryOperations
companion object {
private var INSTANCE: PlantDatabase? = null
fun getDatabase(context: Context): PlantDatabase {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context.applicationContext,
PlantDatabase::class.java, DB_NAME // contains directory of sqlite database
)
.fallbackToDestructiveMigration()
.build()
}
return INSTANCE!!
}
}
}
My dao class:
#Dao
interface PlantOperations {
#Query("SELECT * FROM Plant")
fun getAll(): Flow<List<Plant>>
#Insert
fun insertPlant( plant: Plant)
#Delete
fun delete(plant:Plant)
#Update
fun updatePlant(plant:Plant)}
This is my repository class:
class PlantRepository(application:Application){
private var allPlants = MutableLiveData<List<Plant>>()
private val plantDAO = PlantDatabase.getDatabase(application).plantDao()
init {
CoroutineScope(Dispatchers.IO).launch {
val plantData = plantDAO.getAll()
plantData.collect{
allPlants.postValue(it)
}
}
}
fun getAllPlants(): MutableLiveData<List<Plant>> {
return allPlants
}
}
My Viewmodel class:
class PlantViewModel(
application: Application
): AndroidViewModel(application) {
private var repository = PlantRepository(application)
private var _allPlants = repository.getAllPlants()
val allPlants: MutableLiveData<List<Plant>>
get() = _allPlants
}
My Recycler in Fragment:
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?, savedInstanceState: Bundle?): View? {
lateinit var photoAdapter: Photo_Adapter
lateinit var plantViewModel: PlantViewModel
val view: View = inflater.inflate(R.layout.fragment_edit__form, container, false)
val fab = view.findViewById(R.id.floatingActionButton) as FloatingActionButton
val recyclerView = view.findViewById(R.id.recyclerView) as RecyclerView
recyclerView.layoutManager = GridLayoutManager(context, 2)
photoAdapter = Photo_Adapter(context)
recyclerView.adapter = photoAdapter
plantViewModel = ViewModelProvider(this).get(PlantViewModel::class.java)
plantViewModel.allPlants.observe(viewLifecycleOwner, androidx.lifecycle.Observer {
photoAdapter.setDataList(it)
})
// photoAdapter.setDataList(dataList)
//Floating button that opens the Form in order to add plant
fab?.setOnClickListener {
val intent = Intent(view.context, Edit_Form::class.java)
startActivity(intent);
}
return view
}
This is my adapter class:
class Photo_Adapter(var context: Context?) : RecyclerView.Adapter<Photo_Adapter.ViewHolder>() {
var dataList = emptyList<Plant>()
internal fun setDataList(dataList: List<Plant>) {
this.dataList = dataList
notifyDataSetChanged()
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// Get the data model based on position
var data = dataList[position]
holder.title.text = data.name
holder.desc.text = data.type.toString()
holder.image.setImageResource(data.image)
holder.relativeLayout.setOnClickListener { view -> //Toast.makeText(view.getContext(),"click on item: "+model.getTitle(),Toast.LENGTH_LONG).show();
val intent = Intent(view.context, PlantDetails::class.java)
intent.putExtra("plant_name", data.name)
intent.putExtra("plant_image",data.image)
intent.putExtra("plant_type", data.type.type)
intent.putExtra("plant_water", data.type.water_time)
intent.putExtra("plant_details", data.type.details)
view.context.startActivity(intent)
}
}
// Provide a direct reference to each of the views with data items
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var image: ImageView
var title: TextView
var desc: TextView
var relativeLayout: CardView
init {
image = itemView.findViewById(R.id.image)
title = itemView.findViewById(R.id.title)
desc = itemView.findViewById(R.id.desc)
relativeLayout = itemView.findViewById<View>(R.id.relativeLayout) as CardView
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Photo_Adapter.ViewHolder {
// Inflate the custom layout
var view = LayoutInflater.from(parent.context).inflate(R.layout.photo_layout, parent, false)
return ViewHolder(view)
}
// total count of items in the list
override fun getItemCount() = dataList.size
}
Perhaps I forgot to add something? In Anyway I will be grateful for your help.
You should not observe data on your repository, your activity/view should observe this data. Take a look at this:
First, add this dependency to your gradle:
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
Then in your repository
class PlantRepository(application:Application){
private val plantDAO = PlantDatabase.getDatabase(application).plantDao()
fun getAllPlants(): Flow<List<Plant>> = plantDAO.getAll()
}
In your view model:
class PlantViewModel(
application: Application
): AndroidViewModel(application) {
private var repository = PlantRepository(application)
val allPlants = repository.getAllPlants()
.flowOn(Dispatchers.IO)
.asLiveData()
}
And your activity is fine, but check your adapter, make sure that you're notifing your adapter that your list has changed.
You can also work (collect) flows on your view, but this depends on you

Input validation with MVVM and Data binding

I try to learn the MVVM Architecture by implementing a very simple app that takes three inputs from the user and stores them in a Room Database then display the data in a RecyclerView.
From the first try it seems to work well, then the app crashes if one of the inputs is left empty. Now, I want to add some input validations (for now the validations must just check for empty string), but I can't figure it out. I found many answers on stackoverflow and some libraries that validates the inputs, but I couldn't integrate those solutions in my app (most probably it is due to my poor implementation of the MVVM).
This is the code of my ViewModel:
class MetricPointViewModel(private val repo: MetricPointRepo): ViewModel(), Observable {
val points = repo.points
#Bindable
val inputDesignation = MutableLiveData<String>()
#Bindable
val inputX = MutableLiveData<String>()
#Bindable
val inputY = MutableLiveData<String>()
fun addPoint(){
val id = inputDesignation.value!!.trim()
val x = inputX.value!!.trim().toFloat()
val y = inputY.value!!.trim().toFloat()
insert(MetricPoint(id, x , y))
inputDesignation.value = null
inputX.value = null
inputY.value = null
}
private fun insert(point: MetricPoint) = viewModelScope.launch { repo.insert(point) }
fun update(point: MetricPoint) = viewModelScope.launch { repo.update(point) }
fun delete(point: MetricPoint) = viewModelScope.launch { repo.delete(point) }
override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
}
override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
}
}
and this is the fragment where everything happens:
class FragmentList : Fragment() {
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null
//Binding object
private lateinit var binding: FragmentListBinding
//Reference to the ViewModel
private lateinit var metricPointVm: MetricPointViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
//Setting up the database
val metricPointDao = MetricPointDB.getInstance(container!!.context).metricCoordDao
val repo = MetricPointRepo(metricPointDao)
val factory = MetricPointViewModelFactory(repo)
metricPointVm = ViewModelProvider(this, factory).get(MetricPointViewModel::class.java)
// Inflate the layout for this fragment
binding = FragmentListBinding.inflate(inflater, container, false)
binding.apply {
lifecycleOwner = viewLifecycleOwner
myViewModel = metricPointVm
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initRecyclerview()
}
private fun displayPoints(){
metricPointVm.points.observe(viewLifecycleOwner, Observer {
binding.pointsRecyclerview.adapter = MyRecyclerViewAdapter(it) { selecteItem: MetricPoint -> listItemClicked(selecteItem) }
})
}
private fun initRecyclerview(){
binding.pointsRecyclerview.layoutManager = LinearLayoutManager(context)
displayPoints()
}
private fun listItemClicked(point: MetricPoint){
Toast.makeText(context, "Point: ${point._id}", Toast.LENGTH_SHORT).show()
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* #param param1 Parameter 1.
* #param param2 Parameter 2.
* #return A new instance of fragment FragmentList.
*/
// TODO: Rename and change types and number of parameters
#JvmStatic
fun newInstance(param1: String, param2: String) =
FragmentList().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
I'm planning also to add a long click to the recyclerview and display a context menu in order to delete items from the database. Any help would be appreciated.
My recycler view adapter implementation:
class MyRecyclerViewAdapter(private val pointsList: List<MetricPoint>,
private val clickListener: (MetricPoint) -> Unit): RecyclerView.Adapter<MyViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding: RecyclerviewItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.recyclerview_item, parent, false)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(pointsList[position], clickListener)
}
override fun getItemCount(): Int {
return pointsList.size
}
}
class MyViewHolder(private val binding: RecyclerviewItemBinding): RecyclerView.ViewHolder(binding.root){
fun bind(point: MetricPoint, clickListener: (MetricPoint) -> Unit){
binding.idTv.text = point._id
binding.xTv.text = point.x.toString()
binding.yTv.text = point.y.toString()
binding.listItemLayout.setOnClickListener{
clickListener(point)
}
}
}
Try the following,
fun addPoint(){
val id = inputDesignation.value!!.trim()
if(inputX.value == null)
return
val x = inputX.value!!.trim().toFloat()
if(inputY.value == null)
return
val y = inputY.value!!.trim().toFloat()
insert(MetricPoint(id, x , y))
inputDesignation.value = null
inputX.value = null
inputY.value = null
}
Edit:
you can try the following as well if you wish to let the user know that the value a value is expected
ViewModel
private val _isEmpty = MutableLiveData<Boolean>()
val isEmpty : LiveData<Boolean>
get() = _isEmpty
fun addPoint(){
val id = inputDesignation.value!!.trim()
if(inputX.value == null){
_isEmpty.value = true
return
}
val x = inputX.value!!.trim().toFloat()
if(inputY.value == null){
_isEmpty.value = true
return
}
val y = inputY.value!!.trim().toFloat()
insert(MetricPoint(id, x , y))
inputDesignation.value = null
inputX.value = null
inputY.value = null
}
//since showing a error message is an event and not a state, reset it once its done
fun resetError(){
_isEmpty.value = null
}
Fragment Class
metricPointVm.isEmpty.observe(viewLifecycleOwner){ isEmpty ->
isEmpty?.apply{
if(it){
// make a Toast
metricPointVm.resetError()
}
}
}

Categories

Resources