简体   繁体   English

如何使用 Retrofit/Moshi 解析嵌套的 JSON 对象

[英]How to parse nested JSON object with Retrofit/Moshi

I used this CodeLabs tutorial to learn how to make an HTTP request from the Google Books API https://codelabs.developers.google.com/codelabs/kotlin-android-training-internet-data/#4我使用这个 CodeLabs 教程来学习如何从 Google Books API https://codelabs.developers.google.com/codelabs/kotlin-android-training-internet-data/#4发出 HTTP 请求

Right now, I'm trying to access a nested JSON object that the Google Books API spits out Ie现在,我正在尝试访问 Google Books API 吐出的嵌套 JSON 对象,即

"items": [{
"kind": "books#volume",
"id": "mOsbHQAACAAJ",
"volumeInfo" : {
"description": "Young wizard Harry Potter finds himself back at the miserable Hogwarts School of Witchcraft and Wizardry. He doesn't realize the difficulty of the task that awaits him. Harry must pull out all the stops in order to find his missing friend. No Canadian Rights for the Harry Potter Series HARRY POTTER and all related characters and elements are trademarks of and (c) Warner Bros. Entertainment Inc. Harry Potter publishing rights (c) J. K. Rowling. (s05)",
"imageLinks": {
"smallThumbnail": "http://books.google.com/books/content?id=mOsbHQAACAAJ&printsec=frontcover&img=1&zoom=5&source=gbs_api",
"thumbnail": "http://books.google.com/books/content?id=mOsbHQAACAAJ&printsec=frontcover&img=1&zoom=1&source=gbs_api"
}
},

I just want the description and thumbnail property.我只想要描述和缩略图属性。

My interface for the API service is我的 API 服务接口是

package com.example.customapp.network
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.GET

//Code from https://codelabs.developers.google.com/codelabs/kotlin-android-training-internet-data/#3

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

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

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

interface BookApiService {
    //Get annotation specifies the endpoint for this web service method.
    //when getProperties() method is invoked, Retrofit appends the endpoint 'book' to the base URL
    //And creates a Call object. The Call object is used to start the request.
    @GET("volumes?q='harry+potter")
    suspend fun getProperties(): BookProperty
}

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

My BookProperty.kt is我的 BookProperty.kt 是

data class BookProperty(@field:Json(name = "items" ) val bookDetail: List<BookDetail>)
data class BookDetail(@field:Json(name = "volumeInfo") val volumeInfo: VolumeInfo)
data class VolumeInfo(@field:Json(name = "description") val description: String, @field:Json(name= "imageLinks") val imageLink: ImageLink)
data class ImageLink(@field:Json(name = "thumbnail") val thumbnail: String)

I'm calling the API from my ViewModel我正在从我的 ViewModel 调用 API

val readAllData: LiveData<List<BookItem>>
    private val repository: BookRepository
    private val _response = MutableLiveData<String>()

    val response: LiveData<String>
        get() = _response

    init {
        val bookDao = BookDatabase.getDatabase(application).bookDao()
        repository = BookRepository(bookDao)
        readAllData = repository.readAllData
    }

    fun addBook(book: BookItem) {
        viewModelScope.launch(Dispatchers.IO) {
            repository.addBook(book)
        }
    }

    fun updateBook(book: BookItem) {
        viewModelScope.launch(Dispatchers.IO) {
            repository.updateBook(book)
        }
    }

    fun getBookDetailProperties() {
        viewModelScope.launch {
            try {
                //calling get properties from the BookApi service creates and starts the network call
                //on a background thread
                var listResult = BookApi.retrofitService.getProperties()
                _response.value = "${
                    listResult.bookDetail[0].volumeInfo.description} book properties received"
            } catch (e: Exception) {
                _response.value = "Failure: ${e.message}"
            }
        }
    }

I'm trying to make an HTTP request each time I update an item on my CRUD app ie when I click a button, but I can't seem to get any response back.每次我更新我的 CRUD 应用程序上的项目时,即当我单击一个按钮时,我都试图发出 HTTP 请求,但我似乎无法得到任何响应。 This is my UpdateFragment where I initiate the API call.这是我发起 API 调用的 UpdateFragment。


class UpdateFragment : Fragment() {
    //Read up on delegation
    //https://codelabs.developers.google.com/codelabs/kotlin-bootcamp-classes/#7

    //UpdateFragmentArgs is a class that is automatically generated
    //when we created an argument for our Update Fragment in the nav graph
    //UpdateFragmentArgs will contain our current book
    //we can also use bundle
    private val args by navArgs<UpdateFragmentArgs>()

    private lateinit var mBookViewModel: BookViewModel

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        val view = inflater.inflate(R.layout.fragment_update, container, false)

        //So the keyboard doesn't push the EditText fields up
        this.activity?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)

        Glide
            .with(this)
            .load(args.currentBook.image)
            .into(view.bookImageDetail)

        mBookViewModel = ViewModelProvider(this).get(BookViewModel::class.java)


        view.apply {
            updateInputName.setText(args.currentBook.title)
            updateInputAuthor.setText(args.currentBook.author)
            updateBookDesc.text = args.currentBook.desc
            updateRatingBar.rating = args.currentBook.rating.toFloat()
            updateBookCompleted.isChecked = args.currentBook.finished
            updateBookCompleted.text =
                if (updateBookCompleted.isChecked) getString(R.string.book_completed) else getString(
                    R.string.book_not_completed
                )
            updateDateCreated.text = getString(R.string.date_created, args.currentBook.dateCreated)
        }

        view.updateBtn.setOnClickListener {
            updateItem()
        }

        view.updateBookCompleted.setOnCheckedChangeListener { _, isChecked ->
            if (isChecked) {
                view.updateBookCompleted.text = getString(R.string.book_completed)
            } else {
                view.updateBookCompleted.text = getString(R.string.book_not_completed)
            }
        }

        return view
    }

    private fun updateItem() {
        val bookName = updateInputName.text.toString()
        val bookAuthor = updateInputAuthor.text.toString()
        val bookRating = updateRatingBar.rating.toDouble()
        val bookFinished = updateBookCompleted.isChecked

        if (inputCheck(bookName, bookAuthor)) {
            
            //***Initiate API call here ****
            mBookViewModel.getBookDetailProperties()

            //Get description and image from API
            mBookViewModel.response.observe(viewLifecycleOwner, {
                println("Get resp " + it)
            })

            //Create book object
            val updatedBook = BookItem(
                args.currentBook.id,
                bookName,
                bookAuthor,
                args.currentBook.desc,
                args.currentBook.image,
                bookRating,
                args.currentBook.dateCreated,
                bookFinished
            )

            //update current book
            mBookViewModel.updateBook(updatedBook)

            Toast.makeText(requireContext(), "Updated book successfully!", Toast.LENGTH_SHORT)
                .show()

            //navigate back
            findNavController().navigate(R.id.action_updateFragment_to_listFragment)
        }
    }

    private fun inputCheck(bookName: String, authorName: String): Boolean {
        return !(TextUtils.isEmpty(bookName) && TextUtils.isEmpty(authorName))
    }

}

