ViewPager child to swipe next the parent - android

I have a ViewPager2 which I'm using with a RecyclerView Adapter. I'm binding the children of each viewpager item via the ViewHolder and everything is working okay.
I have a bunch of elements in this ViewPager item XML, some RadioButton components and a Button. I want this button to move it to the next item.
I know how to do that externally, assigning a sibling to the ViewPager2 and then setting a click-listener in the activity and then comparing currentItem of the ViewPager with the adapter's total item count.
I want the "Move to next" button inside the ViewPager as I want to change it based on the inputs supplied to the RadioButton components.
I'm currently stuck at failing getting the ViewPager item's button in the activity to set the click-listener. Is there any workaround to get the child element via the adapter itself?
Here's the activity_quiz.xml:
<?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="match_parent"
android:layout_height="match_parent"
tools:context=".ui.quiz.view.QuizActivity">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/quiz_list"
android:layout_width="match_parent"
android:layout_height="600dp"
android:clipToPadding="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:layout_editor_absoluteX="16dp"
tools:listitem="#layout/quiz_item" />
</androidx.constraintlayout.widget.ConstraintLayout>
The quiz_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:orientation="vertical"
android:padding="#dimen/bigSpacing"
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/question"/>
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/choices">
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/choice_1"/>
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/choice_2"/>
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/choice_3"/>
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/choice_4"/>
</RadioGroup>
<Button
android:id="#+id/result_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Next question" />
</LinearLayout>
The MyAdapter class (kotlin)
class MyAdapter: RecyclerView.Adapter<MyAdapter.MyViewHolder> {
override fun onCreateViewHolder(...) {
...
}
override fun getItemCount(): Int {
...
}
override fun onBindViewHolder(...) {
...
}
class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
fun bind(someData: SomeData) {
itemView.question.text = somedata.question
itemView.choice_1.text = somedata.choice_1
itemView.choice_2.text = somedata.choice_2
itemView.choice_3.text = somedata.choice_3
itemView.choice_4.text = somedata.choice_4
val answerKey = someData.answerKey
var rightOrWrong = ""
itemView.choices.setOnCheckedChangeListener {_, checkedID ->
val checkedIndex: Int = itemView.choices.indexOfChild(itemView.choices.findViewById(checkedID))
if(checkedIndex + 1 == answerKey.toInt()) {
rightOrWrong = "Correct! Next Question."
} else {
rightOrWrong = "Incorrect :/ Next Question."
}
itemView.result_button.text = rightOrWrong
}
}
}
And the QuizActivity.kt file
class QuizActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_quiz)
val myAdapter = MyAdapter()
quiz_list.adapter = myAdapter
// quiz_list.result_button.setOnClickListener {
// No idea how to get the children correctly, as this one throws errors
// }
}
}

First, why do you want to use RecyclerView.Adapter<MyAdapter.MyViewHolder> as an adapter for viewpager2? There is FragmentStateAdapter a built-in class implementation of PagerAdapter that uses a Fragment to manage each page, which is recommended for viewpager2.
Second, you are not inflating the views in MyViewHolder, I don't know if you left them intentionally for this post.
You cant access child views like this quiz_list.result_button.setOnClickListener {} while using something like RecyclerView, Viewpagger. You can access them in the ViewHolder after you inflate them, you can set any listener there aswell

Related

Call to Initialise and Display Fragment from Adapter Class from Buttons/Menu Generated at Runtime

