Incompatible types: Int and Article - android-recyclerview

I am developing news app I have implemented multipleview types in recyclerview adapter class but I am getting following error
Incompatible types: Int and Article
below BBCSportAdapter class where I have implemented multipleview types
#Suppress("UNREACHABLE_CODE")
class BBCSportAdapter(private val listViewType: List<Int>) : RecyclerView.Adapter<BBCSportAdapter.MyViewHolder>() {
companion object {
val ITEM_A = 1
var ITEM_B = 2
}
var articleList: List<Article> = listOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val inflater =
LayoutInflater.from(parent.context)
return when (viewType) {
ITEM_A -> ViewHolderItemA(inflater.inflate(R.layout.bbc_sport_list, null))
else -> {
ViewHolderItemB(inflater.inflate(R.layout.bbc_sport_item, null))
}
}
}
#SuppressLint("NewApi")
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val viewType = articleList[position]
when (viewType) {
ITEM_A -> {
val viewHolderA = holder as ViewHolderItemA
Picasso.get().load(articleList[position].urlToImage)
.into(viewHolderA.topFlameImageView)
}else -> {
val viewHolderB = holder as ViewHolderItemB
}
}
}
override fun getItemCount(): Int {
return articleList.size
}
// holder.articleTitle.text = articleList[position].title
// holder . articleSourceName . text = articleList [position].source.name
// Picasso . get ().load(articleList.get(position).urlToImage).into(holder.image)
//
// val input = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX")
// val output = SimpleDateFormat("dd/MM/yyyy")
// var d = Date()
// try {
// d = input.parse(articleList[5].publishedAt)
// } catch (e: ParseException) {
// try {
// val fallback = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
// fallback.timeZone = TimeZone.getTimeZone("UTC")
// d = fallback.parse(articleList[5].publishedAt)
// } catch (e2: ParseException) {
// // TODO handle error
// val formatted = output.format(d)
// val timelinePoint = LocalDateTime.parse(formatted)
// val now = LocalDateTime.now()
//
// var elapsedTime = Duration.between(timelinePoint, now)
//
// println(timelinePoint)
// println(now)
// elapsedTime.toMinutes()
//
// holder.articleTime.text = "${elapsedTime.toMinutes()}"
// }
// }
// }
fun setMovieListItems(articleList: List<Article>) {
this.articleList = articleList
notifyDataSetChanged()
}
open inner class MyViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView!!){}
inner class ViewHolderItemA(itemView: View) : MyViewHolder(itemView) {
val topFlameImageView: ImageView = itemView.findViewById(R.id.topFlameImageView)
}
inner class ViewHolderItemB(itemView: View?) : MyViewHolder(itemView) {
val image: ImageView = itemView!!.findViewById(R.id.imageView)
val articleTitle: TextView = itemView!!.findViewById(R.id.articleTitle)
val articleSourceName: TextView = itemView!!.findViewById(R.id.articleSourceName)
val imageCategory: ImageView = itemView!!.findViewById(R.id.imageCategory)
val articleTime: TextView = itemView!!.findViewById(R.id.articleTime)
}
}
I have followed this link https://github.com/CoderJava/Multiple-View-Type-RecyclerView-Kotlin-Android/blob/master/app/src/main/java/com/ysn/multipleviewtypeexample/AdapterRecyclerView.kt

In your onBindViewHolder articleList is list of artical but in your when statement you are comparing Article with an Int i.e. ITEM_A, which is wrong. Instead you should have some type in your article object and comparison is based on that type. Moreover you have not implemented getItemViewType() where you can make decision which view will be inflated. In your case viewType in parameter of oncreateViewHolder will always return 0 and else condition will be executed always and you will always have single type of view.

Related

Observing live data from an API is not updating ui when data changes

