Need MotionLayout Reference using DataBinding Tree - kotlin

I'm trying to get the reference to my MotionLayout inside a Fragment inside my RecyclerView so that I can attach my addTransitionListener. I've got the listener prepared, but I don't know the valid path to this object.
I've seen many great examples that reference motionLayout vars, but I can't seem to get a valid one using DataBinding methods.
My RecyclerView is inside my Fragment as:
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/shipper_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:background="#color/mainList"
tools:listitem="#layout/row_shipper_line"
/>
My MotionLayout is inside my "row_shipper_line" as:
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="#+id/line_container"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="#color/mainList"
app:layoutDescription="#xml/row_shipper_line_container_scene"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
My binding var is set as:
binding = DataBindingUtil.inflate(
inflater,
R.layout.create_shipper_fragment,
container,
false
)
I've got this code entered and it compiles, but the events aren't called:
rec = LayoutInflater.from(context).inflate(R.layout.shipper_list, null)
ml = rec.findViewById(R.id.line_container)
var transitionListener = object : MotionLayout.TransitionListener {
override fun onTransitionStarted(p0: MotionLayout?, startId: Int, endId: Int) {
Log.d("MyApp", "Transition onTransitionStarted.")
}
override fun onTransitionChange(
p0: MotionLayout?,
startId: Int,
endId: Int,
progress: Float
) {
Log.d("MyApp", "Transition onTransitionChange.")
}
override fun onTransitionCompleted(p0: MotionLayout?, currentId: Int) {
Log.d("MyApp", "Transition onTransitionCompleted.")
}
override fun onTransitionTrigger(
p0: MotionLayout?,
triggerId: Int,
positive: Boolean,
progress: Float
) {
Log.d("MyApp", "Transition onTransitionTrigger.")
}
}
ml.setTransitionListener(transitionListener)
My line swipes LEFT perfectly. I just need to listen for onTransitionComplete.
My binding tree looks like this:
Fragment containing my RecyclerView -> Row Layout containing the MotionLayout tag.
What I'm trying to do is something like this:
binding.shipper_list.line_container
Thank you in advance!

Related

Kotlin, embed root_preferences in existing fragment

Kotlin, I want to embed root_preferences.xml in a fragment with existing content. I navigate using <navigation..> I want to embed the preferences like this:
// ... existing items
<include
android:id="#+id/summaryPrefs"
layout="#layout/fragment_container_settings"
/>
// ... more items here
What I did
Create new Settings Fragment. This does NOT create a layout file, but an XML file at res/xml/root_preferences.xml, make no changes to code.
Create a 'container fragment' new Fragment (Blank) name it, ContainerSettingsFragment. it creates the layout file fragment_container_settings.
Delete code in class ContainerSettingsFragment and replace with this:
// Make a container fragment that holds the preference xml.
class ContainerSettingsFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_container_settings, container, false)
requireActivity().supportFragmentManager
.beginTransaction()
.replace(
R.id.settings, // this is the id in fragment_container_settings <FrameLayout>
SettingsFragment() // SR: ?? This loads the XML into this fragment
)
.commit()
// Inflate the layout for this fragment
return view
}
}
Replaced code in fragment_container_settings with this:
FrameLayout
android:id="#+id/settings"
android:layout_width="250dp"
android:layout_height="300dp"
Expected result: FrameLayout #+id/settings displays the contents of root_preferences
Actual: Size is 250dp x250dp but none of the items are displayed
Was easy, if using nav_graph, don't mix with .supportFragmentManager
I removed all the code in the container settings fragment.
): View? {
return inflater.inflate(R.layout.fragment_container_settings, container, false)
}
Completed layout in fragment_container_settings.xml
<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:id="#+id/settingsSummary"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.settingssummary.ContainerSettingsFragment">
</androidx.constraintlayout.widget.ConstraintLayout>
Used include to add to fragment_settings_summary

OnClickListener for multiple buttons does not work