I'm trying to write a simple runtime user interface that's generated from the contents of files read from the SD card. Am using a RecyclerView to generate buttons, the user can then make a selection (via the button) before moving onto the next menu on the next fragment. This program combines, RecyclerViews, Viewbinding, Fragments and Filehandling (the filehandling being the least important).
For example (I can't get a photo of the first fragment running unfortunately):
would be generated from this text file:
Priory Hotel, Victoria Street, Bristol
Landsdown Hotel, Landsdown, Bristol
The Hilton, Walcot Street, Bristol
MerryFields, Milsom Street, Bristol
Loz's Kitchen, St Jame's Square, Bristol
The top button would contain the text Priory Hotel, Victoria Street, Bristol the second button Landsdown Hotel, Landsdown, Bristol the 3rd The Hilton, Walcot Street, Bristol and so forth, the number of buttons displayed being the same as the number of lines (number of clients) of text.
I'm hoping to generate the 'button/menu interface' at runtime. The next fragment would be another list of buttons, also generated from a different text file (in the same way) and so on and so forth.
Is doing this even a good idea ? Should I be using Fragments ?
Am using Android Studio and developing for Android 5.1 (Lollipop I think). The error comes when I call the onClickListener in the Adapter Class. I just can't get this to work and have tried the following Stack Overflow Answers:
This doesn't work and isn't kotlin,
nor this (again kotlin but I can convert). Can't call supportFragmentManager
This is unanswered
I have read this, watched this and read these SO posts: 1, 2, 3, 4. 5. And finally this for good measure.
Adapter Class.kt
// I've not included the imports for brevity
class MyAdapter(private val aList : ArrayList<String>, private val destination: Int) : RecyclerView.Adapter<MyAdapter.MyViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.list_item,
parent, false)
return MyViewHolder(itemView)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
Log.d("Loz", "start onBindViewHolder()")
val currentItem = aList[position]
holder.buttonInRecyclerV.text = currentItem
val context = holder.buttonInRecyclerV.context
Log.d("Loz", "onBindViewHolder() before setOnClickLIstener")
// **************************************************************
// According to the error message provided by Logcat the crash is
// this line below. The 2 log messages in this function are being
// outputted too.
// **************************************************************
holder.buttonInRecyclerV.setOnClickListener {v ->
v.findNavController().navigate(destination)
// The toast message gets printed if I get rid of all the navigation
// and just run 1 fragment
Toast.makeText(context,
"${aList[position]} button pressed", Toast.LENGTH_LONG).show()
}
}
class MyViewHolder(itemView: View) :RecyclerView.ViewHolder(itemView)
{
private val binding = ListItemBinding.bind(itemView)
val buttonInRecyclerV : Button = binding.clientsNameButton
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var clientArrayList: ArrayList<String>
private lateinit var productArrayList: ArrayList<String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
Log.d("Loz", "before dataInit()")
dataInitialise(view)
Log.d("Loz", "after dataInit()")
replaceFragment(savedInstanceState, FirstFragment(), clientArrayList, "clientsListBundle")
Log.d("Loz", "after 1st frag Init()")
// The following may well be a load of twoddle, I was desparate when I wrote the
// code below. However if I just have:
// replaceFragment(savedInstanceState, SecondFragment(), productArrayList, "productListBundle)
// uncommented and comment out the first call of replaceFragment() the second fragment
// is created no problem...
if (FirstFragment().lifecycle.currentState == Lifecycle.State.RESUMED) {
replaceFragment(
savedInstanceState,
SecondFragment(),
productArrayList,
"productListBundle"
)
}
Log.d("Loz", "after 2nd frag Init()")
}
// This function (again) seems to work without the navigation elements
// in the app, Logs outputted.
private fun replaceFragment(savedInstanceState: Bundle?, aFragment: Fragment, aList: ArrayList<String>, TOKEN: String){
if (savedInstanceState == null) {
val fragmentManager = supportFragmentManager
val bundle = Bundle()
Log.d("Loz", "after replaceFragment()")
bundle.putStringArrayList(TOKEN, aList)
aFragment.arguments = bundle
fragmentManager.commit {
setReorderingAllowed(true)
replace(R.id.frameLayout, aFragment)
}
}
}
private fun listFromFile(view: View, filename :String) : MutableList<String> {
// Had no problems with this, and it works as expected when there's no navigation
// component to this app.
}
private fun dataInitialise(view: View) {
clientArrayList = listFromFile(view, "NEades/Client List.txt") as ArrayList<String>
productArrayList = listFromFile(view, "NEades/Product List.txt") as ArrayList<String>
}
}
activity_main.xml
<?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="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<?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="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!-- If I put this in neither fragment works, if I take it out, both -->
<!-- do independently -->
<!-- <androidx.fragment.app.FragmentContainerView-->
<!-- android:id="#+id/fragment_container_view"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent"-->
<!-- app:navGraph="#navigation/nav_graph">-->
<FrameLayout
android:id="#+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="#id/frameLayout"
app:layout_constraintBottom_toTopOf="#id/frameLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
</FrameLayout>
</androidx.fragment.app.FragmentContainerView>
<!-- </androidx.fragment.app.FragmentContainerView-->
</androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout
android:id="#+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="#id/frameLayout"
app:layout_constraintBottom_toTopOf="#id/frameLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
</FrameLayout>
</androidx.fragment.app.FragmentContainerView>
</androidx.constraintlayout.widget.ConstraintLayout>
first_fragment.xml
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:overScrollMode="always"
android:id="#+id/FirstFragment">
<FrameLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="0dp"
tools:context=".FirstFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:listitem="#layout/list_item"/>
</FrameLayout>
</androidx.core.widget.NestedScrollView>
list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:paddingTop="1dp"
android:layout_marginHorizontal="2dp"
android:background="#color/white">
<androidx.appcompat.widget.AppCompatButton
android:id="#+id/clientsNameButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textColor="#color/black"
android:text="#string/default_value"
android:textSize="11sp"
android:textStyle="bold"
android:layout_marginStart="16dp"
android:layout_marginEnd="32dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_margin="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent"
android:background="#color/underline"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Eventually, if this is possible, the goal is to refactor even more.
My question: (other than should I be using activities other than fragments ?) is how to 'call' the second fragment from any of the buttons in the first fragment (from the Adapter class ?) so that the second fragment then initialises and displays it's contents (retrieved from a text file)

