【问题标题】:Unable to parse nested JSON with Kotlin, Retrofit, RxJava and MVVM pattern无法使用 Kotlin、Retrofit、RxJava 和 MVVM 模式解析嵌套 JSON
【发布时间】:2020-04-15 05:49:38
【问题描述】:

在我的项目中,我遇到了一些关于在 MVVM 模式中使用 Kotlin、Retrofit 和 RxJava 返回嵌套 JSON 数据的问题。

我对 RxJava、MVVM 和 Retrofit 还比较陌生,仍然很困惑,但我想最终获得一个当前嵌套在 JSON 中的选择类别列表。

JSON 响应看起来像这样......

{
  "status": "ok",
  "totalResults": 38,
  "articles": [
    {
      "source": {
        "id": "business-insider",
        "name": "Business Insider"
      },
      "author": "Hayley Peterson",
      "title": "Amazon executive was killed after colliding with a van delivering the company's packages, report reveals - Business Insider",
      "description": "\"I heard a scream, immediately followed by a crash,\" the van's driver testified, according to the report.",
      "url": "https://www.businessinsider.com/amazons-joy-covey-killed-company-delivery-van-report-2019-12",
      "urlToImage": "https://image.businessinsider.com/5e01376e855cc215577a09f1?width=1200&format=jpeg",
      "publishedAt": "2019-12-23T22:32:01Z",
      "content": "The former Amazon executive Joy Covey was killed after colliding with a van delivering Amazon packages, according to an explosive investigation into the company's logistics network by BuzzFeed News and ProPublica. \r\nCovey was Amazon's first chief financial of… [+1462 chars]"
    },
...

我的数据类看起来像这样...

data class Base (

    @SerializedName("status") val status : String,
    @SerializedName("totalResults") val totalResults : Int,
    @SerializedName("articles") val articles : List<Story>
)

data class Story (

    @SerializedName("source") val source : Source,
    @SerializedName("author") val author : String,
    @SerializedName("title") val title : String,
    @SerializedName("description") val description : String,
    @SerializedName("url") val url : String,
    @SerializedName("urlToImage") val urlToImage : String,
    @SerializedName("publishedAt") val publishedAt : String,
    @SerializedName("content") val content : String
)

data class Source (

    @SerializedName("id") val id : String,
    @SerializedName("name") val name : String
)

我首先使用我的 API 和 @GET 注释来通过这段代码获得头条新闻...

interface StoriesApi {

    @GET("v2/top-headlines?country=us&apiKey=###MYKEY###")
    fun getStories(): Single<List<Story>>
}

我的 StoriesService 又使用它来获取 Single>

class StoriesService {

    @Inject
    lateinit var api: StoriesApi

    init {
        DaggerApiComponent.create().inject(this)
    }

    fun getStories(): Single<List<Story>> {
        return api.getStories()
    }
}

最后,我使用下面的代码在我的 ViewModel 中调用它...

private fun fetchStories() {
        loading.value = true
        disposable.add(
            storiesService.getStories()
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeWith(object: DisposableSingleObserver<List<Story>>() {
                    override fun onSuccess(value: List<Story>?) {
                        stories.value = value
                        storyLoadError.value = false
                        loading.value = false
                    }

                    override fun onError(e: Throwable?) {
                        storyLoadError.value = true
                        loading.value = false
                    }

                })
        )
    }

有什么方法可以让我只输入 JSON 的文章部分,这样我就不必对整个 JSON 响应进行过多的摆弄?我最终希望只得到一篇文章,而不是“状态”和“总结果”。

【问题讨论】:

  • 你遇到了什么错误,你能提一下吗?

标签: android kotlin mvvm rx-java retrofit2


【解决方案1】:

请试试这个

interface StoriesApi {
    @GET("v2/top-headlines?country=us&apiKey=###MYKEY###")
    fun getStories(): Single<Story> /* change this */
}

class StoriesService {

    @Inject
    lateinit var api: StoriesApi

    init {
        DaggerApiComponent.create().inject(this)
    }

    /* Change this*/
    fun getStories(): Single<Story> {
        return api.getStories()
    }
}

在视图模型中

.subscribeWith(object: DisposableSingleObserver<Story>() {
                    override fun onSuccess(value: Story?) {
                        //this a list of articles List that you won
                        stories.value = value.articles
                        storyLoadError.value = false
                        loading.value = false
                    }

                    override fun onError(e: Throwable?) {
                        storyLoadError.value = true
                        loading.value = false
                    }

                })

跳这个帮助

【讨论】:

    【解决方案2】:

    您实际上似乎并没有在这里使用 Retrofit。以下是如何在 Retrofit 和 Coroutines 中做到这一点 - 如果您不熟悉该主题,您可能需要学习 coroutines:

    data class ApiResultModel (
    
       val totalResults : Int
    )
    
    data class Story (
    
        val source : Source,
        val author : String,
        val title : String,
        val description : String,
        val url : String,
        val urlToImage : String,
        val publishedAt : String,
        val content : String
    )
    

    构建api接口:

    interface StoriesApiService {
    
        @GET("top-headlines" - you insert just the end point here, the search parameters should go bellow because you might need to change them)
        fun getStories(country: String = us,
                       apiKey: String = {your api key}
                        ): Deferred<ApiResultModel>
    }
    

    然后创建一个包含所有这些的对象 MyApi:

    private val okHttpClient = OkHttpClient.Builder()
        .build()
    
    private val moshi = Moshi.Builder()
        .add(KotlinJsonAdapterFactory())
        .build()
    
    private val retrofit = Retrofit.Builder()
        .addConverterFactory(MoshiConverterFactory.create(moshi))
        .addCallAdapterFactory(CoroutineCallAdapterFactory())
        .baseUrl({the base url of the API})
        .client(okHttpClient)
        .build()
    
    object MyApi {
        val storiesApiService: StoriesApiService by lazy {
            retrofit.create(StoriesApiService::class.java)
        }
    }
    

    然后,为了从 API 访问数据,您将需要一个协程范围来进行调用:

    private val job = Job()
    
    private val coroutineScope = CoroutineScope(Job() + Dispatchers.Main)
    
    coroutineScope.launch {
        val apiResult = MyApi.storiesApiService.getstories().await()
    }
    

    代码可能仍有一些拼写错误,如果您打算使用该方法进行 API 调用,请再次检查。

    您还应该观看由 Google 开发人员创建的关于 Udacity 的免费教程。我用了和他们一样的方法,但是他们有更多的解释。

    【讨论】:

      【解决方案3】:

      步骤:1 创建改造单例类

      object RetrofitClient {
      var loggingInterceptor = HttpLoggingInterceptor()
          .setLevel(HttpLoggingInterceptor.Level.BASIC)
      var okHttpClient = OkHttpClient.Builder()
          .addInterceptor(loggingInterceptor)
          .build()
      var service: ApiInterface
      
      init {
          val retrofit = Retrofit.Builder().baseUrl(BASE_URL)
              .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
              .addConverterFactory(GsonConverterFactory.create())
              .client(okHttpClient)
              .build()
      
          service = retrofit.create(ApiInterface::class.java)
        }
      }
      

      步骤:2个API接口来定义你的api

       interface ApiInterface {
        @GET("comments")
        fun getComments(
           @Query("id") postId: Int
         ): Observable<List<Comments>> (For rx java adapter)
      
         @GET("comments")
        fun getComments(
            @Query("id") postId: Int
         ): Call<List<Comments>>  (For retrofit call adapter)
      
        }
      

      步骤:3 存储库

      class CommentsRepository {
      private val TAG = AuthRepository::class.java.getSimpleName()
      private var apiRequest = RetrofitClient.service
      var data = MutableLiveData<List<Comments>>()
      
      
      fun getPostComments(id: Int): LiveData<List<Comments>> {
           data = MutableLiveData()
      
           apiRequest.getComments(id).enqueue(object : Callback<List<Comments>> {
               override fun onResponse(
                  call: Call<List<Comments>>,
                   response: Response<List<Comments>>
               ) {
                  if (response.isSuccessful) {
                      data.value = response.body()
                   }
              }
      
               override fun onFailure(call: Call<List<Comments>>, t: Throwable) {
                   Log.e(TAG, "FFFeeeild:: " + t.message)
               }
           })
          return data
      }
      
       fun getPostComments(id: Int): LiveData<List<Comments>> {  //For call object 
          data = MutableLiveData()
          apiRequest.getComments(id)
              .subscribeOn(Schedulers.io())
              .observeOn(AndroidSchedulers.mainThread())
              .subscribe(object : Observer<List<Comments>> {
                  override fun onComplete() {
                  }
      
                  override fun onSubscribe(d: Disposable) {
                   }
                  override fun onNext(commentList: List<Comments>) {
                      data.value = commentList
                  }
      
                  override fun onError(e: Throwable) {
                      Log.e(TAG, e.toString())
                  }
              })
          return data
      
        }
      }
      

      步骤:4 视图模型

      class CommentsViewModel(application: Application):AndroidViewModel(application){
      private val repository: CommentsRepository = CommentsRepository()
      private var postId: Int = -1
      lateinit var userPost: LiveData<List<Comments>>
      
      fun getPostComments(id: Int): LiveData<List<Comments>> {
          this.postId = id
          userPost = repository.getPostComments(id)
          return userPost
       }
      }
      

      步骤:5 在活动或片段中观察此实时数据

      class CommentsActivity : AppCompatActivity() {
       private lateinit var viewModel: CommentsViewModel
       viewModel = ViewModelProvider(this ).get(CommentsViewModel::class.java)
      
        viewModel.getPostComments(1).observe(this, object : Observer<List<Comments>> {
              override fun onChanged(commentsList: List<Comments>?) {
                  commentsList?.forEach {
                      it.apply {
                          var content = ""
                          content += "ID: $id\n"
                          content += "Post ID: $postId\n"
                          content += "Name: $name\n"
                          content += "Email: $email\n"
                                  content += "Text: $text\n\n"
                          text_view_result.append(content)
      
                      }
      
                  }
              }
      
          })
      
        }
      

      还要确保添加必要的库。

      // Retrofit
      def retrofitVersion = "2.5.0"
      implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
      implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
      
      // ViewModel and LiveData
      def lifecycle_version = '2.2.0-alpha03'
      implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
      implementation "com.squareup.okhttp3:okhttp:4.2.2"
      implementation 'com.squareup.okhttp3:logging-interceptor:3.12.1'
      
      
      
      //Rx android library
      implementation 'io.reactivex.rxjava2:rxjava:2.1.9'
      implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
      implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
      

      希望它可以帮助您让我知道是否仍有任何疑问或遇到任何问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-01-11
        • 2015-04-07
        • 1970-01-01
        • 1970-01-01
        • 2019-07-30
        • 2018-07-12
        • 1970-01-01
        • 2017-02-16
        相关资源
        最近更新 更多