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.
Related
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
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 want to retrieve data from Datasource. But turns out it didnt get executed at all. How can i solve it? here is my code
Repository.kt
// all logcat in repository is executed
override fun getDetailGame(id: Int): Flow<Resource<Game>> {
Log.d("Repo", "getDetailGame: called")
return flow {
Log.d("Repo", "getDetailGame: before flow")
remoteDataSource.getDetailGame(id)
Log.d("Repo", "getDetailGame: after flow")
}
}
Datasource.kt
suspend fun getDetailGame(id: Int): Flow<ApiResponse<GameResponse>> =
flow {
try {
// didnt get executed all
Log.d(TAG, "getDetailGame: called")
val response = apiService.getDetailGame(id)
if (response != null) {
emit(ApiResponse.Success(response))
} else {
emit(ApiResponse.Empty)
}
} catch (ex: Exception) {
emit(ApiResponse.Error(ex.message.toString()))
Log.e(TAG, "getDetailGame: ${ex.message} ")
}
}.flowOn(Dispatchers.IO)
Edit: add additional code for other file
ApiResponse.kt (response state management for the datasource)
sealed class ApiResponse<out R> {
data class Success<out T>(val data: T) : ApiResponse<T>()
data class Error(val errorMessage: String) : ApiResponse<Nothing>()
object Empty : ApiResponse<Nothing>()
}
Resource.kt (state management for UI like loading state etc)
sealed class Resource<T>(val data: T? = null, val message: String? = null) {
class Success<T>(data: T) : Resource<T>(data)
class Loading<T>(data: T? = null) : Resource<T>(data)
class Error<T>(message: String, data: T? = null) : Resource<T>(data, message)
}
GameResponse.kt (same like Game but with serializedname for json)
data class GameResponse(
#field:SerializedName("id")
var id: Int,
#field:SerializedName("name")
var title: String,
#field:SerializedName("released")
var released: String? = null,
#field:SerializedName("metacritic")
var metacritic: Int? = null,
#field:SerializedName("metacritic_url")
var metacriticUrl: Int? = null,
#field:SerializedName("background_image")
var bgImage: String? = null,
#field:SerializedName("description")
var description: String? = null,
#field:SerializedName("game_series_count")
var gameSeriesCount: Int? = 0
)
Game.kt (same like GameResponse but the clean version of it)
data class Game(
var id: Int,
var title: String,
var released: String? = null,
var metacritic: Int? = null,
var metacriticUrl: Int? = null,
var bgImage: String? = null,
var description: String? = null,
var gameSeriesCount: Int? = 0
)
Flows are cold streams, this means they wont be executed until you collect them.
If you are trying to convert Flow<ApiResponse> to Flow<Resource>, you should use the map function. If you need a more complex transformation, use transform instead.
override fun getDetailGame(id: Int): Flow<Resource<GameResponse>> {
return remoteDataSource.getDetailGame(id).map { response ->
when (response) {
ApiResponse.Empty -> Resource.Loading()
is ApiResponse.Success -> Resource.Success(response.data)
is ApiResponse.Error -> Resource.Error(response.errorMessage)
}
}
}
Or if you need to emit a value before perform the transformation:
override fun getDetailGame(id: Int): Flow<Resource<GameResponse>> = flow {
emit(Resource.Loading())
emitAll(
remoteDataSource.getDetailGame(id).map { response ->
when (response) {
is ApiResponse.Success -> Resource.Success(response.data)
is ApiResponse.Error -> Resource.Error(response.errorMessage)
ApiResponse.Empty -> Resource.Error("")
}
}
)
}
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.
onSuccess() of retrofit
override fun onSuccess(call: Call<Any?>?, response: Response<Any?>, tag: Any?) {
when (tag) {
RequestCodes.API.LOGIN -> {
val apiResponse = response.body() as? ModelAPIResponse
val toast = Toast.makeText(
MainApplication.applicationContext(),
"Api Success=${apiResponse?.message}",
Toast.LENGTH_SHORT
)
toast.show()}}}
Image of database body
Model For Api Response
#SerializedName("statusCode")
var statusCode: String? = null
#SerializedName("message")
var message: String? = null
try make the Json Response to Object class Like this
data class ApiResponse(
#SerializedName("statusCode")
var statusCode: String? = null
#SerializedName("message")
var message: String? = null
)
then on your retrofit API call
#GET("your_endpoint")
fun getCartListAsync(): Call<ApiResponse>
then on your success change the Call<Any> into Call<ApiResponse>
I have Found a Way to map the response the do the same thing in IOS which is writing a mapper
val apiResponse = ModelAPIResponse(response.body() as LinkedTreeMap<String, String>)
then in data model
class ModelAPIResponse(data: LinkedTreeMap<String, String>) {
var statusCode: String? = null
var message: String? = null
init {
this.message = data["message"]
this.statusCode = data["statusCode"]
}}