Pass data from adapter to activity using interface callback method with Kotlin

折月煮酒 提交于 2021-01-29 21:49:31

问题


I have read through this Kotiln: pass data from adapter to activity and am attempting option 1 from the answer given.

I have a game with various levels. I send all levels to a recycler view using the the below itemview in gridlayout

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:paddingTop="6dp"
    android:paddingBottom="6dp"
    app:layout_constraintHorizontal_chainStyle="spread">

    <RatingBar
        android:id="@+id/ratingBar"
        style="@style/Widget.AppCompat.RatingBar.Small"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="2dp"
        android:layout_marginEnd="8dp"
        android:isIndicator="true"
        android:numStars="3"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button" />

    <Button
        android:id="@+id/button"
        android:layout_width="90dp"
        android:layout_height="60dp"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:backgroundTint="@color/game_button_color"
        android:textColor="@color/game_button_text_color"
        app:autoSizeTextType="uniform"
        app:layout_constraintDimensionRatio="1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

My adapter currently looks like this;

    package com.maxcell.sumitup

import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.RatingBar
import androidx.core.content.ContextCompat.startActivity
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView


class LevelsAdaptor(private val callbackInterface:CallbackInterface) : ListAdapter<Levels, LevelsAdaptor.LevelsViewHolder>(LevelsComparator()) {

    interface CallbackInterface {
        fun passDataCallback(main:Int,sub: Int,star: Int)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LevelsViewHolder {
        return LevelsViewHolder.create(parent)
    }

    override fun onBindViewHolder(holder: LevelsViewHolder, position: Int) {
        val current = getItem(position)
        holder.bind(
            current.id,
            current.mainLevel,
            current.subLevel,
            current.stars,
            current.unlocked
        )

    }


    class LevelsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {



        private val btn: Button = itemView.findViewById(R.id.button)
        private val rating: RatingBar = itemView.findViewById(R.id.ratingBar)
        private val myContext = itemView.context
        //private val myContext: Context = sumView.context
        fun bind(ID: Int, ML: Int, SL: Int, Stars: Int, Unlocked: Boolean) {

            btn.text = ID.toString()
            btn.setTag(R.id.mainLevel, ML)
            btn.setTag(R.id.subLevel, SL)
            btn.setTag(R.id.visualLevel, ID)
            btn.setTag(R.id.currentLevelStars, Stars)
            rating.rating = Stars.toFloat()
            if (Unlocked==true){btn.isEnabled=true; btn.isClickable=true}else{btn.isEnabled=false; btn.isClickable=false}

            itemView.setOnClickListener {
                //Set your codes about intent here
                CallbackInterface.passDataCallback(ML,SL,Stars)
            }

        }



        companion object {
            fun create(parent: ViewGroup): LevelsViewHolder {
                val view: View = LayoutInflater.from(parent.context)
                    .inflate(R.layout.rv_storymode_items, parent, false)
                return LevelsViewHolder(view)
            }
        }
    }

    class LevelsComparator : DiffUtil.ItemCallback<Levels>() {
        override fun areItemsTheSame(oldItem: Levels, newItem: Levels): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: Levels, newItem: Levels): Boolean {
            return oldItem == newItem
        }
    }
}

The current issue I have is with my companion object, because I have included the callback interface in my onbindviewholder it needs me to include a parameter for it in the return. I am struggling to get my head round this and understand what parameter should be included at this point

Also I am not following it 100%. In the link I shared the interface function is called passDataCallback() and then they use passResultCallback() in the onBindViewHolder. If I use passResultCallback it doesnt resolve so I dont know if I am doing something wrong or if indeed it was meant to be as I have written it in my adapter above

I am not collecting this data in my activity yet as want the adapter to at least be written correctly first, if people think I need to re think the approach completely I will listen to that advice.

Here is the activity that holds the recyclerview. This is where I believe I should be launching the new activity for result

package com.maxcell.sumitup

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.Toast
import androidx.activity.viewModels
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentTransaction
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.activity_story_levels.*

class StoryLevels : AppCompatActivity(), View.OnClickListener {

