1

I am going through reso-coder Weather app tutorial. Most of the things changed with the passage of time, so is apixu weather website.
Now it's the time of Retrofit 2.6.1 which means kotlin coroutines
The problem is that i am getting everything Null in network Response
I have go through all data classes with SerializedName, everything seems pretty fine, still can't get the problem..
BTW i'm not using ViewModel right now just directly hoping into the fragment textView

interface ApixuWeatherApiService {


 @GET("current")
    suspend fun getCurrentWeather(
        @Query("query") location: String,
        @Query("lang") languageCode: String = "en"
    ): CurrentWeatherResponse

    //to handle this above interface we need companion object

    companion object WeatherAPis {
        private val requestInterceptor = Interceptor { chain ->
            val url = chain.request()
                .url().newBuilder().addQueryParameter("access_key", API_KEY)
                .build()

            val request = chain.request().newBuilder().url(url).build()
            chain.proceed(request)
        }
        private val okHTTPClient = OkHttpClient.Builder().addInterceptor(requestInterceptor).build()
        private fun retroFit(): Retrofit = Retrofit
            .Builder()
            .client(okHTTPClient)
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()

        val weatherApi: ApixuWeatherApiService =
            retroFit().create(ApixuWeatherApiService::class.java)
    }
}

Fragment Class


class CurrentWeatherFragment : Fragment() {


    private lateinit var viewModel: CurrentWeatherViewModel

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.current_weather_fragment, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProviders.of(this).get(CurrentWeatherViewModel::class.java)
        CoroutineScope(IO).launch {
            widCOntext()
        }

    }


    private suspend fun widCOntext() {
        val apiService = ApixuWeatherApiService.weatherApi
            withContext(Main) {
                val currentWeatherResponse =
                    withContext(IO) {
                        apiService.getCurrentWeather(
                            "London"
                        )
                    }
                txtView.text = currentWeatherResponse.toString()
            }

    }
}

I have used plugin to convert JSON to kotlin file to get these
Four data classes
Current

data class Current(
    @SerializedName("observation_time")
    val observationTime: String,
    val temperature: Int,
    @SerializedName("weather_code")
    val weatherCode: Int,
    @SerializedName("weather_icons")
    val weatherIcons: List<String>,
    @SerializedName("weather_descriptions")
    val weatherDescriptions: List<String>,
    @SerializedName("wind_speed")
    val windSpeed: Int,
    @SerializedName("wind_degree")
    val windDegree: Int,
    @SerializedName("wind_dir")
    val windDir: String,
    val pressure: Int,
    val precip: Int,
    val humidity: Int,
    val cloudcover: Int,
    val feelslike: Int,
    @SerializedName("uv_index")
    val uvIndex: Int,
    val visibility: Int,
    @SerializedName("is_day")
    val isDay: String
)

CurrentWeatherResponse

data class CurrentWeatherResponse(
    val request: Request,
    val location: Location,
    val current: Current
)

Location

data class Location(
    val name: String,
    val country: String,
    val region: String,
    val lat: String,
    val lon: String,
    @SerializedName("timezone_id")
    val timezoneId: String,
    val localtime: String,
    @SerializedName("localtime_epoch")
    val localtimeEpoch: Int,
    @SerializedName("utc_offset")
    val utcOffset: String
)

Request

data class Request(
    val type: String,
    val query: String,
    val language: String,
    val unit: String
)
Usman sam
  • 21
  • 8
  • So it neither throws an Exception nor returns a result? Have you tried changing return type from CurrentWeatherResponse to Response ? – r2rek Oct 29 '19 at 13:53
  • nope. my _textView_ was just showing null, it was written **null** on _textView_. after changing to **Response** it is showing some weird output like `protocol=h2, code=200,message, url= websiteurl ` – Usman sam Oct 29 '19 at 14:08
  • Could you try printing `response.body()` ? Also, how does your `CurrentWeatherResponse` class look like? – r2rek Oct 29 '19 at 14:10
  • @r2rek yes i try to print `response.body()` and got the exact same **null** response. Here it is, `debug: CurrentWeatherResponse(request=null, location=null, current=null)` this is what i was getting in _textView_ .. BTW i have updated my question and added `data classes`. – Usman sam Oct 29 '19 at 15:39
  • I know this might seem silly, but try to remove `withContext(IO) ` - you don't need it when using suspend funs with retrofit. – r2rek Oct 29 '19 at 15:45
  • @r2rek already tried it, just used `withContext(Main)` for main Thread(textView) and `CoroutineScope(IO).launch{}` in main function to call suspend function.. still same. – Usman sam Oct 29 '19 at 15:59
  • 1
    try adding an httpInterceptor and paste the result of request https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor – r2rek Oct 29 '19 at 16:01
  • Solved! Look below... – Usman sam Nov 02 '19 at 09:43

1 Answers1

0

Issue Solved : I hope this gonna help someone
Here what i did
Whenever i stuck somewhere, i make new project and try to work on that specific thing.
Now what i did over here which i think resolved my problem.
I have used singleton pattern, I have made Singleton instance of ApiService Interface, before singleton it was showing response 200 but output was null as well and now just by making singleton it resolved my Problem and now i am getting desired output
Here is code:

object RetrofitRequest {

    private val interceptor = Interceptor {chain ->
        val url = chain.request()
            .url()
            .newBuilder()
            .addQueryParameter("access_key/key", yourApiKey)
            .build()

        val request = chain.request().newBuilder().url(url).build()

        return@Interceptor chain.proceed(request)
    }

    private val okHttpClient = OkHttpClient
        .Builder()
        .addInterceptor(interceptor)
        .build()

    @Volatile
    private var instance : ApiService? = null
    fun getInstance() : ApiService = instance ?: synchronized(this) {
        instance ?: fullResponse().also {
            instance = it
        }
    }

    private fun retrofitBuild() : Retrofit =
        Retrofit.Builder()
           .client(okHttpClient)
           .baseUrl(BASE_URL)
           .addConverterFactory(GsonConverterFactory.create())
            .build()


    private fun fullResponse(): ApiService {
        return retrofitBuild().create(ApiService::class.java)
    }

}

Specifically this is the part i am talking about

@Volatile
    private var instance : ApiService? = null
    fun getInstance() : ApiService = instance ?: synchronized(this) {
        instance ?: fullResponse().also {
            instance = it
        }
    }

Now what i think what is happening over here:
Before Singleton the response was successful but instance was not visible for other threads. That is why it was null. As i am using coroutines it might be running from different thread, after making the singleton @volatile which makes the singleton visible for all threads. When the program try to run from different thread and @Volatile has capability to access to all threads,which made program execute successfully without null

Usman sam
  • 21
  • 8