something wrong with my API call, using Retrofit with moshi converter - android

I am learning Android development and I am trying to build a simple news app.
but I have been stuck for 3 days not able to fetch the json response from the server.
I think something wrong with my retrofit call but I can't figure what it is. I check the logcat and there is no response printed !
I appreciate any help. (I changed my API key because it is personal)
private const val BASE_URL = "https://newsapi.org"
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
interface CallNewsApi {
#GET("/v2/top-headlines")
suspend fun getHeadlines(
#Query("apiKey") key: String,
#Query("category") category: String,
#Query("country") country: String
): Response<NewsApiResponse>
}
object NewsApi {
val retrofitService: CallNewsApi by lazy {
retrofit.create(CallNewsApi::class.java)
}
}
data class NewsApiResponse(
var status: String,
var totalResults: Int,
var articles: List<NewsHeadlines>
)
data class NewsHeadlines(
var source: Source,
var author: String,
var title: String,
var description: String,
var url: String,
var urlToImage: String,
var publishedAt: String,
var content: String
)
data class Source(
var id: String,
var name: String
)
class NewsViewModel : ViewModel() {
private val _newsList = MutableLiveData<List<NewsHeadlines>>()
val status: LiveData<List<NewsHeadlines>> = _newsList
init {
getNewsLines()
}
private fun getNewsLines() {
viewModelScope.launch {
try {
Log.d("ViewModel"," before retrofit")
val call = NewsApi.retrofitService.getHeadlines(
"myApiKey",
"sports",
"il"
)
Log.d("ViewModel", call.body().toString())
} catch (e: java.lang.Exception) {
_newsList.value = listOf()
}
}
}
}

Looking at the sample responses in the API documentation, it seems that a lot of the fields in the json response can be null like source.id, author, content, description and urlToImage. For these fields you should use nullable properties i.e. String?.
Since the documentation doesn't explicitly mention which fields can be null and which can't be, it's upto you whether you want to mark all of them as nullable or not. But you can skip those which you think will always be present. For example, I think that status, totalResults, publishedAt, title, source.name should always be present in the news response, so you can choose to keep these as non-nullable.

Related

What is the right way to post with retrofit 2 and moshi

