我的RecyclerView没有得到适当的通知

时间:2017-09-26 12:17:59

标签: android android-recyclerview kotlin recycler-adapter

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

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

不是在RecyclerView中放置一个项目,而是等待500millis再放另一个,它等待500 millis * numberOfItems,然后将它们全部拉到一起。

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

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

这是我对三个类的实现:

RecipeList

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)
        }


    }

}
}

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

1 个答案:

答案 0 :(得分: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)
  }