i am stuck in the problem that, i want to visit my ViewModel in a RecyclerView´s Adapter, but i have the Exception:
"Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call"
class FoodInSortAdapter( val activity:MainActivity,private val list:List<String>):RecyclerView.Adapter<FoodInSortAdapter.Holder>() {
val viewModel :FoodViewModel by lazy {
println("servus")
ViewModelProvider(activity).get(FoodViewModel::class.java)}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.sortitem, parent, false)
return Holder(view)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
holder.sortName.text = list[position]
holder.itemView.setOnClickListener {
val m=list[position]
viewModel.setQuery(m)
}
}
anyone know what ´s wrong?
thanks
First things first, you should not initialize viewModel outside of activity or fragment.
I assume you do not create adapter in onCreate but during initialization phase. There are two solution for your concern.
Initialize vm in activity, and set it in adapter by newly added method setupViewModel, called in onCreate. That's the better one.
class FooActivity : Activity{
private val viewModel: FoodViewModel by lazy {
println("servus")
ViewModelProvider(activity).get(FoodViewModel::class.java)
}
private val adapter = FoodInSortAdapter(emptyList())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter.setupViewModel(viewModel)
}
}
class FoodInSortAdapter(private val list:List<String>):RecyclerView.Adapter<FoodInSortAdapter.Holder>() {
private lateinit var viewModel: FoodViewModel
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.sortitem, parent, false)
return Holder(view)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
holder.sortName.text = list[position]
holder.itemView.setOnClickListener {
val m=list[position]
viewModel.setQuery(m)
}
}
fun setupViewModel(vm: FoodViewModel){
viewModel = vm
}
}
Create adapter in onCreate method, that should also work for you.
Cheers
Related
I know that there are similar questions to this, but I just cant find something that is similar, I've been studying new things to learn, and while converting kotlin synthetics to viewvbinding mode, I've encountered this error
kotlin.UninitializedPropertyAccessException: lateinit property binding has not been initialized
at com.codepalace.chatbot.ui.MessagingAdapter.onBindViewHolder(MessagingAdapter.kt:60)
at com.codepalace.chatbot.ui.MessagingAdapter.onBindViewHolder(MessagingAdapter.kt:17)
It says that I have to initialize the binding, but I dont know where to put it.
This is the code.
class MessagingAdapter: RecyclerView.Adapter<MessagingAdapter.MessageViewHolder>() {
var messagesList = mutableListOf<Message>()
private lateinit var binding: MessageItemBinding
inner class MessageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
init {
itemView.setOnClickListener {
//Remove message on the item clicked
messagesList.removeAt(adapterPosition)
notifyItemRemoved(adapterPosition)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder {
return MessageViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.message_item, parent, false)
)
}
override fun getItemCount(): Int {
return messagesList.size
}
#SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: MessageViewHolder, position: Int) {
val currentMessage = messagesList[position]
when (currentMessage.id) {
SEND_ID -> {
holder.itemView.findViewById<View>(R.id.tv_message).apply {
binding.tvMessage.text = currentMessage.message
visibility = View.VISIBLE
}
holder.itemView.findViewById<View>(R.id.tv_bot_message).visibility = View.GONE
}
RECEIVE_ID -> {
holder.itemView.findViewById<View>(R.id.tv_bot_message).apply {
binding.tvBotMessage.text = currentMessage.message
visibility = View.VISIBLE
}
holder.itemView.findViewById<View>(R.id.tv_message).visibility = View.GONE
}
}
}
fun insertMessage(message: Message) {
this.messagesList.add(message)
notifyItemInserted(messagesList.size)
}
private lateinit var binding: MessageItemBinding
You didn't initialize the binding object, as you defined it as lateinit, then you should define it.
This is typically in onCreateViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrackedActivityHolder {
val inflater = LayoutInflater.from(parent.context)
binding = DataBindingUtil.inflate<MessageItemBinding>(inflater, R.layout.message_item, parent, false)
return MessageViewHolder(
binding
)
}
UPDATE
You need to accept MessageItemBinding type instead of View in the MessageViewHolder constructor, and use itemView.root to get the root view of the list item.
inner class MessageViewHolder(itemView: MessageItemBinding) : RecyclerView.ViewHolder(itemView.root) {
init {
itemView.root.setOnClickListener {
//Remove message on the item clicked
messagesList.removeAt(adapterPosition)
notifyItemRemoved(adapterPosition)
}
}
}
The reason why You get this error is that MessageItemBinding should not be in Adapter but in ViewHolder class. You can make RecyclerView like this:
object MessageDiffCallback : DiffUtil.ItemCallback<Message>()
{
override fun areItemsTheSame(
oldItem: Message,
newItem: Message
): Boolean =
oldItem.id == newItem.id
override fun areContentsTheSame(
oldItem: Message,
newItem: Message
): Boolean =
oldItem == newItem
}
I assume that Message is a data class. You have to create this MessageDiffCallback and override these 2 methods in a similar way that I did it.
Now Create ViewHolder class:
class MessageViewHolder private constructor(
private val binding: MessageItemBinding
) : RecyclerView.ViewHolder(binding.root)
{
companion object
{
fun create(parent: ViewGroup): MessageViewHolder
{
val layoutInflater = LayoutInflater.from(parent.context)
val binding = MessageItemBinding.inflate(layoutInflater, parent, false)
return MessageViewHolder(
binding
)
}
}
fun bind(
message: Messaage
)
{
// Here You can do everything.
// You pass the message and based on this You can set up a view and use binding
}
}
And now Adapter class
class MessageAdapter : ListAdapter<Message, MessageViewHolder>(MessageDiffCallback)
{
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder =
MessageViewHolder.create(parent)
override fun onBindViewHolder(holder: MessageViewHolder, position: Int) =
holder.bind(
message = getItem(position),
)
}
Now You should have a working adapter. To put data in it use messageAdapter.submitList(messages) in Your Fragment/Activity
Maybe not the best answer because it changes Your code and uses a little different logic but it should work better. You can check google sample code here or take codelab here. It is free after signing up
I want to refresh my Recycler View, i receive my data by viewModel and pass it for my adapter
so i don’t know how to clear this data and call it again
MainActivity:
class MainActivity : AppCompatActivity() {
private val viewModel: ContatoViewModel = ContatoViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main);
configuraObserver()
viewModel.search()
}
private fun configuraObserver() {
viewModel.contato.observe(this, { data ->
Log.i("API", "Data received")
contato_recyclerview.apply {
layoutManager = LinearLayoutManager(this.context, LinearLayoutManager.VERTICAL, false)
adapter = ContatoAdapter(this.context, data.conteudoResposta)
}
})
}
My Adapter:
class ContatoAdapter(private val context: Context?, private val contatos : List<Contato>) : RecyclerView.Adapter<RecyclerView.ViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = LayoutInflater.from(context).inflate(R.layout.list_item_contato,parent, false)
return ContatoViewHolder(view)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder as ContatoViewHolder
val contato = contatos.elementAt(position)
holder.bindView(contato)
}
override fun getItemCount(): Int {
return contatos.size
}
if you just want to refresh your data(which you already received) in the recycler view you just need to call notifyDataSetChanged() from your Adapter.
SwipeRefreshLayout is needed when you want to implement pull to refresh, which means you want to trigger the initiation of API call when someone pulls down the screen and then after receiving the data you will pass it to Adapter and notifyDataSetChanged()
For implementing pull to refresh you can follow this Google Doc
how can I implement LiveData Observer in the Recycler View? I have a global var `someDataChanged' that gets updated from another Fragment. Any help would be greatly appreciated!
class GlobalActivity {
companion object {
someDataChanged = MutableLiveData<Int>()
}
}
Then in the Adapter.kt for the RecyclerView I need to listen if the someDataChanged. I have the following code:
Adapter.kt
var mRecyclerView: RecyclerView? = null
class MainAdapter(val myResponse: MyResponse): RecyclerView.Adapter<CustomViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
//Error: this is not recognized.
GlobalActivity.someDataChanged`.observe(this, Observer{
println("Data Changed")
})
val layoutInflater = LayoutInflater.from(parent?.context)
val cellForRow = layoutInflater.inflate(R.layout.my_custom_cell, parent,false)
return CustomViewHolder(cellForRow)
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
//some code
class CustomViewHolder(val view: View, var myPass: Passes? = null): RecyclerView.ViewHolder(view){
}
}
}
Not sure if I understood your question but if you want to update your recycler view every time the liveData is updated you should create something like this:
class MyActivity : AppCompatActivity() {
private val viewModel: MyViewModel by viewModels()
lateinit var adapter: MyAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = MyAdapter()
recyclerView.adapter = adapter
viewModel.myLiveData.observe(this, Observer { newContent ->
adapter.updateContent(newContent)
})
}
}
class MyViewModel: ViewModel() {
private val _myLiveData = MutableLiveData<String>()
val myLiveData: LiveData<String> = _myLiveData
// You should call this to update you liveData
fun updateInfo(newInfo: String) {
_myLiveData.value = newInfo
}
}
class MyAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var content: String = ""
...
fun updateInfo(newString: String) {
content = newString
notifyDataSetChanged() //or you can implement a DiffUtil.Callback
}
}
in RecyclerView.Adapter need to override onCreateViewHolder, pass lifeCycleOwner
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val lifeCycleOwner = parent.context as LifecycleOwner
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_layout, parent, false)
return ItemViewHolder(view, lifeCycleOwner)
}
your inner class constructor, should have lifecycleOwner as param
inner class ItemViewHolder(view: View, private val lifecycleOwner: LifecycleOwner) : RecyclerView.ViewHolder(view) {
then you can use code as normally
mutableLiveData.observe(lifecycleOwner) { /** your logic handle **/ }
In my app, there is an Activity which has a RecyclerView inside, which loads the list of options needed for that screen.
In the code below, i tried to implement a binder, which is needed because of the recent Android changes.
However, when i open the activity starts, the application crashes, throwing this error, linking the line with binding = ItemSettingsBinding.bind(binding.root):
kotlin.UninitializedPropertyAccessException: lateinit property binding has not been initialized
What am i doing wrong? What's the correct way to implement a binder inside an adapter?
AdapterSettings.kt
class AdapterSettings(
var settingsList: List<DataItemSettings>,
var listener: OnItemClickListener
) : RecyclerView.Adapter<AdapterSettings.SettingsViewHolder>() {
private lateinit var binding: ItemSettingsBinding
inner class SettingsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
init {
itemView.setOnClickListener(this)
}
override fun onClick(p0: View?) {
val position : Int = adapterPosition
if (position != RecyclerView.NO_POSITION) {
listener.OnItemClick(position)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_settings, parent, false)
return SettingsViewHolder(view)
}
override fun getItemCount(): Int {
return settingsList.size
}
override fun onBindViewHolder(holder: SettingsViewHolder, position: Int) {
binding = ItemSettingsBinding.bind(binding.root)
holder.itemView.apply {
binding.rvTitle.text = settingsList[position].stringTitle
binding.rvDescription.text = settingsList[position].stringDescription
binding.rvIcon.setImageResource(settingsList[position].itemIcon)
}
}
interface OnItemClickListener {
fun OnItemClick(position: Int)
}
}
I believe you're missing your inflate in onCreateViewHolder:
// Pseudo-Code
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingsViewHolder {
val binding = ItemSettingsBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return SettingsViewHolder(binding)
}
Then you can make use of it.
Create the binding in onCreateViewHolder and pass the binding into the ViewHolder instead of the inflated View. Thus you create a binding for each created view and only need to do the apply stuff in the onBindViewHolder
Example:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_settings, parent, false)
val binding = ItemSettingsBinding.bind(view)
return SettingsViewHolder(binding)
}
override fun onBindViewHolder(holder: SettingsViewHolder, position: Int) {
holder.binding.apply {
rvTitle.text = settingsList[position].stringTitle
rvDescription.text = settingsList[position].stringDescription
rvIcon.setImageResource(settingsList[position].itemIcon)
}
}
Adapt your ViewHolder accordingly
There is indeed another way to ViewBind in an adapter.
First, we need to setup the ViewHolder in a different way:
inner class SettingsViewHolder(private val binding: ItemSettingsBinding):
RecyclerView.ViewHolder(binding.root), View.OnClickListener {
With this, we created a binding value inside the brackets, so we are able to call the items of the actual view or layout trough binding.root
Inside the viewholder, we need to create a function used to bind our items. We can either bind like this:
fun bind(item: Item) {
binding.item = item
binding.executePendingBindings()
}
Or like this:
fun bind(item: DataItemSettings) {
binding.rvTitle.text = settingsList[position].stringTitle
binding.rvDescription.text = settingsList[position].stringDescription
binding.rvIcon.setImageResource(settingsList[position].itemIcon)
}
NOTICE: 'getter for position: Int' is deprecated. Deprecated in Java.
And, final step, we need to write this, inside bindViewHolder:
override fun onBindViewHolder(holder: SettingsViewHolder, position: Int) {
holder.bind(settingsList[position])
}
I am fetching a list of categories from an api and setting them into recyclerview . Adapter code is written in viewModel class and is called by the fragment that is calling the api. Below are the methods for setting adapters.
fun getAdapter(
listener: OtherVideoCategoriesRecyclerAdapter.OtherVideoCategoriesRecyclerAdapterListener,context: Context
): OtherVideoCategoriesRecyclerAdapter {
if (categoriesRecyclerAdapter == null)
categoriesRecyclerAdapter = OtherVideoCategoriesRecyclerAdapter(listener,context)
return categoriesRecyclerAdapter as OtherVideoCategoriesRecyclerAdapter
}
fun setItems(categories: ArrayList<OtherCategoriesItem>) {
categoriesList = categories
categoriesRecyclerAdapter!!.setItems(categoriesList!!)
}
And this is how I call these methods from my fragment class.
otherVideoViewModel.setItems(it.first.data!!.otherCategories as ArrayList<OtherCategoriesItem>)
Set Adapter method
private fun setAdapter() {
otherVideosCategoriesBinding.recyclerView.layoutManager = LinearLayoutManager(activity)
otherVideosCategoriesBinding.recyclerView.itemAnimator = DefaultItemAnimator()
adapter = otherVideoViewModel.getAdapter(adapterListener,activity!!)
otherVideosCategoriesBinding.recyclerView.adapter = adapter
}
And this is the adapter class.
class OtherVideoCategoriesRecyclerAdapter(private val listener: OtherVideoCategoriesRecyclerAdapterListener,val context: Context): RecyclerView.Adapter<OtherVideoCategoriesRecyclerAdapter.ViewHolder>() {
var categories = ArrayList<OtherCategoriesItem>()
interface OtherVideoCategoriesRecyclerAdapterListener {
fun onItemClicked(position: Int)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OtherVideoCategoriesRecyclerAdapter.ViewHolder {
val inflater=LayoutInflater.from(context)
val binding = ItemOtherVideoCategoryBinding.inflate(inflater, parent, false)
return OtherVideoCategoriesRecyclerAdapter.ViewHolder(binding)
}
override fun getItemCount(): Int {
return categories.size
}
override fun onBindViewHolder(holder: OtherVideoCategoriesRecyclerAdapter.ViewHolder, position: Int) {
val item = categories[position]
holder.bindViews(item)
}
class ViewHolder(private val binding: ItemOtherVideoCategoryBinding): RecyclerView.ViewHolder(binding.root) {
fun bindViews(model: OtherCategoriesItem){
binding.model=model
binding.executePendingBindings()
}
}
fun setItems(categoriesList: ArrayList<OtherCategoriesItem>) {
categories = categoriesList
notifyDataSetChanged()
}
}
When I run this code, it crashes with following exception.
java.lang.IllegalStateException: ViewHolder views must not be attached when created. Ensure that you are not passing 'true' to the attachToRoot parameter of LayoutInflater.inflate(..., boolean attachToRoot)
I have tried all the related answers to this error but none of them worked for my case as many of those answers doesn't included data binding.
Hey just changing your onCreateViewHolder a bit. Try this:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OtherVideoCategoriesRecyclerAdapter.ViewHolder {
val inflater=LayoutInflater.from(context)
val binding:ItemOtherVideoCategoryBinding = DataBindingUtil.inflate(inflater,R.layout.your_layout_name, parent, false)
return OtherVideoCategoriesRecyclerAdapter.ViewHolder(binding)
}