i have a fragment with a RecyclerView and TextInputEditText (for searching through Firebase Database)
When I start writing some text to filter the RecyclerView, I get the correct result, however if I remove text from TextInputEditText, the positions of the elements are not updated. For example: I have a RecyclerView of 3 elements, I filter by name and RecyclerView shows me only the third element, then I delete all the text with the TextInputEditText, all elements are returned, but my third element has the position of the first element. How can i fix this problem?
My code and video below:
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
setupPlacesAdapter()
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
FirebaseApp.initializeApp(requireContext())
getPlaces()
binding.searchBar.addTextChangedListener(object: TextWatcher {
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, size: Int) {
val query = binding.searchBar.text.toString().uppercase()
if (query.isEmpty()) {
getPlaces()
} else {
filterByName(query)
}
}
})
}
private fun setupPlacesAdapter() {
placeAdapter = PlaceAdapter()
binding.rvPlaces.layoutManager = LinearLayoutManager(
requireContext(),
LinearLayoutManager.HORIZONTAL,
false
)
binding.rvPlaces.adapter = placeAdapter
}
private fun getPlaces() {
val reference = FirebaseDatabase.getInstance().getReference("Places")
reference.addValueEventListener(object: ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()) {
val list = ArrayList<Place>()
for (placeSnapshot in snapshot.children) {
val place = placeSnapshot.getValue(Place::class.java)
list.add(place!!)
}
placeAdapter.submitList(list)
placeAdapter.notifyDataSetChanged()
}
}
})
}
private fun filterByName(query: String) {
Log.d("MyTag", "Filtered")
val reference = FirebaseDatabase.getInstance()
.getReference("Places")
.orderByChild("name")
.startAt(query)
.endAt(query + "\uf8ff")
reference.addValueEventListener(object: ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()) {
val list = ArrayList<Place>()
for (placeSnapshot in snapshot.children) {
val place = placeSnapshot.getValue(Place::class.java)
list.add(place!!)
}
placeAdapter.submitList(list)
placeAdapter.notifyDataSetChanged()
}
}
})
}
Demonstration of the problem itself on the video at the link: https://youtu.be/YlD9LTppC0s
Related
hello I have an activity that has a navigation bar and fragments inside, in one of my fragments I save data in firebase, I want to get those data and display it in other fragment with recycler view. I watched a tutorial on youtube to do it cuz my teacher didn't teach us anything, the issue is the fragment that was supposed the display the data shows nothing, it's blank but I didn't understand why cuz I'm really new to this, if someone can take a look at my code and help me I would really appreciate it
This is my data class
data class Event(var eventName: String, var eventTime:String?=null)
This is ViewModel
class EventViewModel :ViewModel() {
private val repository : EventRepository
private val _allEvents = MutableLiveData<List<Event>>()
val allEvents : LiveData<List<Event>> = _allEvents
init {
repository= EventRepository().getInstance()
repository.loadEvents(_allEvents)
}
}
This is Repository
class EventRepository {
private val databaseReference: DatabaseReference= FirebaseDatabase.getInstance().getReference("PetEvents")
#Volatile private var INSTANCE : EventRepository ?=null
fun getInstance() : EventRepository{
return INSTANCE ?: synchronized(this) {
val instance = EventRepository()
INSTANCE=instance
instance
}
}
fun loadEvents(eventList : MutableLiveData<List<Event>>){
databaseReference.addValueEventListener(object:ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
try {
val _eventList : List<Event> = snapshot.children.map { dataSnapshot ->
dataSnapshot.getValue(Event::class.java)!!
}
eventList.postValue(_eventList)
}
catch (e: Exception){
}
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})
}
}
This is Adapter
class EventAdapter : RecyclerView.Adapter<EventAdapter.MyViewHolder>() {
private var eventList =ArrayList<Event>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(
R.layout.event_item_cell,
parent, false
)
return MyViewHolder(itemView)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = eventList[position]
holder.eventName.text =currentItem.eventName
holder.eventTime.text =currentItem.eventTime
}
override fun getItemCount(): Int {
return eventList.size
}
fun updateEventList(eventList: List<Event>){
this.eventList.clear()
this.eventList.addAll(eventList)
notifyDataSetChanged()
}
class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
val eventName :TextView = itemView.findViewById(R.id.txEventName)
val eventTime :TextView = itemView.findViewById(R.id.txEventTime)
}
}
And this is the fragment I wish to display the data
private lateinit var viewModel : EventViewModel
private lateinit var eventRecyclerView: RecyclerView
lateinit var adapter: EventAdapter
class MainFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_main, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
eventRecyclerView=view.findViewById(R.id.recyclerView)
eventRecyclerView.layoutManager= LinearLayoutManager(context)
eventRecyclerView.setHasFixedSize(true)
adapter = EventAdapter()
eventRecyclerView.adapter= adapter
viewModel = ViewModelProvider(this).get(EventViewModel::class.java)
viewModel.allEvents.observe(viewLifecycleOwner, Observer {
adapter.updateEventList(it)
})
}
}
In my fragment xml I put a recycler view, there's no issue about that
And my item cell is a cardview idk if it has anything to with the problem I'm getting tho
I want to display the sum of a column from my room database in a textview.
After implementing my Query and setting the text of the Textview, it displays the following:
androidx.lifecycle.CoroutineLiveData#76f6edd
Any idea why this happens? Is it because my Query does not work or my way of implementign it is wrong?
It is my first time ever of actually programming. I know probably most of my code is not 100% correct, but I am working on it.
The Query from my Dao:
#Query("SELECT SUM(total)AS sum_total FROM receipt_table")
fun getSum(): Flow<Float>
The Fragment:
#AndroidEntryPoint
class HistoryFragment : Fragment(R.layout.fragment_history) {
private val viewModel: PurchaseViewmodel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_history, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = FragmentHistoryBinding.bind(view)
val exampleAdapter = ExampleAdapter()
binding.apply{
recyclerView.apply{
layoutManager = LinearLayoutManager(requireContext())
adapter = exampleAdapter
}
totalSumTextView.apply {
val totalSum = viewModel.totalSum
text = totalSum.toString()
}
ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val receipt = exampleAdapter.currentList[viewHolder.adapterPosition]
viewModel.onSwipe(receipt)
}
}).attachToRecyclerView(recyclerView)
}
setFragmentResultListener("add_receipt_request"){_,bundle ->
val result = bundle.getInt("add_receipt_request")
viewModel.onAddResult(result)
}
viewModel.receipts.observe(viewLifecycleOwner){
exampleAdapter.submitList(it)
}
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.addTaskEvent.collect { event->
when(event){
is PurchaseViewmodel.TasksEvent.ShowUndoDelete -> {
Snackbar.make(requireView(),"Tasks deleted", Snackbar.LENGTH_LONG)
.setAction("UNDO"){
viewModel.unDoDeleteClick(event.receipts)
}.show()
}
}
}
}
}
}
And the Viewmodel:
#HiltViewModel
class PurchaseViewmodel #Inject constructor(
private val receiptDao: ReceiptDao
): ViewModel() {
private val tasksEventChannel = Channel<TasksEvent>()
val addTaskEvent = tasksEventChannel.receiveAsFlow()
val receipts = receiptDao.getAllReceipts().asLiveData()
val totalSum = receiptDao.getSum().asLiveData()
fun onAddResult(result: Int){
when (result){
ADD_RECEIPT_RESULT_OK ->showReceiptSavedConfirmation("Receipt has been saved")
}
}
private fun showReceiptSavedConfirmation (text: String) = viewModelScope.launch {
tasksEventChannel.send(TasksEvent.ShowReceiptSavedConfirmation(text))
}
fun onSwipe (receipts: Receipts) = viewModelScope.launch {
receiptDao.delete(receipts)
tasksEventChannel.send(TasksEvent.ShowUndoDelete(receipts))
}
fun unDoDeleteClick (receipts: Receipts) = viewModelScope.launch {
receiptDao.insert(receipts)
}
sealed class TasksEvent {
data class ShowReceiptSavedConfirmation(val msg: String) : TasksEvent()
data class ShowUndoDelete(val receipts: Receipts) : TasksEvent()
}
}
You are getting a LiveData object here, not just a value:
val totalSum = receiptDao.getSum().asLiveData()
When you call:
totalSum.toString()
It calls toString method on the LiveData object that the reason, why you have "androidx.lifecycle.CoroutineLiveData#76f6edd" inside your TextView.
To fix the issue, just replace:
totalSumTextView.apply {
val totalSum = viewModel.totalSum
text = totalSum.toString()
}
with:
viewModel.totalSum.observe(this) { totalSumTextView.text = it}
Detailed review, how to work with a LiveData you can find here:
https://developer.android.com/topic/libraries/architecture/livedata
I've been trying to delete an item from my list so that it updates without the removed item, but the list seems to redraw itself and keeps displaying all the original items as before. For a short bit of time it's possible to see the item as if it's being removed, however, due to this redrawing everything gets back to what it was before the removal.
I've tried several combinations of the following methods but none of them seem to work in this case.
adapter.notifyItemRangeChanged(position, adapter.itemCount)
adapter.notifyItemRemoved(position)
adapter.notifyItemChanged(position)
adapter.notifyDataSetChanged()
These are my files. Please notice I'm using the Groupie library as a replacement for the default RecyclerView.
class RecyclerProductItem(
private val activity: MainActivity,
private val product: Product,
private val onItemClickListener: OnItemClickListener?
) : Item<GroupieViewHolder>() {
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.apply {
with(viewHolder.itemView) {
ivTrash.setOnClickListener {
if (onItemClickListener != null) {
Toast.makeText(context, "delete method to be added here", Toast.LENGTH_SHORT).show()
onItemClickListener.onClick(viewHolder.adapterPosition)
// deleteProduct(product.id)
}
}
}
}
}
interface OnItemClickListener {
fun onClick(position: Int) //pass your object types.
}
override fun getLayout() = R.layout.recyclerview_item_row
}
And here my fragment:
class ProductsListFragment : Fragment() {
private lateinit var adapter: GroupAdapter<GroupieViewHolder>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_products_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val linearLayoutManager = LinearLayoutManager(activity)
recyclerView.layoutManager = linearLayoutManager
adapter = GroupAdapter()
recyclerView.adapter = adapter
loadProducts()
}
/**
* API calls
*/
private fun loadProducts() {
GetProductsAPI.postData(object : GetProductsAPI.ThisCallback,
RecyclerProductItem.OnItemClickListener {
override fun onSuccess(productList: List<JsonObject>) {
Log.i(LOG_TAG, "successful network call")
for (jo in productList) {
val gson = GsonBuilder().setPrettyPrinting().create()
val product: Product =
gson.fromJson(jo, Product::class.java)
adapter.add(
RecyclerProductItem(
activity as MainActivity,
Product(
product.id,
product.title,
product.description,
product.price
), this
)
)
}
}
override fun onClick(position: Int) {
Log.i(LOG_TAG, position.toString())
adapter.notifyItemRangeChanged(position,
adapter.itemCount)
adapter.notifyItemRemoved(position)
}
})
}
}
Many thanks.
Simple sample
class GroupAdapter(private val items: MutableList<Any>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
fun removeByPosition(position: Int) {
items.removeAt(position)
notifyItemRemoved(position)
}
i have problem when try to display recyvlerview using kotlin , warn no adapter attach skipping layout and nothing happen in my app, ive tried many way but nothing solve it
how could i do ? please review my code, i will very thankfull to anyone can help
i have problem when try to display recyvlerview using kotlin , warn no adapter attach skipping layout and nothing happen in my app, ive tried many way but nothing solve it
how could i do ? please review my code, i will very thankfull to anyone can help
class ArticleFragment : Fragment() {
private lateinit var mPeopleRVAdapter: FirebaseRecyclerAdapter<News, NewsViewHolder>
//function oncreate
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.activity_news, container, false)
}
//viewholder
class NewsViewHolder internal constructor(var mView: View) : RecyclerView.ViewHolder(mView) {
fun setTitle(title: String?) {
val post_title = mView.findViewById<View>(R.id.post_title) as TextView
post_title.text = title
}
fun setDesc(desc: String?) {
val post_desc = mView.findViewById<View>(R.id.post_desc) as TextView
post_desc.text = desc
}
fun setImage(ctx: Context?, image: String?) {
val post_image = mView.findViewById<View>(R.id.post_image) as ImageView
Picasso.with(ctx).load(image).into(post_image)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//"News" here will reflect what you have called your database in Firebase.
//"News" here will reflect what you have called your database in Firebase.
val mDatabase = FirebaseDatabase.getInstance().reference.child("news")
mDatabase.keepSynced(true)
val mPeopleRV = view.findViewById<View>(R.id.myRecycleView) as RecyclerView
val personsRef =
FirebaseDatabase.getInstance().reference.child("news")
val personsQuery = personsRef.orderByKey()
val personsOptions: FirebaseRecyclerOptions<News> = FirebaseRecyclerOptions.Builder<News>().setQuery(
personsQuery, News::class.java).build()
mPeopleRVAdapter = object : FirebaseRecyclerAdapter<News, NewsViewHolder>(personsOptions) {
override fun onBindViewHolder(holder: NewsViewHolder, position: Int, model: News) {
holder.setTitle(model.title)
holder.setDesc(model.desc)
holder.setImage(activity ,model.image)
holder.mView.setOnClickListener {
val url: String? = model.url
val intent = Intent(activity, NewsWebView::class.java)
intent.putExtra("id", url)
startActivity(intent)
}
mPeopleRV.hasFixedSize()
mPeopleRV.layoutManager = LinearLayoutManager(context)
mPeopleRV.apply {
mPeopleRV.adapter = mPeopleRVAdapter
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.news_row, parent, false)
return NewsViewHolder(view)
}
}
}
override fun onStart() {
super.onStart()
mPeopleRVAdapter.startListening()
}
override fun onStop() {
super.onStop()
this.mPeopleRVAdapter.stopListening()
}
}
Move this code out of adapter:
mPeopleRV.hasFixedSize()
mPeopleRV.layoutManager = LinearLayoutManager(context)
mPeopleRV.apply {
mPeopleRV.adapter = mPeopleRVAdapter
}
I want to create a small application that once the user clicks on floating button action to add element. It will be added automatically
to the top of the recycle view.
I have created for this A main activity which contains the fragment of the recycler and the add flotten.
Once the user clicks on this button a fragmented dialog is shown to insert the element. Once the user confirms i want to add the new element to the top of the recycle view.
TO did I used a global array which contains the item.
I have not implemented the logic yet for that. But what surprised me once I click on confirm button of the shown dialog the element added correctly to the global array, however, the recycle view on his own without any action on it duplicate the latest element.
I used the model view presenter for that and here are my diff classes
1.MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
addFragment(InterFragment(), R.id.container)
fab.setOnClickListener { view ->
val dialog = IntervAddFragment()
val ft = supportFragmentManager.beginTransaction()
dialog.show(ft, ContentValues.TAG)
}
}
}
2.ADDFragmet
class IntervAddFragment : DialogFragment(), AddContract.View
{
val presenter: AddPresenter by lazy { AddPresenter(this) }
var dat=""
var plom=""
var typ=""
override fun onCreate(savedInstanceState: Bundle?) { ....}
override fun onStart() {.... }
override fun onCreateView(inflater: LayoutInflater?, parent: ViewGroup?, state: Bundle?): View? {...}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {....}
fun on_confirmClick()
{
presenter.on_confirmClick(dat,plom,typ)
Log.e("errrr",presenter.getList().toString())
dismiss()
}
fun fillSpinerPlom(view : View?) {.... }
fun fillSpinerType(view : View?) {.... }
private fun updateDateInView(datep:String) {....}
}
3.IntervFragement
InterFragment: Fragment(), InterContract.View {
var views: View? = null
var List_inter_adapter: InterListAdapter? = null
private var recyclerView: RecyclerView? = null
val presenter: InterventionPresenter by lazy { InterventionPresenter(this) }
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {.... }
interface ClickListener {
fun onClick(view: View, position: Int)
fun onLongClick(view: View?, position: Int)
}
internal class RecyclerTouchListener(context: Context, recyclerView: RecyclerView, private val clickListener: ClickListener?) : RecyclerView.OnItemTouchListener {..... }
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {.....}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView = views!!.findViewById<View>(R.id.interv_item_listview) as RecyclerView
List_inter_adapter = InterListAdapter(activity, presenter.getList(true))
recyclerView!!.adapter = List_inter_adapter
recyclerView!!.layoutManager = LinearLayoutManager(activity)
}
}
4.InterPresenter
class InterventionPresenter(val view: InterContract.View)
{
fun getList(firstTime:Boolean): LinkedList<InterItem> {
if(firstTime)
return populateList().list
else
return items
}
fun populateList(): ListInter
{
val item1 = InterItem(1, DateT(2018, Calendar.FEBRUARY, 6).date, plomb.get(0), types.get(0))
val item2 = InterItem(2, DateT(2018, Calendar.MARCH, 8).date, plomb.get(1), types.get(1))
val item3= InterItem(3, DateT(2018, Calendar.MAY, 10).date, plomb.get(2), types.get(2))
val lstint = ListInter()
lstint.list= LinkedList(listOf(item1, item2, item3))
items=lstint.list
return lstint
}
companion object {
val plomb= LinkedList<String>(listOf("Dina", "Lili", "Wiseem"))
val types= LinkedList<String>(listOf("type1","type2","type3"))
lateinit var items: LinkedList<InterItem>
}
}
5.ADD Presenter
class AddPresenter(val view: AddContract.View)
{
fun on_confirmClick(date:String,plom:String,type:String)
{
val item= InterItem(items.size+1,date,plom,type)
items.push(item)
}
fun getList(): LinkedList<InterItem> {
return items
}
fun getplom(): LinkedList<String>
{ return plomb
}
fun getType(): LinkedList<String>
{ return types
}
}
6.InterListAdapter
class InterListAdapter(private val context: Context, linkedList: LinkedList<InterItem>) : RecyclerView.Adapter<InterListAdapter.ViewHolder>()
{
internal var linkedList = LinkedList<InterItem>()
private val inflater: LayoutInflater
//private lateinit var listener: OnTaskSelected
init {... }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {... }
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.numero?.text=(linkedList[position].numero).toString()
holder.date?.text=linkedList[position].date
holder.plom?.text=linkedList[position].plom
holder.type?.text=linkedList[position].type
}
override fun getItemCount(): Int {
return linkedList.size
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var numero: TextView?=null
var date: TextView?=null
var plom: TextView?=null
var type: TextView?=null
var btn_delete: Button?=null
var btn_update: Button?=null
init {
numero = itemView.findViewById(R.id.numero)
date = itemView.findViewById(R.id.date)
plom = itemView.findViewById(R.id.plom)
type = itemView.findViewById(R.id.type)
btn_delete = itemView.findViewById(R.id.btn_supprimer)
btn_update = itemView.findViewById(R.id.btn_modifier)
}
}
}
and here is the ListInter
class ListInter
{
var list= LinkedList<InterItem>()
get() = field
set(value){field = value}
}
RecyclerView can't handle array change event by itself. So, you should notify it by calling the following after you've added element to array:
recyclerView.getAdapter().notifyItemInserted(items.size() - 1)