square / retrofit

A type-safe HTTP client for Android and the JVM
https://square.github.io/retrofit/
Apache License 2.0
43.06k stars 7.31k forks source link

Unexpected behavior retrofit + coroutines. Stack the results. #3479

Closed mbove77 closed 3 years ago

mbove77 commented 4 years ago

I have this services call, the itemNum is: 15 by default. @GET("curated") suspend fun getCurated(@Query("per_page") itemNum: Int, @Query("page") numPage: Int): Response<List<Photo>>

This returns a response object with a list of 15 items, the problem i am facing is in the first call to services returns the first 15 items from page 1, but in the second call it returns a list of 30 items the first 15 + 15 new.

Hope the list only contains the 15 items from each call. Somehow Retrofit stack the results.

This behavior starts when coroutines are implemented, before it works as I expected

I need only the 15 elementos of the current call, someone know how achieve that?

Here i copy the debug panel where you see the call parameters and the result elements. hRDXx

here put all the flow

Gradle

    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.google.code.gson:gson:2.8.6'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation("com.squareup.okhttp3:okhttp:3.12.8") {
        force = true
    }
    implementation "com.squareup.okhttp3:logging-interceptor:3.12.8"

Services:

    @Headers("Authorization: " + ApiKey.PEXELS_TOKEN)
    @GET("curated")
    suspend fun getCurated(@Query("per_page") itemNum: Int, @Query("page") numPage: Int): Response<List<Photo>>

    @Headers("Authorization: " + ApiKey.PEXELS_TOKEN)
    @GET("search")
    suspend  fun getSearch(@Query("query") query: String?, @Query("per_page") itemNum: Int, @Query("page") numPage: Int): Response<List<Photo>>

Repository:

suspend fun getCuratedPhotos(queryString: String?, pageNumber: Int):  Response<List<Photo>> {

        return  if (queryString != null) {
            fotosApi.getSearch(queryString, AppConstants.ITEM_NUMBER, pageNumber)
        } else {
            fotosApi.getCurated(AppConstants.ITEM_NUMBER, pageNumber)
        }
 }

ViewModel Call:

fun getPhotos() {      
        viewModelScope.launch(Dispatchers.IO) {
            val response = mRepo.getCuratedPhotos(queryString.value, pageNumber)
            if (response.isSuccessful) {
                withContext(Dispatchers.Main) {
                    _photos.value = response.body()
                }
            }
        }
    }

Here put the response of the api in the page 1 and 2. api_response.zip

If you want you can see the repository of the project, you need to look for the "MVVM" branch and you also need to generate a Pexels Api key here: https://www.pexels.com/api/

Repository: https://github.com/mbove77/Pexels-Fotos-Wallpapers

Mezah commented 4 years ago

I doubt that is an issue related to retrofit, can you put a sample code for the whole flow?

mbove77 commented 4 years ago

I doubt that is an issue related to retrofit, can you put a sample code for the whole flow?

Now I added all the related code.

JakeWharton commented 4 years ago

Can you express the problem in a failing test case or executable sample? It's impossible to know what your API endpoint is returning. By faking out its responses with MockWebServer such as the example test in the issue template or the suite of tests inside the library we can remove the remote API as being a factory and ensure that it's only the Retrofit code that we are testing. You can use tools like Postman or Charles Proxy to inspect the raw HTTP response in order to import it into a test.

waqasakram117 commented 4 years ago

@mbove77 I inspected your code the problem was in your deserializer.

class GsonDeserializador : JsonDeserializer<List<Foto>> { 

    // this is the problem **var fotos: MutableList<Foto> = ArrayList()**

@Throws(JsonParseException::class)
    override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): List<Foto> {
        val photos = json.asJsonObject["photos"].asJsonArray

      // this is the solution
       var fotos: MutableList<Foto> = ArrayList()

 for (i in 0 until photos.size()) {
            val id = photos[i].asJsonObject["id"].asInt
            val width = photos[i].asJsonObject["width"].asInt
            val height = photos[i].asJsonObject["height"].asInt
            val url = photos[i].asJsonObject["url"].asString
            /**
              more parsing
             **/

            val currentFoto = Foto(id, width, height, url, photographer, original, large, large2x, medium, small, portrait, landscape, tiny)
            if (!fotos.contains(currentFoto)) fotos.add(currentFoto)
        }
        return fotos
    }
    }