RecyclerView во фрагменте не заполняется

#kotlin #android-fragments #android-recyclerview

Вопрос:

Так что я боролся с этим в течение нескольких дней, и мне нужна помощь. Я заставил этот код работать в действии, но затем я перемещаю его во фрагмент, который не работает. Все остальное между ними одинаково.

Используя отладчик с рабочей активностью, строка

 apiService = retrofit.create<HomeJsonApiService>(HomeJsonApiService::class.java)
 

переходит в getItemCount(). Однако во фрагменте он переходит непосредственно к onCreateView во фрагменте. Я прикрепил свой код ниже. Заранее спасибо за помощь! И будь нежен. Я все еще новичок в этом 🙂

Во-первых, это мой фрагмент:

 class TabHomeActivity : Fragment() {

    val itemList = ArrayList<HomeCards>()
    lateinit var adapter: HomeCardsAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        var binding = FragmentTabHomeActivityBinding.inflate(layoutInflater)
        adapter = HomeCardsAdapter()
        var rv = binding.rvHomeCards
        rv.adapter = adapter
        loadData()
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.cards_home, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
    }

    private fun loadData() {
        ApiManager.getInstance().service.listHeroes()
            .enqueue(object : Callback<ResponseData<List<HomeCards>>> {

                override fun onResponse(
                    call: Call<ResponseData<List<HomeCards>>>,
                    response: Response<ResponseData<List<HomeCards>>>
                ) {
                    val listData: List<HomeCards> = response.body()!!.data

                    // updating data from network to adapter
                    itemList.clear()
                    itemList.addAll(listData)
                    adapter.updateData(itemList)
                    adapter.notifyDataSetChanged()
                }

                override fun onFailure(call: Call<ResponseData<List<HomeCards>>>, t: Throwable) {
                }

            })
    }

}
 

HTTP-запрос:

 data class ResponseData<T> (
    val code: Int,
    val data: T
)
interface HomeJsonApiService {
    @GET("marvel-heroes.asp?h=2")
    fun listHeroes(): retrofit2.Call<ResponseData<List<HomeCards>>>
}

class ApiManager {

    private var apiService: HomeJsonApiService? = null

    init {
        createService()
    }

    val service: HomeJsonApiService get() = apiService!!

    private fun createService() {
        val loggingInterceptor =
            HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
                override fun log(message: String) {
                    Log.i("Retrofit", message)
                }
            })

        loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY

        val client = OkHttpClient.Builder()
            .readTimeout(30, TimeUnit.SECONDS)
            .connectTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)
            .addInterceptor(loggingInterceptor)
            .build()

        val retrofit: Retrofit = Retrofit.Builder()
            .client(client)
            .baseUrl("https://www.mywebsite.com/jsonfolder/JSON/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()

        apiService = retrofit.create(HomeJsonApiService::class.java)
    }

    companion object {

        private var instance: ApiManager? = null

        fun getInstance(): ApiManager {
            return instance ?: synchronized(this) {
                ApiManager().also { instance = it }
            }
        }
    }

}
 

И мой адаптер:

 class HomeCardsAdapter() : RecyclerView.Adapter<HomeCardsAdapter.ViewHolder>() {

    private lateinit var itemList: List<HomeCards>
    lateinit var context: Context

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        context = parent.context
        val view = LayoutInflater.from(context).inflate(R.layout.cards_home, parent, false)
        return ViewHolder(view)
    }

    override fun getItemCount(): Int {
        return if (::itemList.isInitialized) itemList.size else 0
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind()
    }

    fun updateData(list: List<HomeCards>) {
        itemList = list;
        notifyDataSetChanged()
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        //var binding = ActivityMainBinding(layoutInflater(inf))

        fun bind() {
            val item = itemList.get(adapterPosition)

            ViewHolder(itemView).itemView.findViewById<TextView>(R.id.cardHomeTitle).text = item.name
            ViewHolder(itemView).itemView.findViewById<TextView>(R.id.cardHomeTitle).text = item.superheroName

            Glide.with(context)
                .load(item.photo)
                .diskCacheStrategy(DiskCacheStrategy.ALL)
                .circleCrop()
                .into(ViewHolder(itemView).itemView.findViewById<ImageView>(R.id.cardHomeIcon))
        }
    }
}

class HomeCards {
    @SerializedName("superhero_name")
    var superheroName: String = ""
    var name: String = ""
    var photo: String = ""
}
 

Ответ №1:

Основная проблема заключается в:

 var binding = FragmentTabHomeActivityBinding.inflate(layoutInflater)
 

Это внутри onCreate , но onCreateView возвращает другой вид inflater.inflate(R.layout.cards_home, container, false)

Таким образом, вы применяете адаптер к переработчику, который находится на привязке, но вид на экране завышен из-за макета. Измените его на этот:

 private lateinit var binding: FragmentTabHomeActivityBinding

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
         binding = FragmentTabHomeActivityBinding.inflate(layoutInflater, container, false)
        return binding.root
    }
 

И переместите код из из onCreate в onViewCreated , но обязательно используйте lateinit binding

     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        adapter = HomeCardsAdapter()
        var rv = binding.rvHomeCards
        rv.adapter = adapter
        loadData()
    }

 

После этого возникает проблема с вашим адаптером: private lateinit var itemList: List<HomeCards> очень конкретно List<HomeCards> . Метод notifyDataSetChanged работает не путем изменения или обновления ссылки на структуру данных, а при изменении коллекции. Измените его на этот:

 private val list = mutableListOf<HomeCards>()


    override fun getItemCount(): Int {
        return list.size()
    }

    fun updateData(list: List<HomeCards>) {
        this.itemList.clear()
        this.itemList.addAll(list)
        notifyDataSetChanged()
    }
 

Комментарии:

1. Спасибо за ответы. К сожалению, ни то, ни другое вместе взятое не решило проблему.

2. val item = itemList.get(adapterPosition) попробуйте перенести это на адаптер и сделать bind так, чтобы метод получал 1 аргумент HomeCard

3. Ваша основная проблема в том, что вы сделали слишком много вещей, не подтвердив, что предыдущие работают. Как вам удалось выполнить HTTP-запрос, если представление не работало?

4. У меня это работало, когда это было основной деятельностью другого проекта. Когда я попытался перенести его на фрагмент, у меня возникли проблемы

5. @SteveRauco Я не ожидал ответа, потому что это не то, что вы должны мне сказать, а то, что вы должны спросить сами. Я пытаюсь помочь вам, указав, что отладка-это пошаговый процесс. Поэтому, прежде чем пытаться выяснить, почему адаптер не работает, измените цвет фона переработчика программно, binding.rv.setBackgroundResource... если это сработает, вы сможете сузить круг проблем. Но если вы накопили там 3 файла, вот что произойдет.

Ответ №2:

Если вызывается функция onResponse() и предоставляет ответ, убедитесь, что пользовательский интерфейс обновления кода запущен в основном потоке/пользовательском интерфейсе. Распространенный источник проблем при работе с сетью (другие потоки).

 activity?.runOnUiThread {
   itemList.clear()
   itemList.addAll(listData)
   adapter.updateData(itemList)
   adapter.notifyDataSetChanged()
}
 

Комментарии:

1. Об этом позаботится обратный вызов дооснащения enqueue