Trouble setting an image for a Kotlin/Anko DSL defined ImageView - android

I'm trying to use Kotlin and Anko's DSL to create an alert dialog that lets a user pick an image, and then loads it into an ImageView. Right now I'm just trying go get the ImageView to work, so I have the button click to load a preselected image from a URL using Picasso.
When I click the button in the alert dialog, I get this error:
kotlin.TypeCastException: null cannot be cast to non-null type
android.widget.ImageView
I'm guessing for some reason the ImageView isn't being loaded through findViewById. Does anyone know why this might be? I'm guessing Anko's DSL has some weird behavior I don't know about.
fab.setOnClickListener { view ->
alert {
title = "New Post"
customView {
verticalLayout {
val subject = editText {
hint = "Subject"
}
imageView {
id = R.id.picked_image
}
linearLayout {
gravity = Gravity.CENTER
button("Choose Photo") {
onClick {
Picasso.with(this#MainActivity)
.load("http://SomeUrl/image.jpg")
.into(findViewById(R.id.picked_image) as ImageView)
}
}
button("Choose Image") {}
}
positiveButton("Post") { }
negativeButton("Cancel") {}
}
}
}.show()

You can get a reference to the ImageView like this and avoid having to deal with IDs altogether:
val iv = imageView()
...
onClick {
Picasso.with(this#MainActivity)
.load("http://SomeUrl/image.jpg")
.into(iv)
}
...

Related

Android resources.getColor() freezes code

I need to add views to LinearLayout dynamically on POST request finished inside a fragment.
Here is my function that creates and setups the view and returns it, then I add it to the chatsRoot via addView() method. No exceptions, but views are not added.
After some debug I noticed, that in logcat there is only one message "Banner created1" and no message "Banner created2", like if the program freezed on loading the color resource.
Log.d("VIEW", "Banner created1") // appears in logcat
val colorFrom = resources.getColor(R.color.banner_regular, null)
Log.d("VIEW", "Banner created2") // not appears in logcat
private fun generateBanner(chatData: ChatData):View? {
val banner = inflater.inflate(R.layout.chat_banner, null, false)
banner.run {
findViewById<TextView>(R.id.user_name).text = chatData.name
findViewById<TextView>(R.id.short_code).text = chatData.shortCode
findViewById<TextView>(R.id.short_message).text =
chatData.lastMessage.let { it.substring(0, min(40, it.length)) + "..." }
findViewById<ImageView>(R.id.unread_dot).visibility =
if (chatData.hasUnread) VISIBLE else GONE
findViewById<TextView>(R.id.date_time_text).text = chatData.lastMessageTime
}
Log.d("VIEW", "Banner created1")
val colorFrom = resources.getColor(R.color.banner_regular, null)
Log.d("VIEW", "Banner created2")
val colorTo = resources.getColor(R.color.banner_clicked, null)
val animation = ValueAnimator.ofObject(
ArgbEvaluator(), colorFrom, colorTo, colorFrom
).apply {
addUpdateListener {
banner.setBackgroundColor(animatedValue as Int)
}
}
banner.setOnClickListener {
animation.start()
openChat(chatData.uid)
}
return banner
}
generateBanner() is called here, same situation: top is printed, bottom is not
private fun addChatsToRoot(chats:List<ChatData>) {
Log.d("VIEW", "Adding chats to root") // printed to logcat
for(chatData in chats) {
chatsData += chatData.uid to chatData
val banner = generateBanner(chatData)
chatsRoot.addView(banner)
}
Log.d("VIEW", "Chats are added to the layout") // not in logcat
}
I tried removing null parameter for the theme, nothing changed.
I also changed resources call to raw number representing white colors, layout created, nothing freezed the code, but layout inflated wrongly, banner.height is always zero, even if in its drawable file I set minHeight.
So it looks like I just cant load any kind of a resource from this fragment
Try this
ContextCompat.getColor(applicationContext,R.color.banner_regular)
Turned out all the problems were because I had a fragment inside another fragment. This caused problems not only with resource access, but also with inflating additional layout views.
My solution is to move fragments to one level and navigate using one navController

How to get the name of view programatically such as whether it's textview or button by View params in onClick callback in Android?

In particular class, from many places call back is coming, i just want to whether it's coming text or button for example, so that I can set the data accordingly.
NOTE: I'm not talking about parent layout, I want to know exact name where the event click is happened!
If I'm doing this: Log.d("Hello", "Clicked finally: "+ view?.id)
This is coming:
D: Clicked finally: 2131296625
If you are using Kotlin, You can simply check if the View is Button or Image using is operator like:
when(view) {
is Button -> {
// a Button is clicked.
}
is AppCompatImageView -> {
// an Image is clicked.
}
else -> {
// any other view is clicked.
}
}
You can do something like this:
if (view.javaClass.simpleName == Button::class.java.simpleName) {
// it is a button
} else if (view.javaClass.simpleName == TextView::class.java.simpleName) {
// it is a text view
}
Please note that this won't work if you are using any subclass of Button or TextView. You will need to explicitly specify the class you want to check for.

Is DrawableCompat.setTint() lazy?

I am designing an app that has 3 button in main activity, and several buttons in a fragment. I want to change the color of a button in the fragment, depending on which button of main activity is toggled.
color1.setOnClickListener {
brush_chosen = 1
color1.setBackgroundColor(R.color.black)
color2.setBackgroundColor(0x00000000)
color3.setBackgroundColor(0x00000000)
if (frag_num == 8 ){
frag_8p.set_frag_value(frag_num,brush_chosen)
}
}
The function set_frag_value is :
fun set_frag_value(frag_num:Int,brush:Int) : Int
{
brush_chosen=brush
return brush
}
This change the value of brush_chosen. Then I made a function :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
ib0.setOnClickListener { view ->
Log.d("brush_color","Brush of 0 : "+brush_chosen)
if (brush_chosen==1)
{
Log.d("brush_color","Brush Confirm : "+brush_chosen)
DrawableCompat.setTint(ib0.drawable, ContextCompat.getColor(requireContext(),R.color.rndcolor1))
}
else if (brush_chosen==2)
{
Log.d("brush_color","Brush Confirm : "+brush_chosen)
DrawableCompat.setTint(ib0.drawable, ContextCompat.getColor(requireContext(),R.color.purple_500))
}
else if (brush_chosen==3)
{
Log.d("brush_color","Brush Confirm : "+brush_chosen)
DrawableCompat.setTint(ib0.drawable, ContextCompat.getColor(requireContext(),R.color.teal_200))
}
Log.d("brush_color","End of onclicklistener ")
}
}
I checked the log and theoretically this code should work correctly. However, I found that the button color did not change properly, even I checked my app prints all log correctly. For example, when I clicked button color1 in main activity, variable brush_chosen becomes 1 and the first button in fragment I clicked changes its color. But the second button I clicked does not change its color.
Is there any problem on my code using DrawableCompat ??
Android does some Drawable state caching under the hood. You might need to call mutate() on the Drawable you want to tint and then set the new Drawable in order for the tint to show up properly.

How to perform click on TextInputLayout EndIcon button

I know that it is possible to perform a click on a view like this :
view.PerformClick()
How do I do it on TextInputLayout EndIcon button?
Update
The problem is that I have a bunch of InputLayouts and use a generic function to set the click listeners on them like so
fun setTextInputLayoutListeners(
inputLayout: TextInputLayout, editText: TextInputEditText,
actionSet: () -> Unit,
actionClear: () -> Unit
) {
with (inputLayout) {
setOnClickListener { actionSet() }
setEndIconOnClickListener { actionClear() }
}
editText.setOnClickListener { actionSet() }
}
and call it with different parameteres like this
setTextInputLayoutListeners(
categoryInputLayout, categoryEditText, { onCategoryClick() }, { onCategoryClear() }
)
setTextInputLayoutListeners(
dateInputLayout, dateEditText, { onDateClick() }, { onDateClear(calendar) }
)
so I'm looking for a generic solution, sort of
inputLayout.EndIcon.PerformClick()
textinput.setEndIconOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// do some code
}
});
hope it helps..
EndIcon in TextInputLayout is of type CheckableImageButton and its id is R.id.text_input_end_icon (use R.id.text_input_start_icon for StartIcon). To simulate clicks, you need to find the button using findViewById, then cast it as a CheckableImageButton and call performClick().
In your case:
inputLayout.findViewById<CheckableImageButton>(R.id.text_input_end_icon)?.performClick()
Thank you so much for you clear and helpful answer.I did a little trick that worked and i wanna share my answer to everyone who will need it.
First thing i did is get the string value of my view :
Log.d("tag", String.valueOf(v));
i got that : com.google.android.material.internal.CheckableImageButton{ba604a VFED..C.. ...P.... 8,3-104,99 #7f090125 app:id/text_input_end_icon}
and like i suspected then icon is a different view of the text field layout with different id (in the end of the string value of the view).
So i changed my if condition from if (v.getId() == R.id.start_date_Layout) to if (v.getId() == R.id.text_input_end_icon), and it work now
i hope this answer will be helpful to someone, thank you again for all your answer

Bottom OnClickListener always unresolved

I have a simple button in my recyclerview, when clicked the first time it should make the text editable, when clicked the second time, it should confirm the change. The problem I'm having is that I have the two onClickListeners set up, but they refer to each other, and the bottom one can always resolve the top one, but the top one can't resolve the bottom one.
Recyclerview: bindIngredient
fun bindIngredient(ingredient: ListIngredientsQuery.Item, clickListener: RecyclerViewClickListener) {
val ocl1 = View.OnClickListener{
//Text Editable
view.ingEditText.setText(view.ingNameTV.text.toString())
view.ingNameTV.visibility = View.GONE
view.ingEditText.visibility = View.VISIBLE
view.ingEditButton.text = "Confirm"
view.ingEditButton.setOnClickListener(ocl2)
}
var ocl2 = View.OnClickListener {
//Text Not Editable
view.ingNameTV.text = view.ingEditText.text
view.ingEditText.visibility = View.GONE
view.ingNameTV.visibility = View.VISIBLE
view.ingEditButton.setOnClickListener(ocl1)
clickListener.onConfirmSelect(ingredient)
}
this.ingredient = ingredient
view.ingNameTV.text = ingredient.name()
view.ingEditButton.setOnClickListener(ocl1)
view.veganSpinner.setSelection(Vegan.valueOf(ingredient.vegan().toString()).ordinal, false)
view.gfSpinner.setSelection(GlutenFree.valueOf(ingredient.glutenfree().toString()).ordinal, false)
}
In this example the line
view.ingEditButton.setOnClickListener(ocl2)
errors because ocl2 is unresolved. If I switch the order of the two onClickListeners being declared and initialized, the line
view.ingEditButton.setOnClickListener(ocl1)
errors because ocl1 is resolved. I take this to mean that it won't look further down to find what it needs, it'll only rely on objects that have already been initialized.
Is there a way to fix this? Is there a better way to do this? I'm tempted to just put two buttons in the same spot, give them each their own onclicklistener and swap their visibility, but this seems like a waste of resources.
You need to declare your objects before you use them.
fun bindIngredient(ingredient: ListIngredientsQuery.Item, clickListener: RecyclerViewClickListener) {
val ocl1: View.OnClickListener
val ocl2: View.OnClickListener
ocl1 = View.OnClickListener{
//Text Editable
view.ingEditText.setText(view.ingNameTV.text.toString())
view.ingNameTV.visibility = View.GONE
view.ingEditText.visibility = View.VISIBLE
view.ingEditButton.text = "Confirm"
view.ingEditButton.setOnClickListener(ocl2)
}
ocl2 = View.OnClickListener {
//Text Not Editable
view.ingNameTV.text = view.ingEditText.text
view.ingEditText.visibility = View.GONE
view.ingNameTV.visibility = View.VISIBLE
view.ingEditButton.setOnClickListener(ocl1)
clickListener.onConfirmSelect(ingredient)
}
this.ingredient = ingredient
view.ingNameTV.text = ingredient.name()
view.ingEditButton.setOnClickListener(ocl1)
view.veganSpinner.setSelection(Vegan.valueOf(ingredient.vegan().toString()).ordinal, false)
view.gfSpinner.setSelection(GlutenFree.valueOf(ingredient.glutenfree().toString()).ordinal, false)
}
However, it would be better if you just used one OnClickListener. You can simply save which state you are in, and when the button is clicked, you just check which state you are in, perform your action, and then change the state. This way you don't have to worry about switching your listeners, which can get messy.

Categories

Resources