I've been trying to make a POST with Retrofit 2 and Moshi but I've been unable to get it to work.
My data classes look like this:
data class Order(
val items: List<Item>?,
val customerName: String,
val customerPhoneNo: String,
val customerAddress: String,
val note: String
)
data class Item(
val productUid: String,
var quantity: Int
)
The interface looks like this
interface ProductService {
#POST("/api/order/saveorder")
suspend fun postProds(
#Field("customerName") customerName: String,
#Field("customerPhoneNo") customerPhone: String,
#Field("customerAddress") address: String,
#Field("note") customerNote:String,
#Field("items") orderItems: List<Item>
): Response<Order>
#GET("/api/product/allproducts")
suspend fun getProds(): Response<List<ProdsItem>>
}
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
object Network {
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create(moshi)
.asLenient()
)
.build()
object ProdsApi {
val retrofitService: ProductService by lazy {
retrofit.create(ProductService::class.java)
}
}
}
The postProds fun is called like this:
suspend fun sendOrder(order: Order) {
withContext(Dispatchers.Main){
try {
val orderResponse = Network.ProdsApi.retrofitService.postProds(
order.customerName,
order.customerPhoneNo,
order.customerAddress,
order.note,
order.items )
}
catch (e: Exception) {
Timber.e(e)
}
}
}
Trying to POST this way keeps yielding this response:
Response{protocol=h2, code=400, message=, url=
However, I tried converting the Order object to json directly in my viewModel as follows:
val moshi: Moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val jsonAdapter: JsonAdapter<Order> = moshi.adapter(Order::class.java)
val jsonString = jsonAdapter.toJson(customerOrder)
Timber.d(jsonString)
I then tested the generated jsonString on Postman and got a 200 response.
I need some help figuring out what I'm doing wrong in my code, please.
In postman, you are sending data in the body of the request. But in your code, it is going as key-value params. Try to send it in the body from your code. Try below snippet.
Update your Order Data class:
#JsonClass(generateAdapter = true)
data class Order(
#Json(name = "items")
val items: List<Item>?,
#Json(name = "customerName")
val customerName: String,
#Json(name = "customerPhoneNo")
val customerPhoneNo: String,
#Json(name = "customerAddress")
val customerAddress: String,
#Json(name = "note")
val note: String
)
#JsonClass(generateAdapter = true)
data class Item(
#Json(name = "productUid")
val productUid: String,
#Json(name = "quantity")
var quantity: Int
)
Now the ProductService Interface:
interface ProductService {
#POST("/api/order/saveorder")
suspend fun postProds(
#Body request: Order
): Response<Order>
#GET("/api/product/allproducts")
suspend fun getProds(): Response<List<ProdsItem>>
}
Now Pass the request object in your function call:
suspend fun sendOrder(order: Order) {
withContext(Dispatchers.Main){
try {
val orderResponse = Network.ProdsApi.retrofitService.postProds(order)
}
catch (e: Exception) {
Timber.e(e)
}
}
}

retrofit get request returns null

I use extract articles from news api and display them using paging 3 library in my project, but for some reason #GET request returns my response class but with null variable though on my profile on the news api site it shows that there was a request.
NewsApi class:
interface NewsApi {
companion object{
const val CLIENT_ID = "356d64b4bfde4cd492ef415beabba030"
const val BASE_URL = "https://newsapi.org/"
}
#Headers("X-Api-Key: ${CLIENT_ID}")
#GET("v2/everything")
suspend fun searchArticles (
#Query("q") query: String,
#Query("page") page: Int,
#Query("pageSize") pageSize: Int,
) : NewsResponse
}
My Response class:
data class NewsResponse (
val results: List<NewsArticle>)
I tried your api and fetch the result,
Your Api is fine, the problem is you try to get the result as
data class NewsResponse (
val results: List<NewsArticle>)
when the result is actually formed like this :
{
"status":"ok",
"totalResults":14925,
"articles":[
{
"author":"Igor Bonifacic",
"title":"University of ",
"description":"In aide built a robot to com…"
}
]
}
so change you result into :
data class NewsResponse(
val status : String,
val totalResults: Int,
val articles: List<NewsArticle>
)
data class NewsArticle(
val author: String,
val description: String
)

Not enough information to infer variable T: How to structurate service method into Retrofit2 Kotlin App

I was developing an App in kotlin, which connect with an example API which return some users information, in order to test the library of Retrofit.
I developed the main structure of the App, an I get which the follofing error when I try to implement the funtionality of select by Id, into the API.
Not enough information about variable T
The code where comes this error is in the Provider an the service as I define my proyect.
The code of the Model data is the following:
data class QuoteModel(
#SerializedName("id") val quote: Number,
#SerializedName("name") val name: String,
#SerializedName("email") val email: String,
#SerializedName("address") val addrees: String,
#SerializedName("phone") val phone: String,
#SerializedName("website") val website: String,
#SerializedName("company") val company: String,
)
My provider is the next:
#Singleton
class QuoteProvider #Inject constructor() {
var allUsers: List<QuoteModel> = emptyList1()
var userById: QuoteModel = emptyList1() as QuoteModel
}
my Service is this:
class QuoteService #Inject constructor(private val api:QuoteApiClient) {
suspend fun getAllUsers(): List<QuoteModel> {
return withContext(Dispatchers.IO) {
val response = api.getAllUUsers()
response.body() ?: emptyList1()
}
}
suspend fun getUserById(id:Number): QuoteModel{
return withContext(Dispatchers.IO){
val response = api.getUserById(id)
response.body() ?: emptyList1() as QuoteModel --here is the error
}
}
}
The error comes in this line: response.body() ?: emptyList1() as QuoteModel
I know that is a type variable error but I start just a few mounth more serious with Kotlin, and I don't know if exit something similar to emptyList(), but with just one object.
Take thanks in advance !
You can use listOf<QuoteModel>() for creating empty array
you are trying to return list in second function but it requires QuoteModel . So you should create an empty QuoteModel object or return null using QuoteModel? in return type

Using default JsonDeserializer inside custom JsonDeserializer in Kotlin

