How to Update item values without changing the whole List?

Viewed 1377

I'm working on a Grid RecyclerView for Scores App. Scores will be updated every 5 secs from API Call...

I'm using this library for my Recyclerview Adapter.

I'm using Observable Fields & List.

I need to update only the scores (textviews) but now the RecyclerView is getting updated every 5 secs.

Please Guide me in right direction. Thank you!

Full Code in Gist

class DashboardFragment : Fragment() {

private lateinit var dashboardViewModel: DashboardViewModel
private lateinit var binding: FragmentDashboardBinding

lateinit var retrofit: Retrofit
lateinit var apiService: APIService
lateinit var disposable: Disposable

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    dashboardViewModel = ViewModelProvider(this).get(DashboardViewModel::class.java)
    fetchData()
}

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {

    binding = DataBindingUtil
        .inflate(inflater, R.layout.fragment_dashboard, container, false)
    binding.viewModel = dashboardViewModel

    return binding.root
}

fun fetchData() {
    val interceptor = HttpLoggingInterceptor()
    interceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
    val client = OkHttpClient.Builder()
        .addInterceptor(interceptor)
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .writeTimeout(30, TimeUnit.SECONDS)
        .build()

    val gson = GsonBuilder()
        .setLenient()
        .create()

    retrofit = Retrofit.Builder()
        .baseUrl(APIService.BASE_URL)
        .client(client)
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build()

    apiService = this.retrofit.create(APIService::class.java)

    callIndicesEndpoint(null)

    disposable = Observable.interval(1000, 2000, TimeUnit.MILLISECONDS)
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe({ aLong: Long? -> this.refreshIndices(aLong)})
        { throwable: Throwable -> this.onError(throwable) }
}

@SuppressLint("CheckResult")
private fun callIndicesEndpoint(aLong: Long?) {
    val observable =
        apiService.indices
    observable.subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread())
        .map { result: ObservableArrayList<Indices> -> result }
        .subscribe(
            { data: ObservableArrayList<Indices> ->
                this.handleResults(data)
            }
        ) { t: Throwable ->
            this.handleError(t)
        }
}

@SuppressLint("CheckResult")
private fun refreshIndices(aLong: Long?) {
    val observable =
        apiService.indices
    observable.subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread())
        .map { result: ObservableArrayList<Indices> -> result }
        .subscribe({ data: ObservableArrayList<Indices> -> this.refreshResults(data)})
        { t: Throwable ->this.handleError(t)}
}

private fun handleResults(data: ObservableArrayList<Indices>) {
    dashboardViewModel.populate(data)
}


private fun refreshResults(data: ObservableArrayList<Indices>) {
    dashboardViewModel.refresh(data)
}

private fun onError(throwable: Throwable) {
    Log.e(">>>", "ERROR")
}

private fun handleError(t: Throwable) {
    Log.e("> >", t.localizedMessage + " - Err - " + t.cause)
    //Add your error here.
}

override fun onPause() {
    super.onPause()
    disposable.dispose()
}
 }

VIEWMODEL

class DashboardViewModel : ViewModel() {

val indices: ObservableList<Indices> = ObservableArrayList<Indices>()

fun populate(data: ArrayList<Indices>) {
    indices.clear()
    indices.addAll(data)
}

fun refresh(data: ArrayList<Indices>) {
    // How to refresh only Items without recreating the List
}

val itemIds: ItemIds<Any> =
    ItemIds { position, item -> position.toLong() }

val indicesItem: ItemBinding<Indices> =
    ItemBinding.of<Indices>(BR.item, R.layout.item_futures)

 }
4 Answers

You can use broadcast receiver functionality for update particular items from recyclerview .

When I had something like that I used the DiffUtil.

I had to update rates every X seconds and not change the whole list.

So the adapter should be something like this:

class RatesAdapter :
ListAdapter<Rate, RecyclerView.ViewHolder>(RateDiffCallback()) {
private val baseRateView = 0
private val rateView = 1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    return if (viewType == rateView) {
        RateHolder(
            RateItemBinding.inflate(
                LayoutInflater.from(parent.context), parent, false
            )
        )
    } else {
        BaseRateHolder(
            BaseRateLayoutBinding.inflate(
                LayoutInflater.from(parent.context), parent, false
            )
        )
    }
}


override fun getItemViewType(position: Int): Int {
    return if (position == 0) {
        baseRateView
    } else rateView
}


override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    val rate = getItem(position)
    if (holder.itemViewType == rateView) {
        (holder as RateHolder).bind(rate)
        holder.itemView.setOnClickListener {
            swapper.itemSwap(rate)
        }
    } else {
        (holder as BaseRateHolder).bind(rate)
    }
}

class RateHolder(
    private val binding: RateItemBinding
) : RecyclerView.ViewHolder(binding.root) {
    fun bind(item: Rate) {
        binding.apply {
            rate = item
            executePendingBindings()
        }
    }


}

class BaseRateHolder(
    private val binding: BaseRateLayoutBinding
) : RecyclerView.ViewHolder(binding.root) {
    fun bind(item: Rate) {
        binding.apply {
            rate = item
            executePendingBindings()
        }
    }

    val value = binding.value
}
}

private class RateDiffCallback : DiffUtil.ItemCallback<Rate>() {


override fun areItemsTheSame(oldItem: Rate, newItem: Rate): Boolean {
    return oldItem.currencyCode == newItem.currencyCode
}

override fun areContentsTheSame(oldItem: Rate, newItem: Rate): Boolean {
    return oldItem.rate == newItem.rate
}

}

Here I used binding in the adapter, and if you are not familiar with it, I will be more than happy to explain as well

In the Activity: Before the onCreate:

private val ratesAdapter = RatesAdapter() 

In the onCreate:

ratesList.adapter = ratesAdapter

Whenever you need to update the adapter including the first time you need to call it:

ratesAdapter.submitList(rates)

Rate is the model class that I am using rates is the mutable list of < Rate > ratesList is the recyclerview

Related