0

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.

UnknownError
  • 103
  • 14
  • have you decided on using the second approach from the post you mentioned or would it be fine to have solution based on 3rd approach (passing function as adapter argument)? – Stachu Jan 11 '21 at 12:36
  • I am open to any approach. The requirement is simply on click of button to launch a new activity for result which takes the data from the specific button pressed. – UnknownError Jan 11 '21 at 12:40
  • what result do you want to see on the screen after button is pressed? is it because you want to launch other app? if you just want a new layout you could use single activity + Fragments, it's much easier approach – Stachu Jan 11 '21 at 12:45
  • I have the minigame in a fragment. So when I say launch activity for result I can simply launch the fragment from the same activity. The issue I am facing at the root level is getting data for each button that would be set via the adapter that I can then feed into the fragment. – UnknownError Jan 11 '21 at 12:55
  • For example befor feeding data to test the approach I launched a fragment transaction in the adapters on click. But I could not find a way to add data to transaction. That's when I started following different guides like the linked one to get data to the host activity – UnknownError Jan 11 '21 at 12:57
  • You should initialize your interface in your adapter , for example you can initialize it as an argument like this : `class LevelsAdaptor(private val callbackInterface:CallbackInterface)` then you can use it in your `adapter` and change this line `val adapter = LevelsAdaptor()` in your activity to `val adapter = LevelsAdaptor(this)` this like a bridge that make communication between `activity` and `adapter` – milad salimi Jan 11 '21 at 19:19
  • @milad salimi I will make those changes. I'm assuming my companion object will still expect a value or do you mean remove it from the onbindviewholder and only initiliaze it in the adapter section? – UnknownError Jan 11 '21 at 21:12

1 Answers1

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

Stachu
  • 1,614
  • 1
  • 5
  • 17
  • Can you explain how this helps me access the data of the button / interface? Or am I meant to write a separate statement for every level in the activity 480) instead to set the data to my fragment... – UnknownError Jan 12 '21 at 08:55
  • there is no need for an interface with this - you just define what you would put in callback in `onClick`. You just need to add the data from button as arguments in the `onClick` - I only put id there, but you can add as many as you want – Stachu Jan 12 '21 at 08:58
  • Ok so where you have put ID I can actually call my current."whatever is available in the data class"? E.g. mainLevel,subLevel, stars? – UnknownError Jan 12 '21 at 08:59
  • yes, you can add all `ID: Int, ML: Int, SL: Int, Stars: Int, Unlocked: Boolean`, you just need to modify `onClick:(id:Int)->Unit` accordingly – Stachu Jan 12 '21 at 09:00
  • I understand it now. Sorry was reading from mobile and didn't scroll further. Sorry I understand now. Will test that as soon as I get home. Thank you – UnknownError Jan 12 '21 at 09:02
  • Does the onClick method also need to be passed into the LevelsViewHolder because within the setOnClickListener 'onClick' does not resolve. – UnknownError Jan 12 '21 at 09:47
  • not if you make `LevelsViewHolder` inner class of `LevelsAdaptor` as I did, otherwise yes – Stachu Jan 12 '21 at 09:54
  • I didnt spot that apologies. That works thank you – UnknownError Jan 12 '21 at 09:55
  • This has had an impact on override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LevelsViewHolder { return LevelsViewHolder.create(parent) } causing the .create not to resolve. For inner class does this need to be referenced differently as well? It also causes problems with the companion object – UnknownError Jan 12 '21 at 09:58
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/227189/discussion-between-stachu-and-unknownerror). – Stachu Jan 12 '21 at 10:01