Android - 使用数据绑定和改造加载数据失败
Android - Failed to load data using data binding and retrofit
我正在尝试制作简单的应用程序来使用数据绑定和改造来显示鸡尾酒列表。我可以通过记录拦截器请求看到 200 但是当我调试时我可以看到结果列表是空的。debug screenshot
responce screenshot
片段class
class CocktailListFragment : Fragment() {
private val viewModel: CocktailListViewModel by lazy {
ViewModelProvider(this).get(CocktailListViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = CocktailListFragmentBinding.inflate(inflater)
binding.lifecycleOwner = this
setHasOptionsMenu(true)
binding.cocktail = viewModel
binding.cocktailList.adapter = CocktailListAdapter()
return binding.root
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.filter_menu, menu)
super.onCreateOptionsMenu(menu, inflater)
}
}
视图模型
class CocktailListViewModel : ViewModel() {
private val _cocktails = MutableLiveData<CocktailsList>()
val cocktails: LiveData<CocktailsList>
get() = _cocktails
private var viewModelJob = Job()
private val coroutineScope = CoroutineScope(viewModelJob + Dispatchers.Main)
init {
getCocktails()
}
private fun getCocktails() {
coroutineScope.launch {
val getCocktailsDeferred = CocktailsApi.RETROFIT_SERVICE.getCocktailsAsync()
try {
val result = getCocktailsDeferred.await()
_cocktails.value = result
} catch (e: Exception) {
Log.d("error", "error")
}
}
}
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}
适配器
class CocktailListAdapter :
ListAdapter<Cocktail, CocktailListAdapter.CocktailListViewHolder>(DiffCallback) {
class CocktailListViewHolder(private var binding: CocktailItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(cocktail: Cocktail) {
binding.cocktail = cocktail
binding.executePendingBindings()
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CocktailListViewHolder {
return CocktailListViewHolder(CocktailItemBinding.inflate(LayoutInflater.from(parent.context)))
}
override fun onBindViewHolder(holder: CocktailListViewHolder, position: Int) {
val cocktail = getItem(position)
holder.bind(cocktail)
}
companion object DiffCallback : DiffUtil.ItemCallback<Cocktail>() {
override fun areItemsTheSame(oldItem: Cocktail, newItem: Cocktail): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: Cocktail, newItem: Cocktail): Boolean {
return oldItem.id == newItem.id
}
}
}
绑定适配器
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, data: CocktailsList?) {
val adapter = recyclerView.adapter as CocktailListAdapter
adapter.submitList(data?.list)
}
@BindingAdapter("strDrinkThumb")
fun bindImage(imgView: ImageView, imgUrl: String?) {
imgUrl?.let {
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
Picasso.get()
.load(imgUri)
.placeholder(R.drawable.loading_animation)
.error(R.drawable.ic_broken_image)
.into(imgView)
}
}
Api服务
private const val BASE_URL =
"https://www.thecocktaildb.com"
private val gson = GsonConverterFactory.create(GsonBuilder().create())
val logging = HttpLoggingInterceptor()
val okhttpClient = OkHttpClient.Builder()
.addInterceptor(logging.setLevel(HttpLoggingInterceptor.Level.BODY))
.build()
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okhttpClient)
.addConverterFactory(gson)
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()
interface CocktailsApiService {
@GET("./api/json/v1/1/filter.php?c=Ordinary_Drink")
fun getCocktailsAsync():
Deferred<CocktailsList>
}
object CocktailsApi {
val RETROFIT_SERVICE: CocktailsApiService by lazy {
retrofit.create(CocktailsApiService::class.java)
}
}
数据classes
data class CocktailsList(
val list: List<Cocktail>
)
data class Cocktail (
@SerializedName("idDrink")
val id: String,
@SerializedName("strDrinkThumb")
val drinkImg: String,
@SerializedName("strDrink")
val drinkTitle: String
)
cocktail_list_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="cocktail"
type="com.example.coctaildb.cocktaillist.CocktailListViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="30dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@android:color/white"
app:menu="@menu/filter_menu"
app:title="@string/drinks"
app:titleTextColor="@android:color/black" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/cocktail_list"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appBarLayout2"
app:listData="@{cocktail.cocktails}"
tools:listitem="@layout/cocktail_item" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
cocktail_item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="cocktail"
type="com.example.coctaildb.network.Cocktail" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/cocktail_img"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
app:strDrinkThumb="@{cocktail.drinkImg}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:id="@+id/cocktail_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="21dp"
android:layout_marginEnd="20dp"
android:fontFamily="@font/roboto"
android:text="@{cocktail.drinkTitle}"
android:textColor="#7E7E7E"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@+id/cocktail_img"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/cocktail_img"
app:layout_constraintTop_toTopOf="@+id/cocktail_img" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
我想我在绑定中弄乱了数据类型,请帮忙。
我认为问题出在序列化上。尝试
data class CocktailsList(
@SerializedName("drinks")
val list: List<Cocktail>
)
我正在尝试制作简单的应用程序来使用数据绑定和改造来显示鸡尾酒列表。我可以通过记录拦截器请求看到 200 但是当我调试时我可以看到结果列表是空的。debug screenshot responce screenshot
片段class
class CocktailListFragment : Fragment() {
private val viewModel: CocktailListViewModel by lazy {
ViewModelProvider(this).get(CocktailListViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = CocktailListFragmentBinding.inflate(inflater)
binding.lifecycleOwner = this
setHasOptionsMenu(true)
binding.cocktail = viewModel
binding.cocktailList.adapter = CocktailListAdapter()
return binding.root
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.filter_menu, menu)
super.onCreateOptionsMenu(menu, inflater)
}
}
视图模型
class CocktailListViewModel : ViewModel() {
private val _cocktails = MutableLiveData<CocktailsList>()
val cocktails: LiveData<CocktailsList>
get() = _cocktails
private var viewModelJob = Job()
private val coroutineScope = CoroutineScope(viewModelJob + Dispatchers.Main)
init {
getCocktails()
}
private fun getCocktails() {
coroutineScope.launch {
val getCocktailsDeferred = CocktailsApi.RETROFIT_SERVICE.getCocktailsAsync()
try {
val result = getCocktailsDeferred.await()
_cocktails.value = result
} catch (e: Exception) {
Log.d("error", "error")
}
}
}
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}
适配器
class CocktailListAdapter :
ListAdapter<Cocktail, CocktailListAdapter.CocktailListViewHolder>(DiffCallback) {
class CocktailListViewHolder(private var binding: CocktailItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(cocktail: Cocktail) {
binding.cocktail = cocktail
binding.executePendingBindings()
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CocktailListViewHolder {
return CocktailListViewHolder(CocktailItemBinding.inflate(LayoutInflater.from(parent.context)))
}
override fun onBindViewHolder(holder: CocktailListViewHolder, position: Int) {
val cocktail = getItem(position)
holder.bind(cocktail)
}
companion object DiffCallback : DiffUtil.ItemCallback<Cocktail>() {
override fun areItemsTheSame(oldItem: Cocktail, newItem: Cocktail): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: Cocktail, newItem: Cocktail): Boolean {
return oldItem.id == newItem.id
}
}
}
绑定适配器
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, data: CocktailsList?) {
val adapter = recyclerView.adapter as CocktailListAdapter
adapter.submitList(data?.list)
}
@BindingAdapter("strDrinkThumb")
fun bindImage(imgView: ImageView, imgUrl: String?) {
imgUrl?.let {
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
Picasso.get()
.load(imgUri)
.placeholder(R.drawable.loading_animation)
.error(R.drawable.ic_broken_image)
.into(imgView)
}
}
Api服务
private const val BASE_URL =
"https://www.thecocktaildb.com"
private val gson = GsonConverterFactory.create(GsonBuilder().create())
val logging = HttpLoggingInterceptor()
val okhttpClient = OkHttpClient.Builder()
.addInterceptor(logging.setLevel(HttpLoggingInterceptor.Level.BODY))
.build()
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okhttpClient)
.addConverterFactory(gson)
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()
interface CocktailsApiService {
@GET("./api/json/v1/1/filter.php?c=Ordinary_Drink")
fun getCocktailsAsync():
Deferred<CocktailsList>
}
object CocktailsApi {
val RETROFIT_SERVICE: CocktailsApiService by lazy {
retrofit.create(CocktailsApiService::class.java)
}
}
数据classes
data class CocktailsList(
val list: List<Cocktail>
)
data class Cocktail (
@SerializedName("idDrink")
val id: String,
@SerializedName("strDrinkThumb")
val drinkImg: String,
@SerializedName("strDrink")
val drinkTitle: String
)
cocktail_list_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="cocktail"
type="com.example.coctaildb.cocktaillist.CocktailListViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="30dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@android:color/white"
app:menu="@menu/filter_menu"
app:title="@string/drinks"
app:titleTextColor="@android:color/black" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/cocktail_list"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appBarLayout2"
app:listData="@{cocktail.cocktails}"
tools:listitem="@layout/cocktail_item" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
cocktail_item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="cocktail"
type="com.example.coctaildb.network.Cocktail" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/cocktail_img"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
app:strDrinkThumb="@{cocktail.drinkImg}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:id="@+id/cocktail_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="21dp"
android:layout_marginEnd="20dp"
android:fontFamily="@font/roboto"
android:text="@{cocktail.drinkTitle}"
android:textColor="#7E7E7E"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@+id/cocktail_img"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/cocktail_img"
app:layout_constraintTop_toTopOf="@+id/cocktail_img" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
我想我在绑定中弄乱了数据类型,请帮忙。
我认为问题出在序列化上。尝试
data class CocktailsList(
@SerializedName("drinks")
val list: List<Cocktail>
)