【问题标题】:Json Parsing failed using Retrofit and moshi -Android使用 Retrofit 和 moshi -Android 解析 Json 失败
【发布时间】:2021-04-24 12:41:07
【问题描述】:

这是我得到的错误(这是一个次要错误,当我将数据绑定与 ListItemView 一起使用时我得到了这个)

    import com.example.bookkotlin.databinding.ListViewItemBindingImpl;
                                         ^
  symbol:   class ListViewItemBindingImpl
  location: package com.example.bookkotlin.databinding
> Task :app:kaptDebugKotlin FAILED
Execution failed for task ':app:kaptDebugKotlin'.
> A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptExecution
   > java.lang.reflect.InvocationTargetException (no error message)

build.gradle(项目:app)

 buildscript {
    ext {
        kotlin_version = "1.4.20"
        version_glide = "4.8.0"
        version_lifecycle = "2.2.0"
        version_moshi = "1.9.3"
        version_navigation = "1.0.0"
        version_retrofit = "2.9.0"
        version_recyclerview = "1.2.0-alpha05"
    }
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.1.1"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

build.gradle(模块:app)

  plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id  'kotlin-parcelize'
    id 'kotlin-kapt'
}


android {

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    compileSdkVersion 30
    buildFeatures {
        dataBinding true
    }
    buildToolsVersion "30.0.1"

    defaultConfig {
        applicationId "com.example.bookkotlin"
        minSdkVersion 22
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {

    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    implementation 'androidx.navigation:navigation-fragment-ktx:2.3.2'
    implementation 'androidx.navigation:navigation-ui-ktx:2.3.2'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

    // Glide
    implementation "com.github.bumptech.glide:glide:$version_glide"

    // RecyclerView
    implementation "androidx.recyclerview:recyclerview:$version_recyclerview"

    // Retrofit with Moshi Converter
    implementation "com.squareup.retrofit2:converter-moshi:$version_retrofit"
    // Moshi
    implementation "com.squareup.moshi:moshi:$version_moshi"
    implementation "com.squareup.moshi:moshi-kotlin:$version_moshi"

    // ViewModel and LiveData
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$version_lifecycle"

    // Navigation
    implementation "android.arch.navigation:navigation-fragment-ktx:$version_navigation"
    implementation "android.arch.navigation:navigation-ui-ktx:$version_navigation"

    implementation "org.jetbrains.kotlin:kotlin-reflect:1.1.0"
}

这是 BookApiService

    package com.example.bookkotlin.network

import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.GET
import retrofit2.http.Query

private const val BASE_URL = "https://www.googleapis.com/books/"

private val moshi = Moshi.Builder()
        .add(KotlinJsonAdapterFactory())
        .build()

private val retrofit = Retrofit.Builder()
        .addConverterFactory(MoshiConverterFactory.create(moshi))
        .baseUrl(BASE_URL)
        .build()

interface BookApiService {

    @GET("v1/volumes")
    suspend fun getProperties(@Query("q") type : String) : BookResponse
}

object BookApi {
    val retrofitService : BookApiService by lazy { retrofit.create(BookApiService::class.java) }
}

这是BookResponse

      package com.example.bookkotlin.network
    
    import android.os.Parcelable
    import kotlinx.android.parcel.Parcelize
    
       @Parcelize
data class BookResponse( val items : Array<Items> ) : Parcelable

@Parcelize
data class Items (val id: String,
                  val volumeInfo : BookProperty) : Parcelable

@Parcelize
data class BookProperty( val authors: Array<String>?, val title: String,
                         val imageLinks: ImageLink) : Parcelable

@Parcelize
data class ImageLink( val smallThumbnail: String) : Parcelable

这是概览片段

  package com.example.bookkotlin.overview

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.example.bookkotlin.R
import com.example.bookkotlin.databinding.FragmentOverviewBinding


class OverviewFragment : Fragment() {

    private val viewModel: OverviewViewModel   by lazy {
        ViewModelProvider(this).get(OverviewViewModel::class.java)
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?): View? {


     val binding: FragmentOverviewBinding = DataBindingUtil.inflate(
            inflater, R.layout.fragment_overview, container, false)

        binding.lifecycleOwner = this

        binding.viewModel = viewModel
       binding.recyclerList.adapter = RecyclerviewAdapter()

        return binding.root
    }
}

我从我的 OverviewViewModel 调用 API

    package com.example.bookkotlin.overview

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.bookkotlin.network.BookApi
import com.example.bookkotlin.network.BookResponse
import kotlinx.coroutines.launch

class OverviewViewModel : ViewModel() {

    // Internally, we use a MutableLiveData, because we will be updating the List of MarsProperty
    // with new values
    private val _properties = MutableLiveData<BookResponse> ()

    // The external LiveData interface to the property is immutable, so only this class can modify
    val properties: LiveData<BookResponse>
        get() = _properties

    init {
        getBookProperties()
    }

    private fun getBookProperties() {
        viewModelScope.launch {
            try {
                _properties.value =BookApi.retrofitService.getProperties("Harry Potter")
            } catch (e : Exception) {

            }
        }
    }
}

RecyclerviewAdapter

  package com.example.bookkotlin.overview

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.bookkotlin.databinding.ListViewItemBinding
import com.example.bookkotlin.network.Items


class RecyclerviewAdapter() : ListAdapter<Items, RecyclerviewAdapter.BookPropertyViewHolder>(DiffCallback()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookPropertyViewHolder {
        return BookPropertyViewHolder.from(parent)
    }

    override fun onBindViewHolder(holder: BookPropertyViewHolder, position: Int) {
    
       val item = getItem(position)
        holder.bind(item)
    }

    class BookPropertyViewHolder private constructor(val binding: ListViewItemBinding): RecyclerView.ViewHolder(binding.root) {

       fun bind(books: Items) {
            binding.property = books
           binding.executePendingBindings()
        }

        companion object {
            fun from(parent: ViewGroup): BookPropertyViewHolder {
                val layoutInflater = LayoutInflater.from(parent.context)
                val binding = ListViewItemBinding.inflate(layoutInflater, parent, false)
                return BookPropertyViewHolder(binding)
            }
        }
    }
}

class  DiffCallback : DiffUtil.ItemCallback<Items>() {
    override fun areItemsTheSame(oldItem: Items, newItem: Items): Boolean {
        return oldItem === newItem
    }

    override fun areContentsTheSame(oldItem: Items, newItem: Items): Boolean {
        return oldItem.volumeInfo.id == newItem.volumeInfo.id

    }
}

绑定适配器

  package com.example.bookkotlin

import android.widget.ImageView
import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.example.bookkotlin.network.Items
import com.example.bookkotlin.overview.RecyclerviewAdapter


@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, data: BookResponse) {
    val adapter = recyclerView.adapter as RecyclerviewAdapter
    adapter.submitList(data.items.toList())
}
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String) {
        Glide.with(imgView)
                .load(imgUrl)
                .into(imgView)

}