I am trying to develop a football app demo. Data comes from an API from the api
It loads data as expected when app started, but when score of match changes, ui is not updating for scores by itself. I am using DiffUtil getChangePayload() to detect changes in score and status fields of Match objects which comes from the response. But it is not triggering when live match data changes. What am i missing?
P.S. I put layout in SwipeRefreshLayout and when i refresh, it gets scores and update the ui. But i want to see the match status and scores updating by itself.
Here is my code:
class MatchesViewModel(
app: Application,
private val repository: MatchesRepository
): AndroidViewModel(app) {
val matchesToday: MutableLiveData<List<Matche>> = MutableLiveData()
init {
getMatchesToday()
}
fun getMatchesToday() = viewModelScope.launch {
safeMatchesToday()
}
private suspend fun safeMatchesToday() {
if (Constants.checkConnection(this)) {
val response = repository.getMatchesToday()
if (response.isSuccessful) {
response.body()?.let {
matchesToday.postValue(it.matches)
}
}
}
}
}
class MatchesTodayFragment : Fragment() {
private var _binding: FragmentMatchesTodayBinding? =null
private val binding get() = _binding!!
private lateinit var mMatchesAdapter: MatchesAdapter
private val viewModel: MatchesViewModel by viewModels {
MatchesViewModelFactory(requireActivity().application, (requireActivity().application as MatchesApplication).repository)
}
#RequiresApi(Build.VERSION_CODES.N)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
viewModel.matchesToday.observe(viewLifecycleOwner) { matches ->
mMatchesAdapter.differ.submitList(matches)
}
binding.srlMatchesToday.setOnRefreshListener {
viewModel.getMatchesToday()
binding.srlMatchesToday.isRefreshing = false
}
}
}
class MatchesAdapter(val fragment: Fragment): RecyclerView.Adapter<MatchesAdapter.ViewHolder>() {
private val differCallback = object: DiffUtil.ItemCallback<Matche>() {
override fun areItemsTheSame(oldItem: Matche, newItem: Matche): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Matche, newItem: Matche): Boolean {
return oldItem.status == newItem.status &&
oldItem.score.fullTime.home == newItem.score.fullTime.home &&
oldItem.score.fullTime.away == newItem.score.fullTime.away &&
oldItem == newItem
}
override fun getChangePayload(oldItem: Matche, newItem: Matche): Any? {
val bundle: Bundle = bundleOf()
if (oldItem.status != newItem.status) {
bundle.apply {
putString(Constants.MATCH_STATUS, newItem.status)
}
}
if (oldItem.score.fullTime.home != newItem.score.fullTime.home) {
bundle.apply {
putInt(Constants.HOME_SCORE, newItem.score.fullTime.home)
}
}
if (oldItem.score.fullTime.away != newItem.score.fullTime.away) {
bundle.apply {
putInt(Constants.AWAY_SCORE, newItem.score.fullTime.away)
}
}
if (bundle.size() == 0) {
return null
}
return bundle
}
}
val differ = AsyncListDiffer(this, differCallback)
#SuppressLint("UseCompatLoadingForDrawables")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val match = differ.currentList[position]
holder.apply {
Glide.with(fragment)
.load(match.homeTeam.crest)
.placeholder(fragment.resources.getDrawable(R.drawable.ic_ball))
.into(ivHomeTeamImage)
Glide.with(fragment)
.load(match.awayTeam.crest)
.placeholder(fragment.resources.getDrawable(R.drawable.ic_ball))
.into(ivAwayTeamImage)
tvHomeTeamName.text = match.homeTeam.name
tvAwayTeamName.text = match.awayTeam.name
when (match.status) {
Constants.TIMED -> {
tvMatchTime.text = Constants.toTimeForTR(match.utcDate)
tvHomeTeamScore.text = "-"
tvAwayTeamScore.text = "-"
}
Constants.PAUSED -> {
tvMatchTime.text = Constants.FIRST_HALF
tvHomeTeamScore.text = match.score.fullTime.home.toString()
tvAwayTeamScore.text = match.score.fullTime.away.toString()
}
Constants.FINISHED -> {
tvMatchTime.text = Constants.FINISHED
tvHomeTeamScore.text = match.score.fullTime.home.toString()
tvAwayTeamScore.text = match.score.fullTime.away.toString()
}
else -> {
tvMatchTime.text = Constants.IN_PLAY
tvHomeTeamScore.text = match.score.fullTime.home.toString()
tvAwayTeamScore.text = match.score.fullTime.away.toString()
}
}
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.isNotEmpty()) {
val item = payloads[0] as Bundle
val status = item.getString(Constants.MATCH_STATUS)
val homeScore = item.getInt(Constants.HOME_SCORE)
val awayScore = item.getInt(Constants.AWAY_SCORE)
holder.apply {
tvMatchTime.text = status
tvHomeTeamScore.text = homeScore.toString()
tvAwayTeamScore.text = awayScore.toString()
Log.e("fuck", status.toString())
}
}
super.onBindViewHolder(holder, position, payloads)
}
override fun getItemCount(): Int {
return differ.currentList.size
}
}
LiveData only pushes new values if you command it to. Since you want to do it repeatedly, you need to create a loop. This is very easy to do using the liveData coroutine builder.
class MatchesViewModel(
app: Application,
private val repository: MatchesRepository
): AndroidViewModel(app) {
val matchesToday = liveData {
while (true) {
if (Constants.checkConnection(this)) {
val response = repository.getMatchesToday()
if (response.isSuccessful) {
response.body()?.let {
emit(it.matches)
}
}
}
delay(5000) // however many ms you want between fetches
}
}
}
If this is a Retrofit response, I think checking isSuccessful is redundant because body() will be non-null if and only if isSuccessful is true. So it could be simplified a bit from what you have:
class MatchesViewModel(
app: Application,
private val repository: MatchesRepository
): AndroidViewModel(app) {
val matchesToday = liveData {
while (true) {
if (Constants.checkConnection(this)) {
repository.getMatchesToday()?.body()?.matches?.let(::emit)
}
delay(5000) // however many ms you want between fetches
}
}
}

Can't Update child item in NestedRecyclerView

