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
)
Related
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.
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
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).
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
)
I have JSON data form response looks like this
{
"message": "",
"data" : [...]
}
data contains an array of
data class News(
#SerializedName("id") val id: Int,
#SerializedName("title") val title: String,
#SerializedName("description") val desc: String
)
or
data class Product(
#SerializedName("id") val id: Int,
#SerializedName("name") val name: String
)
based on what endpoint I hit.
To obtain the data from json object, I create 2 functions
fun JSONObject.toNewsList() = Gson().fromJson<List<News>>(getJSONArray("data").toString(),
object : TypeToken<List<News>>(){}.type)!!
fun JSONObject.toProductList() = Gson().fromJson<List<Product>>(getJSONArray("data").toString(),
object : TypeToken<List<Product>>(){}.type)!!
Those functions work perfectly until I try to combine them into a function using generic type as the parameter looks like this
fun <T> JSONObject.toList() = Gson().fromJson<List<T>>(getJSONArray("data").toString(),
object : TypeToken<List<T>>(){}.type)!!
Whenever I call the function jsonResponse.toList<News>(), it always return error com.google.gson.internal.LinkedTreeMap cannot be cast to com.example.News
Any idea where I went wrong, and how to fix it?
Edit :
Every response in my project always received as encrypted string, that's why I have to map the response by myself and can't put the response type in Call method. This is my Call function looks like
#POST("endpoint")
fun service(#Body body: RequestBody): Call<String>
You can use TypeToken.getParameterized like this:
inline fun <reified T> JSONObject.toList(): List<T> {
val typeToken = TypeToken.getParameterized(List::class.java, T::class.java)
return Gson().fromJson<List<T>>(json, typeToken.type)!!
}
Your response clas should be like below
class ResponseList<E> {
var message:String
var data:ArrayList<E>
}
you can call your API like
#GET("url")
fun getNews():Call<ResponseList<News>>