I am trying to populate two or more recycler views on one activity, the process works fine with one view, but the moment I add another one then it crashes.
Can someone see if I'm doing something wrong? I know how I'm doing it at the moment isn't the best practice or efficient.
My code from the activity
private var shouldInitRecyclerView = true
private lateinit var testSection: Section
private lateinit var assSection: Section
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_edit_mod)
FirestoreUtil.addModInfoListener("test","xbIpNiDXGsIPmb5sTwbH", this, this::updateRecyclerViewTest)
FirestoreUtil.addModInfoListener("assignment","xbIpNiDXGsIPmb5sTwbH", this, this::updateRecyclerViewAss)
}
private fun updateRecyclerViewTest(items: List<Item>) {
fun init() {
recycler_view_test.apply {
layoutManager = LinearLayoutManager(this#EditModActivity)
adapter = GroupAdapter<ViewHolder>().apply {
testSection = Section(items)
add(testSection)
setOnItemClickListener(onItemClick)
}
}
shouldInitRecyclerView = false
}
fun updateItems() = testSection.update(items)
if (shouldInitRecyclerView)
init()
else
updateItems()
}
private fun updateRecyclerViewAss(items: List<Item>) {
fun init() {
recycler_view_ass.apply {
layoutManager = LinearLayoutManager(this#EditModActivity)
adapter = GroupAdapter<ViewHolder>().apply {
assSection = Section(items)
add(assSection)
setOnItemClickListener(onItemClick)
}
}
shouldInitRecyclerView = false
}
fun updateItems() = assSection.update(items)
if (shouldInitRecyclerView)
init()
else
updateItems()
}
and the layout:
<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="match_parent"
tools:context=".EditModActivity">
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Groups"
android:layout_marginLeft="8dp"
android:textStyle="bold"
android:textSize="20sp"/>
<android.support.v7.widget.RecyclerView
android:layout_marginTop="8dp"
android:layout_below="#id/textView"
android:id="#+id/recycler_view_test"
android:layout_marginLeft="8dp"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<android.support.v7.widget.RecyclerView
android:layout_marginTop="8dp"
android:layout_below="#id/recycler_view_test"
android:id="#+id/recycler_view_ass"
android:layout_marginLeft="8dp"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
Related
I hope is all well with you.
I have constructed my code and build according the MVP style as well pulling JSON data but when I run the the code the horizontal recycle view and the JSON data is not being displayed. I tried going through every line of code and watching other tutorials but still no results.
Here is below my main activity:
class ProductCategoryActivity : BaseMvpActivity<ProductCategoryActivityView, ProductCategoryActivityPresnter> (),
ProductCategoryActivityView, CategoryAdapter.onItemClickListener{
private lateinit var binding: FragmentProductCategoryBinding
val data :MutableList<CateogryResponse> = ArrayList()
val adapter= CategoryAdapter(data, this)
#Inject
lateinit var presenter: ProductCategoryActivityPresnter
#Inject
lateinit var progressDialog: ProgressDialog
override fun onCreateComponent() {
userComponent.plus(CategoryActivityModule(this)).inject(this)
}
override fun providePresenter(): ProductCategoryActivityPresnter {
return presenter
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = FragmentProductCategoryBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
presenter.fetchcatogries()
showdata(ArrayList())
}
override fun showError(message: String) {
}
override fun showProgress() {
}
override fun hideProgress() {
}
override fun showdata(data: ArrayList<CateogryResponse>) {
val layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
recyclerViewPrimary.layoutManager = layoutManager
recyclerViewPrimary.setHasFixedSize(true)
recyclerViewPrimary.adapter = adapter
for(i in data)
data.add(CateogryResponse("product"))
}
override fun onItemClick(position: Int) {
Toast.makeText(this, "Item $position clicked", Toast.LENGTH_SHORT).show()
val clickedItem = data[position]
adapter.notifyItemChanged(position)
}
}
Here is my presenter:
#ActivityScope
class ProductCategoryActivityPresnter #Inject constructor(
private val stringProvider: StringProvider,
#AndroidScheduler private val observeOnScheduler: Scheduler,
#IOScheduler private val subscribeOnScheduler: Scheduler,
private val getCategoryUseCase: CategoryUseCase
) : BasePresenter<ProductCategoryActivityView>() {
lateinit var catogriesservice: CategoryRepositoryImpl
val catogriesLoadError = MutableLiveData<Boolean>()
val loading = MutableLiveData<Boolean>()
var catogries: ArrayList<CateogryResponse> = arrayListOf()
override fun onCreatePresenter(savedInstanceState: Bundle?) {
}
override fun onSaveInstanceState(outState: Bundle?) {
}
override fun onLoadData(arguments: Bundle?) {
fetchcatogries()
}
fun fetchcatogries() {
getCategoryUseCase.execute()
.observeOn(observeOnScheduler)
.subscribeOn(subscribeOnScheduler)
.subscribe(
SingleRequestSubscriber(
{
it
if (it != null && it.size > 0) {
catogries.removeAll(catogries)
catogries.addAll(it)
}
},
onFailure = { appException ->
view?.showError(
ErrorHandler.getErrorMessage(
appException,
stringProvider
)
)
},
onApiError = { apiException ->
view?.showError(
ErrorHandler.getErrorMessage(
apiException,
stringProvider
)
)
},
onAuthenticationError = { requestxception ->
view?.showError(
ErrorHandler.getErrorMessage(
requestxception,
stringProvider
)
)
},
onShowProgress = {
if (it) {
view?.showProgress()
} else {
view?.hideProgress()
}
},
onSubscribed = {
disposable.add(it)
})
)
}
}
Here is my view:
interface ProductCategoryActivityView {
fun showError(message: String)
fun showProgress()
fun hideProgress()
fun showdata ( data: ArrayList<CateogryResponse>)
}
Here is my adapter:
class CategoryAdapter(
private val data: List<CateogryResponse>,
private val listener: onItemClickListener
) : RecyclerView.Adapter<CategoryAdapter.ViewHolder>() {
private val items: MutableList<CardView>
init {
this.items = ArrayList()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context)
.inflate(R.layout.fragment_category_adapter, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.tvTitle.text = data[position].product
items.add(holder.card)
}
override fun getItemCount(): Int {
return data.size
}
inner class ViewHolder(itemView: View
) : RecyclerView.ViewHolder(itemView),
View.OnClickListener {
val tvTitle: TextView = itemView.featured_title
val card: CardView = itemView.CardView
init {
itemView.setOnClickListener(this)
}
override fun onClick(v: View?) {
val position: Int = adapterPosition
if (position != RecyclerView.NO_POSITION) {
listener.onItemClick(position)
}
}
}
interface onItemClickListener {
fun onItemClick(position: Int)
}
}
Image:
Fragment Category Adapter:
android:id="#+id/CardView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:cardCornerRadius="2dp"
app:cardElevation="8dp">
<!-- We Will Add here the card & ImageViews -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="15dp">
<ImageView
android:id="#+id/featured_image"
android:layout_width="match_parent"
android:layout_height="140dp"
android:scaleType="centerCrop" />
<TextView
android:id="#+id/featured_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lineHeight="23dp"
android:text="Chairs"
android:textColor="#color/colorAccent"
android:textSize="20sp" />
<TextView
android:id="#+id/featured_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="asbkd asudhlasn saudnas jasdjasl hisajdl asjdlnas" />
</LinearLayout>
</androidx.cardview.widget.CardView>
Fragment Product Category:
<?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="#fafcfe"
tools:context="ui.category.ProductCategoryActivity">
<ImageView
android:id="#+id/imageView3"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
/>
<ImageView
android:id="#+id/imageView"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraintLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="48dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="#+id/imageView"
app:layout_constraintTop_toTopOf="parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerViewPrimary"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="48dp"
android:layout_marginTop="20dp"
android:text="#string/topselling"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/constraintLayout" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraintLayout2"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="32dp"
android:layout_marginLeft="32dp"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toTopOf="#+id/imageView3"
app:layout_constraintEnd_toEndOf="#+id/constraintLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textView">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerViewSecondary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
android:id="#+id/imageView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
app:layout_constraintBottom_toBottomOf="#+id/imageView3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
/>
<TextView
android:id="#+id/textViewCategories"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="48dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="15dp"
android:text="#string/catogries"
android:textSize="18sp"
app:layout_constraintBottom_toTopOf="#+id/constraintLayout"
app:layout_constraintStart_toStartOf="parent"
tools:layout_editor_absoluteX="39dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
Not sure what went wrong.
If someone can point it out or show me what needs to be done to be able to display the data in horzintal recycle view, I would be really thanlful
You should update your recycler view adapter once your fetch call is executed, not before it ends.
Modify your fetchcatogries declaration so it accepts a callback method, which will be executed after the data are loaded.
This method will accept a list object as a parameter, which you will manage in your ProductCategoryActivity to populate the RecyclerView.
fun fetchcatogries(callback: (MutableList<CateogryResponse>) -> Unit) {
getCategoryUseCase.execute()
.observeOn(observeOnScheduler)
.subscribeOn(subscribeOnScheduler)
.subscribe(
SingleRequestSubscriber(
{ categories ->
callback(categories)
},
...
)
}
You can now edit your onCreate and showdata methods in ProductCategoryActivity like this:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = FragmentProductCategoryBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
presenter.fetchcatogries { categories ->
showdata(categories as ArrayList<CateogryResponse>)
}
}
override fun showdata(data: ArrayList<CateogryResponse>) {
val layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
recyclerViewPrimary.layoutManager = layoutManager
recyclerViewPrimary.setHasFixedSize(true)
recyclerViewPrimary.adapter = CategoryAdapter(data.toList(), this)
}
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 want to make UI like this:
as you can see, there is a vertical recycler view there. that recycler view is inside scroll view. so I want to make the screen can be scrolled down as much as item data in the recycler view. here is the xml I use:
<ScrollView
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="#android:color/white"
android:id="#+id/scrollView_user_control"
tools:context=".Fragments.UserControl.UserControlFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/constraintLayout_profile">
<de.hdodenhof.circleimageview.CircleImageView
android:id="#+id/imageView_profile_image_user_control"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="#drawable/user"
app:civ_border_width="0.5dp"
app:civ_border_color="#FF000000"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginStart="8dp"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="32dp"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:text="User Fullname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/textView_user_fullname_user_control"
android:layout_marginTop="24dp"
app:layout_constraintTop_toBottomOf="#+id/imageView_profile_image_user_control"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="8dp"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="8dp" android:textSize="18sp"/>
<TextView
android:text="user#email.com"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/textView_user_email_user_control"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="8dp"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="8dp"
app:layout_constraintTop_toBottomOf="#+id/textView_user_fullname_user_control"
android:layout_marginTop="8dp"/>
<TextView
android:text="AKAN DIHADIRI"
android:layout_width="0dp"
android:layout_height="35dp"
android:id="#+id/textView_will_come_user_control"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="32dp"
app:layout_constraintTop_toBottomOf="#+id/textView_user_email_user_control"
android:gravity="center|left"
android:paddingStart="16dp" android:background="#D2D1D6"/>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textView_will_come_user_control"
tools:listitem="#layout/item_general_event"
android:id="#+id/recyclerView_attended_event_user_control"/>
<ImageView
android:src="#drawable/ic_settings"
android:layout_width="30dp"
android:layout_height="30dp"
android:id="#+id/imageView_gear_setting_user_control"
app:layout_constraintTop_toTopOf="#+id/imageView_profile_image_user_control"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="24dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
if I fetch 20 data from server and set it those 20 items to the recycler view, it will make my app slow, I mean the fragment will appear around 2 seconds after fragment transaction.
If I limit the data to be only one data that fetched from server that need to be shown to recycler view. it will make my app faster.
so it clears that, the more items in the recycler view inside scroll view, the more slower it takes to show the fragment.
here is my code :
class UserControlFragment : Fragment() {
lateinit var progressBar : ProgressBar
lateinit var gearSettingImageView: ImageView
lateinit var emailTextView: TextView
lateinit var userFullNameTextView :TextView
lateinit var profilePictureImageView : ImageView
lateinit var fragmentView : View
lateinit var recyclerView : RecyclerView
lateinit var sharedPrefManager : SharedPreferenceManager
lateinit var eventAdapter : GeneralEventRecyclerViewAdapter
private var attendedEvents = ArrayList<Event>()
private var userData : User? = null
lateinit var mContext : Context
lateinit var mActivity : FragmentActivity
override fun onAttach(context: Context) {
super.onAttach(context)
mContext = context
activity?.let { mActivity = it }
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragmentView = inflater.inflate(R.layout.fragment_user_control, container, false)
sharedPrefManager = SharedPreferenceManager.getInstance(mContext)
setUpViewsDeclaration()
getUserData()
updateUI()
initRecyclerView()
return fragmentView
}
private fun setUpViewsDeclaration() {
progressBar = mActivity.progressBar_main_activity
gearSettingImageView = fragmentView.findViewById(R.id.imageView_gear_setting_user_control)
emailTextView = fragmentView.findViewById(R.id.textView_user_email_user_control)
profilePictureImageView = fragmentView.findViewById(R.id.imageView_profile_image_user_control)
userFullNameTextView = fragmentView.findViewById(R.id.textView_user_fullname_user_control)
recyclerView = fragmentView.findViewById(R.id.recyclerView_attended_event_user_control)
}
private fun getUserData() {
userData = sharedPrefManager.loadUserData()
if (userData == null) {
performLogOut()
return
}
getAttendedEvents()
}
private fun updateUI() {
userData?.let {
userFullNameTextView.text = it.fullname
emailTextView.text = it.email
Picasso.get()
.load(it.profilePicturePath)
.into(profilePictureImageView)
}
}
private fun initRecyclerView() {
eventAdapter = GeneralEventRecyclerViewAdapter(mContext)
val layoutManager = LinearLayoutManager(mContext, RecyclerView.VERTICAL,false)
recyclerView.adapter = eventAdapter
recyclerView.layoutManager = layoutManager
recyclerView.isNestedScrollingEnabled = false
eventAdapter.setOnItemClickListener(object: OnEventKMListener {
override fun eventKMClicked(position: Int) {
val selectedEvent = attendedEvents[position]
val eventDetailDestination = UserControlFragmentDirections.actionGlobalDestinationEventDetail(selectedEvent)
Navigation.findNavController(fragmentView).navigate(eventDetailDestination)
}
})
}
private fun getAttendedEvents() {
showLoadingState(true)
FirestoreKMClient.getRecommendedEvents(userData!!.domicile) { errorMessage, events ->
errorMessage?.let {
activity?.toast(it)
} ?: run {
val eventList = events ?: ArrayList()
attendedEvents = eventList
eventAdapter.submitList(eventList)
showLoadingState(false)
}
}
}
private fun showLoadingState(enabled: Boolean) {
if (enabled) {
progressBar.visibility = View.VISIBLE
recyclerView.visibility = View.GONE
} else {
progressBar.visibility = View.GONE
recyclerView.visibility = View.VISIBLE
}
}
}
I have tried to move the background processing from onCreateView to onResume but it doesn't make any effect.
so how to solve this ? Java is OK
So I am following for my project this clean architecure in kotlin (https://github.com/android10/Android-CleanArchitecture-Kotlin) and I want to add a progress bar before recycler view loads the data and make it dissapear after the data loads. In this example the progress bar is in a separate toolbar but in my case i want to be in center of the recycler view.
This is my layout:
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/list_wrapper_layout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="8dp"
android:background="#drawable/layout_background"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/movieList_recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toTopOf="#+id/bottom_navigation_bar"
app:layout_constraintEnd_toEndOf="#+id/list_horizontal_guideline_end"
app:layout_constraintStart_toStartOf="#+id/list_horizontal_guideline_start"
app:layout_constraintTop_toBottomOf="#+id/searchView" />
<ProgressBar
android:id="#+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminateTint="#color/black"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
And in my class:
class MoviesFragment : BaseFragment() {
#Inject lateinit var navigator: Navigator
#Inject lateinit var moviesAdapter: MoviesAdapter
private lateinit var moviesViewModel: MoviesViewModel
override fun layoutId() = R.layout.fragment_movies
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
appComponent.inject(this)
moviesViewModel = viewModel(viewModelFactory) {
observe(movies, ::renderMoviesList)
failure(failure, ::handleFailure)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initializeView()
loadMoviesList()
}
private fun initializeView() {
movieList_recyclerView.layoutManager = StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL)
movieList_recyclerView.adapter = moviesAdapter
}
private fun loadMoviesList() {
progressBar.visible()
movieList_recyclerView.invisibile()
moviesViewModel.loadMovies()
}
private fun renderMoviesList(movies: List<MovieView>?) {
moviesAdapter.collection = movies.orEmpty()
progressBar.invisible()
movieList_recyclerView.visible()
}
private fun handleFailure(failure: Failure?) {
when (failure) {
is NetworkConnection -> renderFailure(R.string.failure_network_connection)
is ServerError -> renderFailure(R.string.failure_server_error)
is ListNotAvailable -> renderFailure(R.string.failure_movies_list_unavailable)
}
}
private fun renderFailure(#StringRes message: Int) {
movieList.invisible()
emptyView.visible()
hideProgress()
notifyWithAction(message, R.string.action_refresh, ::loadMoviesList)
}
}
But the progress bar doesn't show up at all. What am I doing wrong?
I'm using Recyclerview inside a Fragment Following Google's sample of MVP Android architecture and I tried to make the View part passive as possible following this article , which makes the whole Recyclerview Adapter passive of the Data Models and the presenter handles it.
Here is my code of the Fragment:
class OrderHistoryFragment : Fragment(), OrderHistoryContract.View {
lateinit var mPresenter: OrderHistoryContract.Presenter
lateinit var rvOrderHistory: RecyclerView
lateinit var orderHistoryAdapter : OrderHistoryAdapter
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val root = inflater!!.inflate(R.layout.order_history_fragment, container, false)
rvOrderHistory = root.findViewById<RecyclerView>(R.id.rvOrderHistory)
rvOrderHistory.layoutManager = LinearLayoutManager(context, LinearLayout.VERTICAL, false)
orderHistoryAdapter = OrderHistoryAdapter(mPresenter, object : HistoryItemListener {
override fun onReorder(orderHistory: OrderHistory) {
}
override fun onOpenOrder(orderHistory: OrderHistory) {
val orderIntent = Intent(activity, OrderDetailActivity::class.java)
orderIntent.putExtra("orderId", orderHistory.id)
startActivity(orderIntent)
}
})
rvOrderHistory.adapter = orderHistoryAdapter
return root
}
override fun onResume() {
super.onResume()
mPresenter.start()
}
override fun setPresenter(presenter: OrderHistoryContract.Presenter) {
mPresenter = checkNotNull<OrderHistoryContract.Presenter>(presenter)
}
override fun showLoadingIndicator(load: Boolean?) {
}
override fun updateOrdersAdapter() {
orderHistoryAdapter.notifyDataSetChanged()
}
override fun showSnackBar(Message: String) {
val parentLayout = activity.findViewById<View>(android.R.id.content)
val snackBar = Snackbar
.make(parentLayout, Message, Snackbar.LENGTH_INDEFINITE)
snackBar.setAction("Dismiss") { snackBar.dismiss() }
snackBar.setActionTextColor(Color.RED)
snackBar.show()
}
interface HistoryItemListener {
fun onReorder(orderHistory: OrderHistory)
fun onOpenOrder(orderHistory: OrderHistory)
}
companion object {
fun newInstance(): OrderHistoryFragment {
return OrderHistoryFragment()
}
}
fun OrderHistoryFragment() {
}
}
And this is my RecyclerView Adapters code
class OrderHistoryAdapter(internal var orderHistoryPresenter: OrderHistoryContract.Presenter, private val listener: OrderHistoryFragment.HistoryItemListener) : RecyclerView.Adapter<OrderHistoryAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.order_history_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
orderHistoryPresenter.onBindOrdersRow(position, holder)
holder.bReOrder!!.setOnClickListener { v -> listener.onReorder(orderHistoryPresenter.getOrderHistoryItem(position)) }
holder.cvOrderItem!!.setOnClickListener { v -> listener.onOpenOrder(orderHistoryPresenter.getOrderHistoryItem(position)) }
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getItemCount(): Int {
return orderHistoryPresenter.getOrdersCount()
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), OrderHistoryContract.orderRowView {
internal var ivOrderVendor: ImageView? = null
internal var tvOrderId: TextView? = null
internal var tvOrderItems: TextView? = null
internal var tvOrderDate: TextView? = null
internal var tvOrderPrice: TextView? = null
internal var bReOrder: Button? = null
internal var cvOrderItem: CardView? = null
init {
ivOrderVendor = itemView.findViewById<ImageView>(R.id.ivOrderVendor)
tvOrderId = itemView.findViewById<TextView>(R.id.tvOrderId)
tvOrderItems = itemView.findViewById<TextView>(R.id.tvOrderItems)
tvOrderDate = itemView.findViewById<TextView>(R.id.tvOrderDate)
tvOrderPrice = itemView.findViewById<TextView>(R.id.tvOrderPrice)
bReOrder = itemView.findViewById<Button>(R.id.bReOrder)
cvOrderItem = itemView.findViewById<CardView>(R.id.cvOrderItem)
}
override fun setOrderImage(url: String) {
Glide.with(itemView.context).load(url).into(ivOrderVendor!!)
}
override fun setOrderDate(orderDate: String) {
tvOrderDate!!.text = orderDate
}
override fun setOrderId(orderId: String) {
tvOrderId!!.text = orderId
}
override fun setOrderItems(orderItems: ArrayList<String>) {
val stringBuilder = StringBuilder()
for (item in orderItems) {
stringBuilder.append(item)
}
tvOrderItems!!.text = stringBuilder.toString()
}
override fun setOrderPrice(orderPrice: String) {
tvOrderPrice!!.text = R.string.price.toString() + " " + orderPrice + " " + R.string.egp
}
}
}
And Here is code of the presenter which handles the Adapter data and it's binding to the ViewHolder
class OrderHistoryPresenter internal constructor(mDataRepository: DataRepository, mOrdeHistoryView: OrderHistoryContract.View) : OrderHistoryContract.Presenter {
private val mDataRepository: DataRepository
//refrence of the View to trigger the functions after proccessing the task
private val mOrdeHistoryView: OrderHistoryContract.View
private var orderHistoryItems = ArrayList<OrderHistory>()
init {
this.mDataRepository = checkNotNull(mDataRepository, "tasksRepository cannot be null")
this.mOrdeHistoryView = checkNotNull<OrderHistoryContract.View>(mOrdeHistoryView, "tasksView cannot be null!")
mOrdeHistoryView.setPresenter(this)
}
override fun start() {
mOrdeHistoryView.showLoadingIndicator(true)
mDataRepository.getCurrentUser(object : LocalDataSource.userRequestCallback {
override fun onUserRequestSuccess(botitUser: BotitUser) {
val urlParams = HashMap<String, String>()
urlParams.put(Endpoints.USER_ID_KEY, botitUser.userId!!)
val url = Endpoints.getUrl(Endpoints.urls.ORDER_HISTORY, urlParams)
mDataRepository.buildEndPointRequest(url, " ", Endpoints.requestsType.GET, object : EndpointDataSource.RequestCallback {
override fun onRequestSuccess(Body: String) {
try {
mOrdeHistoryView.showLoadingIndicator(false)
orderHistoryItems = JSONParser.parseData(JSONParser.parsers.ORDER_HISTORY, JSONObject(Body)) as ArrayList<OrderHistory>
mOrdeHistoryView.updateOrdersAdapter()
} catch (e: JSONException) {
e.printStackTrace()
}
}
override fun onRequestError(Body: String) {
mOrdeHistoryView.showLoadingIndicator(false)
mOrdeHistoryView.showSnackBar("Cannot load data")
}
})
}
override fun onUserRequestError(Body: String) {
}
})
}
override fun refreshData() {
}
override fun getOrdersCount(): Int {
return orderHistoryItems.size
}
override fun onBindOrdersRow(position: Int, orderViewHolder: OrderHistoryContract.orderRowView) {
if (orderHistoryItems.isNotEmpty()) {
val orderHistory = orderHistoryItems[position]
// orderViewHolder.setOrderDate(orderHistory.orderDate!!)
orderViewHolder.setOrderId(orderHistory.orderId!!)
orderViewHolder.setOrderImage(orderHistory.orderImage!!)
orderViewHolder.setOrderItems(orderHistory.orderItems)
orderViewHolder.setOrderPrice(orderHistory.orderPrice!!)
}
}
override fun getOrderHistoryItem(position: Int): OrderHistory {
return orderHistoryItems[position]
}
override fun actionReOrder(ordreId: String) {
}
}
Here is the Fragment XML
<android.support.v7.widget.RecyclerView android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/rvOrderHistory"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
</android.support.v7.widget.RecyclerView>
and Here is the RecyclerView Item XML order_history_item.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView 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="wrap_content"
android:id="#+id/cvOrderItem"
android:layout_margin="4dp"
android:orientation="vertical">
<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="match_parent"
android:orientation="vertical"
android:padding="8dp">
<ImageView
android:id="#+id/ivOrderVendor"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/mac" />
<TextView
android:id="#+id/tvOrderId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="Order #2123"
android:textAppearance="#style/TextAppearance.AppCompat.Body2"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toRightOf="#+id/ivOrderVendor"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/tvOrderItems"
android:layout_width="242dp"
android:layout_height="35dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:text="MacDonald’s: Big Mac Beef, Big Tasty Beef. El Ezaby: Signal 2, Pantene Shampoo"
android:textAppearance="#style/TextAppearance.AppCompat.Small"
android:layout_marginRight="8dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="#+id/tvOrderId"
app:layout_constraintLeft_toRightOf="#+id/ivOrderVendor"
android:layout_marginLeft="8dp"
app:layout_constraintHorizontal_bias="0.0" />
<TextView
android:id="#+id/tvOrderDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="8dp"
android:text="03:22 PM 23/2/2017"
app:layout_constraintBottom_toTopOf="#+id/tvOrderItems"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
android:layout_marginLeft="8dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintHorizontal_bias="1.0" />
<TextView
android:id="#+id/tvOrderPrice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:text="Price: 225.50 LE"
android:textAppearance="#style/TextAppearance.AppCompat.Body2"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tvOrderItems" />
<Button
android:id="#+id/bReOrder"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:layout_margin="4dp"
android:background="#drawable/chip_accent"
android:foreground="?attr/selectableItemBackground"
android:orientation="vertical"
android:padding="8dp"
android:text="Order Again"
android:textAllCaps="false"
android:textColor="#color/colorAccent"
android:textSize="15sp"
app:layout_constraintHorizontal_bias="0.937"
app:layout_constraintLeft_toRightOf="#+id/tvOrderPrice"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tvOrderItems"
tools:layout_editor_absoluteY="74dp" />
</android.support.constraint.ConstraintLayout>
</android.support.v7.widget.CardView>
The issue is that when I start the Activity showing the Fragment, the RecyclerView doesn't show the Item. It only appears if I scrolled the empty RecyclerView or by leaving the App on the foreground and go back to it again.
At the initialization the data of the Adapter is empty but onResume() I make a request which updates the data at the Presenter, Then I notifyDataChange() on the Adapter but nothing updates.
When I debugged I found that onBindViewHolder() isn't called after notifyDataChange() on the Adapter so i don't know why the notifyDataChange() doesn't notify the Adapter that the data is changed.
Anyone has an idea or any solution that might fix this issue?
You need to use runOnUiThread.
if(activity != null) {
activity!!.runOnUiThread {
root.Recycleview.adapter = Adapter(Array)
Adapter(Array).notifyDataSetChanged()
}
}
have a look at this answer
As stupid as it might sound, calling this line of code after setting data for recyclerView, helped me for this issue:
recyclerView.smoothScrollToPosition(0)
PS: technologies that I was using that may have something to do with this were: RJava, Retrofit2, NavigationUI, Fragments, LiveData, and Databinding.
EDIT:
I followed #SudoPlz comment and answer on another question and it also worked too, you have to extend RecyclerView and override requestLayout:
private boolean mRequestedLayout = false;
#SuppressLint("WrongCall")
#Override
public void requestLayout() {
super.requestLayout();
// We need to intercept this method because if we don't our children will never update
// Check https://stackoverflow.com/questions/49371866/recyclerview-wont-update-child-until-i-scroll
if (!mRequestedLayout) {
mRequestedLayout = true;
this.post(() -> {
mRequestedLayout = false;
layout(getLeft(), getTop(), getRight(), getBottom());
onLayout(false, getLeft(), getTop(), getRight(), getBottom());
});
}
}
while still, I would have preferred this to fixed after 4, 5 years, however, this was a good workaround, and you won't forget about them in your view.
I noticed in your item xml your constraintLayout height is match_parent right?
I recommend you to use it as wrap_content