The issue is I can't get any response from the API call - I'm not sure if it's because of the nested objects in the JSON.问题是我无法从 API 调用中得到任何响应 - 我不确定是否是因为 JSON 中的嵌套对象。 Please help me shed some light on this, I'm still new to Kotlin programming.请帮我解释一下,我还是 Kotlin 编程的新手。

I found out the reason why I was not getting any response.我找到了为什么我没有得到任何回应的原因。 In my UpdateFragment, I'm doing this:在我的 UpdateFragment 中,我这样做:

            //Get description and image from API
            mBookViewModel.response.observe(viewLifecycleOwner, {
                println("Get resp " + it)
            })

            //Create book object
            val updatedBook = BookItem(
                args.currentBook.id,
                bookName,
                bookAuthor,
                args.currentBook.desc,
                args.currentBook.image,
                bookRating,
                args.currentBook.dateCreated,
                bookFinished
            )

            //update current book
            mBookViewModel.updateBook(updatedBook)

            Toast.makeText(requireContext(), "Updated book successfully!", Toast.LENGTH_SHORT)
                .show()

            //navigate back
            findNavController().navigate(R.id.action_updateFragment_to_listFragment)

I am navigating back to another fragment before I can observe any changes from the HTTP response.在观察 HTTP 响应的任何更改之前,我正在导航回另一个片段。 This causes the observer to stop observing any changes, and thus I can't get a response.这会导致观察者停止观察任何变化,因此我无法得到回应。 I just need to put my code inside the callback, so I can do something with the data I received.我只需要将我的代码放在回调中,这样我就可以对收到的数据做一些事情。 Like so:像这样:

            //Get description and image from API
            mBookViewModel.response.observe(viewLifecycleOwner, {
                println("Get resp " + it)
          

            //Create book object
            val updatedBook = BookItem(
                args.currentBook.id,
                bookName,
                bookAuthor,
                args.currentBook.desc,
                args.currentBook.image,
                bookRating,
                args.currentBook.dateCreated,
                bookFinished
            )

            //update current book
            mBookViewModel.updateBook(updatedBook)

            Toast.makeText(requireContext(), "Updated book successfully!", Toast.LENGTH_SHORT)
                .show()

            //navigate back
            findNavController().navigate(R.id.action_updateFragment_to_listFragment)
 })

Hopefully this helps out anyone who has just started out learning LiveData and using HTTP requests.希望这对刚开始学习 LiveData 和使用 HTTP 请求的人有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM