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)
Related
This is what I created
And what I want is like this below
Actually in here, I want my cards should look like cards in a wallet (as shown in the 2nd image above) which I can scroll like the native stack card view as shown in first image, problem I am facing is I am not able to align cards vertically straight, cards get cut if I try to do so.
I want to achieve this behaviour using Android Native stackview widget only.
My code is as below:
ActivityMain.kt
class MainActivity : AppCompatActivity() {
lateinit var cardAdapter: CardAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val stackView = findViewById<StackView>(R.id.stack_view)
cardAdapter= CardAdapter(numberWord(),numberImage(),R.layout.item_card,this)
stackView.adapter=cardAdapter
}
// the method numberWord() is used to
// add the text below corresponding images.
private fun numberWord(): List<String> {
val word: MutableList<String> = ArrayList()
word.add("One")
word.add("Two")
word.add("Three")
word.add("Four")
word.add("Five")
return word
}
// the method numberWord() is used to call
// the images that are added to the stack.
private fun numberImage(): List<Int>{
val image: MutableList<Int> = ArrayList()
image.add(R.drawable.ic_filter)
image.add(R.drawable.ic_filter)
image.add(R.drawable.ic_filter)
image.add(R.drawable.ic_filter)
image.add(R.drawable.ic_filter)
return image
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!--We have used RelativeLayout for layout-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".MainActivity">
<!--Add StackView-->
<StackView
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:id="#+id/stack_view"
android:layoutDirection="ltr"
android:layout_width="400dp"
android:layout_height="300dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
CardAdapter.kt
class CardAdapter
(
var numberWord: List<String>,
var numberImage: List<Int>,
var itemLayout: Int,
var c: Context
) : ArrayAdapter<Any?>(
c, itemLayout, numberWord
) {
// getCount() is called to return
// the total number of words to be used
override fun getCount(): Int {
return numberWord.size
}
// getView() is called to get position,
// parent and view of the images.
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
var convertView = convertView
if (convertView == null) {
convertView = LayoutInflater.from(parent.context).inflate(itemLayout, parent, false)
}
val word = numberWord[position]
val image = numberImage[position]
val textView = convertView!!.findViewById<TextView>(R.id.text_view)
val imageView = convertView.findViewById<ImageView>(R.id.image_view)
textView.text = word
imageView.setImageResource(image)
return convertView
}
}
item_card.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="300dp"
android:layout_height="180dp"
android:background="#drawable/border_bg"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="#+id/image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_filter" />
<TextView
android:id="#+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="40sp"
android:textStyle="bold" />
</LinearLayout>
you guys can refer android native stackview: https://developer.android.com/reference/android/widget/StackView
I tried many different solution, I guess we can achieve that with third party library but that's not what I want. I want to create solution with using native android stackview.
Any help will be great.
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" />
Previously I have asked about calling API in fragments and now that I have my data returning in my app I would like to know how to make RecyclerView in this fragments?
Basically this is what i'm looking for to achieve at the end
Code
HomeFragment.kt
class HomeFragment : Fragment() {
private lateinit var homeViewModel: HomeViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
homeViewModel = ViewModelProviders.of(this).get(HomeViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_home, container, false)
val textView: TextView = root.findViewById(R.id.text_home)
//calling the API
callAPIDemo(textView)
// homeViewModel.text.observe(this, Observer {
// textView.text = it
// })
return root
}
fun callAPIDemo(textView: TextView) {
// Instantiate the RequestQueue.
val queue = Volley.newRequestQueue(activity)
val url = "https://example.com/api/listings"
// Request a string response from the provided URL.
val stringRequest = StringRequest(
Request.Method.GET, url,
Response.Listener<String> { response ->
// Display the first 500 characters of the response string.
textView.text = "Response is: ${response.substring(0, 500)}"
},
Response.ErrorListener { textView.text = "Something is wrong!" })
// Add the request to the RequestQueue.
queue.add(stringRequest)
}
}
fragment_home.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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/text_home"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:textAlignment="center"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
This is all i have at the moment.
Please note that I'm newbie in this, giving help with little bit of detailed explanation is much appreciated.
Update
Here is what I've created so far
HomeFragment.kt
class HomeFragment : Fragment() {
private lateinit var homeViewModel: HomeViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
homeViewModel = ViewModelProviders.of(this).get(HomeViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_home, container, false)
val textView: TextView = root.findViewById(R.id.recycler)
//calling the API
//callAPIDemo(textView)
val adapter = RecyclerAdapter(callAPIDemo(textView))
recycler.adapter = adapter
return root
}
fun callAPIDemo(textView: TextView) {
// Instantiate the RequestQueue.
val queue = Volley.newRequestQueue(activity)
val url = "https://example.com/api/listings"
// Request a string response from the provided URL.
val stringRequest = StringRequest(
Request.Method.GET, url,
Response.Listener<String> { response ->
// Display the response.
textView.text = "Response is: ${response.substring(0)}"
},
Response.ErrorListener { textView.text = "Something is wrong!" })
// Add the request to the RequestQueue.
queue.add(stringRequest)
}
}
ListingAdapter.kt
This class is red and returning error of
Parameters must have type annotation
class RecyclerAdapter(List) :
RecyclerView.Adapter<RecyclerAdapter.RecyclerViewHolder>() {
//Binding data for each tile
override fun onBindViewHolder(holder: RecyclerViewHolder, position: Int) {
}
//Creation of view holder for each tile
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerViewHolder {
val inflater = LayoutInflater.from(parent.context)
context = parent.context //this context is red
return RecyclerViewHolder(inflater, parent)
}
//View Holder for each item in recycler
inner class RecyclerViewHolder(inflater: LayoutInflater, parent: ViewGroup) :
RecyclerView.ViewHolder(inflater.inflate(R.layout.listings_layout, parent, false)) {
}
override fun getItemCount(): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
listings_layout.xml
<?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"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:layout_margin="3dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/imageViewImage"
android:layout_width="match_parent"
android:layout_height="195dp"
android:background="#color/colorPrimary"
android:contentDescription="#string/Image"
android:scaleType="matrix" />
<TextView
android:id="#+id/textViewTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/MakeUp"
android:textAppearance="#style/TextAppearance.AppCompat.Large" />
<TextView
android:id="#+id/textViewSlug"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/MakeUp"
android:textAppearance="#style/TextAppearance.AppCompat.Large" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
And finally my fragment_home.xml
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:textAlignment="center"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:id="#+id/recycler" />
First You will need to create a layout for how a single row in your list will look(considering we are creating horizontal listing)
navigate to res > layout folder in your project, in layout folder create a Layout Resource File (fancy name, but it's just a XML file)
for example:-
layout_demo_item.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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp">
<ImageView
android:id="#+id/a_image"
android:layout_width="80dp"
android:layout_height="80dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/text_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="#+id/a_image"
app:layout_constraintStart_toEndOf="#+id/a_image"
app:layout_constraintTop_toTopOf="#+id/a_image" />
<TextView
android:id="#+id/text_random"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="#+id/text_title"
app:layout_constraintStart_toEndOf="#+id/text_title"
app:layout_constraintTop_toTopOf="#+id/text_title" />
</androidx.constraintlayout.widget.ConstraintLayout>
Second you need an Adapter.
An Adapter will take a list/array of data from you, render number of rows based on size of list and assign the data to each row based on position on list
for example:-
You have an array of 6 names, then the adapter will create 6 identical row based on layout_demo_item.xml and assign data like 0th position data in array will be assigned to first element of list
MyAdapter.kt
class MyAdapter(var dataList: MutableList<DataModelObject>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
// inflating a layout in simple word is way of using a xml layout inside a class.
// if you look this is the same thing that your fragment doing inside onCreateView and storing that into "root" variable, we just did that in "view"
// we passed "view" to MyViewHolder and returned an object of MyViewHolder class(this object actually a single row without data)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.layout_demo_item, parent, false)
return MyViewHolder(view)
}
//the returned object of MyViewHolder class will come here and also with the position on which it gonna show,
// now based on this position we can get the value from our list and finally set it to the corresponding view that we have here in variable "holder"
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.textView.text = dataList.get(position).title
holder.textViewRandom.text = dataList.get(position).randomText
//hardcoding the image, just for simplicity, you can set this also from data list same as above
holder.aImage.setImageResource(R.mipmap.ic_launcher)
//here is the image setup by using glide
//Glide.with(holder.aImage.context).load(dataList.get(position).image).into(holder.aImage)
}
// this method telling adapter to how many total rows will be rendered based on count, so we returning the size of list that we passing
override fun getItemCount(): Int {
return dataList.size
}
// while creating the object in onCreateViewHolder, we received "view" here, and as you can see in your fragment,
// we can do find views by id on it same as we doing in fragment
// so your MyViewHolder now hold the reference of the actual views, on which you will set data
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var textView: TextView
var textViewRandom: TextView
var aImage: ImageView
init {
textViewRandom = itemView.findViewById(R.id.text_random)
textView = itemView.findViewById(R.id.text_title)
aImage = itemView.findViewById(R.id.a_image)
}
}
}
here is the data class that we using as our model class(same as a POJO class in Java but it's just in Kotlin)
data class DataModelObject(var randomText:String,var title :String) {
}
We have all things set to create a recyclerView, so now we will define recyclerView in Fragment like
fragment_home.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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/text_home"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:textAlignment="center"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view_home"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/text_home" />
</androidx.constraintlayout.widget.ConstraintLayout>
Finally Inside kotlin class setting up a recyclerView
HomeFragment.kt
(i'm not including the irrelevant methods and variable that you have in this fragment, please consider them)
class HomeFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val root = inflater.inflate(R.layout.fragment_home, container, false)
val textView: TextView = root.findViewById(R.id.text_home)
val recyclerView: RecyclerView = root.findViewById(R.id.recycler_view_home)
callAPIDemo(textView)
setUpRecyclerView(recyclerView);
return root
}
private fun setUpRecyclerView(recyclerView: RecyclerView) {
// first our data list that can come from API/Database
// for now i'm just manually creating the list in getDummyData()
val list: MutableList<DataModelObject> = getDummyData()
// created an adapter object by passing the list object
var myAdapter = MyAdapter(list)
//it's a layoutManager, that we use to tell recyclerview in which fashion we want to show list,
// default is vertical even if you don't pass it and just pass the activity , and we don't want to reversing it so false
// by customizing it you can create Grids,vertical and horizontal lists, even card swipe like tinder
val linearLayoutManager = LinearLayoutManager(activity,LinearLayoutManager.VERTICAL,false)
// just set the above layout manager on RecyclerView
recyclerView.layoutManager = linearLayoutManager
//and give it a adapter for data
recyclerView.adapter = myAdapter
}
private fun getDummyData(): MutableList<DataModelObject> {
val list: MutableList<DataModelObject> = mutableListOf(
DataModelObject("dfgdfg", "title 1"),
DataModelObject("tyuityui", "title 2"),
DataModelObject("yuti", "title 3"),
DataModelObject("uY9NOrc-=s180-rw", "title 4"),
DataModelObject("logo_color_272x92dp.png", "title 5"),
DataModelObject("NOVtP26EKH", "title 6"),
DataModelObject("googlelogo_color", "title 7"),
DataModelObject("sNOVtP26EKHePkwBg-PkuY9NOrc-", "title 8")
)
return list
}
}
At first create a single item xml for each item of your recycler.
Then add the recycler in your fragment xml:
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler"
android:layout_width="match_parent"
android:layout_width="wrap_content" />
Then create an adapter class as shown below:
class RecyclerAdapter(List) :
RecyclerView.Adapter<RecyclerAdapter.RecyclerViewHolder>() {
//Binding data for each tile
override fun onBindViewHolder(holder: RecyclerViewHolder, position: Int) {
}
//Creation of view holder for each tile
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerViewHolder {
val inflater = LayoutInflater.from(parent.context)
context = parent.context
return RecyclerViewHolder(inflater, parent)
}
//View Holder for each item in recycler
inner class RecyclerViewHolder(inflater: LayoutInflater, parent: ViewGroup) :
RecyclerView.ViewHolder(inflater.inflate(R.layout.single_item, parent, false)) {
}
}
Then use the adapter like below in fragment:
val adapter = RecyclerViewAdapter(List)
recyclerview.adapter = adapter
Hope this helps
I have a Fragment that displays a list of cities with weather informations. I am using a RecyclerView and I am trying to implement the Data Binding Library in my RecyclerView Adapter but for some reason I get this compile error :
> error: cannot find symbol import
com.example.zach.weatherapp.databinding.CityListItemBindingImpl;
> ^
> symbol: class CityListItemBindingImpl
> location: package com.example.zach.weatherapp.databinding
It's an auto generated class so i really don't know where the error is. I had the same error previously for other layouts when there was someting wrong in the xml file but here it seems fine.
ForecastAdapter.kt
package com.example.zach.weatherapp.Adapter
import ...
class ForecastAdapter(var myDataset: List<City>) :
RecyclerView.Adapter<ForecastAdapter.ForecastViewHolder>() {
var context:Context?=null
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder.
class ForecastViewHolder(var binding: CityListItemBinding) : RecyclerView.ViewHolder(binding.root){
fun bind(city: City){
binding.city = city
}
}
// Create new views (invoked by the layout manager)
override fun onCreateViewHolder(parent: ViewGroup,
viewType: Int): ForecastAdapter.ForecastViewHolder {
context = parent.context
val layoutIdForListItem = R.layout.city_list_item
val inflater = LayoutInflater.from(context)
val shouldAttachToParentImmediately = false
val binding = DataBindingUtil.inflate<CityListItemBinding>(inflater,layoutIdForListItem,parent,shouldAttachToParentImmediately)
//val view = inflater.inflate(layoutIdForListItem, parent, shouldAttachToParentImmediately)
return ForecastViewHolder(binding)
}
// Replace the contents of a view (invoked by the layout manager)
override fun onBindViewHolder(holder: ForecastViewHolder, position: Int) {
val city = myDataset[position]
holder.bind(city)
Glide.with(context)
.load("http://openweathermap.org/img/w/${city.weather[0].icon}.png")
.into(holder.binding.forceastImageView)
holder.binding.container.setOnClickListener{ view: View ->
Timber.d("Clicked on city %s",city.name)
Navigation.findNavController(view).navigate(ListFragmentDirections.actionListFragmentToForecastDetailsFragment(city.id))}
}
// Return the size of your dataset (invoked by the layout manager)
override fun getItemCount() = myDataset.size
}
city_list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="city" type="com.example.zach.weatherapp.data.City"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/container">
<TextView
tools:text="Caen"
android:text="#{city.name}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="#+id/city_name_textview"
app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="16dp"
android:layout_marginTop="16dp" app:layout_constraintTop_toTopOf="parent"
android:fontFamily="#font/roboto_light" android:textSize="22sp" android:textStyle="bold"
android:maxLines="1" android:ellipsize="end"/>
<TextView
tools:text="Sunny"
android:text="#{city.weather[0].description}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/city_forecast_textview" app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"
app:layout_constraintTop_toBottomOf="#+id/city_name_textview" android:fontFamily="#font/roboto_light"
android:layout_marginBottom="16dp" app:layout_constraintBottom_toBottomOf="parent"/>
<ImageView
android:layout_width="48dp"
android:layout_height="48dp" app:srcCompat="#drawable/sunny"
android:id="#+id/forceast_imageView" android:layout_marginTop="8dp"
app:layout_constraintTop_toTopOf="parent" android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintVertical_bias="0.562"
android:layout_marginEnd="32dp" app:layout_constraintEnd_toStartOf="#+id/temperatures_layout"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="#+id/temperatures_layout"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="16dp" android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginTop="8dp"
app:layout_constraintTop_toTopOf="parent">
<TextView
tools:text="15°"
android:text="#{city.main.temp_max}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="#+id/max_temperature_textview"
android:fontFamily="#font/roboto_light"
tools:layout_editor_absoluteY="17dp" tools:layout_editor_absoluteX="313dp"/>
<TextView
tools:text="9°"
android:text="#{city.main.temp_min}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/min_temperature_textview"
android:fontFamily="#font/roboto_light" tools:layout_editor_absoluteY="45dp"
tools:layout_editor_absoluteX="321dp" android:layout_gravity="right"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
It might just be an Android Studio error because the xml file seems fine.
UPDATE :
Error seems to come from Xml. I removed the android:text="#{city.xxx}" in my xml layout and instead updated my textViews manually in my ViewHolder bind method like so :
fun bind(boundCity: City){
with(binding){
cityNameTextview.text = boundCity.name
cityForecastTextview.text = boundCity.weather[0].description
maxTemperatureTextview.text = "${boundCity.main.temp_max}°"
minTemperatureTextview.text = "${boundCity.main.temp_min}°"
Glide.with(root.context)
.load("http://openweathermap.org/img/w/${boundCity.weather[0].icon}.png")
.into(forceastImageView)
container.setOnClickListener{ view: View ->
Timber.d("Clicked on city %s",boundCity.name)
Navigation.findNavController(view).navigate(ListFragmentDirections.actionListFragmentToForecastDetailsFragment(boundCity.id))}
}
}
And I no longer get the error. The error comes whenever I add android:text="#{city.xx}" in my textviews and bind the city variable in the bind method. I still don't know why though....
This should work for you I believe;
class ForecastAdapter(private var myDataset: List<City>) : RecyclerView.Adapter<ForecastAdapter.ForecastViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ForecastAdapter.ForecastViewHolder {
val itemBinding = CityListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ForecastViewHolder(itemBinding)
}
override fun onBindViewHolder(holder: ForecastViewHolder, position: Int) {
val city = myDataset[position]
holder.bind(city)
}
override fun getItemCount() = myDataset.size
inner class ForecastViewHolder(var binding: CityListItemBinding) : RecyclerView.ViewHolder(binding.root){
fun bind(boundCity: City){
with(binding) {
city = boundCity
Glide.with(root.context)
.load("http://openweathermap.org/img/w/${city.weather[0].icon}.png")
.into(forceastImageView)
container.setOnClickListener { view ->
Timber.d("Clicked on city %s", city.name)
Navigation.findNavController(view).navigate(ListFragmentDirections.actionListFragmentToForecastDetailsFragment(city.id))
}
}
}
Hey you could try adding the following line after the <data> tag :
<import type="android.view.View" />
I found that worked for me when I had that error.
i am having an issue with my gson parsing. I have a nested gson and i would like get the value of "name" under attributes. The problem is my recyclerview is only showing the first value(which is bill 1) and not bill 2 or bill 3. here is my code
// MainActivity.kt
recycler_view.layoutManager = LinearLayoutManager(requireContext())
recycler_view.addItemDecoration(LineItemDecorator(requireContext()))
val bill = GsonBuilder().create().fromJson(jsonString, Bills::class.java)
val nameList: List<Bill> = Arrays.asList(bill)
val adapter = BillsRecyclerAdapter(nameList)
recycler_view.adapter = adapter
adapter.notifyDataSetChanged()
// Bills.kt
data class Bills(
val data: ArrayList<Data>,
val meta: Meta,
val links: Links
)
// BillsRecyclerAdapter.kt
class BillsRecyclerAdapter(private val items: List<Bill>):
RecyclerView.Adapter<BillsRecyclerAdapter.BillsHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BillsHolder {
return BillsHolder(parent.inflate(R.layout.bills_list_item))
}
override fun onBindViewHolder(holder: BillsHolder, position: Int) {
holder.billName.text = items[position].data[position].attributes.name
}
override fun getItemCount() = items.size
class BillsHolder(view: View): RecyclerView.ViewHolder(view) {
val billName: TextView = view.billName
val billAmount: TextView = view.billAmount
}
}
<!-- recyclerview.xml -->
<?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">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_marginTop="#dimen/materialize_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<!-- bill_list_item.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"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/billName"
android:textSize="20sp"
android:layout_marginStart="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"/>
<TextView
android:id="#+id/billAmount"
android:textSize="20sp"
android:layout_marginEnd="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
here is my json string. https://pastebin.com/iEqhmB35
thanks for any help provided
Check the following
Check your items.size if it really is not zero,
Also check your bills_list_item.xml try to set the root parent's height to wrap_content
Also check if you have this on you recyclerview
recyclerView.setHasFixedSize(true);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
What's important there is the layout manager
If it doesn't help, can you share your Bills.java class, there might be something wrong with the serialization of JSON raw data.
Hope this helps.
Cheers