kotlin displaying data from Firebase database in RecyclerView in fragment - android

This is my first time building an application. I want to display my data in firebase realtimedatabase in recyclerview. but 'E/RecyclerView: No adapter attached; skipping layout' is on the Run chart.
I'll show you my codes in order.
at first, this is my Data class in kotlin
data class BalInputDTO(
var Id : String? = null,
var Itype: String? = null,
var Icategory: String? = null,
var ldate : String? = null,
var balance: String? = null,
var commnet: String? = null)
and then this is my adapter.kt
class BalAdapter(val context: Context, val BalList: ArrayList<BalInputDTO>) :
RecyclerView.Adapter<BalAdapter.Holder>() {
override fun onBindViewHolder(holder: Holder, position: Int) {
holder?.bind(BalList[position], context)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val view = LayoutInflater.from(context).inflate(R.layout.household_detail, parent, false)
return Holder(view)
}
override fun getItemCount(): Int {
return BalList.size
}
inner class Holder(view: View?) : RecyclerView.ViewHolder(view!!) {
val recordCategory = view?.findViewById<TextView>(R.id.record_category)
val recordNote = view?.findViewById<TextView>(R.id.record_note)
val recordAmount = view?.findViewById<TextView>(R.id.record_amount)
val recordDate = view?.findViewById<TextView>(R.id.record_date)
val recordDeleteImageView = view?.findViewById<ImageButton>(R.id.record_delete_image_button)
fun bind(bal: BalInputDTO, context: Context) {
recordCategory?.text = bal.Icategory
recordNote?.text = bal.commnet
recordAmount?.text = bal.balance
recordDate?.text = bal.ldate
// recordDeleteImageView.imageb
}
}
}
and the last code. this is my Fragment.kt (only onCreatView part)
var fragmentView : View? = null
var firedatabase : FirebaseDatabase? = null
var BalList : ArrayList<BalInputDTO> ? = null
var ref : DatabaseReference? = null
var mRecyclerView : RecyclerView? =null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragmentView= LayoutInflater.from(activity).inflate(R.layout.fragment_household, container, false)
firedatabase = FirebaseDatabase.getInstance()
mRecyclerView = fragmentView?.findViewById(R.id.household_recyclerview)
mRecyclerView?.setHasFixedSize(true)
mRecyclerView?.layoutManager = LinearLayoutManager(context)
BalList = arrayListOf<BalInputDTO>()
ref = FirebaseDatabase.getInstance().getReference("BalInput")
ref?.addValueEventListener(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun onDataChange(p0: DataSnapshot) {
if(p0!!.exists()){
for (h in p0.children){
val bal = h.getValue(BalInputDTO::class.java)
BalList?.add(bal!!)
}
val adapter = BalAdapter(context!!,BalList = ArrayList<BalInputDTO>())
mRecyclerView?.setAdapter(adapter)
}
}
})
return fragmentView
}
and this is my database enter image description here
Please let me know if I missed something or did something wrong.

It seems like you passing the wrong list to your adapter. Try this
val adapter = BalAdapter(context!!,BalList)
instead of
val adapter = BalAdapter(context!!,BalList = ArrayList<BalInputDTO>())

I'd say the real issue here is this line
val adapter = BalAdapter(context!!,BalList = ArrayList<BalInputDTO>())
try changing it to:
val adapter = BalAdapter(context!!,BalList)
since this is where you are adding all the elements from FB.

Related

My recyclerview resets when I add or delete an item from a Table (with Room) in Kotlin

