#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