Элементы RecyclerView не отображаются, пока я не прокручу их

Я использую Recyclerview внутри Fragment в соответствии с примером Google MVP Android архитектуры, и я попытался сделать часть View максимально пассивной, следуя этой статье. , что делает всю Recyclerview Adapter пассивной моделью данных, и презентатор обрабатывает ее.

Вот мой код Фрагмента:

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() {

    }

}

А это мой код RecyclerView адаптеров

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

        }
    }


}

А вот код презентатора, который обрабатывает данные Adapter и привязывается к 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) {


    }


}

Вот 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>

и вот XML-файл элемента RecyclerView 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>

Проблема в том, что когда я запускаю Activity, показывающий Fragment, RecyclerView не показывает элемент. Он появляется только в том случае, если я прокрутил пустое RecyclerView или оставил приложение на переднем плане и снова вернулся к нему.

При инициализации данные Adapter пусты, но onResume() я делаю запрос, который обновляет данные в Presenter, затем я notifyDataChange() на Adapter, но ничего не обновляется.

При отладке я обнаружил, что onBindViewHolder() не вызывается после notifyDataChange() в адаптере, поэтому я не знаю, почему notifyDataChange() не уведомляет Adapter об изменении данных.

У кого-нибудь есть идея или какое-либо решение, которое может решить эту проблему?


person Ahmed Elshaer    schedule 09.10.2017    source источник
comment
Вы нашли решение?   -  person CiDsTaR    schedule 09.03.2018
comment
да, это была проблема, что код пользовательского интерфейса не работает в потоке пользовательского интерфейса, и я исправил это, вызвав runOnUiThread и уведомив свой адаптер внутри него.   -  person Ahmed Elshaer    schedule 25.03.2018


Ответы (3)


Вам нужно использовать runOnUiThread.

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

       }
 }
person Martin Malmgren    schedule 23.01.2019
comment
я уже сам обнаружил, что я не в основной теме, большое спасибо, это правильный ответ - person Ahmed Elshaer; 03.02.2019

взгляните на этот ответ

Как бы глупо это ни звучало, вызов этой строки кода после установки данных для recyclerView помог мне решить эту проблему:

recyclerView.smoothScrollToPosition(0)

PS: технологии, которые я использовал, которые могут иметь какое-то отношение к этому, были: RJava, Retrofit2, NavigationUI, Fragments, LiveData и Databinding.

EDIT: я следил за комментарием @SudoPlz и ответил на другой вопрос, и это тоже сработало, вы необходимо расширить RecyclerView и переопределить 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());
        });
    }
}

пока еще, я бы предпочел это исправить через 4, 5 лет, тем не менее, это был хороший обходной путь, и вы не забудете о них, на ваш взгляд.

person Amin Keshavarzian    schedule 24.08.2019
comment
спасибо, но проблема была решена выбранным ответом, поскольку я пытался обновить recyclerview из фонового потока. - person Ahmed Elshaer; 24.08.2019
comment
Да, но поскольку эта строка сохранена, я написал, если кто-то еще столкнулся с той же проблемой :) - person Amin Keshavarzian; 24.08.2019

Я заметил в вашем элементе xml, что ваша высота limitedLayout равна match_parent, верно? Я рекомендую вам использовать его как wrap_content.

person Fredy Mederos    schedule 09.10.2017
comment
Попробуйте использовать фиксированный размер в корневом элементе элемента. например 100dp или 200dp, чтобы увидеть, сохраняется ли проблема - person Fredy Mederos; 09.10.2017