First of all, I am Spanish so my english is not good.
I have an app with Kotlin and room, and it has a Recyclerview.
I have 3 tables: coaster, user and favorite.
The user can add coasters to favorite, and this is done succesfully.
The problem that I have is that when the user clicks on the button to add or delete from favorites, the recyclerview resets, it displays again. So it scrolls to the top of the Screen, and also some odd spaces appears after the element.
I also have a function to search, and it happens the same: spaces appears after each element when I am searching.
I have tried everything: notifyItemChanged,
notifyDataSetChanged... it doesnt work! I also tried removing the observer once from the recyclerview...
My main activity:
class CoasterFragment : Fragment() {
lateinit var coasterListener: CoasterListener
lateinit var usuarioCoaster: List\<UsuarioCoaster\>
private lateinit var searchView: SearchView
private lateinit var cAdapter: CoasterRecyclerViewAdapter
private var \_binding: FragmentCoasterBinding? = null
private val binding get() = \_binding!!
private val viewModel: CoastersViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentCoasterBinding.inflate(inflater, container, false)
val root: View = binding.root
/* val livedata = viewModel.coasters()
livedata.observe(viewLifecycleOwner,object: Observer <List<CoasterFavorito>> {
override fun onChanged(it: List<CoasterFavorito>) {
createRecyclerView(it)
livedata.removeObserver(this)
}
})*/
viewModel.coasters().observe(viewLifecycleOwner){createRecyclerView(it)}
coasterListener = CoasterListenerImpl(requireContext(), viewModel)
searchView = binding.search
searchView.clearFocus()
searchView.setOnQueryTextListener(object: SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
if(query != null){
searchDatabase(query)
}
return true
}
override fun onQueryTextChange(query: String?): Boolean {
if(query != null){
searchDatabase(query)
}
return true
}
})
return root
}
fun createRecyclerView(coasters: List<CoasterFavorito>) {
cAdapter =
CoasterRecyclerViewAdapter(
coasters as MutableList<CoasterFavorito>,
coasterListener,
requireContext()
)
val recyclerView = binding.recyclerCoaster
recyclerView.apply {
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
adapter = cAdapter
addItemDecoration(DividerItemDecoration(context, RecyclerView.VERTICAL))
cAdapter.notifyDataSetChanged()
}
}
fun searchDatabase(query: String) {
val searchQuery = "%$query%"
viewModel.searchDatabase(searchQuery).observe(viewLifecycleOwner) { createRecyclerView(it)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
my adapter:
class CoasterRecyclerViewAdapter(val coasters: List<CoasterFavorito>, val listener: CoasterListener,
val context: Context, ) : RecyclerView.Adapter<CoasterRecyclerViewAdapter.ViewHolder>(){
class ViewHolder private constructor(val binding: CoasterItemBinding, private val listener: CoasterListener,
private val context: Context): RecyclerView.ViewHolder(binding.root){
fun relleno(data: CoasterFavorito){
binding.nombre.text = data.coaster.nombre
binding.parque.text = data.coaster.parque
binding.ciudad.text = data.coaster.ciudad
binding.provincia.text = data.coaster.provincia
binding.comunidad.text = data.coaster.comunidadAutonoma
Glide
.with(context)
.load(data.coaster.imagen)
.centerCrop()
.into(binding.imagen)
binding.check.isChecked = data.favorito
binding.check.setOnClickListener{
if (data.favorito) {
listener.delFavorito(data.coaster.id)
binding.check.isChecked = false
} else {
listener.addFavorito(data.coaster.id)
binding.check.isChecked = true
}
}
}
companion object{
fun crearViewHolder(parent: ViewGroup, listener: CoasterListener, adapter: CoasterRecyclerViewAdapter, context: Context):ViewHolder{
val layoutInflater = LayoutInflater.from(parent.context)
val binding = CoasterItemBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding, listener, context )
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder.crearViewHolder(parent, listener, this, context)
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.relleno(coasters[position])
override fun getItemCount() = coasters.size
}
interface CoasterListener {
fun addFavorito(id: Long)
fun delFavorito(id: Long)
}
I have tried everything: notifyItemChanged,
notifyDataSetChanged... it doesnt work! I also tried removing the observer once from the recyclerview...
Your createRecyclerView function should be invoked only once in a whole lifecycle of the Fragment. You should not create any new RecyclerView.Adapter, or set a LayoutManager to the RecyclerView every time your data set changes.
Therefore the Observer used in viewModel.coasters.observe() should only submit a new List to the existing Adapter and call .notifyDataSetChanged(), or other notifying functions.

Can't load firebase details to recycler view ? Kotlin

I need to get details from the Firebase Realtime Database to RecyclerView which is in a fragment. I refer to many tutorials and finally code this. I got this error:
Here's my fragment code.
class busFragment : Fragment() {
private lateinit var dbref: DatabaseReference
private lateinit var routeRecyclerView: RecyclerView
private lateinit var routesArrayList: ArrayList<BusRoutes>
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_bus, container, false)
}
#RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
routeRecyclerView = view.findViewById(R.id.bus_routes)
routeRecyclerView.layoutManager = LinearLayoutManager(activity)
routeRecyclerView.setHasFixedSize(true)
routesArrayList = arrayListOf<BusRoutes>()
getRouteDetails()
}
private fun getRouteDetails() {
dbref = FirebaseDatabase.getInstance().getReference("BusRoutes")
dbref.addValueEventListener(object : ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()){
for (routeSnapshot in snapshot.children){
val route = routeSnapshot.getValue(BusRoutes::class.java)
routesArrayList.add(route!!)
}
routeRecyclerView.adapter = Adapter(routesArrayList)
}
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})
}
companion object {
}
}
here's my adapter.kt
class Adapter(private val routeslist: ArrayList<BusRoutes>) : RecyclerView.Adapter<Adapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.items_busroutes,
parent,false)
return MyViewHolder(itemView as ViewGroup)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = routeslist[position]
holder.route.text = currentItem.routeNo
holder.start.text = currentItem.start
holder.end.text = currentItem.end
}
override fun getItemCount(): Int {
return routeslist.size
}
class MyViewHolder(itemView:ViewGroup) : RecyclerView.ViewHolder(itemView){
val route : TextView = itemView.findViewById(R.id.routeNo)
val start : TextView = itemView.findViewById(R.id.startPlace)
val end : TextView = itemView.findViewById(R.id.endPlace)
}
}
Here's my data class.
Here's my firebase details.
Here's the line 52 val route = routeSnapshot.getValue(BusRoutes::class.java)
I try to fix this many times but still cannot find it. Help me. I'm still Learning Kotlin.
updated
BusRoutes class:
data class BusRoutes(val routeNo: String? = null, val start: String? = null, val end: String? = null)
You are getting the following error:
failed to convert value of type java.lang.long to string
Because the routeNo field in your database is of type number, while in your class is of type String and this is not correct. Both types must match because there is no way in Kotlin in which you can convert an object type Long to String, hence that error.
The simplest solution would to change the declaration fo your BusRoutes class like so:
data class BusRoutes(val routeNo: Long? = null, val start: String? = null, val end: String? = null)
// ^^