Android custom expandable/collapsable view with child elements

I am working on a custom expandable view in android.
The goal is that I can add child elements in the xml files and they will be expanded and collapsed when the user clicks the expand/collapse button as on the picture below.
The expananding/collapsing works fine, but I cannot find out how to handle the child views.
In the constructor of my custom view, I inflate an xml layout, and I have a linear layout inside, in which i would like to put the child elements.
I tried using the solution suggested in the answer to the question here.
But I get StackOverflowError, and about a hundres of these:
"at android.view.ViewGroup.resetResolvedLayoutDirection(ViewGroup.java:7207)", even if I try to use the solution in the second aswer, using a while loop instead of the for.
Here is the kotlin class of my view:
class CollapsableCategoryView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
/** Declare some variables */
private var titleString : String = ""
private var subtitleString : String = ""
private var isExpaneded : Boolean = false
/** The required views */
private lateinit var ivIcon : ImageView
private lateinit var llExpandableContent : LinearLayout
init {
/** Receive the attributes */
context.theme.obtainStyledAttributes(
attrs,
R.styleable.CollapsableCategoryView,
0, 0
).apply {
try {
titleString = getString(R.styleable.CollapsableCategoryView_categoryTitle) ?: ""
subtitleString = getString(R.styleable.CollapsableCategoryView_categorySubtitle) ?: ""
} finally {
recycle()
}
}
/** Inflate the layout */
val root : View = View.inflate(context, R.layout.collapsable_task_category, this)
/** Find the views we need*/
ivIcon = root.findViewById(R.id.ivCollapsableCategoryIcon) as ImageView
llExpandableContent = root.findViewById(R.id.llExpandableContent) as LinearLayout
/** onClickListener for the icon */
ivIcon.setOnClickListener {
toggleExpanded()
}
}
override fun onFinishInflate() {
for(i in 0..childCount){
var view : View = getChildAt(i)
removeViewAt(i)
llExpandableContent.addView(view)
}
super.onFinishInflate()
}
/** This method is called when user clicks the expand/collapse button */
fun toggleExpanded(){
isExpaneded = !isExpaneded
if(isExpaneded)
{
ivIcon.setImageResource(R.drawable.ic_collapse)
llExpandableContent.visibility = VISIBLE
}else{
ivIcon.setImageResource(R.drawable.ic_expand)
llExpandableContent.visibility = GONE
}
}
}
I read somewhere else about a different solution, which also doesn't work. That solution suggests to ovverride the addView() method something like this:
override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams?) {
llExpandableContent.addView(child, params)
}
But if I do so, I get an exception that the lateinint var llExpandableContent is never initialized.
I have also seen solutions that override onMeasure() method but that doesn't seem to be the right approach for me to this problem, since I don't wan't to lay my views out in a special way, just want to add them in a linear layout.
Here is the xml resource file for the layout of the custom view:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="#dimen/collapsable_category_corner_radius"
android:background="#drawable/bg_collapsable_category_top"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/clCollapsableCategoryMain"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#drawable/bg_collapsable_category_middle">
<ImageView
android:id="#+id/ivCollapsableCategoryIcon"
android:layout_width="38dp"
android:layout_height="38dp"
android:layout_marginStart="8dp"
android:src="#drawable/ic_expand"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/clCollapsableCategoryTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Title"
app:layout_constraintStart_toEndOf="#+id/ivCollapsableCategoryIcon"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/clCollapsableCategorySubtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Subtitle"
app:layout_constraintStart_toEndOf="#+id/ivCollapsableCategoryIcon"
app:layout_constraintTop_toBottomOf="#+id/clCollapsableCategoryTitle" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="#+id/llExpandableContent"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#drawable/bg_collapsable_category_middle"
android:visibility="gone">
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="#dimen/collapsable_category_corner_radius"
android:background="#drawable/bg_collapsable_category_bottom"/>
</LinearLayout>
And here is how I am trying to use my custom view in a layout xml file:
<com.test.test.util.CollapsableCategoryView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Child view 1"/>
</com.test.test.util.CollapsableCategoryView>
Does anyone know how to solve this problem?
Thank you very much in advance for any help. Best regards,
Agoston
So I found the solution at another question, which I cannot find again...
But this solution works like a charm :)
override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams?) {
if(llExpandableContent == null){
super.addView(child, index, params)
}else{
llExpandableContent?.addView(child, index, params)
}
}
Hope it will help someone else at some point :)