    private lateinit var storyView: RecyclerView
    val numbers = mutableListOf(1,2,3,4,5,6,7,8,9,10)
    lateinit var btn1: Button
    lateinit var btn2: Button
    lateinit var btn3: Button
    lateinit var btn4: Button
    lateinit var btn5: Button
    lateinit var btn6: Button
    lateinit var btn7: Button
    lateinit var btn8: Button
    lateinit var btn9: Button
    lateinit var btn10: Button

    lateinit var popupLayout: ConstraintLayout
    lateinit var popupBTNstart:Button
    lateinit var popupBTNexit:Button




    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_story_levels)

        storyView = findViewById(R.id.rv_storymode)

        btn1 = findViewById(R.id.button2)
        btn2 = findViewById(R.id.button3)
        btn3 = findViewById(R.id.button4)
        btn4 = findViewById(R.id.button5)
        btn5 = findViewById(R.id.button6)
        btn6 = findViewById(R.id.button7)
        btn7 = findViewById(R.id.button8)
        btn8 = findViewById(R.id.button9)
        btn9 = findViewById(R.id.button10)
        btn10 = findViewById(R.id.button11)

        btn1.text = numbers[0].toString()
        btn2.text = numbers[1].toString()
        btn3.text = numbers[2].toString()
        btn4.text = numbers[3].toString()
        btn5.text = numbers[4].toString()
        btn6.text = numbers[5].toString()
        btn7.text = numbers[6].toString()
        btn8.text = numbers[7].toString()
        btn9.text = numbers[8].toString()
        btn10.text = numbers[9].toString()

        btn1.setOnClickListener(this)
        btn2.setOnClickListener(this)
        btn3.setOnClickListener(this)
        btn4.setOnClickListener(this)
        btn5.setOnClickListener(this)
        btn6.setOnClickListener(this)
        btn7.setOnClickListener(this)
        btn8.setOnClickListener(this)
        btn9.setOnClickListener(this)
        btn10.setOnClickListener(this)

        popupLayout = findViewById(R.id.layout_pregame_intro)
        popupLayout.isVisible = false
        popupBTNexit = findViewById(R.id.popup_btn_exit)

        val levelsViewModel: LevelsViewModel by viewModels {
            LevelsViewModelFactory((application as MyApplication).repository2)}

        val adapter = LevelsAdaptor()
        rv_storymode.adapter = adapter
        rv_storymode.layoutManager = GridLayoutManager(applicationContext, 4);

        levelsViewModel.level1.observe(this) { newValue ->
            // Update the cached copy of the words in the adapter.
            newValue.let { adapter.submitList(it) }
        }
    }

    fun testMessage(){
        Toast.makeText(applicationContext,"this is toast message", Toast.LENGTH_SHORT).show()
    }
    override fun onClick(v: View?) {

        val levelsViewModel: LevelsViewModel by viewModels {
            LevelsViewModelFactory((application as MyApplication).repository2)}

        val adapter = LevelsAdaptor()
        rv_storymode.adapter = adapter
        rv_storymode.layoutManager = GridLayoutManager(applicationContext, 4);

        if (v != null) {
            when (v.id){
                R.id.button2->{
                    levelsViewModel.level1.observe(this) { newValue ->
                        // Update the cached copy of the words in the adapter.
                        newValue.let { adapter.submitList(it) }}
                }
                R.id.button3->{
                    levelsViewModel.level2.observe(this) { newValue ->
                        // Update the cached copy of the words in the adapter.
                        newValue.let { adapter.submitList(it) }}}
                R.id.button4->{
                    levelsViewModel.level3.observe(this) { newValue ->
                        // Update the cached copy of the words in the adapter.
                        newValue.let { adapter.submitList(it) }}}
                R.id.button5->{
                    levelsViewModel.level4.observe(this) { newValue ->
                        // Update the cached copy of the words in the adapter.
                        newValue.let { adapter.submitList(it) }}}
                R.id.button6->{
                    levelsViewModel.level5.observe(this) { newValue ->
                        // Update the cached copy of the words in the adapter.
                        newValue.let { adapter.submitList(it) }}}
                R.id.button7->{
                    levelsViewModel.level6.observe(this) { newValue ->
                        // Update the cached copy of the words in the adapter.
                        newValue.let { adapter.submitList(it) }}}
                R.id.button8->{
                    levelsViewModel.level7.observe(this) { newValue ->
                        // Update the cached copy of the words in the adapter.
                        newValue.let { adapter.submitList(it) }}}
                R.id.button9->{
                    levelsViewModel.level8.observe(this) { newValue ->
                        // Update the cached copy of the words in the adapter.
                        newValue.let { adapter.submitList(it) }}}
                R.id.button10->{
                    levelsViewModel.level9.observe(this) { newValue ->
                        // Update the cached copy of the words in the adapter.
                        newValue.let { adapter.submitList(it) }}}
                R.id.button11->{
                    levelsViewModel.level10.observe(this) { newValue ->
                        // Update the cached copy of the words in the adapter.
                        newValue.let { adapter.submitList(it) }}}
            }
        }
    }
}

