15

I have an RecyclerView which holds some CardViews and each CardView contains EditText which multiplies the user given amount times a specific rate (the rate comes from an endpoint and the rate is different per row). For the CardViews I am using data binding.

Use case of the app:

The app should show how much for example 2, 7.89, 14.34, or 110 € are in other currencies.

  1. User enters an amount (in the EditText) in any line, each line has a "rate" (rate comes from an API endpoint) field with a different value
  2. The user entered amount gets multiplied by the "rate"
  3. Each row in the RecyclerView should be updated

Now is the question how to update the text of all EditTexts elements in a RecyclerView with two-Way data binding

This is my data class for data binding:

data class CurrencyItem(
    var flag: String,
    var shortName: String,
    var fullName: String,
    var rate: Double
) : BaseObservable() {

    @Bindable
    var rateTimesAmount: String = (CurrencyApplication.userEnteredAmount * rate).toString()
        set(amount) {
            val amountAsDouble = amount.toDouble()
            val number2digits: Double = String.format("%.2f", amountAsDouble).toDouble()
            CurrencyApplication.userEnteredAmount = number2digits
            field = number2digits.toString()
            notifyPropertyChanged(BR.rateTimesAmount)
        }

}

This is my EditText in the item_currency.xml

<EditText
                android:id="@+id/currency_rate"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginEnd="16dp"
                android:imeOptions="actionDone"
                android:inputType="numberDecimal"
                android:lines="1"
                android:maxLength="8"
                android:text="@={currency.rateTimesAmount}"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                tools:text="1183.068" />

This is my Application class which stores the user entered amount:

class CurrencyApplication : Application() {

    companion object {
        var userEnteredAmount: Double = 1.00
    }

}

Here I access the RecyclerView through Kotlin Android Extensions:

recycler_view.apply {
            setHasFixedSize(true)
            itemAnimator = DefaultItemAnimator()
            adapter = currencyAdapter
        }

Here is the RecyclerView from activity_main.xml

<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_margin="8dp"
        android:clipToPadding="false"
        android:paddingBottom="8dp"
        android:scrollbars="vertical"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

Here is the Adapter for the RecyclerView:

class CurrencyAdapter(
    val currencies: ArrayList<CurrencyItem>
) : RecyclerView.Adapter<CurrencyViewHolder>() {
    
    override fun getItemCount() = currencies.size

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CurrencyViewHolder {
        val itemCurrencyBinding: ItemCurrencyBinding = DataBindingUtil.inflate(
            LayoutInflater.from(parent.context),
            R.layout.item_currency,
            parent,
            false
        )
        return CurrencyViewHolder(itemCurrencyBinding)
    }

    override fun onBindViewHolder(holder: CurrencyViewHolder, position: Int) {
        holder.itemCurrencyBinding.currency = currencies[position]
    }

    fun setUpCurrencies(newCurrencies: List<CurrencyItem>) {
        currencies.clear()
        currencies.addAll(newCurrencies)
        notifyDataSetChanged()
    }
}

class CurrencyViewHolder(val itemCurrencyBinding: ItemCurrencyBinding) :
    RecyclerView.ViewHolder(itemCurrencyBinding.root)
Fahri Can
  • 353
  • 2
  • 10
  • Did you find any solution? How to update the UI once input is taken? I am having same issue, LiveData observer is not getting called. Can we trigger notify of LiveData change, without introducing new mutable with onTextChange? Please share your findings. – Anshul Kabra Sep 08 '20 at 13:53
  • No, unfortunately I did not found any proper solution for that problem – Fahri Can Sep 08 '20 at 19:28

1 Answers1

1

You might probably want to create another Observable in -say- your viewModel or adapter and bind it as a variable to the adapter items.

Like so:

class CurrencyAdapter: RecyclerView.Adapter<...>() {

    val rate = ObservableField(0.0)

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = DataBindingUtil.inflate<ViewDataBinding>(inflater, viewType, parent, false)
        binding.setVariable(BR.rate, rate)
        return YourViewHolder(binding.root)
    }

}

If you encounter issues with other views eagerly updating the rate variable, try to making a custom data binding adapter to only allow views triggering the updates when they are in focus.

@BindingAdapter("android:textAttrChanged")
fun TextView.setOnTextAttrChangedInFocus(listener: InverseBindingListener) {
    addTextChangedListener(afterTextChanged = {
        if(isFocused) {
            listener.onChange()
        }
    })
}

(example uses androidx.core extension)

I hope it helps ;)


Check out teanity it might help you figure out some stuff like this faster.

diareuse
  • 26
  • 5
  • Unfortunately does not work. The second parameter of DataBindingUtil.inflate() needs to be an Int. Where I get this Int from? – Fahri Can Jul 13 '20 at 19:06
  • If you were to follow lint instructions you'd be able to solve that problem :) either ways it's my bad misleading you. look again at the line with inflation of the binding. I added inflater as the first parameter. – diareuse Jul 13 '20 at 19:20
  • The app crash on the line: val binding = DataBindingUtil.inflate(inflater, viewType, parent, false) the message is Resources$NotFoundException: Resource ID #0x0 – Fahri Can Jul 13 '20 at 20:39
  • Swap `viewType`, in my example, with `R.layout.item_currency` – diareuse Jul 14 '20 at 12:53
  • Okay, but how would my method onBindViewHolder(holder: CurrencyViewHolder, position: Int) look like? When I have to use holder.itemView this causes again new problems – Fahri Can Jul 14 '20 at 20:34