【问题标题】:My RecyclerView isn't notified appropriately我的 RecyclerView 没有得到适当的通知
【发布时间】:2017-09-27 01:30:17
【问题描述】:

我有一个 RecyclerView 活动和一个 ViewModel 类。 Activity 调用 ViewModel 中的一个方法,该方法使用一个 Web 服务,并且它还观察一个 LiveData 字段。每次该方法从 Web 服务返回一个项目时,它都会将其设置为 LiveData,因此 Activity 中的观察者会收到通知,因此所有项目都会进入 RecyclerView。正如我在日志和 UI 中看到的那样,可以保证此流程正常工作。

当我在使用 Web 服务的方法中使用 Thread.slepp(500) 延迟时出现问题。

不是在 RecyclerView 中放入一个项目,然后等待 500 毫秒再放入另一个,而是等待 500 毫秒 * numberOfItems,然后将它们全部绘制在一起。

我可以保证 ViewModel 和 LiveData 设置没有问题,因为日志按预期工作,它打印所创建项目的标题,等待 500 毫秒,然后打印下一个。所以问题只在于适配器,以及方法调用完成后如何通知它。

我的问题是如何在每次调用观察者时通知适配器?

这是我对三个类的实现:

食谱列表

class RecipeList : LifecycleActivity() {

var recipeList: MutableList<Recipe> = mutableListOf()
var adapter: RecipeAdapter? = null
var viewModel: RecipeViewModel? = null

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_recipe_list)

    val ingredients = intent.getStringExtra("ingredients")
    val term = intent.getStringExtra("term")


    viewModel = ViewModelProviders.of(this).get(RecipeViewModel::class.java)


    val url = "http://www.recipepuppy.com/api/?i=${ingredients}onions,garlic&q=${term}"


    val layoutManager = LinearLayoutManager(this)
    adapter = RecipeAdapter(this, recipeList)

    rec_recycler_id.layoutManager = layoutManager
    rec_recycler_id.adapter = adapter



    subscribe()


    viewModel?.getRecipe(url)


}

fun subscribe() {

    val observer = Observer<Recipe> { recipe ->


        if (recipe != null) {

            Log.d("mike", "subscribe ${recipe?.title} ")


            recipeList.add(recipe)
            adapter?.notifyDataSetChanged()


        }

    }

    viewModel?.mRecipe?.observe(this, observer)

  }


}

RecipeViewModel

class RecipeViewModel(application: Application): AndroidViewModel(application) {

var recipes: MutableLiveData<MutableList<Recipe>>? =  MutableLiveData<MutableList<Recipe>>()
var mRecipe: MutableLiveData<Recipe> = MutableLiveData()





fun getRecipe(url:String){

    val requestQueue = Volley.newRequestQueue(this.getApplication())


    val recipeRequest = JsonObjectRequest(Request.Method.GET,url,
            Response.Listener {
                response: JSONObject ->
                try {

                    val results = response.getJSONArray("results")

                    for( i in 0..results.length()-1){
                        var recipeObj = results.getJSONObject(i)

                        var title = recipeObj.getString("title")
                        var link = recipeObj.getString("href")
                        var thumbnail = recipeObj.getString("thumbnail")
                        var ingredients = recipeObj.getString("ingredients")

                        var recipe = Recipe(title,ingredients,thumbnail,link)


                        mRecipe.value = recipe
                        Log.d("mike",title)

                        Thread.sleep(200)

                    }


                }catch (e: JSONException){
                    e.printStackTrace()
                }
            },
            Response.ErrorListener {
                error: VolleyError? ->
                try{
                    Log.d("error",error.toString())

                }catch (e: JSONException){
                    e.printStackTrace()
                }
            })


    requestQueue?.add(recipeRequest)

}

RecipeAdapter

class RecipeAdapter(val context: Context, var recipes: MutableList<Recipe>) : RecyclerView.Adapter<RecipeAdapter.ViewHolder>() {

override fun getItemCount(): Int = recipes.size


override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder {

    val view = LayoutInflater.from(context).inflate(R.layout.recipe_rec_row, null)

    return ViewHolder(view)


}

override fun onBindViewHolder(holder: ViewHolder?, position: Int) {
    holder?.bindViews(recipes[position])
}


inner class ViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView) {

    fun bindViews(recipe: Recipe) {

        itemView.textView7.text = recipe.title
        itemView.textView9.text = recipe.ingredients
        itemView.button6.setOnClickListener() {

            if(!recipe.link.trim().isEmpty())
                context.startActivity<ShowLinkAct>("url" to recipe.link)
            else
                context.toast("No link available")

        }

        if (!recipe.thumbnail.isEmpty()) {

            Picasso.with(context)
                    .load(recipe.thumbnail)
                    .placeholder(android.R.drawable.ic_menu_report_image)
                    .error(android.R.drawable.ic_menu_report_image)
                    .into(itemView.imageView)

        } else {
            Picasso.with(context).load(android.R.drawable.ic_menu_report_image).into(itemView.imageView)
        }


    }

}
}

期待您的建议,提前谢谢您