I'm trying to build custom deserializers for the responses I get from OMDb API.
Here's the data class for Movie:
data class Movie(
val title: String?,
val year: String?,
val imdbID: String?,
val type: String?,
val poster: String?,
val mpaRating: String?,
val runtime: String?,
val genres: String?,
val director: String?,
val writers: List<String>?,
val actors: List<String>?,
val plot: String?,
val awards: String?,
val boxOfficeEarnings: String?,
val ratings: List<Rating>,
val response: Boolean?
)
And for Rating:
data class Rating(
#SerializedName("Source")
val source: String,
#SerializedName("Value")
val value: String
)
This is the custom JsonDeserializer so far:
class MovieDeserializer : JsonDeserializer<Movie>
{
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): Movie
{
val movieJsonObject = json?.asJsonObject
return Movie(
movieJsonObject?.get("Title")?.asString,
movieJsonObject?.get("Year")?.asString,
movieJsonObject?.get("imdbID")?.asString,
movieJsonObject?.get("Type")?.asString,
movieJsonObject?.get("Poster")?.asString,
movieJsonObject?.get("Rated")?.asString,
movieJsonObject?.get("Runtime")?.asString,
movieJsonObject?.get("Genre")?.asString,
movieJsonObject?.get("Director")?.asString,
separateStringByComma(movieJsonObject?.get("Writer")?.asString),
separateStringByComma(movieJsonObject?.get("Actors")?.asString),
movieJsonObject?.get("Plot")?.asString,
movieJsonObject?.get("Awards")?.asString,
movieJsonObject?.get("BoxOffice")?.asString,
// this is where I need help,
movieJsonObject?.get("Response")?.asBoolean
)
}
fun separateStringByComma(stringToSeparate: String?): List<String>?
{
return stringToSeparate?.split(", ")
}
}
How can I convert that JsonElement directly to List<Rating> without some json string manipulation?
By the way, I'm using Retrofit with Gson:
val gsonMovieDeserializer = GsonBuilder()
.registerTypeAdapter(Movie::class.java, MovieDeserializer())
.create()
val retrofit = Retrofit.Builder()
.baseUrl("https://www.omdbapi.com/")
.addConverterFactory(GsonConverterFactory.create(gsonMovieDeserializer))
.build()
val omdbApi = retrofit.create(OmdbApi::class.java)
val movie = omdbApi.getMovie(movieImdbId.value.toString())
First of all, I'd like to point the usage of nullables there: instead of checking wheter movieJsonObject is null or not for every call inside deserialize(), you should change the function parameters not to be null and then check only once, right at the beggining, if json is a JsonObject, just skipping everything if it's not. That way, we have a solid base to extract the data. Also, for the Movie data class, check the API documentation for which fields are optional and only set those to nulalble (I'm pretty sure at least the title and ID there are always present, so it's way more useful to have them as non-nullable).
Now, for the question itself, you should probably be able to deserialize that list using context.deserialize<List<Rating>>(movieJsonObject.get("Ratings"), List::class.java), which, in Kotlin, will return a type-safe List<Rating> (but, again, make sure that's not an optional field in the API and, if it is, make it nullable).

Why am I getting null values when using gson to parse a json object in android studio?

I am reading in the following JSON and using GSON to convert it to an object which I can then use to access the properties of said object to help display the images in my app.
However one of the field I want to use, imageURL, is returning null values. Looking at the json (link below) we can clearly see that it is not null.
https://api.letsbuildthatapp.com/youtube/home_feed
I have used the debugger to demonstrate the null value I am getting for each imageURL:
Debugger output
So the object is giving me null values for the imageURL but the link is not. What is going on?
Here is the function I wrote to fetch and parse the JSON object:
private fun fetchJson() {
println("Attempting to fetch JSON")
val url = "https://api.letsbuildthatapp.com/youtube/home_feed"
val request = Request.Builder().url(url).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
val body = response.body?.string()
println(body)
val gson = GsonBuilder().create()
val homeFeed = gson.fromJson(body, HomeFeed::class.java)
runOnUiThread {
recyclerView_main.adapter = MainAdapter(homeFeed)
}
}
override fun onFailure(call: Call, e: IOException) {
println("failed to execute request")
}
}
)
}
My HomeFeed class is like this:
class HomeFeed(val videos: List<Video>)
class Video(val id: Int, val name: String, val link: String, val imageURL: String, numberOfViews: Int,
val channel: Channel)
class Channel(val name: String, val profileImageUrl: String)
I believe this should be detailed enough but if anymore info is needed please let me know.
Thank you.
Try with this object, you should use the same name for the vals, "imageUrl" instead of "imageURL":
data class HomeFeed(
val user: User,
val videos: List<Video>
)
data class User(
val id: Int,
val name: String,
val username: String
)
data class Video(
val channel: Channel,
val id: Int,
val imageUrl: String,
val link: String,
val name: String,
val numberOfViews: Int
)
data class Channel(
val name: String,
val numberOfSubscribers: Int,
val profileImageUrl: String
)

Categories

Resources