I have been several tutorials to create an API request and printing on the screen, and all the tutorials I did have some deprecated function that now I can't use.
What I have? I have the API request as you can see in the code, but now I need to print on the screen. And I don't know how to do it
PS: I use okhttp and the gson library
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fetchJson()
}
fun fetchJson() {
val url = "https://api.letsbuildthatapp.com/youtube/home_feed"
var request = Request.Builder().url(url).build()
var client = OkHttpClient()
client.newCall(request).enqueue(object: Callback {
override fun onResponse(call: Call, response: Response) {
val body = response.body()?.string()
val gson = GsonBuilder().create()
val homeFeed = gson.fromJson(body, HomeFeed::class.java)
println(homeFeed.videos)
}
override fun onFailure(call: Call, e: IOException) {
println("Failed on execute")
}
})
}
}
class HomeFeed(val videos: List<Video>)
class Video(val id: Int, val name:String, val link: String, val imageUrl: String, numberOfViews: String, val channel: Channel)
class Channel(val name: String, val profileImageUrl: String)
To simply print your debug logs to the Logcat just use:
Log.d("TAG", "videos: ${homeFeed.videos}")
Note: to print your list of videos in a human readable way you must make your Video and Channel classes as data classes (this automatically overrides their toString() functions):
data class Video(val id: Int, val name:String, val link: String, val imageUrl: String, numberOfViews: String, val channel: Channel)
data class Channel(val name: String, val profileImageUrl: String)
Also, as a long term solution, there is a better way to do HTTP logging (for debugging purposes) when using okhttp and it relies on using HttpLoggingInterceptor.
So, to print all your HTTP requests and responses with error codes, simply add a httpLoggingInterceptor to your client like this:
val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
val client: OkHttpClient = OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.build()
You can read more about OkHttp interceptors here.
print()/println() isn't working in Android.
You have two options:
Add TextView to your activity and show text there
textView.text = homeFeed.videos
or
textView.text = "Failed on execute"
Use logcat, Log.d() or similar method from Log class.
Related
I'm new to web APIs and retrofit. I'm interacting with TheCatApi and having trouble fetching data that I posted. The API has a list of images and a feature to choose several as favorites with POST requests and retrieve a list of favorites.
My main issue probably has to do with the data class (I have 2):
-For the Image GET request
data class CatPhoto (
#Json(name = "id")
val id: String,
#Json(name = "url")
val imgSrcUrl: String,
#Json(name = "breeds")
val breeds: List<Any>,
#Json(name = "width")
val width: Int,
#Json(name = "height")
val height: Int
)
#GET("$API_V/favourites?limit=100")
suspend fun getMyFavorites(
#Query("sub_id") subId: String
): List<CatPhoto>
-To make an image a favorite using a POST request
data class FavouriteImage (
val image_id: String,
val sub_id: String,
)
#POST("$API_V/favourites?limit=100")
suspend fun addFavorite(
#Body favouriteImage: FavouriteImage
)
This is the error after I POST a favourite and try to retrieve a list of posted favourites:
com.squareup.moshi.JsonDataException: Required value 'imgSrcUrl' (JSON name 'url') missing at $[.1]
It looks like it's expecting imgSrcUrl attribute on the FavouriteImage data class, which makes me think I shouldn't even have the FavouriteImage class. But then how do I make the post request that requires image_id and sub_id in the body?
Here's how I set up the database in the API service file:
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
object CatsApi {
val catsApiService : CatsApiService by lazy {
retrofit.create(CatsApiService::class.java)
}
}
backend of the github project
Edit: As #extremeoats wrote in comments, the api doesn't support passing urls an image indentifier and you have to save its id also for the operations like making favorite etc.
Old answer
Could you please add some code of how a request is made?
It's a local error of Moshi trying to parse the response and not seing the required field (maybe got an error from server - the data structure of an error would be different from a normal response)
I've built a sample app to test this and get a proper response when marking the image as a fav. You are right to use the structure with an image_id and sub_id. Here are some code parts if it helps
Set up the Retrofit (interceptor for debug only, so you can see what exactly you sent and got back)
private val interceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
private val retrofit = Retrofit.Builder()
.baseUrl(ServerApi.BASE_URL)
.client(
OkHttpClient.Builder()
.addInterceptor(interceptor)
.build()
)
.addConverterFactory(MoshiConverterFactory.create())
.build()
private val api = retrofit.create(ServerApi::class.java)
1.1. A dependency for the logging interceptor and OkHttp
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'
Api interface
interface ServerApi {
companion object {
const val API_KEY: String = **PASTE YOUR API KEY HERE**
const val AUTH_HEADER = "x-api-key"
const val BASE_URL = "https://api.thecatapi.com/v1/"
}
#GET("images/search")
fun getImages(#Header(AUTH_HEADER) authHeader: String = API_KEY, #Query("limit") limit: Int = 5): Call<List<ImagesItem>>
#POST("favourites")
fun postFavourite(#Header(AUTH_HEADER) authHeader: String = API_KEY, #Body payload: PostFavouritePayload): Call<String>
}
private var listMyData = Types.newParameterizedType(List::class.java, ImagesItem::class.java)
private val adapter: JsonAdapter<List<ImagesItem>> = Moshi.Builder().build().adapter(listMyData)
Use the api
api.getImages().enqueue(object : Callback<List<ImagesItem>> {
override fun onResponse(call: Call<List<ImagesItem>>, response: Response<List<ImagesItem>>) {
val images = response.body() ?: return
api.postFavourite(payload = PostFavouritePayload(images[0].id, **PASTE ANY STRING HERE AS USER ID**))
.enqueue(object : Callback<String> {
override fun onResponse(call: Call<String>, response: Response<String>) =
Log.d("TestCatApi", "SUCCESS posting a fav: ${response.body()}")
override fun onFailure(call: Call<String>, t: Throwable) =
t.printStackTrace()
})
}
override fun onFailure(call: Call<List<ImagesItem>>, t: Throwable) =
t.printStackTrace()
})
Side notes:
For such example APIs I find very useful a plugin "Json to Kotlin class". Alt+K, then you can paste the String response from a Server or in example, and you have a decent starting point for the data classes (not affiliated with them)
Just in case: you can pass a baseUrl to the Retrofit builder like that
Retrofit.Builder()
.baseUrl(ServerApi.BASE_URL)...
Then you put in #Get, #Post etc. only the part after the "/" of a base url: "images/search"
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'm new to programming,
i'm trying to get sunrise/sunset time out of yahoo weather api and toast it on Ui
(i'm using gson and anko library )
and this is my mainactivity code :
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fetchJson()
}
fun fetchJson(){
val url = "https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22nome%2C%20ak%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys"
val request = Request.Builder().url(url).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call?, e: IOException?) {
toast("Failed to execute request")
}
override fun onResponse(call: Call?, response: Response?) {
val body = response?.body()?.string()
println(body)
val gson = GsonBuilder().create()
val Info = gson.fromJson(body, astronomy::class.java)
runOnUiThread {
// info.sunrise is returning null ???????
toast("this is running from UiThread ${Info.sunrise}")
}
}
})
}
}
class astronomy(val sunrise: String, val sunset: String)
where should i fix?
Thanks
The response you get back from that Yahoo! API is much larger than just the astronomy section. You've got two options (one real option and one temporary one to check things):
Create a number of models to parse the entire stack (meaning you'd have a Query class with properties like count, created, lang, and results). This would be the better approach since you'll be dealing with real classes each step of the way.
data class Query(val count: Int?, val created: String?, val lang: String?, val results: Results?)
data class Results(val channel: Channel?)
//Channel should include more fields for the rest of the data
data class Channel(val astronomy: Astronomy?)
data class Astronomy(val sunrise: String?, val sunset: String?)
Throw the entire string into a generic JsonObject (which is GSON's provided class) and traverse through that object (query -> results -> channel -> astronomy -> sunrise and sunset). This isn't the proper approach but can work to make sure your data is coming in correctly:
val jsonObj: JsonObject = JsonParser().parse(body).asJsonObject
val astronomy = jsonObj
.getAsJsonObject("query")
.getAsJsonObject("results")
.getAsJsonObject("channel")
.getAsJsonObject("astronomy")
runOnUiThread {
toast("this is running from UiThread ${astronomy.get("sunrise").asString}")
}
Hey ebrahim khoshnood!
Welcome to StackOverflow. The problem seems to be, that you haven't created POJOs (classes) for the parent objects of astronomy. If you would like to parse everything only with Gson, you will have to create objects for "query", "results", "channel" and then inside of the channel you can have the astronomy object.
So for example you could have something like this.
class Query(val results: List<Channel>?)
class Channel(val astronomy: astronomy?) // astronomy? is the class you have posted.
and then you could parse everything like this
val query = gson.fromJson(body, astronomy::class.java)
val astronomy = query.results?.astronomy
I'm new to Kotlin, Android and OOP in general (Natural-ADABAS background, never did Java, C++, etc) so I'm pretty desperate.
I have an API whose data looks like this, an array of book details:
API data sample
I'm confused about data models. I know it's supposed to look like how the data in the API and return an array but how exactly do I code it in Kotlin? And then how do I parse it? I've read some tutorials but they all differ. Some use an object, and some use a class.
I'm also probably breaking some standard by putting everything in the main activity but I haven't gotten to that part yet.
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.GET
import retrofit2.http.Query
class MainActivity : AppCompatActivity()
{
private val api: RestAPI = RestAPI()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val apiGetBooks = api.getBooksList("token123123123")
val response = apiGetBooks.execute()
if (response.isSuccessful) {
val books = response.body()?.title
println(books)
} else {
println("error on API") // What do I do?
}
}
object Model {
val ResultArray : MutableList<BookProperties>? = null
}
data class BookProperties (val id: Int,val title: String, val coverURI: String, val pageURI: String, val pageCount: Int, val languageId: Int,val description: String, val isFree: Boolean) {
}
private val buriApi: MainActivity.BooksAPI? = null
class RestAPI {
private val buriApi: BooksAPI
init {
val retrofit = Retrofit.Builder()
.baseUrl("https://api.someurl.com")
.addConverterFactory(MoshiConverterFactory.create())
.build()
buriApi = retrofit.create(BooksAPI::class.java)
}
fun getBooksList(token: String): Call<BookProperties>{
return buriApi.getBooks(token)
}
}
fun getBooksList(token: String): Call<MainActivity.BookProperties> {
return buriApi!!.getBooks(token)
}
interface BooksAPI {
#GET("/v1/books")
fun getBooks (#Query("token")token: String) : Call<BookProperties>
}
}
After much googling, I finally solved my problem thanks to How to Quickly Fetch Parse JSON with OkHttp and Gson on YouTube.
fun fetchBooks () {
println("fetching books")
val url = "https://api.someurl.com/v1/books?"
val request = Request.Builder().url(url).build()
println(request)
val client = OkHttpClient()
client.newCall(request).enqueue(object: Callback {
override fun onResponse(call: Call?, response: Response?) {
val body = response?.body()?.string()
println(body)
}
override fun onFailure(call: Call?, e: IOException?) {
println("Failed to execute request")
e?.printStackTrace()
}
})
}
Still need to format the data and figure out how to turn on wifi in my Android emulator but at least I can consume the JSON.
Let's start with a sample and I guess you can map it accordingly to your requirement.
I don't have your JSON as text so I am giving an example of mine.
sample JSON response
{
"status": true,
"message": "User created Successfully.",
"response": {
"user": {
"id": 12,
"email": "testmail#gmailtest.com"
},
"token": "eyJlbWFpbCI6ImVzaGFudHNhaHUxMTBAZ21hc2kyMmwuY29tIiwidXNlcklkIjoxNSwiaWF0IjoxNTIxNTYyNjkxfQ"
}
}
so create a new class and name it something like this
CreateResponse.kt
and just map those objects and arrays from json to data classes and list here.
data class CreateUserResponse(override val status: Boolean? = null,
override val message: String? = null,
val response: Response? = null)
data class Response(val user: User?, val token: String)
data class User(val id: Int, val email: String)
easy right, now with Kotlin you can declare your data classes without creating separate files each time for each object just create one file and declare all of them at once.
I'm attaching some of the resources here which may help you understand the things better.
https://antonioleiva.com/retrofit-android-kotlin/
https://segunfamisa.com/posts/using-retrofit-on-android-with-kotlin
Working on a small android app to help me learn about JSON queries. The little test app works until I try to drill a little deeper in to my test JSON data.
I'll post a link to the JSON data I'm working with to save space here in the question. It is example code pulled from weatherwunderground's API and hosted on myjson.com.
JSON: https://api.myjson.com/bins/19uurt
MAIN ACTIVITY CODE
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val retriever = CycloneRetriever()
val callback = object : Callback<Cyclones> {
override fun onResponse(call: Call<Cyclones>?, response: Response<Cyclones>?) {
println("Got a response!")
println(response?.body()?.currenthurricane?.get(1)?.stormInfo?.get(0)?.stormName).toString()
}
override fun onFailure(call: Call<Cyclones>?, t: Throwable?) {
println("The thing, it failed!")
}
}
retriever.getCyclones(callback)
}
}
This is my class that helps build/gather the JSON data
CLASS
interface WeatherWunderGroundAPI {
#GET("bins/19uurt")
weatherwunderground.com API
fun getCyclones() : Call<Cyclones>
}
class Cyclones(val currenthurricane: List<CurrentHurricane>)
class CurrentHurricane(val stormInfo: List<StormInfo>)
class StormInfo(val stormName: String)
class CycloneRetriever {
val apiRetriever: WeatherWunderGroundAPI
init {
val retrofitCyclone =
Retrofit.Builder().baseUrl("https://api.myjson.com/")
.addConverterFactory(GsonConverterFactory.create()).build()
apiRetriever =retrofitCyclone.create(WeatherWunderGroundAPI::class.java)
}
fun getCyclones(callback: Callback<Cyclones>) {
val call = apiRetriever.getCyclones()
call.enqueue(callback)
}
}
Right now I'm just trying to get a good response and print that to the console. Eventually, I will take the JSON data and dump it into a RecyclerView.
I can get a good response if I do the following Println, but it does not return anything of use:
println(response?.body()?.currenthurricane
But once I try to dig further into .currenthurricane, onFailure() is called.
According to some JSON docs, this should get me what I want: $.currenthurricane.[stormInfo].stormName As an example.
But I cannot figure out how to get this working in my code. I was gonna give Klaxxon a try, but have not quite figured out how to get that working either.
I'm using Retrofit2 and GSON plugins in the code above. I'm fairly confident the issue is my JSON query.
Finally figured out the problem. I was on the right track, but needed a little tweaking on my JSON queries and need to change my classes.
I'm still working on it, and I'm sure there is a much better way to do this, but here is the quick and dirty.
Changed my classes and added a couple data classes:
class Cyclones(val currenthurricane: List<CurrentHurricane>)
class CurrentHurricane(val stormInfo: StormInfo, val Current: Current)
data class StormInfo(val stormName: String)
data class Current(val lat: Double, val lon: Double, val
SaffirSimpsonCategory: Int, val Category: String, val WindSpeed: WindSpeed,
val Movement: Movement)
data class WindSpeed(val Kts: Int, val Mph: Int, val Kph: Int)
data class Movement(val kts: Int, val mph: Int, val kph: Int)
And changed my JSON queries to the following:
val cycloneInfo =
response?.body()?.currenthurricane?.get(0)?.stormInfo?.stormName
val cycloneCurrentLat =
response?.body()?.currenthurricane?.get(0)?.Current?.lat
val cycloneCurrentLon =
response?.body()?.currenthurricane?.get(0)?.Current?.lon
val cycloneCurrentSSCat =
response?.body()?.currenthurricane?.get(0)?.Current?.SaffirSimpsonCategory
val cycloneCurrentCategory =
response?.body()?.currenthurricane?.get(0)?.Current?.Category
val cycloneCurrentWindKts =
response?.body()?.currenthurricane?.get(0)?.Current?.WindSpeed?.Kts
val cycloneCurrentMovement =
response?.body()?.currenthurricane?.get(0)?.Current?.Movement?.kts