【问题讨论】:

    标签: android kotlin android-recyclerview


    【解决方案1】:

    我建议您将适配器的数据存储在适配器中。如果您使用 AAC,您还应该查看 GithubBrowser 的示例。这是一个小(未经测试)样本。

    警告:您不应该在 RecyclerView 中使用上下文操作,因为您可能会发生泄漏。

    BaseAdapter(所有适配器都扩展了这个具有 DiffUtil 的适配器)

    abstract class DataBoundListAdapter<T, V : ViewDataBinding> : RecyclerView.Adapter<DataBoundViewHolder<V>>() {
    
        val log = AnkoLogger(javaClass.simpleName)
    
        private var items: List<T>? = null
    
        private var dataVersion = 0
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DataBoundViewHolder<V> {
            val binding = createBinding(parent)
            return DataBoundViewHolder(binding)
        }
    
        protected abstract fun createBinding(parent: ViewGroup): V
    
        override fun onBindViewHolder(holder: DataBoundViewHolder<V>, position: Int) {
            bind(holder.binding, items!![position])
            holder.binding.executePendingBindings()
        }
    
    
        @SuppressLint("StaticFieldLeak")
        @MainThread
        fun replace(update: List<T>?) {
            dataVersion++
            if (items == null) {
                if (update == null) {
                    return
                }
                items = update
                notifyDataSetChanged()
            } else if (update == null) {
                val oldSize = items!!.size
                items = null
                notifyItemRangeRemoved(0, oldSize)
            } else {
                val startVersion = dataVersion
                val oldItems = items
                object : AsyncTask<Void, Void, DiffUtil.DiffResult>() {
                    override fun doInBackground(vararg voids: Void): DiffUtil.DiffResult {
                        return DiffUtil.calculateDiff(object : DiffUtil.Callback() {
                            override fun getOldListSize(): Int {
                                return oldItems!!.size
                            }
    
                            override fun getNewListSize(): Int {
                                return update.size
                            }
    
                            override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                                val oldItem = oldItems!![oldItemPosition]
                                val newItem = update[newItemPosition]
                                return this@DataBoundListAdapter.areItemsTheSame(oldItem, newItem)
                            }
    
                            override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                                val oldItem = oldItems!![oldItemPosition]
                                val newItem = update[newItemPosition]
                                return this@DataBoundListAdapter.areContentsTheSame(oldItem, newItem)
                            }
                        })
                    }
    
                    override fun onPostExecute(diffResult: DiffUtil.DiffResult) {
                        if (startVersion != dataVersion) {
                            // ignore update
                            return
                        }
                        items = update
                        diffResult.dispatchUpdatesTo(this@DataBoundListAdapter)
    
                    }
                }.execute()
            }
        }
    
        protected abstract fun bind(binding: V, item: T)
    
        protected abstract fun areItemsTheSame(oldItem: T, newItem: T): Boolean
    
        protected abstract fun areContentsTheSame(oldItem: T, newItem: T): Boolean
    
        override fun getItemCount(): Int {
            return if (items == null) 0 else items!!.size
        }
    
    
    }
    

    这是一个示例适配器。如果您使用 AAC,您可能需要使用数据绑定。我建议在你的情况下!注意上下文操作不应该在 RecyclerView 中,因为你可能会遇到泄漏。

    class RecipeAdapter(private val dataBindingComponent: DataBindingComponent,
                        private val yourVm: ViewModel, private val context: Context) : DataBoundListAdapter<Recipe, RecipeRecRowBinding>() {
    
    
       override fun createBinding(parent: ViewGroup): RecipeRecRowBinding {
            val binding = DataBindingUtil.inflate<RecipeRecRowBinding>(LayoutInflater.from(parent.context), R.layout.recipe_rec_row, parent, false, dataBindingComponent)
            return binding
        }
    
        override fun bind(binding: RecipeRecRowBinding, recipe: Recipe) {
            binding.model = recipe
            binding.viewModel = yourVm
    
    
            binding.itemView.textView7.text = recipe.title
            binding.itemView.textView9.text = recipe.ingredients
            binding.itemView.button6.setOnClickListener() {
    
                if(!recipe.link.trim().isEmpty())
    //ohoh, you shouldnt call something on your activity within your adapter 
                    context.startActivity<ShowLinkAct>("url" to recipe.link)
                else
    //ohoh, you shouldnt call something on your activity within your adapter 
                    context.toast("No link available")
    
            }
    
            if (!recipe.thumbnail.isEmpty()) {
    
                Picasso.with(context)
                        .load(recipe.thumbnail)
                        .placeholder(android.R.drawable.ic_menu_report_image)
                        .error(android.R.drawable.ic_menu_report_image)
                        .into(itemView.imageView)
    
            } else {
                Picasso.with(context).load(android.R.drawable.ic_menu_report_image).into(itemView.imageView)
            }
    
        }
    
    
        override fun areItemsTheSame(oldItem: Recipe, newItem: Recipe) = oldItem.id == newItem.id
    
    
        override fun areContentsTheSame(oldItem: Recipe, newItem: Recipe) = oldItem.equals(newItem)
    
    
    }
    

    最后,您的订阅者将数据推送到您的适配器并处理更改 (DiffUtil)

    fun subscribe() {
        val observer = Observer<Recipe> { 
    
            if (it!= null) {
    // it cant be null since you validate it here
                Log.d("mike", "subscribe ${it.title} ")
                adapter.replace(it)
    
            }
        }
    
        viewModel?.mRecipe?.observe(this, observer)
      }
    

    【讨论】:

      猜你喜欢
      • 2011-12-27
      • 1970-01-01
      • 2013-11-08
      • 1970-01-01
      • 2011-03-21
      • 1970-01-01
      • 2014-11-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多