Recyclerview view not populating

I'm a Kotlin newbie learning how to create simple recyclerview apps. My code is supposed to list the integers 1..10 in vertically stacked cells. However, it only lists the first item. I've consulted several tutorials and reviewed my code several times(after long breaks), but I can't see anything wrong in my code.
I got the bright idea early today to print Log statements. Examining them, I note that my onBindViewHolder function is only called once. What blunder am I making?
Here is my log output:
D/QuoteAdapter: value is: 1
D/QuoteAdapter: index is: 0
D/QuoteAdapter: Size is: 10
my activity:
class MainActivity : AppCompatActivity() {
lateinit var mRecyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mRecyclerView = findViewById(R.id.recyclerView)
mRecyclerView.layoutManager = LinearLayoutManager(this)
mRecyclerView.adapter = QuoteAdapter()
//mRecyclerView.setHasFixedSize(true)
}
}
my adapter:
class QuoteAdapter : RecyclerView.Adapter<QuoteViewHolder>() {
private val listOfInts = intArrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
private val TAG = "QuoteAdapter"
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QuoteViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.recyclerview_item_row, parent, false)
return QuoteViewHolder(view)
}
override fun getItemCount(): Int {
Log.d(TAG, "Size is: ${listOfInts.size.toString()}")
return listOfInts.size
}
override fun onBindViewHolder(holder: QuoteViewHolder, position: Int) {
val item = listOfInts[position]
Log.d(TAG, "value is: ${item.toString()}")
Log.d(TAG, "index is: ${position.toString()}")
holder.listTitle.text = item.toString()
}
}
my viewholder:
class QuoteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val listTitle = itemView.findViewById(R.id.itemString) as TextView
}
my layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/itemString"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
my main layout is shown below:
<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="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
In your "my layout" try this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/itemString"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Notice layout_height of LinearLayout has changed to wrap_content
Also doubt you need the android:orientation="vertical" on your ViewHolders item xml unless you will add more than just 1 TextView in the future.
Like Zain says, you can just use a TextView on its own in a layout file, which will also fix the problem (so long as its height is wrap_content!)
There are actually a few included with Android - type android.R.layout. and you'll see a few things, like simple_list_item_1 which is just a styled TextView (you can ctrl+click the reference or whatever to look at the file). Can be nice if you just want to make a quick thing!
The ID of the TextView in android.R.layout.simple_list_item_1 is #android:id/text1 - note the android prefix, because its part of the android resources, not your app's. Which means you have to reference the ID in the same way as the layout, with android at the front: android.R.id.text1
You can get rid of the LinearLayout in the list item layout, and only keep the TextView.
So, replace your list item layout with:
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/itemString"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

