Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to update the text of all EditTexts elements in a RecyclerView with two-way data binding

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)
like image 772
Fahri Can Avatar asked Jul 11 '20 23:07

Fahri Can


1 Answers

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.

like image 120
diareuse Avatar answered Oct 07 '22 15:10

diareuse