Recyclerview does not refresh

I try to populate an array with data from a room database.
The data is only populated in the recyclerview after I refresh the
fragment. Once refreshed, everything is displayed. Anyone know why?
So the weird thing is that everything works fine but only once the fragment is refreshed.
I already implemented the notifydataset changed function but without success.
class WorkoutFragment : Fragment() {
private lateinit var homeViewModel: HomeViewModel
private var _binding: FragmentWorkoutBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
#SuppressLint("NotifyDataSetChanged")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
homeViewModel =
ViewModelProvider(this).get(HomeViewModel::class.java)
_binding = FragmentWorkoutBinding.inflate(inflater, container, false)
val root: View = binding.root
val sharedPreference = activity?.getSharedPreferences("WORKOUT_BUTTON", Context.MODE_PRIVATE)
val sharedPreference2 = activity?.getSharedPreferences("PREFERENCE_NAME", Context.MODE_PRIVATE)
var editor = sharedPreference?.edit()
val recyclerView = binding.recyclerview
recyclerView?.layoutManager = LinearLayoutManager(context)
var workout: MutableList<String> = mutableListOf()
var workoutid: MutableList<Int> = mutableListOf()
workout.clear()
workoutid.clear()
val adapter = AdapterWorkoutsHeader(workoutid as ArrayList<Int>,
workout as ArrayList<String>
)
recyclerView?.adapter = adapter
val user = ParseUser.getCurrentUser()
val parkname = user.get("parkname")
val currentSpot: TextView = binding.currentSpot
currentSpot.text = parkname.toString()
val openNav: ImageButton = binding.openNav
openNav.setOnClickListener {
val mDrawerLayout: DrawerLayout? = activity?.findViewById(R.id.drawer_layout)
mDrawerLayout!!.openDrawer(GravityCompat.START)
}
val addWorkout: Button = binding.button
addWorkout.setOnClickListener {
val intent = Intent(requireContext(), ChooseWorkoutActivity::class.java)
startActivity(intent)
}
Thread{
val db = Room.databaseBuilder(
requireContext(),
AppDatabase::class.java, "workout.db"
).build()
val userDao = db.todoDao()
for (i in 0 until userDao.getWorkoutSize()){
userDao.getChosenWorkout(i)[0].workout?.let { workout.add(it) }
userDao.getChosenWorkout(i)[0].workoutid?.let { workoutid.add(it) }
activity?.runOnUiThread {
val adapter = AdapterWorkoutsHeader(workoutid as ArrayList<Int>,
workout as ArrayList<String>
)
recyclerView?.adapter = adapter
adapter.notifyDataSetChanged()
}
}
//var parkNames2 = parkNames.toTypedArray()
//recyclerView.adapter?.notifyDataSetChanged()
}.start()
return root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Data updating should be done from the UI thread
runOnUiThread {
recyclerView?.adapter = adapter
adapter.notifyDataSetChanged()
}
Synchronization problem
var workout: MutableList<String> = mutableListOf()
var workoutid: MutableList<Int> = mutableListOf()
is on UI thread, but modified from the other thread, so synchronization or synchronized collections Collections.synchronizedList can be used

Is there any way to refresh custom view inside baseadapter after updating data?

I have a custom view inside a baseadapter that is shown on listview. Currently I can update the items inside database but I cannot refresh the view. I want to refresh the view automatically after updating.Is there anyway to refresh? Please take a look at my code.
This is modle class-
class Item_Detail {
var qty:Int = 0
var id:Int = 0
constructor()}
This is DatabaseHandler-
val Detail: List<Item_Detail>
get() {
val bb=this.writableDatabase
val seItem=ArrayList<Item_Detail>()
val myPath=DB_PATH + REAL_DATABASE
val db=SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY)
val selectQuery="Select * From Transaction_table Where date='$rowview_date' And location='$rowview_location'"
val cursor=db.rawQuery(selectQuery, null)
if (cursor.moveToFirst()) {
do {
val item=Item_Detail()
item.id=cursor.getInt(cursor.getColumnIndex("_id2"))
item.qty=cursor.getInt(cursor.getColumnIndex("quantity2"))
seItem.add(item)
} while(cursor.moveToNext())
}
db.close()
return seItem
}
This is MainActivity-
class Detail : AppCompatActivity() {
internal lateinit var db: DataBaseHelper
internal var seItem: List<Item_Detail> = ArrayList<Item_Detail>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_detail)
db = DataBaseHelper(this)
detail_date.text = rowview_date.toString()
detail_location.text = rowview_location.toString()
getDetail()
}
private fun getDetail() {
seItem = db.Detail
val adapter = Adapter_detail(this, seItem,this)
list_detail.adapter=adapter
list_detail.invalidate()
}}
This is Adapterclass-
class Adapter_detail(
internal var activity: Activity,
internal var stitem: List<Item_Detail>,
val context:Context
) : BaseAdapter() {
internal lateinit var db: DataBaseHelper
internal var inflater: LayoutInflater
init {
inflater = activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val rowView:View
rowView = inflater.inflate(R.layout.detail_row, null)
rowView.detail_id.text = stitem[position].id.toString()
rowView.detail_qty.text = stitem[position].qty.toString()
rowView.btn_detail.setOnClickListener{
db=DataBaseHelper(context)//Initiate the database
val builder=android.app.AlertDialog.Builder(context)
inflater=activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val view=inflater.inflate(R.layout.edit_layout, null)
builder.setView(view)
val dialog: android.app.AlertDialog=builder.create()
var tran_edit=view.findViewById<EditText>(R.id.edt_edit)
tran_edit.setText(stitem[position].qty.toString())
dialog.show()
var btn_edt=view.findViewById<Button>(R.id.btn_edit)
btn_edt.setOnClickListener {
val item=Item(
Integer.parseInt(stitem[position].id.toString()),
Integer.parseInt(1.toString()),
tran_edit.text.toString(),
name.toString(),
record_date.toString(),
record_location.toString(),
master_record.toString().toInt()
)
db.updateItem(item)
dialog.dismiss()
}
}
return rowView
}
override fun getItem(position: Int): Any {
return stitem[position]
}
override fun getItemId(position: Int): Long {
return stitem[position].id!!.toLong()
}
override fun getCount(): Int {
return stitem.size
}
You need to call notifyDataSetChanged() on your adapter object when you refresh data. You need to do following changes to make use of it,
MainActivity
class Detail : AppCompatActivity() {
internal lateinit var db: DataBaseHelper
internal var seItem: MutableList<Item_Detail> = ArrayList<Item_Detail>()
internal lateinit var adapter : Adapter_detail
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_detail)
db = DataBaseHelper(this)
detail_date.text = rowview_date.toString()
detail_location.text = rowview_location.toString()
adapter = Adapter_detail(this, seItem,this)
list_detail.adapter=adapter
getDetail()
}
private fun getDetail() {
adapter.refresh(db.Detail)
}
}
Adapterclass
class Adapter_detail(
internal var activity: Activity,
internal var stitem: MutableList<Item_Detail>,
val context:Context
) : BaseAdapter() {
...
fun refresh(newList: List<Item_Detail>) {
stitem.clear()
stitem.addAll(newList)
notifyDataSetChanged()
}
}
N.B. I wrote this solution using mobile so please excuse any syntax error.
Let me know if it works?
P.S. There is a better adapter implementation i.e. CursorAdapter which is especially designed for using with database cursor. You can follow this tutorial to integrate it.
Update
I forgot one thing, you need to call refresh() inside your ClickListener callback in getView() function where you perform the update operation. So, it will look like as follows,
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
...
btn_edt.setOnClickListener {
val item= ...
db.updateItem(item)
dialog.dismiss()
refresh(db.Detail)
}
}
return rowView
}

