Мой RecyclerView не будет уведомлен соответствующим образом

У меня есть активность RecyclerView и класс ViewModel . Эта активность вызывает метод в ViewModel, который использует веб-сервис, а также наблюдает за полем LiveData. Каждый раз, когда метод возвращает элемент из веб-сервиса, он устанавливает его в LiveData, и поэтому наблюдатель в Activity уведомляется, и поэтому все элементы входят в RecyclerView. Гарантируется, что этот поток работает правильно, как я вижу это в журналах и в пользовательском интерфейсе.

Проблема возникает, когда я добавляю задержку с Thread.slepp (500) в методе, в котором потребляется веб-сервис.

Вместо того, чтобы поместить элемент в RecyclerView, подождите 500 миллионов, а затем поставьте еще один, он ждет 500 миллисекунд * числоOfItems, а затем рисует их все вместе.

Я могу заверить, что нет никаких проблем с установкой 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) } } } } 

Я с нетерпением жду ваших предложений, спасибо заранее

Я рекомендую вам хранить данные для вашего адаптера внутри адаптера. Если вы используете 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) }