I have implemented RecyclerView in my app with Kotlin using Refrofit, MVVM, DataBinding, Coroutines. The problem is i have some nested arrays which i want to parse using viewModel. Here is the structure of the api:
What i want is to parse the inner services arrays inside the categoryService array.
The xml items layout is like this:
<data>
<variable
name="services"
type="com.techease.fitness.models.categoryServices.Service" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_margin="#dimen/tenDP"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/round_card_view"
android:orientation="vertical"
android:padding="#dimen/tenDP"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="#+id/ivService"
android:layout_width="150dp"
android:layout_height="150dp"
android:scaleType="fitXY"
android:padding="#dimen/tenDP"
android:src="#drawable/test_image_24"
bind:avatar="#{services.attachment}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:hint="#string/user_name"
android:text="#{services.title}"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:hint="#string/user_name"
android:text="#{services.duration}" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Here is my existing viewModel which only returns the last item from categoryService[last] and displaying the services items.
private lateinit var job: Job
private val catServices = MutableLiveData<List<Service>>()
val discoverServices: MutableLiveData<List<Service>>
get() = catServices
fun discoverServices(context: Context) {
job = CoroutinesIO.ioThenMain(
{
repository.discoverServices(context)
}, {
for (i in 0 until it!!.data.categoryServices.size) {
for (services in 0 until it.data.categoryServices.get(i).services.size) {
catServices.value = it.data.categoryServices.get(i).services
}
}
}
)
}
i have nested for loops in viewModel which is parsing the target array correctly but only displaying the last item service array. Here is the recyclerView implementation:
val apiRepository = ApiRepository()
factory = DiscoverViewModelFactory(apiRepository)
viewModel = ViewModelProvider(this, factory).get(DiscoverServiceViewModel::class.java)
binding.viewModel = viewModel
viewModel.discoverServices(this)
viewModel.discoverServices.observe(this, Observer { services ->
rvFeatured.also {
it.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
it.setHasFixedSize(true)
if (services != null) {
it.adapter = DiscoverServicesAdapter(services, this)
}
}
})
And here is the adapter which is fully binded:
private val services: List<Service>,
private val listenerHomeServices: DiscoverServicesClickListener
) : RecyclerView.Adapter<DiscoverServicesAdapter.ServicesViewHolder>() {
override fun getItemCount() = services.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
ServicesViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.custom_discover_layout,
parent,
false
)
)
override fun onBindViewHolder(holder: ServicesViewHolder, position: Int) {
holder.recyclerViewServicesBinding.services = services[position]
}
class ServicesViewHolder(
val recyclerViewServicesBinding: CustomDiscoverLayoutBinding
) : RecyclerView.ViewHolder(recyclerViewServicesBinding.root)}
Again i want to parse the nested service array, but currently only the last categoryService1 service array items are displayed by recyclerView. Hope this make sense.
Related
I am trying to fetch the data from Google Spreadsheet and display it in a recyclerView in Kotlin. I could do that without any error but the issue I am facing is when I scroll up or down the data in the recyclerView get disappeared. When I scroll up and then scroll down I can see that all the data that went up is missing and the same with scrolling down. But if I scroll up for more I can see one line of data after every few scrolls.
Another issue I have is with the date that is being displayed. My data in the Google Spreadsheet starts from 01-Jan-2023 (this is how it's shown in the spreadsheet, and without time in it), when it's shown in the recyclerView, all dates are one day earlier. I mean, it shows 31-Dec-2022 for 01-Jan-2023, 01-Jan-2023 for 02-Jan-2023 and so on.
Can somebody help correct my mistakes and improve my code? I have been after this for a couple of days and I couldn't fix the issue.
My code is,
SalesData.kt
class SalesData : AppCompatActivity() {
private lateinit var binding: ActivitySalesDataBinding
#SuppressLint("NotifyDataSetChanged")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySalesDataBinding.inflate(layoutInflater)
setContentView(binding.root)
val salesList = arrayListOf<SalesDataModel>()
val queue = Volley.newRequestQueue(this)
val url = "https://script.google.com/macros/s/AKfsdaffdbghjhfWVM2FeIH3gZY5kAnb6JVeWpg2XeBOZyU6sghhfkuytytg/exec"
val jsonObjectRequest = object: JsonObjectRequest(
Request.Method.GET,url,null,
Response.Listener {
val data = it.getJSONArray("items")
for(i in 0 until data.length()){
val salesJasonObject = data.getJSONObject(i)
val dt = salesJasonObject.getString("Date")
val dateFmt = SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(dt)
val formattedDatesString = dateFmt?.let { it1 -> SimpleDateFormat("dd-MMM-yyyy", Locale.US).format(it1) }
val salesObject = formattedDatesString?.let { it1 ->
SalesDataModel(
// salesJasonObject.getString("Date"),
it1,
salesJasonObject.getString("Branch"),
salesJasonObject.getDouble("NetSale"),
salesJasonObject.getDouble("Profit"),
)
}
if (salesObject != null) {
salesList.add(salesObject)
}
val adapter = SalesDataRecyclerAdapter(this#SalesData,salesList)
binding.rvSalesData.adapter = adapter
binding.rvSalesData.layoutManager = LinearLayoutManager(this#SalesData)
binding.rvSalesData.setHasFixedSize(true)
adapter.notifyDataSetChanged()
}
Toast.makeText(this#SalesData, "Data loaded successfully", Toast.LENGTH_LONG).show()
},Response.ErrorListener {
Toast.makeText(this#SalesData, it.toString(), Toast.LENGTH_LONG).show()
}
){
override fun getHeaders(): MutableMap<String, String> {
return super.getHeaders()
}
}
Toast.makeText(this#SalesData, "Hi", Toast.LENGTH_LONG).show()
queue.add(jsonObjectRequest)
}
}
SalesDataRecyclerAdapter.kt
class SalesDataRecyclerAdapter(
val context: Context,
private val saleDataList:ArrayList<SalesDataModel>
):RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return MyViewHolder(
SalesDataLayoutBinding.inflate(
LayoutInflater.from(parent.context),
parent, false
)
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val model = saleDataList[position]
if (holder is MyViewHolder){
holder.binding.tvSales.text = model.salesAmount.toString()
holder.binding.tvBranch.text = model.branch
holder.binding.tvDate.text = model.date
holder.binding.tvProfit.text = model.profit.toString()
}
}
override fun getItemCount(): Int {
return saleDataList.size
}
class MyViewHolder(val binding: SalesDataLayoutBinding) : RecyclerView.ViewHolder(binding.root)
}
activity_sales_data.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:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white"
tools:context=".SalesData">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<!--
<androidx.core.widget.ContentLoadingProgressBar
android:id="#+id/progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>-->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/new_green"
android:padding="10dp"
android:text="SALES DATA"
android:textColor="#color/white"
android:textSize="24sp"
android:layout_gravity="bottom|end"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvSalesData"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
sales_data_layout.xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="#+id/tvDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="Date"
android:layout_weight="1"/>
<TextView
android:id="#+id/tvBranch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="Branch"
android:layout_weight="1"/>
<TextView
android:id="#+id/tvSales"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="Sales"
android:layout_weight="1"/>
<TextView
android:id="#+id/tvProfit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="Profit"
android:layout_weight="1"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Since your root view in sales_data_layout.xml has the size:
android:layout_width="match_parent"
android:layout_height="match_parent"
Each item will take up the whole parent area, which in your case will end up with each individual item taking up the whole screen, and thus needing multiple scrolls to see the next item. You probably want to change the height to wrap_content for the root view, to see more items on the screen at once.
Add a comparator that tells the recylcer view exactly when to redraw.
class WordsComparator : DiffUtil.ItemCallback<Word>() {
override fun areItemsTheSame(oldItem: Word, newItem: Word): Boolean {
//=== here doesn't work for complex objects
// simple high-speed code goes here it is called over and over
// my app the same item has the same id easy compare
return (oldItem._id == newItem._id)
}
override fun areContentsTheSame(oldItem: Word, newItem: Word): Boolean { // you developer have to compare the contents of complex objects
// you need high speed code here for best results
// if possible don't call any functions that could do other
// unecessary things.
// compare the contents of the complex items.
return (oldItem._id == newItem._id
&& oldItem.checked == newItem.checked
&& oldItem.word == newItem.word
&& oldItem.color == newItem.color
&& oldItem.recolor == newItem.recolor
&& oldItem.rechecked == newItem.rechecked)
}
}
I'm using MVVC pattern and I'm populating a recyclerView with data from database using Room. At the Logcat, data return correctly and is looped correctly, but recyclerview display seven elements and in the eighth starts to overwrite it with the nineth and tenth elements e after that create 2 more elements with the first 2 elements from list.
I'm coudn't find what is wrong with my code.
So, I'm asking for some help.
AvaliacaoFragment.kt:
class AvaliacaoFragment : Fragment() {
private lateinit var ctx: Context
private var _binding: FragmentAvaliacaoBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
private lateinit var textoSemSecoes: TextView
private lateinit var nomeAvaliacao: TextView
private lateinit var dataAvaliacao: TextView
private val args by navArgs<AvaliacaoFragmentArgs>()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
_binding = FragmentAvaliacaoBinding.inflate(inflater, container, false)
return binding.root
// return inflater.inflate(R.layout.fragment_avaliacoes, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
ctx = view.context
// dialogNovaAvaliacao = MaterialAlertDialogBuilder(ctx, android.R.style.Theme_DeviceDefault_Light_NoActionBar_Fullscreen)
// dialogNovaAvaliacao = MaterialAlertDialogBuilder(ctx,R.style.AlertDialogTheme)
// val builder = MaterialDatePicker.Builder.datePicker()
textoSemSecoes = view.findViewById(R.id.texto_sem_secoes)
nomeAvaliacao = view.findViewById(R.id.nome_avaliacao)
dataAvaliacao = view.findViewById(R.id.data_avaliacao)
nomeAvaliacao.text = args.currentAvaliacao.nome
dataAvaliacao.text = args.currentAvaliacao.dataCriacao
/*
btnAddAvaliacao = view.findViewById(R.id.btn_add_avaliacao)
btnAddAvaliacao.setOnClickListener {
findNavController().navigate(R.id.action_navigation_avaliacoes_to_addAvaliacaoFragment)
}
*/
// Recycler
val recyclerAdapter = SecaoAdapter()
val recyclerView = binding.secaoRecyclerView
recyclerView.adapter = recyclerAdapter
recyclerView.layoutManager = LinearLayoutManager(requireContext())
// ViewModelFactory para passar argumentos para a ViewModel
val factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return SecaoViewModel(Application(), args.currentAvaliacao.id) as T
}
}
// ViewModel
mSecaoViewModel = ViewModelProvider(this, factory).get(SecaoViewModel::class.java)
mSecaoViewModel.readAllData.observe(viewLifecycleOwner, Observer { secaoList ->
if(secaoList.isNotEmpty()){
Log.d(TAG, "secaoList: ${secaoList.toString()}")
Log.d(TAG, "secaoList.size: ${secaoList.size}")
textoSemSecoes.visibility = View.GONE
recyclerView.visibility = View.VISIBLE
recyclerAdapter.setData(secaoList.sortedBy { it.codigo.toInt() })
} else {
textoSemSecoes.visibility = View.VISIBLE
recyclerView.visibility = View.GONE
}
})
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object {
private val TAG: String = AvaliacaoFragment::class.java.name
lateinit var mSecaoViewModel: SecaoViewModel
private lateinit var btnAddSecao: FloatingActionButton
private lateinit var dialogNovaSecao: MaterialAlertDialogBuilder
}
}
SecaoAdapter.kt:
class SecaoAdapter: RecyclerView.Adapter<SecaoAdapter.SecaoViewHolder>() {
private val TAG: String = SecaoAdapter::class.java.name
private var secaoList = emptyList<Secao>()
private lateinit var binding: ItemSecaoBinding
class SecaoViewHolder(itemBinding: ItemSecaoBinding): RecyclerView.ViewHolder(itemBinding.root) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SecaoViewHolder {
binding = ItemSecaoBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return SecaoViewHolder(binding)
}
override fun getItemCount(): Int {
return secaoList.size
}
override fun onBindViewHolder(holder: SecaoViewHolder, position: Int) {
Log.d(TAG, "position: $position")
val currentItem = secaoList[position]
Log.d(TAG, "currentItem: ${currentItem.toString()}")
binding.secaoCodigo.text = currentItem.codigo
binding.secaoNome.text = currentItem.nome
binding.secaoMediaTotal.text = currentItem.mediaPositivo.toString()
binding.secaoPerguntasNaoAplicaveis.text = currentItem.qdePerguntasNaoAplicaveis.toString()
binding.secaoPerguntasRespondidas.text = currentItem.qdePerguntasRespondidas.toString()
binding.secaoPerguntasTotais.text = currentItem.qdePerguntas.toString()
binding.cardSecao.setOnClickListener {
val action = AvaliacaoFragmentDirections.actionAvaliacaoFragmentToSecaoFragment(currentItem)
holder.itemView.findNavController().navigate(action)
}
}
fun setData(secao: List<Secao>){
this.secaoList = secao
notifyDataSetChanged()
}
}
fragment_avaliacao.xml:
<?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"
android:background="#color/colorPrimaryDark"
tools:context=".ui.secoes.AvaliacaoFragment">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="#+id/container_titulo_avaliacao"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
>
<TextView
android:id="#+id/nome_avaliacao"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="#string/padrao_avaliacao_sem_nome"
android:textSize="24sp"
android:textColor="#color/colorIcons"
android:textAlignment="center"
/>
<TextView
android:id="#+id/data_avaliacao"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="#string/padrao_formato_data_hora"
android:textSize="14sp"
android:textColor="#color/colorPrimaryLight"
android:textAlignment="center"
/>
</LinearLayout>
<TextView
android:id="#+id/texto_sem_secoes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="#+id/container_titulo_avaliacao"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:textAlignment="center"
android:textSize="16sp"
android:text="#string/nenhuma_secao_criada"
android:textColor="#color/colorPrimaryLight"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/secaoRecyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="#+id/container_titulo_avaliacao"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout></androidx.constraintlayout.widget.ConstraintLayout>
Logcat loop from secaoList at the SecaoAdapter (is looping correctly to all elements):
I create a gif to show what is happening with the elements on recyclerView:
Here you can see that it displays seventh element then eighth is replaced by nineth and subsequently replaced by tenth. And final two elements (that should be ninth and tenth) is constructed with first and second list elements.
Thanks for the help in advance.
So the mistake here is that you are referencing only one binding in your adapter which is getting overwritten. Every time you call onCreateViewHolder you are changing the binding reference. The reason this looks okay to start with is that the onCreateViewHolder calls are followed by the onBindViewHolder calls for items visible on the screen. However as you scroll, just onBindViewHolder is called in order to rebind the recycled views.
What you should be doing is using your ViewHolder to store the individual bindings and then obtaining a reference in onBindViewHolder with something like holder.binding.
I would recommend you have a read into the view holder pattern and how to implement it!
I know there are few questions about this problem. But none of them didn't solve my problem especially my code is in Kotlin and new working with Fragments. Don't rush to say my question is duplicated.
My problem is exactly what title said, my RecyclerView is populated just with one item(child) from Firebase in my Fragment.
Adapter:
class NewsList(private val userList: List<News>) : RecyclerView.Adapter<NewsList.ViewHolder>() {
private val Context = this
override fun onBindViewHolder(p0: ViewHolder?, p1: Int) {
val news: News = userList[p1]
p0?.mesajTextView?.text = news.text
val time = news.time
val getTimeAgo = GetTimeAgo()
val lastMsg = getTimeAgo.getTimeAgo(time, Context)
p0?.timeNewsTextView!!.text = lastMsg
}
override fun onCreateViewHolder(p0: ViewGroup?, p1: Int): ViewHolder {
val v = LayoutInflater.from(p0?.context).inflate(R.layout.news_layout, p0, false)
return ViewHolder(v)
}
override fun getItemCount(): Int {
return userList.size
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val mesajTextView = itemView.findViewById(R.id.mesajTextView) as TextView
val timeNewsTextView = itemView.findViewById(R.id.timeNewsTextView) as TextView
}
}
My fragment where ReyclerView is populated:
override fun onActivityCreated(savedInstanceState: Bundle?) {
newsRecyclerView.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
}
private fun populalteQuestionsList() {
val mChatDatabaseReference = FirebaseDatabase.getInstance().reference.child(Constants.NEWS)
mListenerPopulateList = mChatDatabaseReference.addValueEventListener(object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
for (convSnapshot in dataSnapshot.children) {
val news = ArrayList<News>()
val conv = convSnapshot.getValue(News::class.java)
news.add(conv!!)
val adapter = NewsList(news)
newsRecyclerView!!.adapter = adapter
adapter.notifyDataSetChanged()
}
}
override fun onCancelled(databaseError: DatabaseError) {
}
})
mChatDatabaseReference.addListenerForSingleValueEvent(mListenerPopulateList)
}
Layout for items:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="#+id/card_view"
android:layout_gravity="center"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
card_view:cardCornerRadius="2dp"
card_view:contentPadding="10dp"
card_view:cardElevation="10dp">
<RelativeLayout
android:id="#+id/relativeLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="10dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="10dp">
<TextView
android:id="#+id/mesajTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginStart="64dp"
android:textAppearance="?android:attr/textAppearanceLarge"
tools:text="Mesaj" />
<TextView
android:id="#+id/timeNewsTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginEnd="16dp"
android:layout_marginTop="4dp"
android:maxLength="15"
android:maxLines="1"
android:textAppearance="?android:attr/textAppearanceSmall"
tools:text="14:20" />
</RelativeLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
Hope you understand, please help me I really need to finish this app.
You are creating new list with having single element in loop and passing it to adapter so it has only one element to show so
Move this outside loop
val adapter = NewsList(news)
newsRecyclerView!!.adapter = adapter
adapter.notifyDataSetChanged()
and initialise list outside for loop
val news = ArrayList<News>()
for (convSnapshot in dataSnapshot.children) {
val conv = convSnapshot.getValue(News::class.java)
news.add(conv!!)
}
val adapter = NewsList(news)
newsRecyclerView!!.adapter = adapter
adapter.notifyDataSetChanged()
Note : fill_parent has been deprecated so us match_parent
You're recreating the dataset at every iteration, so it will always have the last added item, move the instantiation of the datasource to outside the loop. Also, try not adding the values for the adapter at every iteration. When you're done adding items to the datasource, then you should add them to the adapter and set the adapter in the recyclerview. :
val news = ArrayList<News>()
for (convSnapshot in dataSnapshot.children) {
val conv = convSnapshot.getValue(News::class.java)
news.add(conv!!)
}
val adapter = NewsList(news)
newsRecyclerView!!.adapter = adapter
adapter.notifyDataSetChanged()
I'm currently trying to create a dynamic header with a recyclerView. I have written the ListAdapter aswell as the ViewHolder. The custom list elements are added, and also the numer of the elements within the list is correct, but somehow it's not showing the object data, but only the dummyText that was added at the layoutdesign.
HeaderlistAdapter:
class HeaderListAdapter(val context: Context, val headers: List<CustomHeader>) : RecyclerView.Adapter<HeaderViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): HeaderViewHolder {
val view = LayoutInflater.from(context).inflate(R.layout.ui_basic_custom_list_element_header, parent, false)
return HeaderViewHolder(view)
}
override fun getItemCount(): Int {
return headers.size
}
override fun onBindViewHolder(holder: HeaderViewHolder?, position: Int) {
holder?.bindHeader(headers[position])
}
fun setFocus(step:UIStep)
{
for(header in headers)
header.Active=header.MainContent==step
notifyDataSetChanged()
}
}
HeaderViewHolder:
class HeaderViewHolder:RecyclerView.ViewHolder{
#Bind(R.id.ui_adapter_main) var mainText:TextView?=null
#Bind(R.id.ui_adapter_additional) var additionalText:TextView?=null
#Bind(R.id.ui_adapter_layout) var layout:LinearLayout?=null
constructor(itemView: View): super(itemView){
ButterKnife.bind(this,itemView)
}
fun bindHeader(header:CustomHeader){
if(header.Active) {
mainText?.text = header.MainContent.description
additionalText?.text=header.AdditionalText
layout?.setBackgroundColor(R.color.colorBackgroundActive.toInt())
}
else{
mainText?.text=header.MainContent.number.toString()
additionalText?.text=""
layout?.setBackgroundColor(R.color.colorBackgroundInactive.toInt())
}
}
}
Here is, how the listAdapter looks within the view
<android.support.v7.widget.RecyclerView
android:id="#+id/ui_basic_lv_header"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:background="#color/colorBackgroundInactive"
android:layout_weight="1" />
Below here you se the xml of the custom element
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
android:background="#color/colorBackgroundInactive"
android:textColor="#color/colorHeaderFont"
android:id="#+id/ui_adapter_layout">
<TextView
android:id="#+id/ui_adapter_main"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="Main Info"
android:textSize="36sp"/>
<TextView
android:id="#+id/ui_adapter_additional"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="additional"
android:gravity="center_vertical"/>
Looks like there was something wrong with the
#Bind
I replaced that by using findViewById within the binHeader function:
fun bindHeader(header:CustomHeader){
val mainText = itemView.findViewById<TextView>(R.id.ui_adapter_main)
val additionalText = itemView.findViewById<TextView>(R.id.ui_adapter_additional)
val layout = itemView.findViewById<LinearLayout>(R.id.ui_adapter_layout)
if(header.Active) {
mainText?.text = header.MainContent.description
additionalText?.text=header.AdditionalText
layout?.setBackgroundColor(R.color.colorBackgroundActive.toInt())
}
else{
mainText?.text=header.MainContent.number.toString()
additionalText?.text=""
layout?.setBackgroundColor(R.color.colorBackgroundInactive.toInt())
}
}
contentList may be null ,if contentList is Null, the method following '?' will not execute . and after setting the adapter can not call notifyDataSetChanged ;
You should set layout manager for recyclerview
myRecyclerView.setLayoutManager(linearLayoutManagerVertical);
I have a recyclerView with a list of items that I want to scroll to when the certain event happens. I am using smoothScrollToPosition but to my surprise, it is not only not smooth at all, but also I get a flicker effect like it has to restore a base position before actually scrolling. The effect can be seen here:
http://i.imgur.com/rrpLr7N.mp4
Is this normal behavior?
Adapter code:
#ActivityScope
class HighlightListAdapter #Inject constructor() : RecyclerView.Adapter<HighlightListAdapter.ViewHolder>() {
private var highlights: List<Highlight> = emptyList()
private var itemClick: ((Highlight, Int) -> Unit)? = null
private var selectedRow: MutableList<Int> = mutableListOf()
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val binding = holder.binding
val highlight = highlights[position]
var viewModel = binding.viewModel
viewModel?.unbind()
viewModel = HighlightViewModel(highlight)
binding.viewModel = viewModel
viewModel.bind()
if(selectedRow.contains(position)) {
binding.rootItemView.alpha = 1.0f
}
else {
binding.rootItemView.alpha = 0.5f
}
holder.setClickListener(itemClick)
}
override fun getItemCount(): Int = highlights.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = DataBindingUtil.inflate<ItemHighlightListBinding>(
LayoutInflater.from(parent.context),
R.layout.item_highlight_list,
parent,
false
)
return ViewHolder(binding)
}
fun updateEvents(highlights: List<Highlight>) {
this.highlights = highlights
notifyDataSetChanged()
}
fun setClickListener(itemClick: ((Highlight, Int) -> Unit)?) {
this.itemClick = itemClick
}
fun enableRow(index: Int) {
//Clear previous selection (only if we want single selection)
selectedRow.clear()
//Select specified row
selectedRow.add(index)
//Let the adapter redraw
notifyDataSetChanged()
}
class ViewHolder(val binding: ItemHighlightListBinding) : RecyclerView.ViewHolder(binding.root) {
fun setClickListener(callback: ((Highlight, Int) -> Unit)?) {
binding.viewModel.clicks().subscribe {
callback?.invoke(binding.viewModel.highlight, adapterPosition)
}
}
}
fun getSelected() = selectedRow
}
layout file:
<data>
<variable
name="viewModel"
type="tv.mycujoo.mycujooplayer.ui.video.highlights.HighlightViewModel" />
</data>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/root_item_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:alpha="0.5"
android:orientation="horizontal"
android:clickable="true"
android:onClick="#{() -> viewModel.onClick()}"
android:background="#color/dark_black"
android:padding="#dimen/single_padding"
>
<TextView
android:layout_width="48dp"
android:layout_height="wrap_content"
android:textStyle="bold"
android:gravity="right"
android:text="#{viewModel.time}"
android:textColor="#color/light_gray"
android:singleLine="true"
android:ellipsize="marquee"
android:layout_marginRight="#dimen/single_padding"
style="#style/Base.TextAppearance.AppCompat.Title"
tools:text="122''"/>
<ImageView
android:background="#drawable/bg_circle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="#dimen/single_padding"
android:padding="#dimen/single_padding"
tools:src="#mipmap/ic_launcher_round"
app:imageResource="#{viewModel.image}"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#color/light_gray"
tools:text="#{viewModel.name}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#color/light_gray"
tools:text="#{viewModel.team}"/>
</LinearLayout>
<ImageView
android:visibility="gone"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="#drawable/tv_avatar_default"
/>
</LinearLayout>
</layout>
I think the problem lies in enable row notifyDataSetChanged().It refreshes the entire dataset which results in flicking of the list.Try this
fun enableRow(index: Int) {
//Clear previous selection (only if we want single selection)
selectedRow.clear()
//Select specified row
selectedRow.add(index)
//Let the adapter redraw
notifyItemRangeChanged(index,highlights.size())
}