Error in RecyclerViewAdapter's onBindViewHolder() Method ㅠㅠ [duplicate]

Why do I get a NullPointerException in my ViewHolder's bindItems() method?
I've highlighted the line where I get the NullPointerException. The blogpost_author ID exists, as you can see in the XML, so what's the problem here? How is findViewById<TextView>(R.id.blogpost_author) returning null?
Adapter and ViewHolder code:
class BlogPostAdapter(val blogList: ArrayList<BlogPost>) : RecyclerView.Adapter<BlogPostAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) : BlogPostAdapter.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.blog_post_list, parent, false)
return ViewHolder(v)
}
override fun getItemCount(): Int {
return blogList.size
}
override fun onBindViewHolder(holder: BlogPostAdapter.ViewHolder, position: Int) {
holder.bindItems(blogList[position])
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindItems(blogPost: BlogPost) {
val blogPostAuthor = itemView.findViewById<TextView>(R.id.blogpost_author) // THIS LINE - NULL POINTER EXCEPTION
val blogPostTitle = itemView.findViewById<TextView>(R.id.blogpost_title)
blogPostAuthor.text = blogPost.author
blogPostTitle.text = blogPost.title
}
}
}
Activity code:
class BlogPostListActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.blog_post_list)
// Get the RecyclerView from XML itself
val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
// Add a layout manager - What does a layout manager do?
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayout.VERTICAL, false)
// Create an array list to store blogposts using the the data class blogPost
val blogPosts = ArrayList<BlogPost>()
// Add some dummy data to the list
blogPosts.add(BlogPost(123, "First Blog Post", "John"))
blogPosts.add(BlogPost(456, "Second Blog Post", "Bob"))
blogPosts.add(BlogPost(789, "Third Blog Post", "Mary"))
// Create an adapter
val adapter = BlogPostAdapter(blogPosts)
// Add the adapter to the recyclerview
recyclerView.adapter = adapter
}
}
Kotlin data class:
data class BlogPost(val id: Int, val title: String, val author: String)
XML for RecyclerView:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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="match_parent"
android:layout_height="match_parent"
tools:context="com.topzap.android.kotlinlistapptest.BlogPostListActivity">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout_editor_absoluteX="8dp"
tools:layout_editor_absoluteY="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</android.support.constraint.ConstraintLayout>
XML for CardView layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/blogpost_author"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="AuthorPlaceHolder"
android:textAppearance="#style/Base.TextAppearance.AppCompat.Large"
/>
<TextView
android:id="#+id/blogpost_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="TitlePlaceHolder"
android:textAppearance="#style/Base.TextAppearance.AppCompat.Medium"
/>
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
You may be inflating the wrong layout within your RecyclerView.
This line within your onCreateViewHolder method:
val v = LayoutInflater.from(parent.context).inflate(R.layout.blog_post_list, parent, false)
You are inflating the blog_post_list.xml, which I'm assuming is the wrong layout file due to the fact you're also inflating that layout within your BlogPostListActivity here:
setContentView(R.layout.blog_post_list)
So when this line is called:
val blogPostAuthor = itemView.findViewById<TextView>(R.id.blogpost_author)
It is looking for the id 'blogpost_author' within R.layout.blog_post_list and as you can see there is no blogpost_author TextView within that layout so it returns null.
To sort it out, it should be straight forward and just change the layout resource that you're assigning to each ViewHolder within your onCreateViewHolder method with the correct layout for your CardView layout.
Which means the line should read something like:
val v = LayoutInflater.from(parent.context).inflate(R.layout.your_card_layout, parent, false)

4 clickable views in one Iteme of recyclerView (kotlin)

