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.
Related
I am developing an Android application in Kotlin to allow a smartphone to communicate with an nRF52840 via BLE to read/write data.
I am developing the view that displays this data. This view comes in the form of different parameters, which can be acted upon (with a button or a spinner) or not (simple TextView containing a data).
So I first created a RecyclerView for each type of view (parameter with TextView, Button or Spinner).
activity.xml:
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_parameters_text"
tools:listitem="#layout/parameter_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"
app:layout_constraintTop_toBottomOf="#id/currentConnexion"
app:layout_constraintBottom_toTopOf="#+id/rv_parameters_spinner"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_parameters_spinner"
tools:listitem="#layout/parameter_spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="#id/rv_parameters_text"
app:layout_constraintBottom_toTopOf="#+id/rv_parameters_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_parameters_button"
tools:listitem="#layout/parameter_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="#id/rv_parameters_spinner"
app:layout_constraintBottom_toTopOf="#+id/disconnect"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
parameter_text.xml:
<TextView
android:id="#+id/parameterName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="#string/parameter_name"
android:textColor="#color/black"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="#id/parameterValue"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/parameterValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="#string/parameter_value"
android:textSize="16sp"
app:layout_constraintStart_toEndOf="#id/parameterName"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="#+id/parameterName"
app:layout_constraintTop_toTopOf="parent"/>
parameter_spinner.xml:
<TextView
android:id="#+id/parameterName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="#string/parameter_name"
android:textColor="#color/black"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="#id/parameterSpinner"
app:layout_constraintTop_toTopOf="parent" />
<Spinner
android:id="#+id/parameterSpinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
app:layout_constraintStart_toEndOf="#id/parameterName"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="#id/parameterName"
app:layout_constraintTop_toTopOf="parent"/>
parameter_button.xml:
<TextView
android:id="#+id/parameterName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="#string/parameter_name"
android:textColor="#color/black"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="#id/parameterButton"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/parameterButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="#string/parameter_value"
android:textSize="16sp"
app:layout_constraintStart_toEndOf="#id/parameterName"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="#id/parameterName"
app:layout_constraintTop_toTopOf="parent"/>
However, I would like to have only one RecyclerView containing these 3 types of parameters. Can you tell me how I should achieve this?
EDIT:
My RecyclerView is implemented following this link
So I don't have the same implementation as you. So I modified my code this way to make it work:
rv_parameters.setup {
withDataSource(parametersList)
withItem<ParameterText, ParameterTextViewHolder>(R.layout.parameter_text) {
onBind(::ParameterTextViewHolder) { _, item ->
parameterName.text = item.parameterName
parameterText.text = item.parameterValue
}
}
withItem<ParameterButton, ParameterButtonViewHolder>(R.layout.parameter_button) {
onBind(::ParameterButtonViewHolder) { _, item ->
parameterName.text = item.parameterName
parameterButton.text = item.parameterButton
}
onClick {
}
}
withItem<ParameterSpinner, ParameterSpinnerViewHolder>(R.layout.parameter_spinner) {
onBind(::ParameterSpinnerViewHolder) { _, item ->
parameterName.text = item.parameterName
parameterSpinner.adapter = item.parameterAdapter
}
onClick {
}
}
}
Here is my adapter:
// Adapter
private val parametersList = emptyDataSourceTyped<Any>()
Everything works except my function to update the value associated with the parameter:
private fun updateValue(index: Int, value: String) {
//parametersList[index].parameterValue = value
rv_parameters.adapter?.notifyDataSetChanged()
}
Can you help me?
You need to create your own RecyclerView.Adapter which will provide different ViewHolders by supporting different view types. The data that backs up your adapter will contain those types (text, spinner, button) and you'll have corresponding ViewHolders for each type.
This a sample implementation I've quickly typed up just so you can get an idea:
sealed class MyData {
data class Data1(val name: String)
data class Data2(val number: Int)
}
class CustomAdapter(private val dataSet: Array<MyData>) :
RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
sealed class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
class ViewHolder1(itemView: View) : ViewHolder(itemView) {
val textView: TextView
init {
textView = view.findViewById(R.id.textView1)
}
class ViewHolder2(itemView: View) : ViewHolder(itemView) {
val textView: TextView
init {
textView = view.findViewById(R.id.textView2)
}
}
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
if (viewType == 1) {
val view = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.text_row_item1, viewGroup, false)
return ViewHolder1(view)
} else {
val view = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.text_row_item2, viewGroup, false)
return ViewHolder2(view)
}
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
when (viewHolder) {
is ViewHolder1 -> viewHolder.textView = "${dataSet[position]}"
is ViewHolder2 -> viewHolder.textView = "${dataSet[position]}"
}
}
override fun getItemViewType(int position) = when (dataSet[position]) {
is MyData.Data1 -> 1
is MyData.Data2 -> 2
}
override fun getItemCount() = dataSet.size
}
This may not be the best way to do this, but I edited my updateValue function like this:
private fun updateValue(index: Int, value: ParameterText) {
parametersList.removeAt(index)
parametersList.insert(index, value)
rv_parameters.adapter?.notifyItemChanged(index)
}
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)
I am displaying a list of countries along with their flags in recylerview
The 1st element does not have a image and uses a default image which is visible on launch of page
But when I scroll and come back to it the image gets changed to some random from the list which should not happen
This is my adapter
class CountryAdapter(private val list: MutableList<Data?>?) :
RecyclerView.Adapter<CountryAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = ElementCountryBinding.inflate(inflater)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val country: Data? = list?.get(position)
if (country != null) {
holder.bind(country)
}
holder.itemView.setOnClickListener {
}
}
override fun getItemCount(): Int = list!!.size
inner class ViewHolder(val binding: ElementCountryBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(country: Data) {
binding.data = country
if (country.filePath != null)
Glide.with(binding.root.context)
.load(country.filePath!!.trim()).into(binding.ivFlag)
}
}
}
This is the xml layout
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="data"
type="com.mountmeru.model.Data" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:id="#+id/main_cardview"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginBottom="5dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatImageView
android:id="#+id/iv_flag"
android:layout_width="100dp"
android:layout_height="70dp"
android:layout_marginStart="10dp"
android:adjustViewBounds="true"
android:src="#drawable/ic_share"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/tvCountryName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="#{data.countryName}"
app:layout_constraintBottom_toBottomOf="#+id/iv_flag"
app:layout_constraintLeft_toRightOf="#+id/iv_flag"
app:layout_constraintTop_toTopOf="#+id/iv_flag" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
< /RelativeLayout>
</layout>
screenshot
So the default image you specified in your XML layout is the ic_share, this means that when onBindViewHolder is called, the image gets substituted by:
.load(country.filePath!!.trim()).into(binding.ivFlag)
However, you never specified that at position 0, the icon must be ic_share, so because of RecyclerView's nature, when you scroll downwards and upwards and the first itemHolder is created (again) it uses a recycled view from further down, and as you're not setting ic_share to iv_flag at position 0 it just uses the recycled view image.
If you just add a line of code like #ADM suggested in your bind method like this:
if(adapterPosition==0){
binding.ivFlag.setImageResource(R.drawable.ic_share)
}
With the ic_share, I think that should make it work
That's normal due to the recycling mechanism of views in RV/LV. To avoid that set it to null
if (country.filePath != null)
Glide.with(binding.root.context)
.load(country.filePath!!.trim()).into(binding.ivFlag)
else
binding.ivFlag.setImageDrawable(null)
assuming ivFlag is an ImageView, or a default/placeholder if you have it
This is happening because you never set ic_share during bind View.
inner class ViewHolder(val binding: ElementCountryBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(country: Data) {
binding.data = country
if(adapterPosition==0){
binding.ivFlag.setImageResource(R.drawable.binding.ivFlag)
}else {
if (country.filePath != null)
Glide.with(binding.root.context)
.load(country.filePath!!.trim()).into(binding.ivFlag)
}
}
}
Having the function getItemViewType also solves the problem
override fun getItemViewType(position: Int): Int {
return position
}
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 set the margin of the view (card view) in my xml of my item file, this xml item file will be used in for my recyclerView adapter.
As you can see in my xml below, that I have given margin to top, bottom, start and end. and I want to change the margin from my fragment
Here is my xml file, item_category_list.xml:
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="8dp"
app:cardElevation="4dp"
android:id="#+id/cardView_item_category_list" android:layout_marginStart="8dp" android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp" android:layout_marginTop="8dp">
<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="wrap_content"
android:background="#android:color/background_light">
<ImageView
android:layout_width="0dp"
android:layout_height="0dp"
app:srcCompat="#drawable/logo_apps"
android:id="#+id/categoryImageView_Item"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="24dp"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="24dp"
app:layout_constraintDimensionRatio="w,1:1" android:scaleType="centerCrop"/>
<TextView
android:text="#string/Category"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/categoryName_textView_item"
app:layout_constraintTop_toBottomOf="#+id/categoryImageView_Item"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="4dp"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="4dp"
android:textAlignment="center"
android:minLines="1"
android:maxLines="2"
app:autoSizeTextType="uniform"
app:autoSizeMinTextSize="10sp"
app:autoSizeMaxTextSize="15sp"
app:autoSizeStepGranularity="1sp"
android:layout_marginBottom="24dp"
android:layout_marginTop="24dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
Here is the adapter:
class CategoryAdapter(val context: Context, val categories: List<Category>) : RecyclerView.Adapter<CategoryAdapter.ViewHolderCategory>() {
private lateinit var mListener : CategoryAdapterListener
interface CategoryAdapterListener {
fun onItemClick(position: Int)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderCategory {
val layoutInflater = LayoutInflater.from(parent.context)
val itemView = layoutInflater.inflate(R.layout.item_category_list,parent, false)
return ViewHolderCategory(itemView,mListener)
}
override fun getItemCount(): Int {
return categories.size
}
override fun onBindViewHolder(holder: ViewHolderCategory, position: Int) {
val category = categories[position]
holder.categoryNameTextView.text = category.name
Glide
.with(context)
.load(category.getFormattedImageURL())
.into(holder.categoryImageView)
}
inner class ViewHolderCategory(itemView: View, listener: CategoryAdapterListener) : RecyclerView.ViewHolder(itemView) {
val categoryImageView = itemView.findViewById<ImageView>(R.id.categoryImageView_Item)
val categoryNameTextView = itemView.findViewById<TextView>(R.id.categoryName_textView_item)
val cardView = itemView.findViewById<CardView>(R.id.cardView_item_category_list)
init {
itemView.setOnClickListener {
val position = adapterPosition
if (position != RecyclerView.NO_POSITION) {
listener.onItemClick(position)
}
}
}
}
fun setCategoryAdapterListener(listener: CategoryAdapterListener) {
mListener = listener
}
}
and in the fragment, I set the adapter to the recycler view:
val categoryAdapter = CategoryAdapter(mContext,parentCategory)
val layoutManager = GridLayoutManager(mContext,4,RecyclerView.VERTICAL,false)
recyclerViewParentCategory.adapter = categoryAdapter
recyclerViewParentCategory.layoutManager = layoutManager
recyclerViewParentCategory.setHasFixedSize(true)
I want to change that margin in card view in my item_category_list.xml programatically in my java/kotlin file (in my fragment file), so I can change the margin from my fragment.
So how can I achieve it ? Java/Kotlin any language is preferred.
First Of all its a long way . So i'm just suggesting a way .
First of all . In your Fragment when some action happen you need to change cardview
size in adapter list item xml.
So . You need a interface for that (Let's say interface ChangeMargin). create
interface in Fragment and implement that interface in your adapter like this
class CategoryAdapter(val context: Context, val categories: List<Category>):RecyclerView.Adapter<CategoryAdapter.ViewHolderCategory>(),ChangeMargin()
For how to create interface so can go through this
Now in that interface you need to get cardview and assign new margin .
#Override
public void ChangeMargin() {
val linear_params=LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.WRAP_CONTENT)
linear_params.setMargins(leftMargin,topmargin,rightMargin,bottomMargin)
cardView?.layoutParams=linear_params
}
and don't forget to notify adapter
You can do something like this.
Make id of cardview , create instance of it in your adapter and do this code
ViewGroup.MarginLayoutParams layoutParams =
(ViewGroup.MarginLayoutParams) myCardView.getLayoutParams();
layoutParams.setMargins(10, 10, 10, 10);
myCardView.requestLayout();
To set margins to the cardView, you will have to create layoutParams, set margins to it and then set it as cardView LayoutParams like:
inner class ViewHolderCategory(itemView: View, listener: CategoryAdapterListener) : RecyclerView.ViewHolder(itemView) {
val categoryImageView = itemView.findViewById<ImageView>(R.id.categoryImageView_Item)
val categoryNameTextView = itemView.findViewById<TextView>(R.id.categoryName_textView_item)
val cardView = itemView.findViewById<CardView>(R.id.cardView_item_category_list)
//Main code here
val lparams=LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.WRAP_CONTENT)
lparams.setMargins(leftMargin,topmargin,rightMargin,bottomMargin)
cardView?.layoutParams=lparams
}