I am trying to use executePendingBinding with gridView adapter. On some data change the binding is not executing at first but other UI operations are being executed before that. How do I prevent that. Here is my ImageAdapter :
class ImageAdapter constructor(
private val mContext: Context,
private val resource_layout: Int,
private val viewModelList: ArrayList<ProfileViewModel>
) :
ArrayAdapter<ImageAdapter.ViewHolder>(mContext, resource_layout) {
private lateinit var imageCardBinding: ImageCardBinding
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
var convertView = convertView
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(resource_layout, null)
imageCardBinding = DataBindingUtil.bind(convertView)!!
convertView.tag = position
convertView.tag = imageCardBinding
} else imageCardBinding = convertView.tag as ImageCardBinding
imageCardBinding.profileViewModel = viewModelList[position]
imageCardBinding.executePendingBindings()
return imageCardBinding.root
}
override fun getCount(): Int {
return viewModelList.size
}
inner class ViewHolder
}
And on some value successfully changed I am executing this:
gridView.get(imageGridPosition).grid_item_progress.visibility = View.GONE
profile_root.snackBar("Image is set ! You can tap on the image to change it")
But as the binding is not done immediately the gridView.size is 0 and if I comment out the line the picture is set some times later but snackbar is showing before that.
Can it be fixed with executePendingBinding(), if that then how to do that ? or is there any other way?
Related
I'm trying to give context using "this" to a adapter
Code is:
val shoeid = arrayOf(
"Nike air","Adidas","asics"
)
val progress = intArrayOf(
10,50,60
)
shoeArrayList = ArrayList()
for ( i in shoeid.indices ) {
val shoe = UserShoes(shoeid[i], progress[i])
shoeArrayList.add(shoe)
}
binding.listviewProfile.adapter = ShoeAdapter(this,shoeArrayList)
Data Class:
data class UserShoes(var shoeName: String, var usage: Int)
Adapter Code:
class ShoeAdapter(private val context: Activity,private val arrayList: ArrayList<UserShoes>): ArrayAdapter<UserShoes>(context, R.layout.list_view,arrayList) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val inflater: LayoutInflater = LayoutInflater.from(context)
val view: View = inflater.inflate(R.layout.list_view, null)
val shoe: TextView = view.findViewById(R.id.shoeID)
val progress: ProgressBar = view.findViewById(R.id.progressID)
shoe.text = arrayList[position].shoeName
progress.progress = arrayList[position].usage
return super.getView(position, convertView, parent)
}
}
Type mismatch.
Required: Activity
Change
class ShoeAdapter(private val context: Activity,...
to
class ShoeAdapter(private val context: Context,...
and
binding.listviewProfile.adapter = ShoeAdapter(this,shoeArrayList)
to
context?.run { binding.listviewProfile.adapter = ShoeAdapter(this,shoeArrayList) }
You could also keep the Activity but the adapter doesn't need an Activity so it's best to go with the more abstract Context.
I manage to display the items stored in a room database in a recycler view with checkboxes and i want to store the checked items in a list, to store the checked items on a list I use setOnClickListener on the checkbox like the code below in adapter but the application stop when i click to display the list or even if the list displayed with success sometimes it stops when i click on the checkbox of an item (for information when i remove the listener the list is displayed well and I can click on the checkboxes and everything works well but the problem is when i add the listener to store the checked items).
class Adapter (val selectedFluxs : MutableList<Flux>) : RecyclerView.Adapter<Adapter.VH>(
) {
class VH(itemView: View) : RecyclerView.ViewHolder(itemView){
lateinit var feed : Flux
}
var allFluxs : List<Flux> = listOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
val v = LayoutInflater
.from(parent.getContext())
.inflate(R.layout.item_layout, parent, false)
val holder = VH( v )
v.check.setOnClickListener( ) {
// it as CheckBox
if( v.check.isChecked ){
selectedFluxs.add ( holder.feed )
}else{
selectedFluxs.remove( holder.feed )
}
}
return holder
}
override fun getItemCount(): Int {
return allFluxs.size
}
fun setFlux( allFlux : List<Flux> ) {
this.allFluxs = allFlux
notifyDataSetChanged()
}
override fun onBindViewHolder(holder: VH, position: Int) {
holder.itemView.apply {
Source.text = allFluxs[position].source
Tag.text = allFluxs[position].tag
Url.text = allFluxs[position].adr
check.isChecked =
holder.feed in selectedFluxs
}
}
}
It's too early to use ViewHolder views before instantiating the ViewHolder instance, so Move the code of the listener from onCreateViewHolder to onBindViewHolder
So, your class code should be:
class Adapter (val selectedFluxs : MutableList<Flux>) : RecyclerView.Adapter<Adapter.VH>(
) {
class VH(itemView: View) : RecyclerView.ViewHolder(itemView){
lateinit var feed : Flux
}
var allFluxs : List<Flux> = listOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
val v = LayoutInflater
.from(parent.getContext())
.inflate(R.layout.item_layout, parent, false)
val holder = VH( v )
}
return holder
}
override fun getItemCount(): Int {
return allFluxs.size
}
fun setFlux( allFlux : List<Flux> ) {
this.allFluxs = allFlux
notifyDataSetChanged()
}
override fun onBindViewHolder(holder: VH, position: Int) {
holder.itemView.apply {
Source.text = allFluxs[position].source
Tag.text = allFluxs[position].tag
Url.text = allFluxs[position].adr
setBackgroundColor(
if (position % 2 == 0)
Color.argb(30,0,220,0)
else
Color.argb(30,0,0,220)
)
check.isChecked =
holder.feed in selectedFluxs
holder.check.setOnClickListener( ) {
// it as CheckBox
if( v.check.isChecked ){
selectedFluxs.add ( holder.feed )
}else{
selectedFluxs.remove( holder.feed )
}
}
}
}
I solved the problem a minute after posting the question, in fact the problem is that I did not initialize the variable : lateinit var feed: Flux of the Holder with : holder.feed = allFluxs[position] in the onBindViewHolder method . i did that and it works .
I have a button on the fist activity(listView) which takes you to the second activity (where an image will be added relating to that clicked item). How do I set the onClick event in the adapter's getView method?
Adapter
class ChallengeListAdapter: BaseAdapter {
private var challengeDatabase: ChallengeDatabase? = null
private var context: Context? = null
constructor(context: Context) {
challengeDatabase = ChallengeDatabase()
this.context = context
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val challenge: Challenge = challengeDatabase?.challengesList?.get(position)
?: Challenge(
"No Name", "No Description")
var challengeView: View
var layoutInflater: LayoutInflater = context?.getSystemService(
Context.LAYOUT_INFLATER_SERVICE
) as LayoutInflater
challengeView = layoutInflater.inflate(R.layout.challenge_row, null)
challengeView.lblChallengeName.setText(challenge.name)
challengeView.lblChallengeDesc.setText(challenge.desc)
return challengeView
}
override fun getItem(position: Int): Any {
return challengeDatabase?.challengesList?.get(position) ?: Challenge(
"No Name", "No Des")
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getCount(): Int {
return challengeDatabase?.challengesList?.size ?: 0
}
}
How do I fix this?
challengeView.setOnClickListener {
val solutionButton: Button = findViewById(R.id.solution_button)
val Intent = Intent(context, SolutionActivity::class.java)
Intent.putExtra()
startActivity(context!!, Intent, null)
return challengeView
As Furqan said, you set the click listener to the item's view.. in this case set it to challengeView.
To open the activity from it you need to put the desired info inside a Bundle and create an Intent to open the activity with it. You can start the activity from the view's context or propagate that event up to the adapter's owner until you reach the activity and start it from there (usually better if you are seeking for division of responsibilities).
Some tips to write it in a more idiomatic way:
the context is not necessary in the constructor. It's a bad habit to pass the context along. You can inflate the view like that:
LayoutInflater.from(parent.context)
.inflate(R.layout.challenge_row, null)
you can make challengeView immutable and even inline it like this:
...
return LayoutInflater.from(parent.context)
.inflate(R.layout.challenge_row, null)
?.apply {
lblChallengeName.text = challenge.name
lblChallengeDesc.text = challenge.desc
setOnClickListener {
//add your code to load activity
}
}
challangeDatabase can be a val and not null
private val challengeDatabase = ChallengeDatabase()
You need to handle the parameters for Adapter setOnClickListener
challengeView.setOnItemClickListener { parent, view, position, id ->
val element = adapter.getItemAtPosition(position) // The item that was clicked
val intent = Intent(this, SolutionActivity::class.java)
intent.putExtra("key", element)
startActivity(intent)
}
Add this to your getView method
challengeView.setOnClickListener{
//add your code to load activity
}
Now add your getView method will look like
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val challenge: Challenge = challengeDatabase?.challengesList?.get(position)
?: Challenge(
"No Name", "No Description")
var challengeView: View
var layoutInflater: LayoutInflater = context?.getSystemService(
Context.LAYOUT_INFLATER_SERVICE
) as LayoutInflater
challengeView = layoutInflater.inflate(R.layout.challenge_row, null)
challengeView.lblChallengeName.setText(challenge.name)
challengeView.lblChallengeDesc.setText(challenge.desc)
challengeView.setOnClickListener{
val element = adapter.getItemAtPosition(position)
val intent = Intent(this, SolutionActivity::class.java)
intent.putExtra("key", element)
parent.context.startActivity(intent)
}
return challengeView
}
Make sure your Challange class implementes Serializable
java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter convertView
Adapter.getView
at android.widget.AbsListView.obtainView(AbsListView.java:2346)
at android.widget.ListView.makeAndAddView(ListView.java:1876)
at android.widget.ListView.fillDown(ListView.java:702)
at android.widget.ListView.fillFromTop(ListView.java:763)
at android.widget.ListView.layoutChildren(ListView.java:1671)
at android.widget.AbsListView.onLayout(AbsListView.java:2148)
This is logcat of android.
I tried with java it's working fine base adapter something wrong in Adapter or other.
I tried with the public constructor and also array list count 3 found i checked it. Alway it's crash at getView
MyAdapter Code::
inner class MyAppAdapter constructor(private val parkingList: ArrayList<App>, private val mContext: Context) : BaseAdapter() {
override fun getCount(): Int {
return this.parkingList.size
}
override fun getItem(position: Int): Any {
return position
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getView(position: Int, convertView: View, parent: ViewGroup): View? {
val viewHolder: ViewHolder
var rowView: View? = convertView
if (rowView == null) {
rowView = LayoutInflater.from(mContext).inflate(R.layout.item_more_apps, parent, false)
viewHolder = ViewHolder()
viewHolder.appIcon = rowView.findViewById(R.id.appIcon)
viewHolder.appName = rowView.findViewById(R.id.appName)
viewHolder.appDescription = rowView.findViewById(R.id.appDescription)
rowView.tag = viewHolder
} else {
viewHolder = convertView.tag as ViewHolder
}
viewHolder.appName!!.text = String.format("%s", this.parkingList[position].name)
viewHolder.appDescription!!.text = String.format("%s", this.parkingList[position].description)
Glide.with(applicationContext).load(this.parkingList[position].icon).into(viewHolder.appIcon!!)
rowView?.setOnClickListener {
try {
startActivity(Intent("android.intent.action.VIEW", Uri.parse("market://details?id=" + this#MyAppAdapter.parkingList[position].link)))
} catch (e: ActivityNotFoundException) {
startActivity(Intent("android.intent.action.VIEW", Uri.parse("http://play.google.com/store/apps/details?id=" + this#MyAppAdapter.parkingList[position].link)))
}
}
return rowView
}
inner class ViewHolder {
var appDescription: TextView? = null
var appIcon: ImageView? = null
var appName: TextView? = null
}
}
Used at AsyncTask -> onPostExecute
myAppAdapter = MyAppAdapter(appArrayList, applicationContext)
lvPoses!!.adapter = myAppAdapter
Variable Decleared like this
lateinit var myAppAdapter: MyAppAdapter
private val appArrayList = ArrayList<App>()
private var lvPoses: ListView? = null
convertView can be null if no view has been created yet. Fix parametr declaration:
override fun getView(position: Int, convertView: View?, parent:
ViewGroup): View? {
...
}
I had similar issues with Asynctasks, not sure if this will help you, but if you just execute the asynctask (without the .get() ). Your code after calling asynctask will run, but the element is still NULL as the Asynctask didnt execute yet, and when it did it was already too late. Try adding .get() after execute (it will freeze the UI, but the postExecute method will be called in time). Or another approach is to set a boolean variable false and make it true when post executed was executed then check every second or so (Timer, Thread) if the variable is true and then execute the rest of the code. For better UI experience add progressdialog between preExecute and postExecute.
Or you can also set the view to be NULL but I dont think that fixes your problem as you dont want it to be NULL.
I'm trying to replicate the following ListView in my Android app using Kotlin: https://github.com/bidrohi/KotlinListView.
Unfortunately I'm getting an error I'm unable to resolve myself.
Here's my code:
MainActivity.kt:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val listView = findViewById(R.id.list) as ListView
listView.adapter = ListExampleAdapter(this)
}
private class ListExampleAdapter(context: Context) : BaseAdapter() {
internal var sList = arrayOf("Eins", "Zwei", "Drei")
private val mInflator: LayoutInflater
init {
this.mInflator = LayoutInflater.from(context)
}
override fun getCount(): Int {
return sList.size
}
override fun getItem(position: Int): Any {
return sList[position]
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View? {
val view: View?
val vh: ListRowHolder
if(convertView == null) {
view = this.mInflator.inflate(R.layout.list_row, parent, false)
vh = ListRowHolder(view)
view.tag = vh
} else {
view = convertView
vh = view.tag as ListRowHolder
}
vh.label.text = sList[position]
return view
}
}
private class ListRowHolder(row: View?) {
public val label: TextView
init {
this.label = row?.findViewById(R.id.label) as TextView
}
}
}
The layouts are exactly as here: https://github.com/bidrohi/KotlinListView/tree/master/app/src/main/res/layout
The full error message I'm getting is this:
Error:(92, 31) Type inference failed: Not enough information to infer parameter T in fun findViewById(p0: Int): T!
Please specify it explicitly.
I'd appreciate any help I can get.
You must be using API level 26 (or above). This version has changed the signature of View.findViewById() - see here https://developer.android.com/about/versions/oreo/android-8.0-changes#fvbi-signature
So in your case, where the result of findViewById is ambiguous, you need to supply the type:
1/ Change
val listView = findViewById(R.id.list) as ListView to
val listView = findViewById<ListView>(R.id.list)
2/ Change
this.label = row?.findViewById(R.id.label) as TextView to
this.label = row?.findViewById<TextView>(R.id.label) as TextView
Note that in 2/ the cast is only required because row is nullable. If label
was nullable too, or if you made row not nullable, it wouldn't be required.
Andoid O change findViewById api from
public View findViewById(int id);
to
public final T findViewById(int id)
so, if you are target to API 26, you can change
val listView = findViewById(R.id.list) as ListView
to
val listView = findViewById(R.id.list)
or
val listView: ListView = findViewById(R.id.list)
Its Working
API Level 25 or below use this
var et_user_name = findViewById(R.id.et_user_name) as EditText
API Level 26 or Above use this
val et_user_name: EditText = findViewById(R.id.et_user_name)
Happy Coding !
Change your code to this. Where the main changes occurred are marked with asterisks.
package com.phenakit.tg.phenakit
import android.content.Context
import android.os.Bundle
import android.support.design.widget.BottomNavigationView
import android.support.v7.app.AppCompatActivity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ListView
import android.widget.TextView
public class MainActivity : AppCompatActivity() {
private var mTextMessage: TextView? = null
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_home -> {
mTextMessage!!.setText(R.string.title_home)
return#OnNavigationItemSelectedListener true
}
R.id.navigation_dashboard -> {
mTextMessage!!.setText(R.string.title_dashboard)
return#OnNavigationItemSelectedListener true
}
R.id.navigation_notifications -> {
setContentView(R.layout.activity_list_view)
return#OnNavigationItemSelectedListener true
}
}
false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mTextMessage = findViewById(R.id.message) as TextView?
val navigation = findViewById(R.id.navigation) as BottomNavigationView
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
**val listView = findViewById<ListView>(R.id.list)**
**listView?.adapter = ListExampleAdapter(this)**
}
private class ListExampleAdapter(context: Context) : BaseAdapter() {
internal var sList = arrayOf("Eins", "Zwei", "Drei")
private val mInflator: LayoutInflater
init {
this.mInflator = LayoutInflater.from(context)
}
override fun getCount(): Int {
return sList.size
}
override fun getItem(position: Int): Any {
return sList[position]
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View? {
val view: View?
val vh: ListRowHolder
if(convertView == null) {
view = this.mInflator.inflate(R.layout.list_row, parent, false)
vh = ListRowHolder(view)
view.tag = vh
} else {
view = convertView
vh = view.tag as ListRowHolder
}
vh.label.text = sList[position]
return view
}
}
private class ListRowHolder(row: View?) {
public var label: TextView
**init { this.label = row?.findViewById<TextView?>(R.id.label) as TextView }**
}
}
I would suggest you to use synthetics kotlin Android extension:
https://kotlinlang.org/docs/tutorials/android-plugin.html
https://antonioleiva.com/kotlin-android-extensions/
In your case the code will be something like this:
init {
this.label = row.label
}
As simple as that ;)
In android 12 remove as from your code and put your model in <> in front of getParcelableExtra.
change
intent.getParcelableExtra("MyModel") as MyModel
to
intent.getParcelableExtra `<MyModel>` ("MyModel")!!
I came across this error while using the
DataBindingUtil.setContentView(this,R.layout.activity_main)
The error came here because this is supposed to return the binding associated with the layout_main(inflatted layout) so I resolved this error by :
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main)
So, your error may be mostly with handling the return value.Try to set that and check.