这是 list_view_item

    <?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto" >

    <data>
        <variable
            name="property"
            type="com.example.bookkotlin.network.Items" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:orientation="horizontal"
        android:paddingStart="@dimen/margin_16_dp"
        android:paddingLeft="@dimen/margin_16_dp"
        android:paddingEnd="@dimen/margin_16_dp"
        android:paddingRight="@dimen/margin_16_dp"
        android:id="@+id/container">
        
        <ImageView
            android:id="@+id/cover_image"
            android:layout_width="@dimen/image_view_width"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:scaleType="centerInside"
            tools:src="@mipmap/ic_launcher"
            app:imageUrl ="@{property.volumeInfo.imageLinks.smallThumbnail}"/>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginStart="@dimen/margin_16_dp"
            android:layout_marginLeft="@dimen/margin_16_dp"
            android:layout_weight="1"
            android:orientation="vertical">

            <TextView
                android:id="@+id/author"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:fontFamily="sans-serif-medium"
                android:maxLines="1"
                android:textAllCaps="true"
                android:textColor="@color/textColorAuthor"
                android:textSize="@dimen/author_text_size"
                android:text="@{property.volumeInfo.authors.toString()}" />

            <TextView
                android:id="@+id/book_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:elegantTextHeight="true"
                android:ellipsize="end"
                android:lineSpacingMultiplier="1.1"
                android:maxLines="2"
                android:textColor="@color/textColorBookTitle"
                android:textSize="@dimen/title_text_size"
                android:text="@{property.volumeInfo.title}"
                />

        </LinearLayout>


    </LinearLayout>
</layout>