I searched here fo a solution and on google but it doesn't work for me and I can't understand why.
I did everything like in this solution: Multiple OnClickListener in Kotlin
XML Code:
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="#+id/btnP1"
android:layout_width="60dp"
android:layout_height="54dp"
android:layout_marginTop="200dp"
android:fontFamily="sans-serif-medium"
android:shape="rectangle"
android:text="1"
android:textColor="#000000"
android:textSize="18sp"
android:textStyle="bold"
app:backgroundTint="#98BFD1"
app:layout_constraintEnd_toStartOf="#+id/btnP2"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/txtDart1"
tools:text="1" />
<Button
android:id="#+id/btnP2"
android:layout_width="60dp"
android:layout_height="54dp"
android:layout_marginTop="334dp"
android:shape="rectangle"
android:text="2"
android:textColor="#000000"
android:textSize="18sp"
android:textStyle="bold"
app:backgroundTint="#98BFD1"
app:layout_constraintEnd_toStartOf="#+id/btnP3"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#+id/btnP1"
app:layout_constraintTop_toTopOf="parent"
tools:text="2" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt:
package com.example.test
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity(), View.OnClickListener {
private var toast: Toast? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnP1.setOnClickListener(this)
btnP2.setOnClickListener(this)
}
override fun onClick(view: View?)
{
when ( view!!.id)
{
TODO()
}
}
private fun myToast(string: String)
{
toast = Toast.makeText(this,string,Toast.LENGTH_SHORT)
toast?.show()
}
}
Problem: btnP1.setOnClickListener(this) and btnP2.setOnClickListener(this)
-> btnP1 and btnP2 are always red -> "unresolved reference"
Why does it work for everyone but not in my case?
Thank you!
At Build.gardle(app)
ADD
plugins {
id 'kotlin-android-extensions'
}
or
apply plugin: 'kotlin-android-extensions'
depends on your version. and then sync
After that you would be able to use your button or whatever without using findViewById
if you still don't find it.
make sure to import this at your KotlinFile
***normally it will import auto after you type the id name like in your case btnP1 ***
import kotlinx.android.synthetic.main.YOUR_XML_FILE_NAME.* (Don't forget *)
example
import kotlinx.android.synthetic.main.activity_main.*
more info https://antonioleiva.com/kotlin-android-extensions/
What you have declared in the XML is not automatically available in your code.
You have not defined or declared the btnP1 and btnP2 in your Kotlin code, and therefore compiler does not know what to refer to, hence the error
Use findViewById to obtain a reference to your buttons
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val btnP1 = findViewById<Button>(R.id.btnP1)
val btnP2 = findViewById<Button>(R.id.btnP2)
btnP1.setOnClickListener(this)
btnP2.setOnClickListener(this)
}
You can read more about this method here