i want set many buttons (i mean clickable views) in item of recyclerView like every item has download,play,stop,delete how can i do that? if i setOnlickListener like holder.download_image.setOnClickListenr(){} its works for all item i want every item has difrent link for download
class RecyclerAdapter(private val soundList: List<SoundItem>,private val mListener: AdapterView.OnItemClickListener?) :
RecyclerView.Adapter<RecyclerAdapter.RecyclerViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerViewHolder {
val itemView =
LayoutInflater.from(parent.context).inflate(R.layout.recycler_view, parent, false)
return RecyclerViewHolder(itemView)
}
override fun onBindViewHolder(holder: RecyclerViewHolder, position: Int) {
val currnetItem = soundList[position]
holder.soundNmae.text = currnetItem.soundName
holder.play_image.setImageResource(currnetItem.playImage)
holder.stop_image.setImageResource(currnetItem.stopImage)
holder.download_image.setImageResource(currnetItem.donloadImage)
holder.delete_image.setImageResource(currnetItem.deleteImage)
}
override fun getItemCount() = soundList.size
class RecyclerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val soundNmae = itemView.soundName
val play_image = itemView.play_image
val stop_image = itemView.stop_image
val download_image = itemView.download_image
val delete_image = itemView.delete_image
}
}
this is acivity for recyclerView im new in kotlin sorry if my question is maybe simple or i couldent say if what is my mean
class RelaxSoundActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_relax_sound)
val exampleList=generatorSoundList(500)
recyceler_view.adapter=RecyclerAdapter(exampleList)
recyceler_view.layoutManager=LinearLayoutManager(this)
recyceler_view.setHasFixedSize(true)
}
fun generatorSoundList(size:Int ):List<SoundItem>{
val list=ArrayList<SoundItem>()
for(i in 0 until size){
val drawable=when(i % 5){
0->R.drawable.ic_play_arrow_black_24dp
1->R.drawable.ic_play_arrow_black_24dp
2->R.drawable.ic_play_arrow_black_24dp
3->R.drawable.ic_play_arrow_black_24dp
else->R.drawable.ic_play_arrow_black_24dp
}
val drawable2=when(i % 5){
0->R.drawable.ic_stop_black_24dp
1->R.drawable.ic_stop_black_24dp
2->R.drawable.ic_stop_black_24dp
3->R.drawable.ic_stop_black_24dp
else->R.drawable.ic_stop_black_24dp
}
val drawable3=when(i % 5){
0->R.drawable.ic_file_download_black_24dp
1->R.drawable.ic_file_download_black_24dp
2->R.drawable.ic_file_download_black_24dp
3->R.drawable.ic_file_download_black_24dp
else->R.drawable.ic_file_download_black_24dp
}
val drawable4=when(i % 5){
0->R.drawable.ic_delete_black_24dp
1->R.drawable.ic_delete_black_24dp
2->R.drawable.ic_delete_black_24dp
3->R.drawable.ic_delete_black_24dp
else->R.drawable.ic_delete_black_24dp
}
val item=SoundItem("item $i" ,drawable,drawable2,drawable3,drawable4)
list+=item
}
return list
}
}
and recycler_view.xml file is here
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_height="wrap_content"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:padding="8dp"
>
<TextView
android:id="#+id/soundName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="TextView"
android:textSize="20dp"/>
<ImageView
android:id="#+id/play_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
app:srcCompat="#drawable/ic_play_arrow_black_24dp" />
<ImageView
android:id="#+id/stop_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
app:srcCompat="#drawable/ic_stop_black_24dp" />
<ImageView
android:id="#+id/download_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
app:srcCompat="#drawable/ic_file_download_black_24dp" />
<ImageView
android:id="#+id/delete_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
app:srcCompat="#drawable/ic_delete_black_24dp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
you can do it in multiple ways. You can add click listeners on your views and directly perform the operation in your adapter or you can transfer function in your recycler view holder class.
So for short cut I can just give you the way that you can understand the process.
In you adapter onBindViewHolder you can add
holder.download_image.setOnCLickListener{
val downloadLink = currnetItem.getDownloadLink()
// Download task start by this download link
}
So this one click listener on download image will be effected on each item and perform separately.
IMO you have to attach an clicklistener to each button, and then you could either make a callback to the adapter/activity and handle it from there on.
If you yous the View.OnClickListener, then you always have the clicked view. From there on, it is just a matter of writing a switch statement, where you can determine via the id of the clicked view, which action was selected.
If you need extra data, you could just add a tag to the view, and pass it via the clickListener.

Categories

Resources