fragment_overview

 <?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="viewModel"
            type="com.example.bookkotlin.overview.OverviewViewModel" />
    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context = "com.example.bookkotlin.MainActivity">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <LinearLayout
                android:id="@+id/searchSection"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginRight="8dp"
                android:orientation="horizontal">

                <SearchView
                    android:id="@+id/search_view_edit_text"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:iconifiedByDefault="false"
                    android:queryHint="Enter a Book Title"
                    tools:queryHint="Enter a Title" />

                <Button
                    android:id="@+id/search_book_button"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Search" />
            </LinearLayout>

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/recycler_list"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical"
                app:listData="@{viewModel.properties}"
                tools:listitem="@layout/list_view_item"
                app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
            
        </LinearLayout>
        <TextView
            android:id="@+id/empty_view"
            android:visibility="invisible"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:textAppearance="?android:textAppearanceMedium" />

        <ProgressBar
            android:id="@+id/loading_progress"
            style="?android:progressBarStyle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true" />
        
    </RelativeLayout>
</layout>

请帮我修复错误,我还是 Kotlin 编程的新手。

【问题讨论】:

    标签: android kotlin data-binding android-databinding


    【解决方案1】:

    我在 kotlin 中解析 Json 并将 List&lt;ObjectType&gt; 替换为 Array&lt;ObjectType&gt; 时遇到了类似的问题。

    试试下面的

        @Parcelize
        data class BookResponse( val items : Array<Items> ) : Parcelable
        
          @Parcelize 
         data class Items (val volumeInfo : BookProperty) : Parcelable
        
        
    // the volumeinfo returns a JsonObject and that object doesn't contain an **id** field, so remove the **id** member variable from BookProperty
        @Parcelize
        data class BookProperty( val authors: Array<String>?, val title: String, 
       val imageLinks: ImageLink
            ) : Parcelable
        
        @Parcelize
        data class ImageLink( val smallThumbnail: String) : Parcelable
    

    此外,API 只返回一个对象而不是列表,因此也进行以下更改

    interface BookApiService {
    
        @GET("v1/volumes")
        suspend fun getProperties(@Query("q") type : String) : BookResponse
    }
    

    编辑:

    你这个formatter 来可视化 Json 响应。例如,当您在 API 中查询 Harry Potter 时,您会得到一个 single 对象而不是对象列表。

    OverviewViewModel

    //change the datatype to hold a single object instead of a list
    
     private val _properties = MutableLiveData<BookResponse>()
    
     val properties: LiveData<BookResponse>
            get() = _properties
    

    BindingUtils

    @BindingAdapter("listData")
    fun bindRecyclerView(recyclerView: RecyclerView, data: BookResponse) {
        val adapter = recyclerView.adapter as RecyclerviewAdapter
        adapter.submitList(data.items.toList())
    }
    

    项目

    @Parcelize
    data class Items(val volumeInfo: BookProperty, val id: String) : Parcelable
    

    DiffCallback

    class DiffCallback : DiffUtil.ItemCallback<Items>() {
        override fun areItemsTheSame(oldItem: Items, newItem: Items): Boolean {
            return oldItem.id == newItem.id
        }
    
        override fun areContentsTheSame(oldItem: Items, newItem: Items): Boolean {
            return oldItem == newItem
        }
    }
    

    data parsed successfully

    【讨论】:

    • 我更改了代码,但我在fragment_overview.xml ..at &lt;androidx.recyclerview.widget.RecyclerView.. app:listData="@{viewModel.properties}" 中遇到错误。我遇到的错误是Cannot find a setter for &lt;RecyclerView app:listData&gt; that accepts parameter type 'androidx.lifecycle.LiveData&lt;com.example.bookkotlin.network.BookResponse&gt;'.. 我刚刚卡在这个项目中。我在 java 中构建了相同的应用程序,但是当我想在 kotlin 中构建数据绑定时,我遇到了很多错误。 Google codelab 没有展示如何在 kotlin @rcs 中使用复杂的 JSON
    • 问题在于绑定适配器,propertiesList&lt;BookResponse&gt;,而绑定适配器需要 List&lt;Items&gt;
    • 我应该在绑定适配器中进行哪些更改? ..我很困惑@rcs
    • 嘿,我能够成功构建它并且能够添加在应用程序中点击的搜索。非常感谢您的帮助,如果没有您的帮助,这是不可能的。我有一些关于 BindingAdapter 和 databinding 的问题。我们可以在讨论聊天中讨论它吗? @rcs
    猜你喜欢
    • 2020-02-10
    • 2018-07-15
    • 1970-01-01
    • 1970-01-01
    • 2021-02-03
    • 2018-07-26
    • 1970-01-01
    • 1970-01-01
    • 2016-01-11
    相关资源
    最近更新 更多