How do I update the child when clicking on the categories item or when doing a search like the one in the picture
I am updating the data from Viewmodel but the child element does not update
I also tried calling the adapter child and updating it directly, but to no avail
This is the code, if you need any additional details, I will add it down
In ParentAdapter
class BaseAdapter(private val listener: OnItemClickListener
): RecyclerView.Adapter<BaseViewHolder>() {
var items : MutableList<HomeItem<Any>> = mutableListOf()
lateinit var adapter : SubjectsAdapter
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
return when (viewType){
VIEW_ITEM_SEARCH ->{
val view = LayoutInflater.from(parent.context).inflate(
R.layout.item_search_view,
parent,false
)
BaseViewHolder.SearchItemViewHolder(view)
}
VIEW_ITEM_STUDENT ->{
val view = LayoutInflater.from(parent.context).inflate(
R.layout.item_student, parent,false
)
BaseViewHolder.StudentItemViewHolder(view)
}
VIEW_ITEM_COLOR ->{
val view = LayoutInflater.from(parent.context).inflate(
R.layout.item_chip_list, parent,false
)
BaseViewHolder.YearsItemViewHolder(view)
}
VIEW_ITEM_SUBJECT ->{
val view = LayoutInflater.from(parent.context).inflate(
R.layout.item_subject_list, parent,false
)
BaseViewHolder.SubjectsItemViewHolder(view)
}
else -> throw Exception("UnKnow View Type")
}
}
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
when (holder){
is BaseViewHolder.SearchItemViewHolder -> bindSearch(holder,position)
is BaseViewHolder.StudentItemViewHolder -> bindStudent(holder,position)
is BaseViewHolder.YearsItemViewHolder -> bindYears(holder,position)
is BaseViewHolder.SubjectsItemViewHolder -> bindSubjects(holder,position)
}
}
fun bindYears(holder: BaseViewHolder.YearsItemViewHolder,position: Int){
val p = items[position].item as List<SubjectsColor>
val adapterchip = ChipAdapter { chip ->
listener.onSubjectColorClick(chip)
}
holder.binding.apply {
recyclerChipList.layoutManager = LinearLayoutManager(root.context,
LinearLayoutManager.HORIZONTAL,
false)
recyclerChipList.adapter = adapterchip
recyclerChipList.setHasFixedSize(true)
}
adapterchip.submitList(p)
}
fun bindSubjects(holder :BaseViewHolder.SubjectsItemViewHolder,position: Int){
val p = items[position].item as List<SubjectStudent>
adapter = SubjectsAdapter{ subject->
listener.onSubjectStudentClick(subject)
}
holder.binding.apply {
recyclerSubjectList.layoutManager = LinearLayoutManager(root.context,
LinearLayoutManager.VERTICAL,
false)
recyclerSubjectList.adapter = adapter
recyclerSubjectList.setHasFixedSize(true)
}
Log.d("BindSubjects","BindSubjects : $p")
adapter.submitList(p)
}
In ViewModel
#HiltViewModel
class HomeViewModel #Inject constructor(
private val subDao : SubjectsDao,
//state : SavedStateHandle
) : ViewModel(){
private val colorId = MutableLiveData(1)
val colorIdResult = colorId.asFlow().flatMapLatest(){ id ->
id.let {
subDao.getAllSubjectStudentByColor(id)
}
}.stateIn(viewModelScope, SharingStarted.Lazily, null)
fun getAllSubjectsColor() :Flow<List<SubjectsColor>> =
subDao.getAllSubjectColors()
fun onSubjectColorClick(colorI:Int){
colorId.postValue(colorI)
}}
In Fragment
adapterBase = BaseAdapter(this)
val itemsList: MutableList<HomeItem<Any>> = mutableListOf()
itemsList.add(HomeItem("Google Home", HomeItemType.TYPE_SEARCH))
itemsList.add(HomeItem("Student", HomeItemType.TYPE_PROFILE))
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.getAllSubjectsColor().collect {
itemsList.add(HomeItem(it, HomeItemType.TYPE_COLORS))
}
}
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.colorIdResult.collect { id ->
val result = id ?: return#collect
itemsList.add(HomeItem(result.subjectStudent, HomeItemType.TYPE_SUBJECTS))
//adapterBase.notifyDataSetChanged()
}
}
binding.apply {
recyclerBaseFragment.layoutManager = LinearLayoutManager(requireContext())
recyclerBaseFragment.setHasFixedSize(true)
recyclerBaseFragment.adapter = adapterBase
}
adapterBase.items = itemsList
}
enum class HomeItemType {
TYPE_SEARCH,
TYPE_PROFILE,
TYPE_COLORS,
TYPE_SUBJECTS
}
data class HomeItem<T>(
var item :T,
val type: HomeItemType
)

ViewModel Instance inside RecycleView KOTLIN CANNOT CREATE INSTANCE OF VIEWMODAL

