1

In my project I have run into some issues regarding returning nested JSON data using Kotlin, Retrofit and RxJava inside an MVVM pattern.

I'm relatively new to RxJava, MVVM and Retrofit and am quite confused still, but I would like to end up with having a list of a a select category that is currently nested inside a JSON.

The JSON response looks something like this...

{
  "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]"
    },
...

My data class looks like this...

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
)

I first use my API with @GET annotation to get top headlines with this code...

interface StoriesApi {

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

Which in turn is used by my StoriesService to get a Single>

class StoriesService {

    @Inject
    lateinit var api: StoriesApi

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

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

And to end I call it in my ViewModel by using the code below...

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
                    }

                })
        )
    }

Is there any way that I can only enter the articles part of the JSON so that I don't have to fiddle too much with the whole JSON response? I would ultimately like to just end up with a Single> of the articles and not the "status" and "totalResults".

1
  • what error you are facing can you mention the same? Commented Dec 24, 2019 at 5:08

3 Answers 3

1

You don't actually seem to use Retrofit here. Here is how to do it in Retrofit and Coroutines - you might need to study coroutines if you are not familiar with the subject:

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
)

Build the api interface:

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

Then create an object MyApi that holds all this:

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

Then, in order to acces the data from the API, you will need a coroutine scope to make the call:

private val job = Job()

private val coroutineScope = CoroutineScope(Job() + Dispatchers.Main)

coroutineScope.launch {
    val apiResult = MyApi.storiesApiService.getstories().await()
}

The code might still have some spelling errors, so please check it again if you plan on using the approach to make an API call.

You should also watch the free tutorial on Udacity created by developers from Google. I used the same approach as they, but they have more explanations.

Sign up to request clarification or add additional context in comments.

Comments

0

please try this

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

and at view model

.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
                    }

                })

hop this help

Comments

0

Step :1 Create Retrofit Singleton class

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

Step : 2 Api interface to define your 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)

  }

Step :3 Repository

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

  }
}

Step : 4 ViewModel

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

Step : 5 Observer this livedata in activity or fragement

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)

                }

            }
        }

    })

  }

Also Make sure to add necessary library.

// 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'

Hope it will help you let me know if still any query or facing any problem.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.