I am new to Kotlin and/or coding. The below mentioned are the code I made to populate a List View. But as I run the code, the activity_main.xml file is inflated but the text data are not attached.
Datasource.kt
class Datasource{
companion object{
val affirmationList: List<String> = listOf(
"Affirmation_1", "Affirmation_2", "Affirmation_3", "Affirmation_4")}
}
Here is the CustomAdapter.kt,
class CustomAdapter(val mContext:Context, val layoutId:Int, val textId: Int, val x: List<String>) :ArrayAdapter<Datasource>(mContext, layoutId, textId) {}
Here is MainActivity.kt
class MainActivity : AppCompatActivity()
{
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Item.View is the Id of ListView in activity_main.xml
val itemListView= findViewById<ListView>(R.id.Item_View)
// item_list is the Id of item_list.xml & Affirmation_Text is the Id of TextView in item_list.xml
val customAdapterUse = CustomAdapter(this,R.layout.item_list, R.id.Affirmation_Text, Datasource.affirmationList)
itemListView.adapter = customAdapterUse
}
}
You need to learn how the recycler view works. You haven't implemented any of the RecylerView adapter functions. I would recommend you watch some YouTube videos or tutorials on how to populate a list using RecyclerView. After you understand how the recycler view works, you can get some help from the documentation here.
Here is a code sample to give you an idea as to how your recycler view adapter should look like:
class CustomAdapter(private val dataSet: Array<String>) :
RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
/**
* Provide a reference to the type of views that you are using
* (custom ViewHolder).
*/
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView
init {
// Define click listener for the ViewHolder's View.
textView = view.findViewById(R.id.textView)
}
}
// Create new views (invoked by the layout manager)
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
// Create a new view, which defines the UI of the list item
val view = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.text_row_item, viewGroup, false)
return ViewHolder(view)
}
// Replace the contents of a view (invoked by the layout manager)
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
// Get element from your dataset at this position and replace the
// contents of the view with that element
viewHolder.textView.text = dataSet[position]
}
// Return the size of your dataset (invoked by the layout manager)
override fun getItemCount() = dataSet.size
}
Thanks for all of your feedback. I worked it out. Just changed CustomAdaptder class (added String type for ArrayAdapter and added data source variable x to the ArrayAdapter constructor. The final code is as below:
CustomAdapter(val mContext:Context, val layoutId:Int, val textId: Int, val x: List<String>) :ArrayAdapter<String>(mContext, layoutId, textId, x) {}
The code, which was not attaching data:
class CustomAdapter(val mContext:Context, val layoutId:Int, val textId: Int, val x: List<String>) :ArrayAdapter<Datasource>(mContext, layoutId, textId) {}
Related
I want to make a custom view with two Recyclerview data. First one is header titles, second one is content details. When I click on a title, I want different XML files to be shown.
After some research, I saw that I could do it using TabLayout and ViewPager, however, I don't want to use an extra fragment for each tab, instead I want to inflate an XML file. Because I want to use this view on other screens as a custom view.
How can I do that? What should I use as a solution? Thank you.
You can use ViewPager2 :
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/topicViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
you can assign second recycler adapter to ViewPager2 for example:-
class DummyAdapter(var list:ArrayList<String>) :
RecyclerView.Adapter<DummyAdapter.ViewHolder>() {
lateinit var context: Context
var isFullScreen = false
var isDownload: Boolean = false
var doubleClick = false
inner class ViewHolder(val binding: DummyItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(
position: Int,
context: Context,
adapter: DummyAdapter,
) {
with(binding) {
// add data
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
ViewHolder {
context = parent.context
val inflater = LayoutInflater.from(context)
val binding = DummyItemBinding.inflate(inflater, parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) =
holder.bind(position, context, this, list[position])
override fun getItemCount(): Int {
return list.size
}
}
binding.viewPager.adapter = dummyAdapter
then on the click of TabLayout you can update the adapter of second recyclerView.
I am trying to implement onClickListener with recyclerView to open a new fragement, I understand that they are a lot this on stackoverflow already but as a beginner I find them somewhat complex.
Like I said I want to open a fragment when I click on recyclerView, and I have some questions;
Mainly, how do I do this??
how do I controll the Views (i.e textViews, imageViews, etc) of the screens I'm navigating to??
Here's the fragment that has the recyclerView
class NewScreenFragment : Fragment() {
private var _binding: FragmentNewScreenBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
// for the recyclerView
private lateinit var newArrayList: ArrayList<DataClass>
// variables to the recyclerView views
lateinit var imageId: Array<Int>
lateinit var title: Array<String>
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentNewScreenBinding.inflate(inflater, container, false)
val root: View = binding.root
imageId = arrayOf(
R.drawable.ways_start_cpa_marketing,
R.drawable.ways_start_gigging,
R.drawable.ways_teach_english_online,
R.drawable.ways_test_websites,
R.drawable.ways_tutor_on_your_own_time,
R.drawable.ways_use_your_voice,
R.drawable.ways_write_a_list
)
title = arrayOf(
getString(R.string.Become_A_Dog_Walker),
getString(R.string.Become_A_Proofreader),
getString(R.string.Become_A_Virtual_Assistant),
getString(R.string.Make_Things_to_Sell),
getString(R.string.Give_your_opinion),
getString(R.string.Play_Games),
getString(R.string.Start_A_Blog),
)
// this creates a vertical layout Manager
binding.recyclerview.layoutManager = LinearLayoutManager(context)
/** I change LinearLayoutManager(this)
* to LinearLayoutManager(context)
*
* remember this in case of any issue
**/
binding.recyclerview.setHasFixedSize(true)
newArrayList = arrayListOf<DataClass>()
getUserData()
return root
}
private fun getUserData() {
for (i in imageId.indices){
val dataClass = DataClass(imageId[i],title[i])
newArrayList.add(dataClass)
}
// ArrayList of class ItemsViewModel
// val data = ArrayList<DataClass>()
// This loop will create Views containing
// the image with the count of view
// This will pass the ArrayList to our Adapter
// val adapter = RecyclerAdapter(data)
// Setting the Adapter with the recyclerview
binding.recyclerview.adapter = RecyclerAdapter(newArrayList)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Here's the Adapter.kt
class RecyclerAdapter(private val mList: ArrayList<DataClass>) : RecyclerView.Adapter<RecyclerAdapter.ViewHolder>() {
// create new views
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
// inflates the card_view_design view
// that is used to hold list item
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.new_screen_recyclerview_look, parent, false)
return ViewHolder(view)
}
// binds the list items to a view
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val dataClass = mList[position]
// sets the image to the imageview from our itemHolder class
holder.imageView.setImageResource(dataClass.cardImage)
// sets the text to the textview from our itemHolder class
holder.textView.text = dataClass.cardTitle
}
// return the number of the items in the list
override fun getItemCount(): Int {
return mList.size
}
// Holds the views for adding it to image and text
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imageView: ImageView = itemView.findViewById(R.id.card_image)
val textView: TextView = itemView.findViewById(R.id.card_title)
// implement the onClickListener event here
init {
itemView.setOnClickListener {
}
}
}
}
Not needed though, but for more clarify.. Here's my Data class;
data class DataClass(var cardImage: Int, var cardTitle: String) {
}
I really appreciate your help, thanks to you in advance..
Create a new parameter on your Adapter constructor
Change
class RecyclerAdapter(private val mList: ArrayList<DataClass>)
to
class RecyclerAdapter(private val mList: ArrayList<DataClass>, private val onItemClicked: (DataClass) -> Unit)
Set the clickListener for you item inside onBindViewHolder. If you wanna make the whole item clickable, set:
holder.root.setOnClickListener{
onItemClicked(dataClass)
}
On your fragment, when creating the adapter, pass the new parameter
binding.recyclerview.adapter = RecyclerAdapter(newArrayList){ dataClass ->
//Here, 'dataClass' will be the clicked item on recyclerView. Here you can do any logic that's supposed to happen after the click, like opening a new fragment passing 'dataClass' as parameter.
}
To open a new frag passing dataClass as parameter, check "newInstance" pattern.
Understanding the Fragment.newInstance method
I have a recycler view(Parent) and inside it, I have another recycler View (Child).
There are 2 operations in child recycler View which I want to get on Fragment Class and do some things dynamically.
Architecture: MVVM
Yes, you can achieve your desired behavior by following these steps:
I will use Lambda to refer to Higher Order Function.
Pass the Lambda function from Activity/Fragment -> Parent Adapter
Pass the Lambda function from Parent Adapter -> Child Adapter.
For example, this code shows how to get a callback from nested Recyclerview when a user clicks Error Item from child Recyclerview.
//In Activity/Fragment
private var errorClick: () -> Unit
parentAdapter.setErrorClick(errorClick)
//In Parent Adapter
private var errorClick: () -> Unit
childAdapter.setErrorClick(errorClick)
//In Child Adapter - Now use errorClick to callback methods to Activity/Fragment
private var errorClick: () -> Unit // Use IT!
Let me know if you have any questions. Thanks.
Here is an example for higher order function.
TextAdapter.kt class
class TextAdapter(
val onClick: (String) -> Unit
): RecyclerView.Adapter<TextAdapter.ViewHolder>() {
inner class ViewHolder(val binding: ItemTextBinding) :
RecyclerView.ViewHolder(binding.root)
private val list: ArrayList<String> = arrayListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
ViewHolder =
ViewHolder(
ItemTextBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val adapter = InnerAdapter {
onClick.invoke(it)
}
binding.recyclerViewList.adapter = adapter
binding.recyclerViewList.setData(list)
}
override fun getItemCount(): Int = list.size
fun setData(newList: ArrayList<String>) {
list.clear()
list.addAll(newList)
}
}
Inner adapter
class InnerAdapter(
val onClick: (String) -> Unit
): RecyclerView.Adapter<InnerAdapter.ViewHolder>() {
inner class ViewHolder(val binding: ItemText1Binding) :
RecyclerView.ViewHolder(binding.root)
private val list: ArrayList<String> = arrayListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
ViewHolder =
ViewHolder(
ItemText1Binding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
binding.tvTxt.text = list[position]
holder.itemView.setOnClickListener {
//change background color
onClick.invoke(list[position])
}
}
override fun getItemCount(): Int = list.size
fun setData(newList: ArrayList<String>) {
list.clear()
list.addAll(newList)
}
}
Now you can get the value of the item click on the setAdapter
Let's see how
The below function is called from the fragment class where the adapter is set
val adapter = TextAdapter {
showToast(it)
}
I'm new in Android dev.
In my code i don't using onClick method, but i using setOnClickListener and Callback. The main problem that is in this way i don't know how to get the position of the item in RecyclerView.
Here is my Adapter:
class TestAdapter(val test : ArrayList<Test>, private val testAdapterCallback: (Test, Int)->Unit) : RecyclerView.Adapter<TestAdapter.ViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.test_view_item, parent, false)
return ViewHolder(v)
}
override fun getItemCount(): Int {
return test.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val num : Test = test[position]
holder.textView.text = num.id.toString()
holder.cardView.setTag(position)
holder.cardView.setOnClickListener(){
testAdapterCallback(num)
}
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val cardView = itemView.findViewById<CardView>(R.id.testCardView)
val textView = itemView.findViewById<TextView>(R.id.testTextView)
}
}
I have added second parametr into callback but i don't know how i must change my adapter's inicializations in this peace of code:
val adapter = TestAdapter(list) { item ->
testAdapterItemClick(item)
}
In activity i'm using this method :
private fun testAdapterItemClick(item: Test) {}
Please, help me to check the position of the choosen element. I need it later.
Thanks in advance)
P.S. Sorry for my English
Add the position as parameter in the callback.
So, instead of: private val testAdapterCallback: (Test)->Unit
Use: private val testAdapterCallback: (Test, Int)->Unit.
This way you can pass the position in the callback.
holder.cardView.setOnClickListener(){
testAdapterCallback(num, position)
}
In your activity:
val adapter = TestAdapter(list) { item, position ->
testAdapterItemClick(item)
}
Create interface for your OnClickListener
interface OnClickListener{
fun clickItem(test: Test, index: Int)
}
Pass listener to your adapter like below.
class TestAdapter(
var test : ArrayList<Test>?,
val clickListener: OnClickListener,
var mActivity: Activity
) :
RecyclerView.Adapter<TestAdapter.MyViewHolder>() {
}
Now in your onBindViewHolder add click listener.
var mtest= test !!.get(i)
holder.cardView.setOnClickListener {
clickListener.clickItem(mtest, i)
}
Implement Listener to your activity and initialize it.
this.mOnClickListener = this
Pass listener to your adapter where you passing the arraylist.
mTestAdapter = TestAdapter(arrayList, mOnClickListener ,mActivity)
You'll get the position in your activity override method.
override fun editItem(mTest: Test, index: Int) {
if (mTest!= null) {
}
}
I am trying to pass an array from my Recyclerview Activity to its Adapter as such:
//Setting NavBar Title
val navBarTitle = intent.getStringExtra(FirstCustomViewHolder.LESSON_TITLE_KEY)
supportActionBar?.title = navBarTitle
var content : Array<String>
if (navBarTitle == "Introduction"){
content = arrayOf("Intro1", "Intro2")
}
else{
content = arrayOf(":esson1-1", "Lesson 1-2")
}
I am passing the array as such:
recyclerView_main.adapter = SecondAdapter(content)
And I am getting an angry red underline as shown below.
On mouse-over the pop-up error reads:
Too many arguments for public constructor......
Is there a proper way to pass an array or variable to my adapter? I am fairly new to Kotlin and appreciate and pointers.
Thank you.
Edit:
As requested, this is my adapter class:
class SecondAdapter : RecyclerView.Adapter<SecondCustomViewGolder>(){
//Variable below to be replaced by array from Activity
var lessons = arrayOf("Satu", "Dua", "Tiga", "Empat", "Lima", "Enam", "Tujuh",
"Lapan", "Sembilan")
override fun getItemCount(): Int {
return lessons.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SecondCustomViewGolder {
var layoutInflater = LayoutInflater.from(parent.context)
var cellForRow = layoutInflater.inflate(R.layout.lesson_row, parent, false)
return SecondCustomViewGolder(cellForRow)
}
override fun onBindViewHolder(holder: SecondCustomViewGolder, position: Int) {
}
}
class SecondCustomViewGolder(var viewTwo : View) : RecyclerView.ViewHolder(viewTwo){
}
Does your SecondAdapter class constructor accept an Array as an argument? If not, you must add it there. The error is because you're trying to pass an argument to a constructor that accepts no arguments.
EDIT
Do it like so:
class SecondAdapter(val lessonArray: Array<String>) : RecyclerView.Adapter<SecondCustomViewGolder>(){
override fun getItemCount(): Int {
return lessons.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SecondCustomViewGolder {
var layoutInflater = LayoutInflater.from(parent.context)
var cellForRow = layoutInflater.inflate(R.layout.lesson_row, parent, false)
return SecondCustomViewGolder(cellForRow)
}
override fun onBindViewHolder(holder: SecondCustomViewGolder, position: Int) {
}
}
class SecondCustomViewGolder(var viewTwo : View) : RecyclerView.ViewHolder(viewTwo){
}
I made it a val since it's my preference. If you intend to modify the variable, than you just declare it as a var in the constructor. There's no need to assign it inside the class. Just declaring it in the constructor makes it accessible throughout the class.
You can use the ListAdapter
and use submitList()