Im trying to create a View model that contains alist of countries in it.
The View modal class look like this:
class Country_ViewModel(ctx:Context) :ViewModel(){
val itemSelected : MutableLiveData<Int> by lazy{
MutableLiveData<Int>()
}
val p = XmlPullParserHandler()
private var count: MutableList<Country> = p.parse(openCountriesFile(ctx))
val countryArray =MutableLiveData(count)
// This function will open the XML file and return an input stream that will be used by the Parse function
fun openCountriesFile(context: Context): InputStream? {
val assetManager: AssetManager = context.getAssets()
var `in`: InputStream? = null
try {
`in` = assetManager.open("countries.xml")
} catch (e: IOException) {
e.printStackTrace()
}
return `in`
}
// This function will loop thorough the country list and delete the entry that it got from the position
fun removeItem(position: Int) {
count.map { pos ->
if (pos.compare(count[position]) == 0) {
count.remove(pos)
return
}
}
}
The function openCountriesFile will just parse the XML file that contains the Countries and save it in the MutableLiveData object inside the ModelView.
Later I would like to use a Fragment to observe the data that is changed:
This fragment will use the Adapter that I created and populate the Fragment with the country data.
The fragment will look like that:
class frag : Fragment(){
val KEY_COUNTRY = "country"
val KEY_NAME = "name"
val KEY_FLAG = "flag"
val KEY_ANTHEM = "anthem"
val KEY_SHORT = "short"
val KEY_DETAILS = "details"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
val viewModal:Country_ViewModel by viewModels()
/*
* When creating the view we would like to do the following:
* Initiate the Adapter.
* When the adapter has been called he will look for the XML file with the country's in it.
Second one for the anthems
* */
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val v = inflater.inflate(R.layout.fragment_frag, container, false)
val rv = v.findViewById(R.id.rvTodos) as RecyclerView
val adapter = countryAdapter(requireContext(),viewModal,this)
viewModal.itemSelected.observe(viewLifecycleOwner, Observer<Int>{
val fragment2 = details_frag()
val fragmentManager: FragmentManager? = fragmentManager
val fragmentTransaction: FragmentTransaction = fragmentManager!!.beginTransaction()
fragmentTransaction.apply {
replace(R.id.fragLand, fragment2)
commit()
}
})
rv.adapter = adapter
// Apply the new content into the fragment layout
val mLayoutManager = LinearLayoutManager(activity);
rv.layoutManager = mLayoutManager
return v
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater){
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.primary_menu,menu)
}
}
Theer I would observe if there was a country that has been clicked on and if so I would like to move to my other fragment to get some more details.
My adapter look like that:
class countryAdapter(
var ctx: Context, var viewModal:Country_ViewModel, var owner: LifecycleOwner
) : RecyclerView.Adapter<countryAdapter.countryViewHolder>() {
// lateinit var mListener: onItemLongClickListener
// private lateinit var mSListener: onItemClickListener
var player: MediaPlayer? =null
private lateinit var count: MutableList<Country>
/**-----------------------------------INTERFACES --------------------------------------------------*/
// interface onItemLongClickListener {
//
// fun onItemLongClick(position: Int)
// }
//
// interface onItemClickListener {
//
// fun onItemClick(position: Int): Boolean
// }
/**-----------------------------LISTENERS --------------------------------------------------------*/
// fun setOnItemLongClickListener(listener: onItemLongClickListener) {
// mListener = listener
//
// }
// fun setOnItemClickListener(listener: onItemClickListener) {
// mSListener = listener
// }
/**-----------------------------INNER CLASS--------------------------------------------------------*/
inner class countryViewHolder(itemView: View) :
RecyclerView.ViewHolder(itemView) {
val counrtyName = itemView.findViewById<TextView>(R.id.countryName)
val populationNum = itemView.findViewById<TextView>(R.id.countryPopulation)
val imageCount = itemView.findViewById<ImageView>(R.id.imageView)
/*
* Defining the listeners in the initialization of the Row in the adapter
* */
init {
count= viewModal.countryArray.value!!
itemView.setOnLongClickListener {
viewModal.removeItem(adapterPosition)
return#setOnLongClickListener true
}
itemView.setOnClickListener{
viewModal.itemSelected.value=adapterPosition
Log.i("Hello",adapterPosition.toString())
startPlayer(adapterPosition,ctx)
}
viewModal.countryArray.observe(owner, Observer {
notifyDataSetChanged()
})
}
}
/**---------------------------------------VIEW HOLDER CREATE AND BIND ----------------------------- */
/*
* Will inflate the country XML file in the adapter and then inflate it into the parent that is
* the fragment.
* At the end it will return the inner class with all the parameters that was initiated there.
* */
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): countryAdapter.countryViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.itemcountry, parent, false)
val context = parent.context
val inflater = LayoutInflater.from(context)
val contactView = inflater.inflate(R.layout.itemcountry, parent, false)
return countryViewHolder(contactView)
}
/*
* The function will be responsible to get the data that was initiated at the begining in the
* inner class and change the data that is displayed in the XML file to the new data based on the
* Country that it got
* The position is parameter that is changing every time this function is called and adding all the
* Country that are in the XML into the fragment
*
* */
override fun onBindViewHolder(holder: countryViewHolder, position: Int) {
var countryName1 = holder.counrtyName
var countryPopulation1 = holder.populationNum
var imagecount = holder.imageCount
countryName1.setText(viewModal.countryArray.value?.get(position)?.name_of_country)
countryPopulation1.setText(count?.get(position)?.shorty_of_country)
count?.get(position)?.let {
country_drawable.get(it.name_of_country)?.let {
imagecount.setBackgroundResource(
it
)
}
}
}
/**-----------------------------------------Functions ------------------------------------------- */
fun startPlayer(position: Int,ctx:Context){
player?.stop()
player =
count?.get(position)
?.let { country_raw.get(it.name_of_country)?.let { MediaPlayer.create(ctx, it) } }
player?.start()
}
override fun getItemCount(): Int {
return count.size
}
}
The goal is if the user click on one of the countries in the RecyclyView (OnClickListener) then i would like to move to the second fragment.
Im having an error will creating the viewModal instance the error is:
Cannot create an instance of class com.example.Country_ViewModel
Why is that? what I'm initializing wrong?
Where should i create the instance of the ViewModal? inside the adapter or inside the fragment itself? ( is it ok to pass the viewModal instance to the adapter? or there is another way i can observe the change in the CountryArray?)
Thank you.