Expected Result

Recyclerview is filled with buttons which contain a ratingbar (1-3 stars depending on how well they did last time they played that level) - this works

When i click one of the buttons my activity should know the main level, sublevel and current stars for that level

I will then start a new activity for result passing that information into my minigame

Note: You will notice I set button tags. This was one approach I was following, this can be removed if using the interface works well and if people agree its the best approach

...............EDIT.................

Adapter code updated so that the interface is initialised at adapter level rather than within the onbindviewholder.

However, I still cannot get the following to work;

itemView.setOnClickListener {
                //Set your codes about intent here
                CallbackInterface.passDataCallback(ML,SL,Stars)
            }

The specific problem is that 'passDataCallback' will not resolve i.e. Unresolved reference: passDataCallback. I believe it does now resemble the approach shared in the link at the top. I have to admit I am new to interfaces. I have only ever used them to date within a Dao

I have tried moving the interface into the LevelsViewHolder - This did not fix it.


回答1:


change your adapter to this

class LevelsAdaptor(private val onClick:(id:Int)->Unit) : ListAdapter<Levels, LevelsAdaptor.LevelsViewHolder>(LevelsComparator()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LevelsViewHolder {
        val view: View = LayoutInflater.from(parent.context)
            .inflate(R.layout.rv_storymode_items, parent, false)
        return LevelsViewHolder(view)
    }

    override fun onBindViewHolder(holder: LevelsViewHolder, position: Int) {
        val current = getItem(position)
        holder.bind(
            current.id,
            current.mainLevel,
            current.subLevel,
            current.stars,
            current.unlocked
        )
    }
    
    inner class LevelsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val btn: Button = itemView.findViewById(R.id.button)
        private val rating: RatingBar = itemView.findViewById(R.id.ratingBar)
        private val myContext = itemView.context
        //private val myContext: Context = sumView.context
        fun bind(ID: Int, ML: Int, SL: Int, Stars: Int, Unlocked: Boolean) {
            btn.text = ID.toString()
            btn.setTag(R.id.mainLevel, ML)
            btn.setTag(R.id.subLevel, SL)
            btn.setTag(R.id.visualLevel, ID)
            btn.setTag(R.id.currentLevelStars, Stars)
            rating.rating = Stars.toFloat()
            if (Unlocked==true){btn.isEnabled=true; btn.isClickable=true}else{btn.isEnabled=false; btn.isClickable=false}
            itemView.setOnClickListener {
                //Set your codes about intent here
                onClick(ID)
            }
        }
    }

    class LevelsComparator : DiffUtil.ItemCallback<Levels>() {
        override fun areItemsTheSame(oldItem: Levels, newItem: Levels): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: Levels, newItem: Levels): Boolean {
            return oldItem == newItem
        }
    }
}

you initialize the adapter in your activity with

val adapter = LevelsAdaptor(){id->
    //TODO do something with id passed from adapter, e.g. create new fragment
}

I would also recommend using Jetpack Navigation you could use the detail view as entering the level



来源:https://stackoverflow.com/questions/65666654/pass-data-from-adapter-to-activity-using-interface-callback-method-with-kotlin

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!