I have a recyclerView that users can add or delete item rows and that rows saving the Firebase Firestore. Adding function works fine but deleting function does not. I created an interface (Listener) through PairDetailRecyclerAdapter. It contains DeleteOnClick method which have myList and position parameters. Also I have a deleteData method through my viewModel for deleting documents from Firestore. When i clicked the delete button on Firebase side everything is OK but on recyclerView side items duplicating themselves or shuffling
Here is the codes:
Interface and onClickListener from PairDetailRecyclerAdapter :
interface Listener {
fun DeleteOnClick(list: ArrayList<AnalyzeDTO>, position: Int)
}
override fun onBindViewHolder(holder: AnalyzeViewHolder, position: Int) {
holder.itemView.rrRatioText.text = "RR Ratio: ${list[position].rrRatio}"
holder.itemView.resultText.text = "Result: ${list[position].result}"
holder.itemView.causeForEntryText.text = "Reason: ${list[position].reason}"
holder.itemView.conceptText2.text = "Concept: ${list[position].concept}"
if (list[position].tradingViewUrl != null && list[position].tradingViewUrl!!.isNotEmpty()) {
Picasso.get().load(list[position].tradingViewUrl)
.into(holder.itemView.tradingviewImage);
}
holder.itemView.imageView.setOnClickListener {
listener.DeleteOnClick(list, holder.layoutPosition)
}
deleteData from ViewModel :
fun deleteData(position: Int) {
var chosenPair = ozelSharedPreferences.clickiAl().toString()
val currentU = Firebase.auth.currentUser
val dbCollection = currentU?.let {
it.email.toString()
}
database.collection(dbCollection!!).document("Specified").collection("Pairs")
.document(chosenPair).collection("Analysis").get().addOnSuccessListener { result ->
val newList = ArrayList<String>()
if (result != null) {
for (document in result) {
newList.add(document.id)
database.collection(dbCollection!!).document("Specified").collection("Pairs")
.document(chosenPair).collection("Analysis").document(newList[position]).delete()
}
}
}
}
Overrided Listener in PairDetailActivity
override fun DeleteOnClick(list: ArrayList<AnalyzeDTO>, position: Int) {
viewModel.deleteData(position)
list.removeAt(position)
recyclerA.notifyItemRemoved(position)
}
#SuppressLint("NotifyDataSetChanged")
fun deleteItem(i: Int, context: Context) {
question = dataList[i] as Questionio //this my model
dataList.removeAt(i)
notifyDataSetChanged()
}
//
this code works for me
in fact, it would be better if you did it as a model and it would be suitable for mvvm architecture.
Related
I have tried different ways, I've been stuck trying to do it for weeks.
I also tried to add the code in the adapter, I thought about using sqlite, but I just can't figure how to do it.
I just want to check a condition, if this string exists somewhere in gridview, boolean = true
Can I do something like that in the activity?
for (i in 0 until arrayList!!.size){
WordViewAdapter(this,arrayList).getView(i, convertView = null, parent = null).word.text
}
}
This is my grid view:
This is my Adapter:
private lateinit var mTTS : TextToSpeech
class WordViewAdapter(var ctx: Context, var array: ArrayList<Word_Item>?) : BaseAdapter() {
override fun getCount(): Int {
return array!!.size
}
override fun getItem(position: Int): Any {
return array!![position]
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
var view : View = View.inflate(ctx, R.layout.grid_item_list,null)
var word:TextView = view.findViewById(R.id.tx_word)
var translation:TextView = view.findViewById(R.id.tx_transl)
var wordItem : Word_Item = array!![position]
word.text = wordItem.word
translation.text = wordItem.transl
Translate(ctx).question(word,translation)
view.setOnClickListener{
mTTS = TextToSpeech(ctx, TextToSpeech.OnInitListener { status ->
if (status != TextToSpeech.ERROR){
mTTS.setLanguage(Locale.US)
mTTS.setSpeechRate(0.7F)
mTTS.setPitch(0.7F)
mTTS.speak(word.text.toString(),TextToSpeech.QUEUE_FLUSH,null,null)
}
})
}
return view
}
}
Well guys, I did it. And I will post it here, maybe it help someone.
I've created another list:
var faladas = mutableListOf<String>()
And a function that check if it is equal, like that, if OK, then send to adapter, and update it:
private fun checkPalavra(str: String) {
var flip : Boolean = str.lowercase().trim().contains(binding.txPronunciar.text.toString().lowercase().trim().replace("!","").replace(".","").replace("?","").replace(",","") )
if (flip){
faladas.add(str.lowercase())
wordAdapter?.notifyDataSetChanged()
gridView?.invalidateViews()
gridView?.adapter = WordViewAdapter(this,arrayList,faladas)...
In the adapter, I just add it:
for (i in 0 until faladas.size){
if (faladas[i].lowercase() == word.text.toString().lowercase()){
view.setBackgroundColor(ctx.resources.getColor(R.color.green))
}
}
i'm a beginner in android & kotlin and i'm having an issue i been trying to figure out all day...
I have an app that fetches data from NewsApi and displays it in a recycler view , i am using Retrofit library and Room (to save favorite articles) with MVVM architecture. I want to add an option so that the user can select the country of the news from a dialog that pops up by clicking on a icon on the toolbar menu.
I have created a custom DialogFragment and have it show up, the dialog contains a spinner with a list of countries and i'm using FragmentResult and FragmentResultListener to pass the country value between dialog fragment and news fragment.
DialogFragment
class CountrySelectDialog : DialogFragment(R.layout.country_selection_dialog) {
private lateinit var binding: CountrySelectionDialogBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = CountrySelectionDialogBinding.bind(view)
binding.spCountrySelection.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
adapterView: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
Toast.makeText(
context,
"you selected ${adapterView?.getItemAtPosition(position).toString()}",
Toast.LENGTH_SHORT
).show()
}
override fun onNothingSelected(adapterView: AdapterView<*>?) {
}
}
binding.btnCancel.setOnClickListener {
this.dismiss()
}
binding.btnConfirm.setOnClickListener {
val result = binding.spCountrySelection.selectedItem.toString()
setFragmentResult("countryCode", bundleOf("bundleKey" to result))
this.dismiss()
}
}
}
The news Fragment is observing data from the View Model
class BreakingNewsFragment : Fragment(R.layout.fragment_breaking_news) {
lateinit var viewModel: NewsViewModel
lateinit var newsAdapter: NewsAdapter
private lateinit var binding: FragmentBreakingNewsBinding
val TAG = "BreakingNewsFragment"
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentBreakingNewsBinding.bind(view)
viewModel = (activity as NewsActivity).viewModel
setUpRecyclerView()
setFragmentResultListener("countryCode") { countryCode, bundle ->
val result = bundle.getString("countryCode")
viewModel.countryCode = result!!}
viewModel.breakingNews.observe(viewLifecycleOwner, Observer {
when (it) {
is Resource.Success -> {
hideProgressBar()
it.data?.let {
newsAdapter.differ.submitList(it.articles.toList())
val totalPages = it.totalResults / QUERY_PAGE_SIZE + 2
isLastPage = viewModel.breakingNewsPage == totalPages
}
}
is Resource.Error -> {
hideProgressBar()
it.message?.let {
Log.e(TAG, "An error occurred: $it")
}
}
is Resource.Loading -> {
showProgressBar()
}
}
})
newsAdapter.setOnItemClickListener {
val bundle = Bundle().apply {
putSerializable("article", it)
}
findNavController().navigate(
R.id.action_breakingNewsFragment_to_articleFragment, bundle
)
}
}
ViewModel:
class NewsViewModel(val newsRepository: NewsRepository, val app: Application) : AndroidViewModel(app) {
val breakingNews: MutableLiveData<Resource<NewsResponse>> = MutableLiveData()
var breakingNewsPage = 1
var breakingNewsResponse: NewsResponse? = null
val searchNews: MutableLiveData<Resource<NewsResponse>> = MutableLiveData()
var searchNewsPage = 1
var searchNewsResponse: NewsResponse? = null
var countryCode :String = "it"
init {
getBreakingNews(countryCode)
}
fun getBreakingNews(countryCode: String) {
viewModelScope.launch {
breakingNews.postValue(Resource.Loading())
val response = newsRepository.getBreakingNews(countryCode, breakingNewsPage)
breakingNews.postValue(handleBreakingNewsResponse(response))
}
}
fun handleBreakingNewsResponse(response: Response<NewsResponse>): Resource<NewsResponse> {
if (response.isSuccessful) {
response.body()?.let { resultResponse ->
breakingNewsPage++
if (breakingNewsResponse == null) {
breakingNewsResponse = resultResponse
} else {
val oldArticles = breakingNewsResponse?.articles
val newArticles = resultResponse.articles
oldArticles?.addAll(newArticles)
}
return Resource.Success(breakingNewsResponse ?: resultResponse)
}
}
return Resource.Error(response.message())
}
fun searchNews(searchQuery: String) {
viewModelScope.launch {
searchNews.postValue(Resource.Loading())
val response = newsRepository.searchNews(searchQuery, searchNewsPage)
searchNews.postValue((handleSearchNewsResponse(response)))
}
}
fun handleSearchNewsResponse(response: Response<NewsResponse>): Resource<NewsResponse> {
if (response.isSuccessful) {
response.body()?.let { resultResponse ->
searchNewsPage++
if (searchNewsResponse == null) {
searchNewsResponse = resultResponse
} else {
val oldArticles = searchNewsResponse?.articles
val newArticles = resultResponse.articles
oldArticles?.addAll(newArticles)
}
return Resource.Success(searchNewsResponse ?: resultResponse)
}
}
return Resource.Error(response.message())
}
}
When i click on the icon on the toolbar menu the dialog appears and works fine but i can't seem to find a way to have the recycler view update with new data using given value for country
I searched everywhere and couldn't find a solution (or probably didn't understand it :S) can someone guide me into the right direction? I'm so lost...
When I click on the icon on the toolbar menu the dialog appears and works fine but I can't seem to find a way to have the recycler view update with new data using given value for country.
I have data source(in that example it's just a var myState: List)
class MainActivity : AppCompatActivity() {
var generation: Int = 0
var myState: List<User> = emptyList()
val userAdapter = UserAdapter {
val index = myState.indexOf(it)
if (index == -1)
println("🔥 not found")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recycler = findViewById<RecyclerView>(R.id.rvContent)
recycler.layoutManager = LinearLayoutManager(this)
recycler.adapter = userAdapter
Thread {
while (true) {
generateNewData()
Handler(mainLooper).post {
userAdapter.submit(myState)
}
sleep(3000L)
}
}.start()
}
fun generateNewData() {
generation++
myState = (0..5000).map { User("$generation", it) }
}
}
I have RecyclerView, and AsyncListDiffer connected to it
data class User(val name: String, val id: Int) {
val createdTime = System.currentTimeMillis()
}
data class UserViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
fun bindTo(user: User, action: (User) -> Unit) {
val textView = view.findViewById<TextView>(R.id.title)
textView.text = "${user.name} ${user.id} ${user.createdTime}"
textView.setOnClickListener { action(user) }
}
}
class UserAdapter(val action: (User) -> Unit) : RecyclerView.Adapter<UserViewHolder>() {
val differ: AsyncListDiffer<User> = AsyncListDiffer(this, DIFF_CALLBACK);
object DIFF_CALLBACK : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem == newItem
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
return UserViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false))
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
val user = differ.currentList[position]
holder.bindTo(user, action = action)
}
fun submit(list: List<User>) {
differ.submitList(list)
}
override fun getItemCount(): Int {
return differ.currentList.size
}
}
I have OnClickListener binded to every item on RecyclerView
{
val index = myState.indexOf(it)
if (index == -1)
println("🔥 not found")}
That listener checks if item that was clicked is exists in the data source, and if not, outputs it to the console.
Every few seconds data in the source are changed, and pushed to
a AsyncListDiffer via submitList method, some how internally it uses other thread to match data and pass that diffed data
to the RecyclerView, and that takes some time;
If I starts clicking on the items non-stop, and the click event occurs at the same time when the differ inserts new data, then I get into a non-consistent state.
So, how to handle that?
Ignore a click with inconsistent data?(cons: User can see some strange behaviour like list item not collapse/expand, no navigation happen, etc)
Try to find a similar item in the new data by separate fields(positions/etc), and use it?(cons: same as 1. but less probability)
Block OnClickListener events until the data is consistent in both the Recycler and the data source? (cons: same as above, and also lag with action user performed until data became consistent again)
Something else? What is a best way to solve that?
I am using RecyclerView to display a dynamic list of data and after I call an api I need to update my RecyclerView UI but the items in my RecyclerView does not change...
Below is my how I init my RecyclerView in my Fragment:-
forwardedList.layoutManager = LinearLayoutManager(context!!, RecyclerView.VERTICAL, false)
adapter = ForwardListAdapter(SmsHelper.getForwardedSms(context!!))
forwardedList.adapter = adapter
Below is my custom RecyclerView Adapter:-
class ForwardListAdapter(val forwardedList: List<SmsData>) : RecyclerView.Adapter<ForwardListAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ForwardListAdapter.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.forwarded_item, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ForwardListAdapter.ViewHolder, position: Int) {
holder.bindItems(forwardedList[position])
}
override fun getItemCount(): Int {
return forwardedList.size
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindItems(sms: SmsData) {
val simSlotText: TextView = itemView.findViewById(R.id.simSlot)
val senderText: TextView = itemView.findViewById(R.id.sender)
simSlotText.text = "[SIM ${sms.simSlot}] "
senderText.text = sms.sender
}
}
}
I am currently updating my RecyclerView from SmsHelper class as below:-
val fragments = mainActivity!!.supportFragmentManager.fragments
for (f in fragments) {
if (f.isVisible) {
if (f.javaClass.simpleName.equals("ForwardedFragment")) {
val fg = f as ForwardedFragment
fg.adapter.notifyDataSetChanged() <----- HERE
} else if (f.javaClass.simpleName.equals("FailedFragment")) {
val fg = f as FailedFragment
fg.adapter.notifyDataSetChanged()
}
}
}
As I observed, you did not really change the adapter's data but only called notifyDataSetChanged. You cannot just expect the data to be changed automatically like that since notifyDataSetChanged only:
Notifies the attached observers that the underlying data has been changed and any View reflecting the data set should refresh itself.
You need to change the data by yourself first, then call notifyDataSetChanged.
class ForwardListAdapter(private val forwardedList: MutableList<SmsData>) : RecyclerView.Adapter<ForwardListAdapter.ViewHolder>() {
// ...
fun setData(data: List<SmsData>) {
forwardedList.run {
clear()
addAll(data)
}
}
// ...
}
Then do it like this:
adapter.run {
setData(...) // Set the new data
notifyDataSetChanged(); // notify changed
}
I really appropriate it if somebody could help me out.
I apply my second RecyclerView with a custom swipe Button object, here fun handleSwipeClick is set to handle the action.
My question is: how can i make this function (handleSwipeClick) to handle each row specifically?? Like Delete this row item
Adapter #1
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = baxters[position]
holder.listItem_time.text = item.intakeTime
holder.itemView.dose_recycler_view.apply {
dose_recycler_view.setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
dose_recycler_view.layoutManager = layoutManager
//Swipe action
val swipe = object: SwipeHelper(context,dose_recycler_view, 400){
override fun instaniateSwipeButton(
viewHolder: RecyclerView.ViewHolder,
buffer: MutableList<SwipeButton>
) {
// Adding Buttons
buffer.add(
SwipeButton(context,
"",
30,
R.drawable.ic_check_circle,
Color.parseColor("#66ff66"),
object : ButtonClickListener {
override fun handleSwipeClick(id: Int) {
// Click action
// TODO call to change LAST TAKEN and NEW INTAKE
Companion.errorToast(
context,
"Medicijn ingenomen. $id"
)
}
})
)
}
}
adapter = ClientDoseListAdapter(item.doses.toMutableList())
setRecycledViewPool(viewPool)
}
}
Adapter #2
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = doses[position]
/// DELETE THIS ROW
holder.med_name.text = item.medicineItem.name
holder.dose_amount.text = item.amount.toString()
}
First, create a delete method in ClientDoseListAdapter adapter, then call this method in the click action like,
fun delete(dose: Dose) {
val index: Int = doseList.indexOf(dose)
doseList.removeAt(index)
notifyDataSetChanged()
}
override fun handleSwipeClick(id: Int) {
// Click action
adapter.delete(adapter.getItem(viewHolder.adapterPosition))
}
Adapetr #1 looks like this:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = baxters[position]
holder.listItem_time.text = item.intakeTime
holder.itemView.dose_recycler_view.apply {
dose_recycler_view.setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
dose_recycler_view.layoutManager = layoutManager
// First init a new adapter
dosesAdapter = ClientDoseListAdapter(item.doses.toMutableList())
// Swipe action
val swipe = object: SwipeHelper(context, dose_recycler_view, 400){
override fun instaniateSwipeButton(
viewHolder: RecyclerView.ViewHolder,
buffer: MutableList<SwipeButton>
) {
// Adding Buttons
buffer.add(
SwipeButton(context,
"",
30,
R.drawable.ic_check_circle,
Color.parseColor("#66ff66"),
object : ButtonClickListener {
override fun confirmDoseTaken(id: Int) {
// Click action
// TODO call to change LAST TAKEN and NEW INTAKE
Companion.errorToast(
context,
"Medicijn ingenomen. $id"
)
// i can call removeItem method in adapter
dosesAdapter.removeItem()
}
})
)
}
}
// set recyclerView adapter
adapter = dosesAdapter
setRecycledViewPool(viewPool)
}
}