Unresolved reference: myViewHolder after switching to View Binding

After switching from removing kotlin_extensions and switching to view binding, I received a "Unresolved reference: myViewHolder" in my onBindViewHolder method and when I replace "myViewHolder" with "holder", it then gives me a "Unresolved reference: bind". How do I resolve this.
MyAdapter
class MyAdapter(private val context: Context, private val mHelper : TaskDbHelper) : RecyclerView.Adapter<MyAdapter.MyViewHolder>(),
SwipeAndDragHelper.ActionCompletionContract {
class MyViewHolder(val binding: CellCardsBinding): RecyclerView.ViewHolder(binding.root ) {
fun binding() {
}
}
private var touchHelper: ItemTouchHelper? = null
private var list = mutableListOf<MyObject>()
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
initList()
super.onAttachedToRecyclerView(recyclerView)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(CellCardsBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
#RequiresApi(Build.VERSION_CODES.P)
#SuppressLint("ClickableViewAccessibility")
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val myObject = list[position]
myViewHolder.bind(myObject)
val activity: Activity = context as Activity
holder.binding.text.setOnClickListener{
activity.launchActivity<AddNoteActivity>(42) {
putExtra("PositionInList", position.toString())
putExtra("TaskTitle", myObject.title)
putExtra("TaskText", myObject.text)
}
}
activity.findViewById<RecyclerView>(R.id.recyclerView).setOnTouchListener { _, event ->
when (event.actionMasked) {
MotionEvent.ACTION_UP -> {
updateNotesPositionInDb()
false
}
else -> {
false
}
}
}
holder.binding.title.setOnTouchListener { _, event ->
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
touchHelper!!.startDrag(holder)
false
}
else -> {
false
}
}
}
}
private fun initList() {
list.clear()
val db = mHelper.readableDatabase
val cursor = db.query(
TaskContract.TaskEntry.TABLE,
arrayOf(
TaskContract.TaskEntry.ID,
TaskContract.TaskEntry.COL_TASK_TITLE,
TaskContract.TaskEntry.COL_TASK_TEXT,
TaskContract.TaskEntry.COL_TASK_DATE),null, null, null, null, TaskContract.TaskEntry.ID)
while (cursor.moveToNext()) {
val id = cursor.getColumnIndex(TaskContract.TaskEntry.ID)
val idTitle = cursor.getColumnIndex(TaskContract.TaskEntry.COL_TASK_TITLE)
val idText = cursor.getColumnIndex(TaskContract.TaskEntry.COL_TASK_TEXT)
val idDate = cursor.getColumnIndex(TaskContract.TaskEntry.COL_TASK_DATE)
list.add(MyObject(cursor.getString(id), cursor.getString(idTitle), cursor.getString(idText), cursor.getString(idDate)))
}
notifyDataSetChanged()
cursor.close()
db.close()
}
override fun getItemCount(): Int {
return list.size
}
override fun onViewMoved(oldPosition: Int, newPosition: Int) {
val target = list[oldPosition]
list.removeAt(oldPosition)
list.add(newPosition, target)
notifyItemMoved(oldPosition, newPosition)
}
override fun onViewSwiped(position: Int) {
deleteTask(list[position].ID)
list.removeAt(position)
notifyItemRemoved(position)
updateNotesPositionInDb()
}
fun setTouchHelper(touchHelper: ItemTouchHelper) {
this.touchHelper = touchHelper
}
fun addTask(taskTitle : String, taskText: String) {
val values = ContentValues()
val sdf = SimpleDateFormat("dd/MM/yyyy/", Locale.US)
val date = sdf.format(Date())
values.put(TaskContract.TaskEntry.ID, list.size)
values.put(TaskContract.TaskEntry.COL_TASK_TITLE, taskTitle)
values.put(TaskContract.TaskEntry.COL_TASK_TEXT, taskText)
values.put(TaskContract.TaskEntry.COL_TASK_DATE, date)
val db = mHelper.readableDatabase
db.insertWithOnConflict(TaskContract.TaskEntry.TABLE,
null,
values,
SQLiteDatabase.CONFLICT_REPLACE)
db.close()
list.add(MyObject(list.size.toString(), taskTitle, taskText, date))
notifyItemInserted(list.size)
}
fun addTask() {
val test: Activity = context as Activity
test.launchActivity<AddNoteActivity>(42) {
/* putExtra("user", "854")
p utExtra("user2", "46850") */
}
}
private fun deleteTask(taskId: String) {
val db = mHelper.readableDatabase
db.delete(TaskContract.TaskEntry.TABLE,
"id=$taskId", null)
db.close()
}
fun modifyTask(taskPosition: String, taskTitle: String, taskText: String) {
val target = list[taskPosition.toInt()]
target.title = taskTitle
target.text = taskText
val values = ContentValues()
val sdf = SimpleDateFormat("dd/MM/yyyy/", Locale.US)
val date = sdf.format(Date())
values.put(TaskContract.TaskEntry.ID, taskPosition)
values.put(TaskContract.TaskEntry.COL_TASK_TITLE, taskTitle)
values.put(TaskContract.TaskEntry.COL_TASK_TEXT, taskText)
values.put(TaskContract.TaskEntry.COL_TASK_DATE, date)
val db = mHelper.readableDatabase
db.update(TaskContract.TaskEntry.TABLE,
values, TaskContract.TaskEntry.ID + "=" + target.ID, null)
db.close()
notifyItemChanged(taskPosition.toInt())
}
private fun updateNotesPositionInDb() {
val db = mHelper.readableDatabase
var i = 0
while (i < list.size) {
val values = ContentValues()
values.put(TaskContract.TaskEntry.ID, i)
db.update(TaskContract.TaskEntry.TABLE,
values, TaskContract.TaskEntry.ID + "=? AND " + TaskContract.TaskEntry.COL_TASK_TITLE + "=?", arrayOf(list[i].ID, list[i].title))
i++
}
db.close()
}
I've tried reading Android Studio's official documentation, but it cannot solve my specific problem.
in your class MyViewHolder you have method called binding and you need also to implement it and add paramter
shoud be
class MyViewHolder(private val binding: CellCardsBinding): RecyclerView.ViewHolder(binding.root ) {
fun bind(data:MyObject) {
binding.yourView=data.title ...
}
}
in onBindViewHolder
..
holder.bind(myObject)
After switching from removing kotlin_extensions and switching to view binding, I received a "Unresolved reference: myViewHolder" in my onBindViewHolder method
Well, your onBindViewHolder method is passing a variable called holder and you're trying to use a variable called myViewHolder, so that seems like a problem.
// --------------------this-----v
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val myObject = list[position]
// v--- doesn't match this
myViewHolder.bind(myObject)
and when I replace "myViewHolder" with "holder", it then gives me a "Unresolved reference: bind". How do I resolve this.
Your MyViewHolder class has a method called binding that takes no arguments. There is no bind method that takes a "myObject".
class MyViewHolder(val binding: CellCardsBinding): RecyclerView.ViewHolder(binding.root ) {
fun binding() {
}
}
Edit
You should pass an instance of the data class
class MyViewHolder(val binding: CellCardsBinding): RecyclerView.ViewHolder(binding.root ) {
fun bind(object: MyObject) {
// Set variables on binding
}
}
Then pass an instance from your list via onBindViewHolder:
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val myObject = list[position]
holder.bind(myObject)
Please check this blog post for more.
I got my answer.
class MyAdapter(private val context: Context, private val mHelper : TaskDbHelper) : RecyclerView.Adapter<MyAdapter.MyViewHolder>(),
SwipeAndDragHelper.ActionCompletionContract {
class MyViewHolder(val binding: CellCardsBinding): RecyclerView.ViewHolder(binding.root ) {
private val titleView: TextView = itemView.findViewById<View>(R.id.title) as TextView
val textView: TextView = itemView.findViewById<View>(R.id.text) as TextView
private val dateTextView: TextView = itemView.findViewById<View>(R.id.date) as TextView
fun binding (myObject: MyObject) {
titleView.text = myObject.title
textView.text = myObject.text
dateTextView.text = myObject.date
}
}
I simply initialised the view I wanted to reference in my layout and called them in the binding() function.

How to change recyclerview item and sub items when clicked and also get the data in the position clicked from a fragment?

I have 2 Recyclerviews in a fragment. Each item consists of 2 textview. When clicked, i want to change the color of item background and the 2 tvs and get the listofData(position) clicked also then send these s pieces of data to an activity.
The problem is 2 rvs each have its own adapter so i can't call the activity and check if data is selected from both adapters. And when i try it from the fragment i get the adapter position right but the view colors are not being changed correctly.
My RV element
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="2dp"
android:paddingBottom="10dp"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:background="#drawable/date_time_background"
android:elevation="0dp"
android:gravity="center"
android:layout_marginEnd="5dp"
android:orientation="vertical"
android:id="#+id/item_linear_layout">
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/dateNameTV"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Sat"
android:textColor="#color/black_65"
android:textSize="16sp" />
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/dateNumTV"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="23/5"
android:textColor="#color/black_65"
android:textSize="16sp" />
</LinearLayout>
My RV adapter
class TimesAdapter(private var availableTimes: List<String>?, private val onTimeListener: OnTimeListener) :
RecyclerView.Adapter<TimesAdapter.TimeViewHolder>() {
private var itemIndex = -1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TimeViewHolder {
val v = LayoutInflater.from(parent.context)
.inflate(R.layout.date_time_element, parent, false)
return TimeViewHolder(v, onTimeListener)
}
override fun getItemCount(): Int = availableTimes?.size!!
#SuppressLint("ClickableViewAccessibility")
override fun onBindViewHolder(holder: TimeViewHolder, position: Int) {
val currentDate: String? = availableTimes?.get(position) // i.e Sun 23/5
val parts = currentDate?.split(" ")
try {
val part1 = parts?.get(0)
holder.dateNameTV.text = part1
val part2 = parts?.get(1)
holder.dateNumTV.text = part2
} catch (e: IndexOutOfBoundsException) {
e.printStackTrace()
}
holder.itemLinearLayout.setOnClickListener {
itemIndex = position
notifyItemChanged(position)
}
val ctx = holder.itemLinearLayout.context
if (itemIndex == position) {
holder.itemLinearLayout.background = (loadDrawable(ctx, R.drawable.date_time_background_selected))
holder.dateNameTV.setTextColor(loadColor(ctx, android.R.color.white))
holder.dateNumTV.setTextColor(loadColor(ctx, android.R.color.white))
} else {
holder.itemLinearLayout.background = (loadDrawable(ctx, R.drawable.date_time_background))
holder.dateNameTV.setTextColor(loadColor(ctx, R.color.black_65))
holder.dateNumTV.setTextColor(loadColor(ctx, R.color.black_65))
}
}
class TimeViewHolder(itemView: View, onTimeListener: OnTimeListener) :
RecyclerView.ViewHolder(itemView), View.OnClickListener{
var dateNameTV: TextView = itemView.findViewById(R.id.dateNameTV)
var dateNumTV: TextView = itemView.findViewById(R.id.dateNumTV)
var itemLinearLayout: LinearLayout = itemView.findViewById(R.id.item_linear_layout)
private var onTimeListener: OnTimeListener? = null
init {
itemView.setOnClickListener(this)
this.onTimeListener = onTimeListener
}
override fun onClick(v: View?) {
onTimeListener?.onTimeClick(adapterPosition, itemView)
}
}
interface OnTimeListener{
fun onTimeClick(position: Int, itemView: View)
}
}
My fragment
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
/**
* A simple [Fragment] subclass.
* Use the [DoctorAppointmentsFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class DoctorAppointmentsFragment : Fragment(),
DatesAdapter.OnDateListener, TimesAdapter.OnTimeListener {
private lateinit var datesRV: RecyclerView
private lateinit var timesRV: RecyclerView
private lateinit var linearLayoutManager: LinearLayoutManager
private lateinit var datesAdapter: DatesAdapter
private lateinit var timesAdapter: TimesAdapter
private lateinit var dateNameTV: TextView
private lateinit var dateNumTV: TextView
private lateinit var dateLinearLayout: LinearLayout
private var dates: List<String>? = null
private var times: List<String>? = null
private var isDateSelected = false
private var isTimeSelected = false
private var selectedDate: String? = null
private var selectedTime: String? = null
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null
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? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_doctor_appointments, container, false)
datesRV = view.findViewById(R.id.datesRV)
datesRV.setHasFixedSize(true)
linearLayoutManager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false)
linearLayoutManager.isAutoMeasureEnabled = false
datesRV.layoutManager = linearLayoutManager
timesRV = view.findViewById(R.id.timesRV)
timesRV.setHasFixedSize(true)
linearLayoutManager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false)
linearLayoutManager.isAutoMeasureEnabled = false
timesRV.layoutManager = linearLayoutManager
val bookNowBT = view.findViewById<Button>(R.id.bookNowBT)
bookNowBT.setOnClickListener {
if (isDateSelected && isTimeSelected) {
val i = Intent(activity, ConfirmPaymentActivity::class.java)
i.putExtra(SELECTED_DATE, selectedDate)
i.putExtra(SELECTED_TIME, selectedTime)
startActivity(i)
}
}
getDoctorAvailableAppointments("7ab63fd2461bfb0008b72f5d8c0033fs", "basic")
return view
}
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 DoctorAppointmentsFragment.
*/
// TODO: Rename and change types and number of parameters
#JvmStatic
fun newInstance(param1: String, param2: String) =
DoctorAppointmentsFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
private fun getDoctorAvailableAppointments(doctorID: String, type: String) {
setProgressDialog(this.requireActivity())
if (!InternetConnection.isInternetAvailable(this.requireActivity())) {
alertError(this.requireActivity(),
R.string.no_internet_connection,
R.string.check_internet_connection)
} else {
showProgressDialog()
val builder = ServiceBuilder()
val appointments = builder.getDoctorAvailableReservations()
val call = appointments.getDoctorAvailableReservations(
RequestAvailableReservations(doctorID,type))
call?.enqueue(object : Callback<ResponseAvailableReservations?> {
override fun onResponse(call: Call<ResponseAvailableReservations?>, response: Response<ResponseAvailableReservations?>) {
dismissProgressDialog()
if (!response.isSuccessful) {
alertError(requireActivity(),
R.string.code_not_200,
R.string.try_later)
return
}
val body: ResponseAvailableReservations? = response.body()
if (body == null) {
alertError(requireActivity(),
R.string.null_body,
R.string.try_later)
return
}
val status = body.status
val message = body.message
val data = body.data
if (status == null || message == null || data == null) {
alertError(requireActivity(),
R.string.null_body,
R.string.try_later)
return
}
if (status == "error") {
alertError(requireActivity(), R.string.error, message)
} else if (status == "success") {
dates = data.availableDatesList
datesAdapter = DatesAdapter(dates, this#DoctorAppointmentsFragment)
datesRV.adapter = datesAdapter
times = data.availableTimesList
timesAdapter = TimesAdapter(times, this#DoctorAppointmentsFragment)
timesRV.adapter = timesAdapter
}
}
override fun onFailure(call: Call<ResponseAvailableReservations?>, t: Throwable) {
t.printStackTrace()
dismissProgressDialog()
alertError(requireActivity(),
R.string.fail,
R.string.login_fail)
}
})
}
}
private var dateIndex = -1
override fun onDateClick(position: Int) {
selectedDate = dates?.get(position)
isDateSelected = true
Log.e("selectedDate", "selectedDate")
dateIndex = position
datesAdapter.notifyDataSetChanged()
}
private var timeIndex = -1
override fun onTimeClick(position: Int, itemView: View) {
// selectedTime = times?.get(position)
// isTimeSelected = true
// Log.e("isSelectedTime", "true")
// timeIndex = position
// timesAdapter.notifyItemChanged(position)
// Log.e("timeIndex", timeIndex.toString())
//
// val ctx = itemView.context
// if (timeIndex == position) {
// Log.e("timeIndex", "timeIndex == position")
//
// itemView.background = loadDrawable(ctx, R.drawable.date_time_background_selected)
// itemView.dateNameTV?.setTextColor(loadColor(ctx, android.R.color.white))
// itemView.dateNumTV?.setTextColor(loadColor(ctx, android.R.color.white))
// } else {
// itemView.background = (loadDrawable(ctx, R.drawable.date_time_background))
// itemView.dateNameTV.setTextColor(loadColor(ctx, R.color.black_65))
// itemView.dateNumTV.setTextColor(loadColor(ctx, R.color.black_65))
// }
}
}
Solved
For anyone interested i ended up using a very simple idea. Instead of accessing the RecyclerView selected element from outside the adapter, i did all i wanted to do inside the adapter and used 4 static variables in the Fragment. 2 booleans to check
if date and time are selected or not and 2 Strings having the date and time actually selected.
Inside the Fragment:
companion object {
#JvmStatic var isTimeSelected = false
#JvmStatic var selectedTime = ""
#JvmStatic var isDateSelected = false
#JvmStatic var selectedDate = ""
....
}
Inside the Adapter's onBindViewHolder()
holder.itemLinearLayout.setOnClickListener {
selectedDate = dates?.get(position)!!
isDateSelected = true
listener?.onDateClick(position)
selectedIndex = position
notifyDataSetChanged()
}
val ctx = holder.itemLinearLayout.context
if (selectedIndex == position) {
holder.itemLinearLayout.background = (loadDrawable(ctx, R.drawable.date_time_background_selected))
holder.dateNameTV.setTextColor(loadColor(ctx, android.R.color.white))
holder.dateNumTV.setTextColor(loadColor(ctx, android.R.color.white))
} else {
holder.itemLinearLayout.background = (loadDrawable(ctx, R.drawable.date_time_background))
holder.dateNameTV.setTextColor(loadColor(ctx, R.color.black_65))
holder.dateNumTV.setTextColor(loadColor(ctx, R.color.black_65))
}
And i made sure static variables don't have old values using this
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
isTimeSelected = false
selectedTime = ""
isDateSelected = false
selectedDate = ""
}
I know it's bad practice to put onClickListener inside onBindViewHolder but i don't konw if the static variables thing is good or bad practice. Either way it's working fine for now.
There is many ways to do this, And I recommend the first one.
Using shared ViewModels for communication between fragments in the same activity. The idea is that you will tie up the ViewModel with activity and every fragment can access this ViewModel and in this ViewModel you will have a LiveData object that holds the data and observing it from both fragments and every change in the value of LiveData will affect each fragment. Shared ViewModel
Using interfaces for communication between fragments and it's a hard thing to do these days. Basic communication between fragments
Using EventBus it's easy but not recommended while you can use ViewModel at the first solution. EventBus
Try this
TimesAdapter
class TimesAdapter(
availableTimes: List<String>
) : RecyclerView.Adapter<TimesAdapter.TimeViewHolder>() {
var availableTimes:List<String> = availableTimes
set(value) {
field = value
notifyDataSetChanged()
}
var listener:TimeSelectedListener?=null
var selectedIndex:Int = -1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TimeViewHolder {
return TimeViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.date_time_element, parent, false)
)
}
override fun getItemCount(): Int = availableTimes.count()
override fun onBindViewHolder(holder: TimeViewHolder, position: Int) {
val currentDate: String? = availableTimes[position] // i.e Sun 23/5
val parts = currentDate?.split(" ")
try {
val part1 = parts?.get(0)
val part2 = parts?.get(1)
holder.dateNameTV.text = part2
holder.dateNumTV.text = part1
} catch (e: IndexOutOfBoundsException) {
e.printStackTrace()
}
holder.itemLinearLayout.setOnClickListener {
listener?.onTimeClick(position)
selectedIndex = position
}
if (selectedIndex == position) {
// Selection Code
} else {
// De selection code
}
}
class TimeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var dateNameTV: TextView = itemView.findViewById(R.id.dateNameTV)
var dateNumTV: TextView = itemView.findViewById(R.id.dateNumTV)
var itemLinearLayout: LinearLayout = itemView.findViewById(R.id.item_linear_layout)
}
interface TimeSelectedListener {
fun onTimeClick(position: Int)
}
}
Usage from fragment
times = data.availableTimesList
timesAdapter = TimesAdapter(times).apply {
listener = object :TimesAdapter.TimeSelectedListener{
override fun onTimeClick(position: Int) {
// TODO here is where you should implement when an item is selected
}
}
}
timesRV.adapter = timesAdapter