I'm calling data from Breaking bad API https://www.breakingbadapi.com/api/character/random
I'm unable to get data. I think it's because the main Response file has square brackets that I need to call first. But I don't know how to call it. Can I get some help?
Here's my API interface
interface APIRequest {
#GET("character/random")
suspend fun getInfo() : Response<List<ResponseBB>>
}
ResponseBB Class
data class ResponseBB(
#field:SerializedName("ResponseBB")
val responseBB: List<ResponseBBItem?>? = null
)
data class ResponseBBItem(
#field:SerializedName("birthday")
val birthday: Any? = null,
#field:SerializedName("img")
val img: String? = null,
#field:SerializedName("better_call_saul_appearance")
val betterCallSaulAppearance: Any? = null,
#field:SerializedName("occupation")
val occupation: List<String?>? = null,
#field:SerializedName("appearance")
val appearance: List<Int?>? = null,
#field:SerializedName("portrayed")
val portrayed: String? = null,
#field:SerializedName("name")
val name: String? = null,
#field:SerializedName("nickname")
val nickname: String? = null,
#field:SerializedName("char_id")
val charId: Int? = null,
#field:SerializedName("category")
val category: String? = null,
#field:SerializedName("status")
val status: String? = null
)
Client object
object Client {
val gson = GsonBuilder().create()
val retrofit = Retrofit.Builder()
.baseUrl("https://www.breakingbadapi.com/api/")
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
val api = retrofit.create(APIRequest::class.java)
}
Here's my function to call result in the main activity
class MainActivity : AppCompatActivity() {
private var TAG = "MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
getCharacterInfo()
linearLayout.setOnClickListener {
getCharacterInfo()
}
}
private fun getCharacterInfo() {
GlobalScope.launch(Dispatchers.IO) {
try {
val response = Client.api.getInfo()
if (response.isSuccessful) {
val data = response.body()
Log.d(TAG, data.toString())
withContext(Dispatchers.Main) {
Picasso.get().load(data!!.img).into(ivImage)
tvName.text = data.name
tvOccupation.text = data.toString()
tvActor.text = data.toString()
tvAppearance.text = data.appearance.toString()
tvStatus.text = data.status
}
}
}
catch (e:Exception){
withContext(Dispatchers.Main){
Toast.makeText(applicationContext, "Cannot Load Data" , Toast.LENGTH_LONG).show()
}
}
}
}
}
I see that you try to use coroutines in retrofit, I recommend that you do not work with Response, change it to call and remove the suspend.
interface APIRequest {
#GET("character/random")
fun getInfo() : Call<List<ResponseBB>>
}
In your Global Scope you can call it this way:
GlobalScope.launch {
try{
val response = Client.api.getInfo().await()
}catch(e:Exception){}
}
you can use the version 2.9.0 in retrofit and gson Converter
Related
With retrofit I get response LevelsEntity but if I get error it get me ResponseError, NOTE: I cant merge LevelsEntity and ResponseError together in one entity.
LevelsEntity:
class LevelsEntity : ArrayList<LevelsEntityItem>()
LevelsEntityItem:
data class LevelsEntityItem(
#SerializedName("category")
val category: Int? = null,
#SerializedName("completed")
val completed: Boolean? = null,
#SerializedName("completionhascriteria")
val completionhascriteria: Boolean? = null
)
ResponseError:
data class ResponseError(
#SerializedName("errorcode")
val errorcode: String? = null,
#SerializedName("exception")
val exception: String? = null,
#SerializedName("message")
val message: String? = null
)
And I create bellow class for get multiple data like bellow:
class BaseLevelsEntity<LevelsEntity, ResponseError> {
var levelsEntity: LevelsEntity? = null
var responseError: ResponseError? = null
val isSuccess: Boolean
get() = responseError == null
}
And in my #POST of retrofit is:
#POST("/webservice/rest/server.php")
suspend fun getPopularLevelsInLessonsF(
#Query("mdwsrestformat") mdwsrestformat: String?,
#Field("wsfunction") wsfunction: String?,
#Field("wstoken") wstoken: String?,
#Field("userid") userid: Int?
): Call<BaseLevelsEntity<LevelsEntity, ResponseError>>
But I cant get any result in my impl:
class LessonsRepositoryImpl(
private val lessonsRemoteDatasource: LessonsRemoteDatasource
) : LessonsRepository {
override suspend fun getLevelsInLessonsF(
wstoken: String,
userid: Int
): Resource<BaseLevelsEntity<LevelsEntity, ResponseError>> {
return responseToResource(lessonsRemoteDatasource.getLevelsValueInLessonsF(wstoken, userid).execute())
}
private fun responseToResource(response: Response<BaseLevelsEntity<LevelsEntity, ResponseError>>): Resource<BaseLevelsEntity<LevelsEntity, ResponseError>> {
if (response.isSuccessful) {
if (response.body() != null) {
response.body()?.let { result ->
if (!result.levelsEntity.isNullOrEmpty()) {
if (result.levelsEntity!!.size > 0) {
return Resource.Success(result)
}
} else if (result.responseError != null) {
return Resource.Error(result.responseError?.errorcode ?: "unknown")
}
}
} else {
return Resource.Error("unknown_info")
}
}
return Resource.Error(response.message())
}
}
Normally response should be in common format.
If cannot do this from backend then you can receive response as JsonObject and then check the key manually in repository to decide if it is success or error response. Based on that you can then convert the response to object with gson.
I am trying to get a specific value from my JSON data. I could successfully call the entire json data,jsonOutput. But the thing is when I call a specific value in the jsonOutput, it shows me nullPointerError. I do not know why I lost the data when I call my data class. I marked the part I lost them with The problem occurs here. How can I get adminArea1?
I shared one data class as a sample. You can create the data classes with "Kotlin data class File from JSON".
I referred to many answers and examples but was hard to know the reason.
My code
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.networkBtn.setOnClickListener(View.OnClickListener {
var thread = NetworkThread()
thread.start()
})
}
inner class NetworkThread : Thread() {
override fun run() {
var url =
URL("https://www.mapquestapi.com/geocoding/v1/reverse?key=LBK8QWxDPYHfmeYVlEP1IO3BVbWHyznB&" +
"location=Your_laptitue,Your_longitute&includeRoadMetadata=true&includeNearestIntersection=true")
var countryCodeBufferedReader =
BufferedReader(InputStreamReader(url.openConnection().getInputStream()))
var stringBuffer = StringBuffer()
do {
var string = countryCodeBufferedReader.readLine()
if (string != null) {
stringBuffer.append(string)
}
} while (string != null)
var jsonObject = JSONObject(stringBuffer.toString())
val gson: Gson = GsonBuilder().setPrettyPrinting().create()
val jsonOutput: String = gson.toJson(jsonObject)
//The problem occurs here
var countryData = gson.fromJson(jsonOutput, NameValuePairsXXXXX::class.java)
val jsonOutput2 = countryData.adminArea1
Log.d("jsonOutput", jsonOutput)
Log.d("jsonOutput2", jsonOutput2)
runOnUiThread {
binding.lapLonText.text = jsonOutput2
}
}
}
}
Data class
Use this class and use Response data class to parse json,
data class Response(
val options: Options? = null,
val results: List<ResultsItem?>? = null,
val info: Info? = null
)
data class Options(
val thumbMaps: Boolean? = null,
val maxResults: Int? = null,
val ignoreLatLngInput: Boolean? = null
)
data class LatLng(
val lng: Double? = null,
val lat: Double? = null
)
data class Info(
val statuscode: Int? = null,
val copyright: Copyright? = null,
val messages: List<Any?>? = null
)
data class ProvidedLocation(
val latLng: LatLng? = null
)
data class Copyright(
val imageAltText: String? = null,
val imageUrl: String? = null,
val text: String? = null
)
data class DisplayLatLng(
val lng: Double? = null,
val lat: Double? = null
)
data class LocationsItem(
val dragPoint: Boolean? = null,
val displayLatLng: DisplayLatLng? = null,
val adminArea4: String? = null,
val unknownInput: String? = null,
val adminArea5: String? = null,
val adminArea6: String? = null,
val postalCode: String? = null,
val adminArea1: String? = null,
val adminArea3: String? = null,
val sideOfStreet: String? = null,
val type: String? = null,
val adminArea6Type: String? = null,
val geocodeQualityCode: String? = null,
val adminArea4Type: String? = null,
val linkId: String? = null,
val roadMetadata: Any? = null,
val street: String? = null,
val nearestIntersection: Any? = null,
val adminArea5Type: String? = null,
val mapUrl: String? = null,
val geocodeQuality: String? = null,
val adminArea1Type: String? = null,
val adminArea3Type: String? = null,
val latLng: LatLng? = null
)
data class ResultsItem(
val locations: List<LocationsItem?>? = null,
val providedLocation: ProvidedLocation? = null
)
var countryData = gson.fromJson(jsonOutput, Reponse::class.java)
It was caused due to API communication. I solved my problem by putting okHttpClient. I added the code to help anybody having the same question.
val client = OkHttpClient()
val request = Request.Builder().url(url).build()
client.newCall(request).enqueue(object :Callback{
override fun onFailure(call: Call, e: IOException) {
Log.d("fail", "fail")
}
override fun onResponse(call: Call, response: okhttp3.Response) {
var body = response.body?.string()
Log.d("body", "$body")
val jsonObject2 : JSONObject = JSONObject(body.toString())
val jsonOutput2 = gson.fromJson(body, Response::class.java)
val test2 = jsonOutput2.results?.get(0)?.locations?.get(0)?.adminArea1.toString()
Log.d("test2", test2) }}
I want to use coroutines in my project only when I use coroutines I get the error :Unable to invoke no-args constructor. I don't know why it's given this error. I am also new to coroutines.
here is my apiclient class:
class ApiClient {
val retro = Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
Here is my endpoint class:
#GET("v2/venues/search")
suspend fun get(
#Query("near") city: String,
#Query("limit") limit: String = Constants.limit,
#Query("radius") radius: String = Constants.radius,
#Query("client_id") id: String = Constants.clientId,
#Query("client_secret") secret: String = Constants.clientSecret,
#Query("v") date: String
): Call<VenuesMainResponse>
my Repository class:
class VenuesRepository() {
private val _data: MutableLiveData<VenuesMainResponse?> = MutableLiveData(null)
val data: LiveData<VenuesMainResponse?> get() = _data
suspend fun fetch(city: String, date: String) {
val retrofit = ApiClient()
val api = retrofit.retro.create(VenuesEndpoint::class.java)
api.get(
city = city,
date = date
).enqueue(object : Callback<VenuesMainResponse>{
override fun onResponse(call: Call<VenuesMainResponse>, response: Response<VenuesMainResponse>) {
val res = response.body()
if (response.code() == 200 && res != null) {
_data.value = res
} else {
_data.value = null
}
}
override fun onFailure(call: Call<VenuesMainResponse>, t: Throwable) {
_data.value = null
}
})
}
}
my ViewModel class:
class VenueViewModel( ) : ViewModel() {
private val repository = VenuesRepository()
fun getData(city: String, date: String): LiveData<VenuesMainResponse?> {
viewModelScope.launch {
try {
repository.fetch(city, date)
} catch (e: Exception) {
Log.d("Hallo", "Exception: " + e.message)
}
}
return repository.data
}
}
part of activity class:
class MainActivity : AppCompatActivity(){
private lateinit var venuesViewModel: VenueViewModel
private lateinit var adapter: HomeAdapter
private var searchData: List<Venue>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val editText = findViewById<EditText>(R.id.main_search)
venuesViewModel = ViewModelProvider(this)[VenueViewModel::class.java]
venuesViewModel.getData(
city = "",
date = ""
).observe(this, Observer {
it?.let { res ->
initAdapter()
rv_home.visibility = View.VISIBLE
adapter.setData(it.response.venues)
searchData = it.response.venues
println(it.response.venues)
}
})
this is my VenuesMainResponse data class
data class VenuesMainResponse(
val response: VenuesResponse
)
I think the no-args constructor warning should be related to your VenuesMainResponse, is it a data class? You should add the code for it as well and the complete Log details
Also, with Coroutines you should the change return value of the get() from Call<VenuesMainResponse> to VenuesMainResponse. You can then use a try-catch block to get the value instead of using enqueue on the Call.
Check this answer for knowing about it and feel free to ask if this doesn't solve the issue yet :)
UPDATE
Ok so I just noticed that it seems that you are trying to use the foursquare API. I recently helped out someone on StackOverFlow with the foursquare API so I kinda recognize those Query parameters and the Venue response in the code you provided above.
I guided the person on how to fetch the Venues from the Response using the MVVM architecture as well. You can find the complete code for getting the response after the UPDATE block in the answer here.
This answer by me has code with detailed explanation for ViewModel, Repository, MainActivity, and all the Model classes that you will need for fetching Venues from the foursquare API.
Let me know if you are unable to understand it, I'll help you out! :)
RE: UPDATE
So here is the change that will allow you to use this code with Coroutines as well.
Repository.kt
class Repository {
private val _data: MutableLiveData<mainResponse?> = MutableLiveData(null)
val data: LiveData<mainResponse?> get() = _data
suspend fun fetch(longlat: String, date: String) {
val retrofit = Retro()
val api = retrofit.retro.create(api::class.java)
try {
val response = api.get(
longLat = longlat,
date = date
)
_data.value = response
} catch (e: Exception) {
_data.value = null
}
}
}
ViewModel.kt
class ViewModel : ViewModel() {
private val repository = Repository()
val data: LiveData<mainResponse?> = repository.data
fun getData(longLat: String, date: String) {
viewModelScope.launch {
repository.fetch(longLat, date)
}
}
}
api.kt
interface api {
#GET("v2/venues/search")
suspend fun get(
#Query("ll") longLat: String,
#Query("client_id") id: String = Const.clientId,
#Query("client_secret") secret: String = Const.clientSecret,
#Query("v") date: String
): mainResponse
}
MainActivity.kt
private val viewModel by viewModels<ViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.getData(
longLat = "40.7,-74",
date = "20210718" // date format is: YYYYMMDD
)
viewModel.data
.observe(this, Observer {
it?.let { res ->
res.response.venues.forEach { venue ->
val name = venue.name
Log.d("name ",name)
}
}
})
}
}
So I'm trying to use themoviedb for extracting search results for movies. The url is as follows:
https://api.themoviedb.org/3/search/movie?api_key={apikey}&language=en-US&query={query}
Where in the query I insert the keyword that I want to search. I'm using retrofit library to do this.
This is my code for my ApiService:
interface ApiService {
#GET("3/search/movie?api_key=${BuildConfig.MOVIE_TOKEN}&language=en-US&")
fun getMovies(
#Query("query") query: String
): Call<SearchMovieResponse>
}
This is my code for the ApiConfig object:
class ApiConfig {
companion object {
fun getApiService(): ApiService{
val loggingInterceptor =
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
val client = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.themoviedb.org/")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
return retrofit.create(ApiService::class.java)
}
}
}
I also have a RemoteDataSouce class which uses that config to get the movies. I have also generated the data class to using POJO. This is the method in the RemoteDataSource class that uses that the API config.
fun getMovies():List<MoviesItem>?{
val client = ApiConfig.getApiService().getMovies("john")
var listMovies: ArrayList<MoviesItem> = ArrayList<MoviesItem>()
client.enqueue(object: Callback<SearchMovieResponse> {
override fun onResponse(call: Call<SearchMovieResponse>, response: Response<SearchMovieResponse>) {
if (response.isSuccessful){
val rawList = response.body()?.results!!
for (item in rawList){
listMovies.add(item)
}
}
}
override fun onFailure(call: Call<SearchMovieResponse>, t: Throwable) {
return
}
})
return listMovies
}
The json response of the API is this:
The data model that I use for SearchMovieResponse is this:
data class SearchShowResponse(
#field:SerializedName("page")
val page: Int? = null,
#field:SerializedName("total_pages")
val totalPages: Int? = null,
#field:SerializedName("results")
val results: List<ShowsItem?>? = null,
#field:SerializedName("total_results")
val totalResults: Int? = null
)
data class ShowsItem(
#field:SerializedName("first_air_date")
val firstAirDate: String? = null,
#field:SerializedName("overview")
val overview: String? = null,
#field:SerializedName("original_language")
val originalLanguage: String? = null,
#field:SerializedName("genre_ids")
val genreIds: List<Int?>? = null,
#field:SerializedName("poster_path")
val posterPath: String? = null,
#field:SerializedName("origin_country")
val originCountry: List<String?>? = null,
#field:SerializedName("backdrop_path")
val backdropPath: String? = null,
#field:SerializedName("original_name")
val originalName: String? = null,
#field:SerializedName("popularity")
val popularity: Double? = null,
#field:SerializedName("vote_average")
val voteAverage: Double? = null,
#field:SerializedName("name")
val name: String? = null,
#field:SerializedName("id")
val id: Int? = null,
#field:SerializedName("vote_count")
val voteCount: Int? = null
)
However, the listMovies is returning null. I'm not sure what I did wrong here. Can anyone explain? Thanks
Your method getMovies() is returning the list before the Retrofit call is done, you are using enqueue() method that run it asynchronous so your method finish before the onResponse() method is called.
Solution, rewrite your code thinking about this information or use execute()method instead enqueue(), this will execute the call in the main thread so you will have to call it in a new thread or a coroutine.
As, you are using enqueue() that run asynchronous so your function finish before the onResponse() method is called. So you have to return the list after on the complete of the process.
fun getMovies():List<MoviesItem>?{
val client = ApiConfig.getApiService().getMovies("john")
var listMovies: ArrayList<MoviesItem> = ArrayList<MoviesItem>()
client.enqueue(object: Callback<SearchMovieResponse> {
override fun onResponse(call: Call<SearchMovieResponse>, response: Response<SearchMovieResponse>) {
if (response.isSuccessful){
val rawList = response.body()?.results!!
for (item in rawList){
listMovies.add(item)
}
return listMovies
}
}
override fun onFailure(call: Call<SearchMovieResponse>, t: Throwable) {
return
}
})
}
Try to use callback to return your list:
fun getMovies(callback: (List<MoviesItem>) -> Unit) {
val client = ApiConfig.getApiService().getMovies("john")
client.enqueue(object : Callback<SearchMovieResponse> {
override fun onResponse(
call: Call<SearchMovieResponse>,
response: Response<SearchMovieResponse>
) {
var listMovies: ArrayList<MoviesItem> = ArrayList<MoviesItem>()
if (response.isSuccessful) {
val rawList = response.body()?.results!!
for (item in rawList) {
listMovies.add(item)
}
}
callback(listMovies)
}
override fun onFailure(call: Call<SearchMovieResponse>, t: Throwable) {
callback(emptyList()) // or throw error or use Result structure
}
})
}
I am trying to get the response from https://www.reddit.com/r/popular/.rss and map to Kotlin POJO class in Android. But when I am logging that category's label value, getting null. For the title I am getting response value as popular links.
Here is entity class FeedX:-
#Root(name = "feed", strict = false)
class FeedX {
#set: Element(name = "category")
#get: Element(name = "category")
var category: Category? = null
val entry: List<Entry>? = null
val id: String? = null
val link: List<LinkX>? = null
#set: Element(name = "title")
#get: Element(name = "title")
var title: String? = null
val updated: String? = null
}
Category class:-
#Root(name = "category", strict = false)
class Category {
#set: Element(required = false, name = "_label")
#get: Element(required = false, name = "_label")
var _label: String? = null
val _term: String? = null
}
Here is Api Interface:-
interface FeedApi {
#GET("{type}/.rss")
fun getPopularFeeds(
#Path("type") type: String?
): Call<FeedX>?
}
Here is MainActivity:-
class MainActivity : AppCompatActivity() {
private val BASE_URL = "https://www.reddit.com/r/"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(SimpleXmlConverterFactory.create())
.build()
val service = retrofit.create(FeedApi::class.java)
service.getPopularFeeds("popular")?.enqueue(object : Callback<FeedX> {
override fun onFailure(call: Call<FeedX>, t: Throwable) {
Log.d("Response Failed", "${t.localizedMessage}")
}
override fun onResponse(call: Call<FeedX>, response: Response<FeedX>) {
if (response.isSuccessful) {
Log.d("Response Success", "${response.body()!!.title}") // for this I am getting value
Log.d("Response Success", "${response.body()!!.category?._label}") // always getting null value
} else {
Log.d("Response Failed jg", "${response.errorBody()}")
}
}
})
}
}
This is because title contains a value where category tag doesn't. See the difference below.
<title>popular links</title>
<category term="AskReddit" label="r/AskReddit"/>
As you can see category tag is self closing.