Error is shown by logcat. I am not able to recoganise where I am going wrong. Let me know my mistake [duplicate]

This question already has answers here:
Android Fragment no view found for ID?
(40 answers)
Closed 4 years ago.
Its showing that no view found. But what does that I am not able to understand.
I think problem is in OnCreateView() function as there is only the parameter where view is passed.
Should I use try and catch method?
code for MainScreenFragment
class MainScreenFragment : Fragment() {
var getsongsList: ArrayList<Songs>?=null
var nowPlayingButtonBar: RelativeLayout?=null
var playPauseButton: ImageButton?=null
var songTitle: TextView?=null
var visibleLayout: RelativeLayout?=null
var noSongs: RelativeLayout?=null
var recyclerView: RecyclerView?=null
var myActivate: Activity?=null
var _mainScreenAdapter: MainscreenAdapter?=null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view= inflater.inflate(R.layout.main_screen_fragment,container,
false)
visibleLayout=view?.findViewById<RelativeLayout>(R.id.visibleLayout)
noSongs= view?.findViewById<RelativeLayout>(R.id.noSongs)
nowPlayingButtonBar= view?.findViewById<RelativeLayout>
(R.id.hiddenMainScreen)
songTitle = view?.findViewById<TextView>(R.id.songName)
playPauseButton= view?.findViewById<ImageButton>
(R.id.playPauseButton)
recyclerView=view?.findViewById<RecyclerView>(R.id.contentMain)
return view }
#RequiresApi(Build.VERSION_CODES.O)
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
getsongsList = getSongsFromPhone()
_mainScreenAdapter= MainscreenAdapter(getsongsList as
ArrayList<Songs>, myActivate as Context)
val mLayoutManager = LinearLayoutManager(myActivate)
recyclerView?.layoutManager = mLayoutManager
recyclerView?.itemAnimator = DefaultItemAnimator()
recyclerView?.adapter = _mainScreenAdapter
}
override fun onAttach(context: Context?) {
super.onAttach(context)
myActivate = context as Activity
}
override fun onAttach(activity: Activity?) {
super.onAttach(activity)
myActivate= activity
}
#RequiresApi(Build.VERSION_CODES.O)
fun getSongsFromPhone(): ArrayList<Songs> {
val arrayList =ArrayList<Songs>()
val contentResolver = myActivate?.contentResolver
val songUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
val songCursor = contentResolver?.query(songUri,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 songAdded =
songCursor.getColumnIndex(MediaStore.Audio.Media.DATE_ADDED)
while (songCursor.moveToNext()){
val currentID =songCursor.getLong(songId)
val currentTitle =songCursor.getString(songTitle)
val currentArtist =songCursor.getString(songArtist)
val currentData =songCursor.getString(songData)
val currentAdded =songCursor.getLong(songAdded)
arrayList.add(Songs(currentID,
currentTitle,currentArtist,currentData,currentAdded))
songCursor.close()
}
}
return arrayList
}
}
No view found mostly comes when you are taking the wrong id of the FrameLayout which is on another XML file and replacing the fragment in that FrameLayout. Just check once that you are replacing your fragment in the correct FrameLayout.

Categories

Resources