Why my data-binding annotation(#BindingAdapter("imageURL")) doesn't work?

I've written my custom recyclerView and have got a problem. But when I wrote an annotation to mark a setter for my imageView I got a compilation error.
Here are the source code and errors.
Error:
Cannot find a setter for <com.makeramen.roundedimageview.RoundedImageView app:imageURL> that accepts parameter type 'java.lang.String'
If a binding adapter provides the setter, check that the adapter is annotated correctly and that the parameter type matches.
Setter code:
import android.widget.ImageView
import androidx.databinding.BindingAdapter
import com.squareup.picasso.Callback
import com.squareup.picasso.Picasso
import java.lang.Exception
class BindingAdapters {
#BindingAdapter("imageURL")//compilation error
fun setImageURL(imageView: ImageView, URL: String?) {
imageView.alpha = 0f
try {
Picasso.get().load(URL).noFade().into(imageView, object : Callback {
override fun onSuccess() {
imageView.animate().setDuration(300).alpha(1f).start()
}
override fun onError(e: Exception) {
}
})
} catch (ignored: Exception) {
}
}
}
ImageView xml code:
<data>
<variable
name="eventShow"
type="course.ru.qsearcher.models.Event" />
</data>
........
<com.makeramen.roundedimageview.RoundedImageView
android:id="#+id/imageEvent"
android:layout_width="#dimen/_70sdp"
android:layout_height="#dimen/_100sdp"
android:layout_marginStart="#dimen/_10sdp"
app:imageURL="#{eventShow.imagePath}"
android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:riv_corner_radius="#dimen/_4sdp" />
Event is a data class with some fields like the title of an event, description, image URL, and so on.
сode
xml usage
url-field
Change android:imageURL to imageURL, the android: prefix is for built-in attribute like android:text.
And use it like imageURL or app:imageURL in your ImageView.
I didn't add the Kotlin plugin to my gradle file
apply plugin: "kotlin-kapt"
That solved my problem

E/RecyclerView: No adapter attached; skipping layout in kotlin

** I want to know what mistake i did so occure its error please tell meWhat solution of this error can anyone tell me how to fix this problem :E/RecyclerView: No adapter attached; skipping layout,i couldn't solve yet please tell me how i fix this error...........................................................................................................................................**
search.xml
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view_search"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/app_bar_layout_search"
android:visibility="gone">
</androidx.recyclerview.widget.RecyclerView>
search.kt
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view=inflater.inflate(R.layout.fragment_search, container, false)
recyclerView=view.findViewById(R.id.recycler_view_search)
recyclerView?.setHasFixedSize(true)
recyclerView?.layoutManager=LinearLayoutManager(context)
mUser=ArrayList()
userAdapter=context?.let{ UserAdapter(it,mUser as
ArrayList<User>,true) }
recyclerView?.adapter=userAdapter
view.search_edit_text.addTextChangedListener(object: TextWatcher{
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
TODO("Not yet implemented")
}
Try adding "RecyclerView.VERTICAL, false" to your LinearLayoutManager initialization as follow
LinearLayoutManager(context, RecyclerView.VERTICAL, false)
also make sure your adapter is initialized properly (try adding a Log after your useAdapter initialization)
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_hashtags"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:orientation="horizontal"
tools:listitem="#layout/item_trending"/
Check the context which you are using. If its null the adapter might not get attached and the above error might occur
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" This line can help you get rid of the above error.

How to get a RecyclerView Item to update itself from a LiveData value in it's binding object using BindingAdapter?

I have a recyclerview.widget.ListAdapter to display some cards in a grid. When I click on a card, it comes to the front of the grid, flips, and when you see the front, you are supposed to be able to like it. I have an ImageView on the corner of the front of the card, that is supposed to toggle between liking or not, displaying a different drawable that I have already defined.
My ViewHolder looks like this:
class GameCardViewHolder(private val binding : ItemGameCardBinding) :
RecyclerView.ViewHolder(binding.root){
companion object {
fun from(parent: ViewGroup): GameCardViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemGameCardBinding.inflate(layoutInflater, parent, false)
return GameCardViewHolder(binding)
}
...
}
fun bind(productCard: ProductCard, listener: OnClickListener){
binding.productCard = productCard
binding.executePendingBindings()
...
}
}
My item_game_card.xml looks like this:
<layout
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">
<data>
<variable
name="productCard"
type="company.app.model.ProductCard" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout>
...
<ImageView
android:id="#+id/gameItemLikeImageView"
android:layout_width="0dp"
android:layout_height="0dp"
...
app:imageDrawable="#{productCard.liked}"
android:onClick="#{() -> productCard.toggleLike()}"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
My BindingAdapters.kt:
#BindingAdapter("imageDrawable")
fun bindImageDrawable(imgView: ImageView, boolean: LiveData<Boolean>?) {
Timber.d("Binding liked: $boolean")
when(boolean?.value){
true -> imgView.setImageResource(R.drawable.ic_card_like)
false -> imgView.setImageResource(R.drawable.ic_card_dislike)
}
}
And my ProductCard.kt:
data class ProductCard(
val position: Int,
val product: Product
){
...
private val _liked = MutableLiveData(false)
val liked : LiveData<Boolean>
get() = _liked
fun toggleLikeProduct(){
Timber.d("Toggle like before: ${_liked.value}")
val oldValue = _liked.value!!
_liked.value = !oldValue
Timber.d("Toggle like after: ${_liked.value}")
}
}
When I click on the ImageView, the console prints out the before and after values every time correctly, so I know the value of the liked LiveData is actually changing, but the View is not updating accordingly, that is because the binding adapter is only being called once (in the binding.executePendingBindings() I guess, when the RecyclerView binds the ViewHolder, and not every single time the LiveData changes).
I want the BindingAdapter code to be run every time the liked LiveData changes.
When I do this with ViewModel and Fragment layout, every time a LiveData changes in the ViewModel, the View changes because the xml is referencing that LiveData, but I don't know why, when it comes to ProductCard and recyclerview item layout, even if the xml is referencing that LiveData, the View doesn't change, the xml is not actually binding to the LiveData, which is the whole point of having LiveData.
I have already acheived this behaviour with a very brute, unelegant sort of way, setting an onClickListener on that view, and forcing it to change it's resource drawable, on the OnBindViewHolder like this:
fun bind(productCard: ProductCard, listener: OnClickListener){
binding.productCard = productCard
binding.executePendingBindings()
binding.gameItemLikeImageView.setOnClickListener {
it?.let {
val card = binding.productCard
Timber.d("Tried changing liked status: ${card!!.liked}")
val old = card.liked
card.liked = !old
binding.productCard = card
val card2 = binding.productCard
Timber.d("Tried changing liked status: ${card2!!.liked}")
if (!old){
it.setBackgroundResource(R.drawable.ic_card_like)
}
else {
it.setBackgroundResource(R.drawable.ic_card_dislike)
}
}
}
...
}
But I would really like that the behaviour of each card be controlled by the state of the ProductCard object that it is bind to.
I have also tried to force call binding.executePendingBindings() every time I click, but also didn't work.
How can I make the item_game_card.xml to observe the changes in the LiveData?
app:imageDrawable won't accept any LiveData<Boolean> and I'd wonder why it isn't LiveData<Product>? Try to bind the initial value alike this:
app:imageDrawable="#{product.liked ? R.drawable_liked : R.drawable_neutral}"
For LiveData the LifecycleOwner needs to be set for the binding:
class ProductsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Inflate view and obtain an instance of the binding class.
val binding: ProductsBinding = DataBindingUtil.setContentView(this, R.layout.products)
// Specify the current activity as the lifecycle owner.
binding.setLifecycleOwner(this)
}
}
see the documentation.