I am using a recycleview to display latest inputs from the user. When I click edit text, the old inputs move up and the keyboard shows in the app. But when I enter the next entry, I have to minimize the keyboard to be able to see the new entry. It doesnt move up - this was expected behaviour as the recycleview is updated.
Stage 1)
Old Entry
Old Entry
Stage 2) Click edit text to make new entry
Old Entry
Old Entry
Keyboard
Stage 3) New text is entered
Old Entry
Old Entry
Keyboard
Stage 4) Keyboard is hidden,/minimized
Old Entry
Old Entry
New Entry
The layout is stacked from the end... what can I change to make this work?
AndroidManifest setting
<activity
android:name=".Contacts.ui.ContactDetailsHome"
android:configChanges="orientation"
android:parentActivityName=".MainActivity"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize" />
fragment code
class ContactRelationship : Fragment() {
companion object {
#JvmStatic
fun start(context: Context, contact: Contact?, isEdit: Boolean) {
val starter = Intent(context, ContactRelationship::class.java)
.putExtra("contact", contact as Serializable)
.putExtra("Edit", isEdit)
context.startActivity(starter)
}
}
private val _updatesLiveData = MutableLiveData<ArrayList<ContUPDt>>()
private val updatesLiveData: LiveData<ArrayList<ContUPDt>> = _updatesLiveData
var firebaseUser: FirebaseUser? = null //the used id of the user using the app
var userLookupKey: String = ""
private lateinit var contact: Contact
var mupdateList: List<ContUPDt>? = null
var contUpdateAdapter: ContUpdateAdapter? = null
var reference: DatabaseReference? = null
lateinit var rvContRelUpdate: RecyclerView
private lateinit var progressBar: ProgressDialog
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
val view: View =
inflater.inflate(R.layout.fragment_view_contact_relationship, container, false)
progressBar = ProgressDialog(activity)
progressBar.setCancelable(false)
firebaseUser = FirebaseAuth.getInstance().currentUser
rvContRelUpdate = view.findViewById(R.id.rvContRelUpdate)
rvContRelUpdate.setHasFixedSize(true)
val linearLayoutManager = LinearLayoutManager(activity)
linearLayoutManager.stackFromEnd = true
rvContRelUpdate.layoutManager = linearLayoutManager
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
contact = (activity?.intent?.getSerializableExtra("contact") as Contact)
//assigning it to fields to be displayed
userLookupKey = contact.lookupKey
val contId = contact.id
//get updates
retrieveUpdates(userLookupKey)
//Implementing Send Update
send_update_btn.setOnClickListener {
val updateTxt = text_messageact.text.toString()
if (updateTxt == "") {
Toast.makeText(activity, "The update is empty.", Toast.LENGTH_LONG)
.show()
} else {
sendMessageToUser(userLookupKey, contId, updateTxt)
}
text_messageact.setText("")
}
private fun sendMessageToUser(userLookupKey: String, contId: String, updateMsg: String) {
val currentCalendar = Calendar.getInstance()
//creating object in DB
val db = activity?.let { AppDatabase.getDatabase(it) }
val ev = ContUPDt(
0,
userLookupKey,
contId,
currentCalendar.timeInMillis,
updateMsg,
null,
"Update"
)
db!!.ContUpdateDao().addcontUpdate(ev)
}
private fun retrieveUpdates(userLookupKey: String) {
val mupdateList = ArrayList<ContUPDt>()
val db = AppDatabase.getDatabase(requireActivity())
db.ContUpdateDao().getcontUpdate(userLookupKey).observe(viewLifecycleOwner, Observer {
val contUpdt = it
mupdateList.clear()
for (i in contUpdt) {
mupdateList.add(i)
}
})
contUpdateAdapter = ContUpdateAdapter(requireActivity(), mupdateList)
rvContRelUpdate.adapter = contUpdateAdapter
}
}
Adapter code
class ContUpdateAdapter(
mContext: Context,
mupdateList: List<ContUPDt>
) : RecyclerView.Adapter<ContUpdateAdapter.ViewHolder?>() {
private val mContext: Context
private val mupdateList: List<ContUPDt>
init {
this.mContext = mContext
this.mupdateList = mupdateList
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var updateDTTime: TextView? = null
var updateMessage: TextView? = null
var updateType: TextView? = null
init {
updateDTTime = itemView.findViewById(R.id.updateDTTime)
updateMessage = itemView.findViewById(R.id.updateMessage)
updateType = itemView.findViewById(R.id.updateType)
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
position: Int
): ViewHolder {
return ViewHolder (
LayoutInflater.from(parent.context)
.inflate(R.layout.contactupdate, parent, false)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val contUpdte: ContUPDt = mupdateList[position]
val formatter = SimpleDateFormat(" E, HH:MM a, dd MMM yyyy", Locale.getDefault())
val updatetime = formatter.format(contUpdte.timestamp)
holder.updateDTTime!!.text = updatetime
holder.updateMessage!!.text = contUpdte.update
holder.updateType!!.text = contUpdte.actType
}
override fun getItemCount(): Int {
return mupdateList.size
}
}
Just scroll RecyclerView to last position after adding new item:
rvContRelUpdate.scrollToPosition(contUpdateAdapter.itemCount - 1)
Related
I tried to make recyclerView on Fragment with data from Firestore.
I've made viewHolder and Adapter.
And all seems fine, but I keep facing below issue:
java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.recyclerview.widget.RecyclerView.setAdapter(androidx.recyclerview.widget.RecyclerView$Adapter)' on a null object reference
I know many questions are already asked, but I can't solve this problem with their solution.
Please help me which part cause problem in my code.
Data Class
data class DiaryCard(var name: String, var stepCount: String, var mood: Long? =2131230873, var diary: String? = "" )
Fragement:
class AllDiaryFragment : Fragment() {
private val db = Firebase.firestore
private val userDB = db.collection("users")
private val diaryDB = db.collection("diary")
private val userId = Firebase.auth.currentUser?.uid
private var _binding: FragmentAllDiaryBinding? = null
private val binding get() = _binding!!
private var username: String = "안녕"
private var userStepCount: String = "342"
private var items: ArrayList<DiaryCard> = ArrayList()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentAllDiaryBinding.inflate(inflater, container, false)
val view = binding.root
userDB
.document("$userId")
.get()
.addOnSuccessListener { document ->
username = document.data?.getValue("name").toString()
userStepCount = document.data?.getValue("todayStepCount").toString()
}
diaryDB
.get()
.addOnSuccessListener { documents ->
for (document in documents) {
items.add(
DiaryCard(
username,
userStepCount,
(document.data?.getValue("todayMood") as Map<*,*>)["image"] as Long,
document.data?.getValue("todayDiary").toString(),
)
)
}
}
var adapter = DiaryCardAdapter(requireContext(), items)
recyclerDiary.adapter = adapter
recyclerDiary.layoutManager = LinearLayoutManager(
context,
RecyclerView.VERTICAL,
false
)
return view
}
}
Adapter
class DiaryCardAdapter(val context: Context, items: ArrayList<DiaryCard>) :
RecyclerView.Adapter<DiaryCardAdapter.CardViewHolder>() {
var items = items
inner class CardViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var userNameView: TextView = itemView.findViewById<TextView>(R.id.userName)
var userStepCountView: TextView = itemView.findViewById<TextView>(R.id.userStepCount)
var userMoodView: ImageView = itemView.findViewById<ImageView>(R.id.userMood)
var userDiaryView: TextView = itemView.findViewById<TextView>(R.id.userDiary)
fun bind(position: Int) {
userNameView.text = items[position].name
userStepCountView.text = items[position].stepCount
userMoodView.setImageResource((items[position].mood ?: 2131230873).toInt())
userDiaryView.text = items[position].diary
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.diary_card, parent, false)
return CardViewHolder(v)
}
override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
holder.bind(position)
}
override fun getItemCount(): Int = items.size
}
Thank you!
It seems like you have mixed data binding with the kotlin synthetics.
Just avoid using the synthetics to prevent the null pointer exception.
Replace recyclerDiary with the binding.recyclerDiary.
Update:
diaryDB
.get()
.addOnSuccessListener { documents ->
for (document in documents) {
items.add(
DiaryCard(
username,
userStepCount,
(document.data?.getValue("todayMood") as Map<*,*>)["image"] as Long,
document.data?.getValue("todayDiary").toString(),
)
)
}
}
This function is asynchronous. That means addOnSuccessListener will be called after you set up the adapter with recyclerview.
So you have to notify the adapter about the change in items.
You can do that by calling adapter.notifyDatasetChanged() in success listener. So final code will be look a like,
var adapter = DiaryCardAdapter(requireContext(), items)
diaryDB
.get()
.addOnSuccessListener { documents ->
for (document in documents) {
items.add(
DiaryCard(
username,
userStepCount,
(document.data?.getValue("todayMood") as Map<*,*>)["image"] as Long,
document.data?.getValue("todayDiary").toString(),
)
)
}
adapter.notifyDatasetChanged()
}
recyclerDiary.adapter = adapter
Add binding before recyclerDiary and try again!
binding.recyclerDiary.adapter = adapter
binding.recyclerDiary.layoutManager = LinearLayoutManager(
context,
RecyclerView.VERTICAL,
false
)
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
I'm retrieving a list of items from the firebase database and displaying them using RecyclerView in my android app. The problem is when I'm retrieving more than 9 items and updating some data in the 1st item (while running) as you can see in the image below the 10th item also got affected and vice-versa same with 2nd and 11th item and so on.
Like when I'm adding and fixing the quantity of the 1st item , the same happens with the 10th item (the 10th item also get added with the same quantity as of 1st item).
MenuFragment.kt
class MenuFragment : Fragment() {
private var dishList: MutableList<DishModel> = mutableListOf()
private lateinit var myRef: DatabaseReference
lateinit var list: RecyclerView
lateinit var proceedToCartLayout: RelativeLayout
lateinit var addToCartBtn: Button
private var selectedCategory = ""
companion object {
fun newInstance(): Fragment {
return MenuFragment()
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_menu, container, false)
//retrieve id
val bundle = this.arguments
selectedCategory = bundle!!.getString("CATEGORY_ID")!!
list = view.findViewById(R.id.recyclerMenu)
myRef = FirebaseDatabase.getInstance().getReference("Category")
proceedToCartLayout = view.findViewById(R.id.ProceedToCart)
addToCartBtn = view.findViewById(R.id.btn_cart)
return view
}
override fun onResume() {
if (ConnectionManager().checkConnectivity(activity as Context)) {
fetchMenu()
} else {
val alterDialog = androidx.appcompat.app.AlertDialog.Builder(activity as
Context)
alterDialog.setTitle("No Internet")
alterDialog.setMessage("Connect to internet to continue")
alterDialog.setIcon(R.drawable.nointernet)
alterDialog.setPositiveButton("Open Settings") { _, _ ->
val settingsIntent = Intent(Settings.ACTION_SETTINGS)//open wifi settings
startActivity(settingsIntent)
}
alterDialog.setNegativeButton("Exit") { _, _ ->
ActivityCompat.finishAffinity(activity as Activity)
}
alterDialog.setCancelable(false)
alterDialog.create()
alterDialog.show()
}
super.onResume()
}
private fun fetchMenu() {
myRef.child(selectedCategory).addValueEventListener(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
Toast.makeText(context, "$p0", Toast.LENGTH_SHORT).show()
}
override fun onDataChange(p0: DataSnapshot) {
if (p0.exists()) {
dishList.clear()
for (i in p0.children) {
val plan = i.getValue(DishModel::class.java)
dishList.add(plan!!)
}
val adapter = MenuAdapter(
context!!,
R.layout.menu_list_item,
dishList,
proceedToCartLayout,
addToCartBtn, selectedCategory
)
list.adapter = adapter
}
}
})
}
}
MenuAdapter.kt
class MenuAdapter(
private val ctx: Context,
private val layoutResId: Int,
private val dishList:
List<DishModel>,
private val proceedToCartPassed: RelativeLayout,
private val buttonProceedToCart: Button,
private val categoryName: String
) : RecyclerView.Adapter<MenuAdapter.MyViewHolder>() {
private lateinit var proceedToCart: RelativeLayout
private var itemSelectedCount: Int = 0
private var itemsSelectedId = arrayListOf<String>()
private var itemList = arrayListOf<DishModel>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MenuAdapter.MyViewHolder {
val layoutInflater: LayoutInflater = LayoutInflater.from(ctx)
val view: View = layoutInflater.inflate(layoutResId, null)
return MyViewHolder(view)
}
override fun onBindViewHolder(holder: MenuAdapter.MyViewHolder, position: Int) {
var btnClick = false
var no = 0
val range = 1..5
val dish = dishList[position]
holder.dishName.text = dish.dishName
holder.dishPrice.text = "Rs " + dish.cost + " /-"
Picasso.get().load(dish.image).error(R.drawable.defaultdish).into(holder.dishImage)
holder.increaseBtn.setOnClickListener {
if (btnClick) {
no += 1
if (no in range) {
holder.quantity.text = no.toString()
dish.qty = no
}
}
}
holder.decreaseBtn.setOnClickListener {
if (btnClick) {
no -= 1
if (no in range) {
holder.quantity.text = no.toString()
dish.qty = no
}
}
}
proceedToCart = proceedToCartPassed
buttonProceedToCart.setOnClickListener {
val intent = Intent(ctx, CartActivity::class.java)
intent.putExtra(
"categoryId",
categoryName
)
intent.putExtra(
"selectedItemsId",
itemsSelectedId
)
intent.putExtra("itemList", itemList)
ctx.startActivity(intent)
}
holder.buttonAddToCart.setOnClickListener {
if (holder.buttonAddToCart.text.toString() == "Remove") {
itemSelectedCount--//unselected
itemsSelectedId.remove(holder.buttonAddToCart.tag.toString())
itemList.remove(dish)
holder.buttonAddToCart.text = "Add"
holder.buttonAddToCart.setBackgroundResource(R.drawable.rounded_corners)
btnClick = false
no = 0
dish.qty = no
holder.quantity.text = no.toString()
} else {
itemSelectedCount++//selected
itemsSelectedId.add(holder.buttonAddToCart.tag.toString())
itemList.add(dish)
holder.buttonAddToCart.text = "Remove"
holder.buttonAddToCart.setBackgroundResource(R.drawable.rounded_corners_yellow)
btnClick = true
no = 1
dish.qty = no
holder.quantity.text = no.toString()
}
if (itemSelectedCount > 0) {
proceedToCart.visibility = View.VISIBLE
} else {
proceedToCart.visibility = View.GONE
}
}
holder.buttonAddToCart.tag = dishList.indexOf(dish) + 1
}
override fun getItemCount(): Int {
return dishList.size
}
inner class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val dishName: TextView = view.findViewById(R.id.txtDishName)
val dishPrice: TextView = view.findViewById(R.id.txtDishPrice)
val dishImage: ImageView = view.findViewById(R.id.dishImage)
val buttonAddToCart: Button = view.findViewById(R.id.add_dish)
val increaseBtn: Button = view.findViewById(R.id.increase)
val decreaseBtn: Button = view.findViewById(R.id.decrease)
val quantity: TextView = view.findViewById(R.id.quantity)
}
}
Please Add
holder.quantity.text = dish.qty.toString() after or before as you wish Picasso.get().load(dish.image).error(R.drawable.defaultdish).into(holder.dishImage)
Something like below.
Picasso.get().load(dish.image).error(R.drawable.defaultdish).into(holder.dishImage)
holder.quantity.text = dish.qty.toString()
holder.increaseBtn.setOnClickListener
Because I can see you are not setting holder.quantity.text thinking your recyclerview item view have the default as 0 but as you know in recyclerview the views are been reused and when they. are been reused the default value is not 0 for that but the value last view which is going to be reused, which in your case is every 10th item.
The golden rule if you are working with recylerview and thinking of using default value of any view from the XML view itself, so you will be surprised when you start scrolling you will notice that that is not the case and the default value is something other than that.
Always set the default value in onBind so next time the view been reused it takes the default value you want
if you are having an if condition to update something do write it's else as well because the same caching will make you wonder why your items are behaving differently.
Important point, make sure your setting default value in 1st point is outside any clicklistener or any view listener you set up because we have to make sure the setter gets executed every time the onBind is getting called
I have my fair share of experience working with checkbox, edittext , radio button etc in items and giving me surprising result on scrolling hence I always make sure to follow the above points when working with recylerviews.
I added the below code in my adapter (override getItemViewType) and it worked !
override fun getItemViewType(position: Int): Int {
return position
}
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()
}
}
}
I am creating a music player app, In which there are a recycler view and many fragments. I am getting an error on my MainScreenFragment.kt file. I checked the error log and the error its shows is:
******Caused by: kotlin.TypeCastException: null cannot be cast to non-null type
kotlin.collections.ArrayList /* =
java.util.ArrayList */
at
com.thepanku.musicplayer.Fragments.MainScreenFragment.onCreateView(MainScreenFragment.kt:60)
at
android.support.v4.app.Fragment.performCreateView(Fragment.java:2354)******
what would be the reason for the error, any help would be appreciable. I am fed up with the error.
Here is the code:
class MainScreenFragment : Fragment() {
var getSongList : ArrayList<Songs>? = null
var nowPlayingBottomBar: RelativeLayout?=null
var playPauseButton: ImageView?=null
var songTitle: TextView?=null
var visibleLayout: RelativeLayout?=null
var noSongs: RelativeLayout?=null
var recyclerView: RecyclerView?= null
var myActivity:Activity?=null
var _mainScreenAdapter : MainScreenAdapter?=null
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view = inflater?.inflate(R.layout.content_main, container, false)
setHasOptionsMenu(true)
activity.title = "All songs"
visibleLayout = view?.findViewById<RelativeLayout>(R.id.visibleLayout)
noSongs = view?.findViewById<RelativeLayout>(R.id.noSongs)
nowPlayingBottomBar = view?.findViewById<RelativeLayout>(R.id.hiddenBarMainScreen)
songTitle = view?.findViewById<TextView>(R.id.songTitleMainScreen)
playPauseButton = view?.findViewById<ImageButton>(R.id.playpauseButton)
(nowPlayingBottomBar as RelativeLayout).isClickable = false
recyclerView = view?.findViewById<RecyclerView>(R.id.contentMain)
visibleLayout?.visibility = View.INVISIBLE
noSongs?.visibility = View.VISIBLE
//getting error on this line->
_mainScreenAdapter = MainScreenAdapter(getSongList as ArrayList<Songs>, activity)
val mLayoutManager = LinearLayoutManager(activity)
(recyclerView as RecyclerView).layoutManager = mLayoutManager
(recyclerView as RecyclerView).itemAnimator = DefaultItemAnimator()
(recyclerView as RecyclerView).adapter = _mainScreenAdapter
return view
}
fun getSongsFromPhone(): ArrayList<Songs>{
var arrayList = ArrayList<Songs>()
var contentResolver = myActivity?.contentResolver
var songUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
var songCursor = contentResolver?.query(songUri, null, null, null, null)
if(songCursor!=null && songCursor.moveToFirst()){
val songId = songCursor.getColumnIndex(MediaStore.Audio.Media._ID)
val SongTitle = songCursor.getColumnIndex((MediaStore.Audio.Media.TITLE))
val songArtist = songCursor.getColumnIndex(MediaStore.Audio.Media.ARTIST)
val songData = songCursor.getColumnIndex(MediaStore.Audio.Media.DATA)
val dateIndex = songCursor.getColumnIndex(MediaStore.Audio.Media.DATE_ADDED)
while(songCursor.moveToNext()){
var currentId = songCursor.getLong(songId)
var currentTitle = songCursor.getString(SongTitle)
var currentArtist = songCursor.getString(songArtist)
var currentData = songCursor.getString(songData)
var currentDate = songCursor.getString(dateIndex)
}
}
return arrayList
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
getSongList = getSongsFromPhone()
}
override fun onAttach(context: Context?) {
super.onAttach(context)
myActivity = context as Activity
}
override fun onAttach(activity: Activity?) {
super.onAttach(activity)
myActivity = activity
}
}// Required empty public constructor
You have
var getSongList : ArrayList<Songs>? = null
and then you say
getSongList as ArrayList<Songs>
Clearly, getSongList is null, but you insist on casting it into a not-null type ArrayList<Song>. Your hope is that it will be initialized in onCreateActivity, but apparently that callback's turn hasn't yet come at the point when your onCreateView got called.
I am sorry to hear that you are fed up with this error and hope you'll be able to provide a non-null value for your getSongList, for example by calling getSongsFromPhone().