Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RecyclerView Items doesn't appear until i scroll it

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?

like image 362
Ahmed Elshaer Avatar asked Oct 09 '17 12:10

Ahmed Elshaer


Video Answer


3 Answers

You need to use runOnUiThread.

if(activity != null) {
       activity!!.runOnUiThread {
            root.Recycleview.adapter = Adapter(Array)
            Adapter(Array).notifyDataSetChanged()

       }
 }
like image 195
Martin Malmgren Avatar answered Nov 14 '22 23:11

Martin Malmgren


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.

like image 20
Amin Keshavarzian Avatar answered Nov 14 '22 21:11

Amin Keshavarzian


I noticed in your item xml your constraintLayout height is match_parent right? I recommend you to use it as wrap_content

like image 39
Fredy Mederos Avatar answered Nov 14 '22 21:11

Fredy Mederos