簡體   English   中英

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

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

我使用這個 CodeLabs 教程來學習如何從 Google Books API https://codelabs.developers.google.com/codelabs/kotlin-android-training-internet-data/#4發出 HTTP 請求

現在,我正在嘗試訪問 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"
}
},

我只想要描述和縮略圖屬性。

我的 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)
    }
}
}

我的 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)

我正在從我的 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}"
            }
        }
    }

每次我更新我的 CRUD 應用程序上的項目時,即當我單擊一個按鈕時,我都試圖發出 HTTP 請求,但我似乎無法得到任何響應。 這是我發起 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))
    }

}

問題是我無法從 API 調用中得到任何響應 - 我不確定是否是因為 JSON 中的嵌套對象。 請幫我解釋一下,我還是 Kotlin 編程的新手。

我找到了為什么我沒有得到任何回應的原因。 在我的 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)

在觀察 HTTP 響應的任何更改之前,我正在導航回另一個片段。 這會導致觀察者停止觀察任何變化,因此我無法得到回應。 我只需要將我的代碼放在回調中,這樣我就可以對收到的數據做一些事情。 像這樣:

            //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)
 })

希望這對剛開始學習 LiveData 和使用 HTTP 請求的人有所幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM