I'm trying to build Android app in Kotlin using dataBinding and when I'm trying to compile this code
#Bindable
var progress:Int=1
#NotNull
#InverseBindingAdapter(attribute = "progress")
fun SeekBar.getProgress():Int{
return this.progress
}
#BindingAdapter(value = ["progressAttrChanged"])
fun setListeners(seekBar: SeekBar,inverseBindingListener: InverseBindingListener){
var listener=object: SeekBar.OnSeekBarChangeListener{
override fun onProgressChanged(seekBar: SeekBar?, Progress: Int, fromUser: Boolean) {
progress=Progress
mBeatBox.mRange=progress/66.67 as Float
inverseBindingListener.onChange()
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
}
}
seekBar.setOnSeekBarChangeListener(listener)
}
I get this errors https://ibb.co/cBHRwx.
Here is view of modelView
<data>
<variable
name="viewModel"
type="com.bignerdranch.android.beatboxkotlin.Models.BeatBoxViewModel"/>
</data>
....
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:text="progres"
android:gravity="center"
android:layout_weight="9"/>
<android.support.v7.widget.AppCompatSeekBar
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="9"
app:progress="#={viewModel.progress}"/>
How can I solve this?
I solved this error in this way
Updated viewModel
`
var progress:Int=mBeatBox.mRange.toInt()
#Bindable set(value){field=value;notifyChange()}
#Bindable get()=field
fun getEditListener():SeekBar.OnSeekBarChangeListener{
var listener=object: SeekBar.OnSeekBarChangeListener{
override fun onProgressChanged(seekBar: SeekBar?, Progress: Int, fromUser: Boolean) {
progress=Progress
mBeatBox.mRange=(progress/66.67).toFloat()
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
}
}
return listener
}`
And View looks like:
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:text="progress"
android:gravity="center"
android:layout_weight="9"
android:text="#{String.valueOf(viewModel.progress)}"/>
<android.support.v7.widget.AppCompatSeekBar
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="9"
app:OnSeekBarChangeListener="#{viewModel.EditListener}"/>
Not sure exactly what your error is pointing to, but looks like there's already a SeekbarBindingAdapter defined in the library, so you probably don't need to define your own. If you want to update things when progress changes, override set or use ObservableInt with an OnPropertyChangedCallback
Related
android beginner here. any help is much appreciated .
I am working on an app that can do a simple experiment.
the app displays images and asks users to rate it from 1 to 10.
I have 1 activity and two layouts experiment begins.
I have two layouts merged under FrameLayout.
what I want to achieve is :
'====>start experiment >show first image for 10 seconds >change layout to ratings layout>after user selects rating>loopback with different image>finish() when imagelist is empty. '
here is what I tried
I have tried viewflipper but shownext() and showprevious() methods dont update values.
now i am trying using layout visibility ,showing and hiding the layouts
class Presenter : AppCompatActivity() {
//val currentTime = Calendar.getInstance().time
private lateinit var binding: ActivityPresenterBinding
lateinit var layout1:View
lateinit var layout2:View
val imgList=Constants.getImages()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//setContentView(R.layout.activity_presenter)
binding = ActivityPresenterBinding.inflate(layoutInflater)
setContentView(binding.root)
layout1 = binding.imageView
layout2=binding.ratingView
startSlider()
}
private fun startSlider() {
Handler(Looper.getMainLooper()).apply {
//arr.shuffle()
var index = 0
var imageView = binding.imgPresenter
val runnable = object : Runnable {
override fun run() {
imageView.setImageResource(imgList[index].image)
layout1.visibility= View.VISIBLE
Log.i("number ","${index}")
postDelayed(this, 5000)
index++
layout1.visibility=View.GONE
layout2.visibility=View.VISIBLE
binding.sbSeekbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener{
override fun onProgressChanged(p0: SeekBar?, p1: Int, p2: Boolean) {
Toast.makeText(applicationContext, "$p1", Toast.LENGTH_LONG).show()
//save rating (1-10)
}
override fun onStartTrackingTouch(p0: SeekBar?) {
}
override fun onStopTrackingTouch(p0: SeekBar?) {
}
})
}
}
postDelayed(runnable, 1000)
}
}
here my layout file
<FrameLayout android:id="#+id/viewFliper"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="#+id/img_presenter"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/ic_bg"
/>
</LinearLayout>
<LinearLayout 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/ratingView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="how do you feel about this image from one to 10"
android:orientation="vertical"
android:gravity="center"
android:layout_marginBottom="20dp"
/>
<SeekBar
android:id="#+id/sb_seekbar"
style="#style/Widget.AppCompat.SeekBar.Discrete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="10"
android:paddingStart="6dp"
android:paddingEnd="6dp"
android:progress="3"
android:stepSize="1" />
</LinearLayout>
this is not hiding the views after the second image
what am I doing wrong?
try this and apply viewbinding
lateinit var runnable: Runnable
private fun startSlider() {
Handler(Looper.getMainLooper()).apply {
var flag = 0
var index = 0
runnable = Runnable {
if (flag == 0) {
img_presenter.setImageResource(imgList[index])
layout1.visibility = View.VISIBLE
layout2.visibility = View.GONE
postDelayed(runnable, 5000)
flag = 1
index++
} else {
layout1.visibility = View.GONE
layout2.visibility = View.VISIBLE
sb_seekbar.setOnSeekBarChangeListener(object :
SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(p0: SeekBar?, p1: Int, p2: Boolean) {
// Toast.makeText(applicationContext, "$p1", Toast.LENGTH_LONG).show()
//save rating (1-10)
}
override fun onStartTrackingTouch(p0: SeekBar?) {
}
override fun onStopTrackingTouch(p0: SeekBar?) {
index++
flag = 0
postDelayed(runnable, 1000)
}
})
}
}
postDelayed(runnable, 1000)
}
}
I was trying DataBinding on a Small Project which has some fragments and one of the fragments hosts a RecyclerView.
The child item has a DataBindingwhich I am unable to access in the RecyclerView Adapter.
How to solve this?
ShopFragment.KT
class ShopFragment : Fragment(),ShopItemsAdapter.ShopInterface {
lateinit var fragmentShopBinding: FragmentShopBinding;
lateinit var shopItemsAdapter: ShopItemsAdapter ;
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
fragmentShopBinding= FragmentShopBinding.inflate(inflater,container,false)
return fragmentShopBinding.root;
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
shopItemsAdapter = ShopItemsAdapter();
fragmentShopBinding.shopRV.adapter=shopItemsAdapter;
super.onViewCreated(view, savedInstanceState)
}
override fun addItem(productModel: ProductModel) {
TODO("Not yet implemented")
}
override fun onItemClick(productModel: ProductModel) {
TODO("Not yet implemented")
}
}
FragmentShop.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".views.ShopFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/shopRV"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
tools:listitem="#layout/shop_item"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
shopItem.xml
<?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="productModel"
type="com.example.mvvm_shopping_cart.models.ProductModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent">
<ImageView
android:id="#+id/productIV"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
app:srcCompat="#drawable/iphone" />
<TextView
style="#style/TextAppearance.MaterialComponents.Body1"
android:layout_gravity="center"
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{productModel.name}" />
<TextView
android:layout_gravity="center"
android:id="#+id/priceTV"
style="#style/TextAppearance.MaterialComponents.Caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{productModel.price.toString()}" />
<TextView
android:layout_gravity="center"
android:id="#+id/availableTV"
style="#style/TextAppearance.MaterialComponents.Caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{productModel.available? `Available` : `Out of stock`}" />
<Button
android:textAppearance="#style/TextAppearance.MaterialComponents.Caption"
style="#style/Widget.MaterialComponents.Button.TextButton"
android:id="#+id/AddBtn"
android:enabled="#{productModel.available}"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add to cart" />
</LinearLayout>
</layout>
ProductModel.kt
class ProductModel(var id : Number,var name : String,var imageUrl : String, var price : Number, var isAvailable : Boolean) {
override fun toString(): String {
return super.toString()
}
override fun equals(other: Any?): Boolean {
return super.equals(other)
}
class ShopItemCallBack : DiffUtil.ItemCallback<ProductModel>() {
override fun areItemsTheSame(oldItem: ProductModel, newItem: ProductModel): Boolean {
return newItem.id == oldItem.id;
}
override fun areContentsTheSame(oldItem: ProductModel, newItem: ProductModel): Boolean {
return newItem == oldItem;
}
}
}
ShopItemAdapter.kt
class ProductModel(var id : Number,var name : String,var imageUrl : String, var price : Number, var isAvailable : Boolean) {
override fun toString(): String {
return super.toString()
}
override fun equals(other: Any?): Boolean {
return super.equals(other)
}
class ShopItemCallBack : DiffUtil.ItemCallback<ProductModel>() {
override fun areItemsTheSame(oldItem: ProductModel, newItem: ProductModel): Boolean {
return newItem.id == oldItem.id;
}
override fun areContentsTheSame(oldItem: ProductModel, newItem: ProductModel): Boolean {
return newItem == oldItem;
}
}
}
I am unable to find the issue, please help.
Problem:
You are using the wrong generated binding class; as the FragmentShopBinding is the generated class of the fragment_shop.xml which is a fragment layout that contains the RecyclerView ifselft not RecyclerView item layout. And therefore it didn't resolve the product view.
Solution:
To fix this you need to replace the FragmentShopBinding with the generated class associated to the row item layout where it is shopItem.xml, and the generated class would be ShopItemBinding:
class ShopViewHolder(var itemView: ShopItemBinding): RecyclerView.ViewHolder(itemView.root) {
//......
}
I was using MotionLayout to achieve some cool effect when RecyclerView drag up, but it looks like when I want to use SwipeRefreshLayout with RecyclerView, things getting conflict.
If no SwipeRefreshLayout, it was fine
If I surround with SwipeRefreshLayout, drag up behavior was weird like opposed
https://gist.github.com/GHChrisSu/6aadf40dc693bc47cbb83a167a82a4b9
And the motion scene is below
https://gist.github.com/GHChrisSu/a154d59f34555cccfc1b48617989ae16
You should wrap your MotionLayout in a SwipeRefreshLayout like this:
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="#+id/refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="#+id/motion_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="#xml/play_scene">
<!-- Some xml code -->
</androidx.constraintlayout.motion.widget.MotionLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
And then you must add a MotionLayout.TransitionListener to your MotionLayout to enable/disable your SwipeRefreshLayout depending on the MotionLayout state.
binding.motionLayout.setTransitionListener(object :MotionLayout.TransitionListener{
override fun onTransitionTrigger(p0: MotionLayout?, p1: Int, p2: Boolean, p3: Float) {
}
override fun onTransitionStarted(p0: MotionLayout?, p1: Int, p2: Int) {
}
override fun onTransitionChange(p0: MotionLayout?, p1: Int, p2: Int, p3: Float) {
Log.d("RepairsFragment","$p1 $p2 $p3")
if (p3 == 0f) {
binding.refresh.isEnabled=true
} else {
binding.refresh.isEnabled = false
binding.refresh.isRefreshing=false
}
}
override fun onTransitionCompleted(p0: MotionLayout?, p1: Int) {
}
})
I finally solve this by wrap the SwipeRefreshLayout out of the motionlayout, and change the state of SwipeRefreshLayout in onTransitionChange function to deal with this issue
I've been trying for a week loading items inside some horizontal recyclerviews contained in one vertical recyclerview(parent). Both need to have endless scrolling the horizontal ones to the right and the vertical while descending.
Right now the endless scrolling and the loading works. However, this is not working correctly because the horizontal ones are loading crazy data from the other children (My guess is that I'm creating the presenters inside the ViewHolder and some how data gets mixed up)
Is there any other way of doing this. That could work better and faster than my approach. Also, some this I can notice the vertical loading is not as smooth as I've seen in some other apps.
Any help would be greatly appreciated
class EventCatalogAdapter(private val presenter: EventCatalogPresenter):
RecyclerView.Adapter<EventCatalogAdapter.ViewHolder>() {
override fun getItemCount() = presenter.getCategoryEventCount()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
EventCatalogAdapter.ViewHolder(parent.inflate(R.layout.item_event_catalog), viewType)
override fun onBindViewHolder(holder: ViewHolder, position: Int) =
presenter.bind(holder, position)
override fun getItemViewType(position: Int) = presenter.getItemType(position)
class ViewHolder(itemView: View, type: Int): RecyclerView.ViewHolder(itemView),
EventCatalogItemView, EventViewList, OnItemClickListener<Event>,
EndlessScrollListener.DataLoader {
private lateinit var companyId: String
private lateinit var catId: String
private val eventPresenter = EventPresenterImpl(this)
override val titleFormat: String? get() = ""
init {
val layoutManager = HorizontalLinearLayoutManager(itemView.context,
LinearLayoutManager.HORIZONTAL, false)
itemView.rvEvents.layoutManager = layoutManager
itemView.rvEvents.addItemDecoration(HorizontalItemDecorator(itemView.context))
itemView.rvEvents.adapter = EventAdapter(eventPresenter, type, this)
itemView.rvEvents.addOnScrollListener(EndlessScrollListener(layoutManager, this))
}
override fun setTitleVisibility(b: Boolean) {
itemView.tvCategoryTitle.visibility = if(!b) View.GONE else View.VISIBLE
}
override fun setCategoryTitle(title: String) {
itemView.tvCategoryTitle.text = title
}
override fun eventsByCategory(companyId: String, categoryId: String) {
this.companyId = companyId
catId = categoryId
eventPresenter.getEventsByCategories(companyId, categoryId)
}
override fun loadMoreData(totalItems: Int) {
Log.d("LOAD", "Event presenter: $eventPresenter for category: $catId")
eventPresenter.getMoreEventsByCategories(companyId, catId, totalItems)
}
override fun setActiveEvent(event: Event?) {}
override fun showSettings() {}
override fun sendMessage(action: String, bundle: Bundle?) {}
override fun showStatus(status: Int) {}
override fun refresh() {
itemView.rvEvents.adapter.notifyDataSetChanged()
}
override fun showMessageTemplate(code: Int) {}
override fun hideMessageTemplate() {
refresh()
}
override fun onItemClick(item: Event) {
}
}
}
class EventAdapter(private val presenter: EventPresenter,
private val listener: OnItemClickListener<Event>):
RecyclerView.Adapter<EventAdapter.ViewHolderItemView>() {
private var type: Int = EVENT_STANDARD
constructor(presenter: EventPresenter, type: Int, listener: OnItemClickListener<Event>) :
this(presenter, listener) {
this.type = type
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
ViewHolderItemView(parent.inflate(type))
override fun onBindViewHolder(holder: ViewHolderItemView, position: Int) =
presenter.bind(holder, position)
override fun getItemCount() = presenter.getCount()
class ViewHolderItemView(itemView: View) : RecyclerView.ViewHolder(itemView), EventItemView {
override fun setEventDate(date: Date?) {
itemView.tvDate.text = Tools.formatDate(itemView.context, date)
}
override fun setName(name: String) {
itemView.tvName.text = name
}
override fun setScannerVisibility(scannerVisibility: Boolean) {
itemView.ibScanner.visibility = if (scannerVisibility) View.VISIBLE else View.INVISIBLE
}
override fun setEventPoster(posterUrl: String, transformation: Transformation?) {
Tools.loadImage(posterUrl, itemView.ivPoster, transformation, R.mipmap.portrait_test)
}
override fun setTotalRegistrants(totalRegistrants: Long) {
itemView.tvQtyRegs.text = totalRegistrants.toString()
}
override fun addScanAction(event: Event) {
itemView.ibScanner.setOnClickListener {
val auth = FirebaseAuth.getInstance()
val prefs = PreferenceHelper.customPrefs(itemView.context, auth.currentUser!!.uid)
prefs.edit().putString(EventInteractorImpl.FIELD_EVENT_ID, event.eventId).apply()
(itemView.context as SettingsActivity).launchScanner()
}
}
override fun addEventAction(event: Event) {
itemView.setOnClickListener {
(itemView.context as SettingsActivity).showRegistrants(event)
}
}
}
}
// Parent items:
<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="220dp">
<TextView
android:id="#+id/tvCategoryTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="#dimen/vertical_spacing"
android:paddingBottom="#dimen/vertical_spacing"
android:layout_marginEnd="#dimen/horizontal_spacing"
android:layout_marginStart="#dimen/horizontal_spacing"
android:textAlignment="center"
android:textSize="18sp"
android:textAllCaps="true"
android:fontFamily="sans-serif-light"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="#string/txt_cat_title" />
<android.support.v7.widget.RecyclerView
android:id="#+id/rvEvents"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="#dimen/vertical_spacing"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tvCategoryTitle" />
</android.support.constraint.ConstraintLayout>
// Children items
<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="110dp"
android:layout_height="match_parent"
android:stateListAnimator="#animator/tile_elevation">
<ImageView
android:id="#+id/ivPoster"
android:layout_width="0dp"
android:layout_height="0dp"
android:contentDescription="#string/txt_event_image"
android:src="#mipmap/portrait_test"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/tvName"
style="#style/SubTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textIsSelectable="false"
android:textAllCaps="true"
app:layout_constraintBottom_toTopOf="#+id/ibRegistrants"
app:layout_constraintStart_toStartOf="#+id/ibRegistrants"
tools:text="Washington D.C. " />
<TextView
android:id="#+id/tvDate"
style="#style/SubTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="false"
app:layout_constraintBottom_toTopOf="#+id/tvName"
app:layout_constraintStart_toStartOf="#+id/ibRegistrants"
tools:text="03/23" />
<TextView
android:id="#+id/tvQtyRegs"
style="#style/Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textIsSelectable="false"
app:layout_constraintBottom_toBottomOf="#+id/ibRegistrants"
app:layout_constraintStart_toEndOf="#+id/ibRegistrants"
app:layout_constraintTop_toTopOf="#+id/ibRegistrants"
tools:text="140" />
<ImageView
android:id="#+id/ibRegistrants"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginStart="8dp"
android:contentDescription="#string/txt_registrants"
android:src="#drawable/ic_registrants_48px"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageButton
android:id="#+id/ibScanner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="#string/txt_registrants"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ic_scan_action" />
</android.support.constraint.ConstraintLayout>
I went through many kinda-similar questions but none of the answers seemed to solve my problem. I implemented a custom EditText that I want to be compatible with two-way data binding. The problem is, every time I try to compile I get the error:
Error:java.lang.IllegalStateException: failed to analyze: android.databinding.tool.util.LoggedErrorException: Found data binding errors.
****/ data binding error ****msg:Cannot find the getter for attribute 'android:text' with value type java.lang.String on com.app.toolkit.presentation.view.CustomEditText. file:/Users/humble-student/Home/workspace/android/application/app/src/main/res/layout/login_view.xml loc:68:8 - 81:69 ****\ data binding error ****
at org.jetbrains.kotlin.analyzer.AnalysisResult.throwIfError(AnalysisResult.kt:57)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules(KotlinToJVMBytecodeCompiler.kt:137)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:158)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:61)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.java:107)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.java:51)
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:92)
at org.jetbrains.kotlin.daemon.CompileServiceImpl$compile$1$2.invoke(CompileServiceImpl.kt:386)
at org.jetbrains.kotlin.daemon.CompileServiceImpl$compile$1$2.invoke(CompileServiceImpl.kt:96)
at org.jetbrains.kotlin.daemon.CompileServiceImpl$doCompile$$inlined$ifAlive$lambda$2.invoke(CompileServiceImpl.kt:892)
at org.jetbrains.kotlin.daemon.CompileServiceImpl$doCompile$$inlined$ifAlive$lambda$2.invoke(CompileServiceImpl.kt:96)
at org.jetbrains.kotlin.daemon.common.DummyProfiler.withMeasure(PerfUtils.kt:137)
at org.jetbrains.kotlin.daemon.CompileServiceImpl.checkedCompile(CompileServiceImpl.kt:919)
at
Here is my implementation:
CustomEditText
class CustomEditText #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
// ...
private lateinit var editText_input: EditText
private lateinit var textView_errorMessage: TextView
private var isErrorDisplayed = false
private var inputTextOriginalColor: ColorStateList? = null
init {
orientation = VERTICAL
clearContainerFormatting()
createEditTextInput(context, attrs, defStyleAttr)
createTextViewErrorMessage(context)
addView(editText_input)
addView(textView_errorMessage)
}
fun setError(message: String) {
//...
}
fun getText(): String = editText_input.text.toString()
fun setText(text: String) = editText_input.setText(text)
// ...
}
Model
data class SampleData(
private var _content: String
) : BaseObservable() {
var content: String
#Bindable get() = _content
set(value) {
_content = value
notifyPropertyChanged(BR.content)
}
}
Client that uses the CustomView with data binding
<?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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="data"
type="SampleData" />
<variable
name="presenter"
type="SamplePresenter" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
tools:context=".sample_view.presentation.view.SampleView">
<NotificationPopup
android:id="#+id/notificationPopup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:elevation="4dp"
app:allowManualExit="true" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="#+id/textView_mirror"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:text="#{data.content}"
android:textSize="16sp"
android:textStyle="bold"
tools:text="test" />
<CustomEditText
android:id="#+id/customEditText_sample"
style="#style/RegisterInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Type anything"
android:text="#={data.content}" />
<Button
android:id="#+id/button_validateInput"
style="#style/Widget.AppCompat.Button.Colored"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:onClick='#{(v) -> presenter.onValidateDataClick(customEditTextSample.getText())}'
android:text="Validate Input" />
</LinearLayout>
</RelativeLayout>
</layout>
P.S.: If I replace CustomEditText for regular EditText widget, it works perfectly
Funny but I was able to find a great post on medium that helped me with this issue. Basically what I needed was a CustomEditTextBinder:
#InverseBindingMethods(
InverseBindingMethod(
type = CustomEditText::class,
attribute = "android:text",
method = "getText"
)
)
class CustomEditTextBinder {
companion object {
#JvmStatic
#BindingAdapter(value = ["android:textAttrChanged"])
fun setListener(editText: CustomEditText, listener: InverseBindingListener?) {
if (listener != null) {
editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
}
override fun afterTextChanged(editable: Editable) {
listener.onChange()
}
})
}
}
#JvmStatic
#BindingAdapter("android:text")
fun setText(editText: CustomEditText, text: String?) {
text?.let {
if (it != editText.text) {
editText.text = it
}
}
}
It might seem weird but you don't actually need to call it anywhere, just add the class and the framework will take care of finding it through the annotation processing. Note that the setText is really really important in order to prevent infinite loops. I also added:
var text: String?
get() = editText_input.text.toString()
set(value) {
editText_input.setText(value)
}
fun addTextChangedListener(listener: TextWatcher) =
editText_input.addTextChangedListener(listener)
on CustomEditText.
Here is an example of the implementation