I want to pull asynchronous json data with retrofit and rxjava 3 in the foreground service and show it to the user as a notification, but so far I have not been successful.
#Streaming
#GET("v2/top-headlines")
fun getDayNewsRxJava(
#Query("country") language : String,
#Query("apiKey") key : String
) : Observable<Model1>
val retrofit = Retrofit.Builder()
.baseUrl("https://newsapi.org/")
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build()
fun returnEveryDay() : everydayNews {
return retrofit.create(everydayNews::class.java)
}
newsRetrofit.returnEveryDay().getDayNewsRxJava("language" , "apiKey")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<Model1> {
override fun onSubscribe(d: Disposable?) {
}
override fun onNext(t: Model1?) {
val title = t!!.articles[0].title
Log.d(TAG , "Rx Java Data : ${t?.articles[0].title}")
sendNotification(title)
}
override fun onError(e: Throwable?) {
Log.e(TAG , "Rx Java Error : $e")
}
override fun onComplete() {
Log.d(TAG , "Rx Java Completed")
}
})
I looked at different examples in this way, they did not use a very different structure, but I do not understand why an asynchronous call is not made.
thanks for your help
Related
I am trying to perform a unit test and mock a retrofit call without success. When I run my test, I get only end printed. I should receive onResponse() printed as well.
The code works fine when I run my app, only the test does not call the mocked API call.
Method in ViewModel:
fun loadSensors() {
CoroutineScope(Dispatchers.IO).launch {
sensorsService.getUserSensors(getUserToken(), getUserId())
.enqueue(object : Callback<List<Long>> {
override fun onResponse(
call: Call<List<Long>>,
response: Response<List<Long>>
) {
println("onResponse()")
}
override fun onFailure(call: Call<List<Long>>, t: Throwable) {
println("onFailure()")
}
})
}
println("end")
}
Interface:
#GET("/sensors")
fun getUserSensors(): Call<List<Long>>
App module:
#Provides
#Singleton
fun provideRetrofitFactory(gsonConverterFactory: GsonConverterFactory): Retrofit {
val client = OkHttpClient.Builder().build()
return Retrofit.Builder()
.baseUrl("http://<url>")
.addConverterFactory(gsonConverterFactory)
.client(client)
.build()
}
Test:
#OptIn(DelicateCoroutinesApi::class)
private val mainThreadSurrogate = newSingleThreadContext("UI thread")
#OptIn(ExperimentalCoroutinesApi::class)
#BeforeAll
fun beforeAll() {
Dispatchers.setMain(mainThreadSurrogate)
}
#Test
fun loadSensors() {
val mockedCall = mockk<retrofit2.Call<List<Long>>>()
every { mockedCall.enqueue(any()) } answers {
val callback = args[0] as retrofit2.Callback<List<Long>>
val response = retrofit2.Response.success(200, listOf(1L, 2L, 3L))
callback.onResponse(mockedCall, response)
}
every { sensorsService.getUserSensors(any(), any()) } answers {
mockedCall
}
}
I recommended that you see MockWebServer I am sure with use it you can do anything you have in your mind.
I found some Api about Pokemon(https://pokeapi.co). And I try get data from this Api and it's work.
The data look like this
But I try to use name of data from Api to get some image with this path "api/v2/pokemon/{name}"
The problem is how can get name out of onResponse or do the other way to get name and image of Pokemon
MainActivity
val retrofit = Retrofit.Builder()
.baseUrl("https://pokeapi.co/")
.addConverterFactory(GsonConverterFactory.create())
.client(HTTPLogger.getLogger())
.build()
val jsonPlaceholderApi = retrofit.create(pokemonService::class.java)
val myCall: Call<PokemonInGen> = jsonPlaceholderApi.getGen(1)
myCall.enqueue(object : Callback<PokemonInGen> {
override fun onResponse(
call: Call<PokemonInGen>,
response: Response<PokemonInGen>
) {
val DataResponse: PokemonInGen = response.body()!!
Timber.i("on do Respon %s", DataResponse)
}
override fun onFailure(call: Call<PokemonInGen>, t: Throwable) {
Timber.i("on do ERROR")
}
})
My Service
interface pokemonService {
#GET("api/v2/generation/{id}")
fun getGen(
#Path("id") id: Int,
): Call<PokemonInGen>
#GET("api/v2/pokemon/{name}")
fun getArtwork(
#Path("name") name: String,
): Call<PokemonArtwork>
}
My Model Data class
data class PokemonInGen(
val pokemon_species: List<PokemonList>)
data class PokemonList(
val name: String,
val url: String,
)
To fetch Pokemon image you should create additional function in your MainActivity class
fun fetchPokemonArtwork(name: String) {
jsonPlaceholderApi.getArtwork(name).enqueue(object : Callback<PokemonArtwork> {
override fun onResponse(
call: Call<PokemonArtwork>,
response: Response<PokemonArtwork>
) {
// An artwork is successful fetched
val artwork = response.body()!!
}
override fun onFailure(call: Call<PokemonArtwork>, t: Throwable) {
// Handle a failure
}
})
}
You should call this function right after you fetched a Pokemon in generation
val myCall: Call<PokemonInGen> = jsonPlaceholderApi.getGen(1)
myCall.enqueue(object : Callback<PokemonInGen> {
override fun onResponse(
call: Call<PokemonInGen>,
response: Response<PokemonInGen>
) {
val DataResponse: PokemonInGen = response.body()!!
Timber.i("on do Respon %s", DataResponse)
// Extract name
val name = DataResponse.pokemon_species.first().name
// Fetch an artwork
fetchPokemonArtwork(name)
}
override fun onFailure(call: Call<PokemonInGen>, t: Throwable) {
Timber.i("on do ERROR")
}
})
P.S. I proceeded from the assumption that you've implemented PokemonArtwork class. Please let me know if you are facing difficulties in comments below.
P.S.S. It's not recommended to make network calls in Activity or Fragment classes. This guide to app architecture should help you to select correct app structure in your future releases.
You can using #Url to support dynamic link in retrofit. Example below:
interface pokemonService {
#GET
Call<PokemonResponse> getListPokemon(#Url String url);
}
And paste your url to browser to see data format.
My Android Application is base on a TCP protocol.
When I'm initializing a connection to the server, I'm sending a special bytes message and have to wait the response of the server.
In all the repositories example I have seen the repository have always methods to call the source of information with a return (from Android Developers) :
class UserRepository {
private val webservice: Webservice = TODO()
// ...
fun getUser(userId: String): LiveData<User> {
// This isn't an optimal implementation. We'll fix it later.
val data = MutableLiveData<User>()
webservice.getUser(userId).enqueue(object : Callback<User> {
override fun onResponse(call: Call<User>, response: Response<User>) {
data.value = response.body()
}
// Error case is left out for brevity.
override fun onFailure(call: Call<User>, t: Throwable) {
TODO()
}
})
return data
}
}
The function getUser return a data of LiveData.
In my app the method Login return nothing because I wait for the server to send bytes with a special code to know that is responding to my login request.
Is there a way to implement this pattern with TCP protocols like that ?
Thanks
They honestly should have just written the following code:
class UserRepository {
private val webService: WebService = TODO()
// ...
fun getUser(userId: String, successCallback: (User) -> Unit) {
webService.getUser(userId).enqueue(object : Callback<User> {
override fun onResponse(call: Call<User>, response: Response<User>) {
successCallback(response.body())
}
// Error case is left out for brevity.
override fun onFailure(call: Call<User>, t: Throwable) {
}
})
}
}
LiveData is not meant to represent one-off callbacks.
Then call it as
userRepository.getUser(userId) { user ->
// do whatever
}
For a proper reactive implementation, refer to https://stackoverflow.com/a/59109512/2413303
I am currently developing an Android application that uses the Retrofit library for REST api usage.
For instance, I have the following code from MainActivity.kt :
fun userLogin(){
calls.userLogin() { updateUiComponents() }
}
fun updateUiComponents(){
Toast.makeText(applicationContext, "LAMBDA EXECUTED",Toast.LENGTH_SHORT).show()
}
And I have in a separate file the definition of the Retrofit calls:
fun userLogin(postActionMethod: () -> Unit){
val call = service.userLogin()
call.enqueue(object : Callback<LoginResponse>{
override fun onFailure(call: Call<LoginResponse>?, t: Throwable?) {
Log.i("ERROR RUNNING CALL", t?.message.toString())
}
override fun onResponse(call: Call<LoginResponse>?, response: Response<LoginResponse>?) {
postActionMethod()
}
})
}
After the Retrofit call is implemented and it is successful, reaching the onResponse method, I would like to send the Response object as a parameter of the lambda function back to the MainAcativity.kt. From the MainActivity.kt, the lambda function would use this information to perform some specific task.
Is that a way of defining a lambda function like this, with arguments? If it is the case, how can I pass the lambda function as a parameter like done on the following line:
calls.userLogin(body) { updateUiComponents() }
Thank u!
I don't know if I get what your problem is but a lambda does not need to do not have any parameter. You can easily do something like
fun userLogin(postActionMethod: (Response<LoginResponse>?) -> Unit){
val call = service.userLogin()
call.enqueue(object : Callback<LoginResponse>{
override fun onFailure(call: Call<LoginResponse>?, t: Throwable?) {
Log.i("ERROR RUNNING CALL", t?.message.toString())
}
override fun onResponse(call: Call<LoginResponse>?, response: Response<LoginResponse>?) {
postActionMethod(response)
}
})
}
so you consume it with
fun userLogin(){
calls.userLogin() { updateUiComponents(it) }
}
fun updateUiComponents(response: Response<LoginResponse>?){
Toast.makeText(applicationContext, "LAMBDA EXECUTED",Toast.LENGTH_SHORT).show()
}
I would like to get a simple string from an API.
Usually, i can get everything I want from an API with the following functions :
class OrderRepositoryImpl(val orderService: OrderService) : OrderRepository {
override fun getPaymentMethods(id: String, success: (List<PaymentMode>) -> Unit, failure: (Throwable) -> Unit): Subscription {
return orderService.getPaymentMethods(id)
.subscribeOn(Schedulers.io())
.map { it.entrySet() }
.map { it.map { it.value }.map {it.asJsonObject } }
.map { it.map { PaymentMode().apply { loadFromJson(it) } } }
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ success.invoke(it) }, { failure.invoke(it) })
}
}
And in OrderService :
#GET("api/order/payment/modes/list/{id}")
fun getPaymentMethods(#Path("id") id: String): Observable<JsonObject>
This works perfectly, on an API with regular Json Objects.
But today, I have a problem : I have an API with a unique string, like this :
"validated"
or :
"draft"
So I made the followin function (in OrderRepositoryImpl class) :
override fun getOrderStatus(id: String, success: (String) -> Unit, failure: (Throwable) -> Unit) =
orderService.getOrderStatus(id)
.subscribeOn(Schedulers.io())
.map { it }
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ success.invoke(it.toString()) }, { failure.invoke(it) } )
And in Order Service :
#GET("api/order/checkout/{id}/status")
fun getOrderStatus(#Path("id") id: String): Observable<JsonObject>
I call this method like this :
fun getOrderStatus(id : Long) {
orderRepository.getOrderStatus(id.toString(), {
println("SUCCESS !")
println("STATUS == $it")
}, {
println("FAILURE...")
})
}
But I don't get anything from "success.invoke" line. When I call this method in my code, i always have "FAILURE" in my logs... Even if one of the logs lines is :
D/OkHttp: "validated"
which is exactly what I want to see in case of success.
I know it is pretty strange I can get and parse json objects and not a string, but I kind of learned on the job...
How can I get a simple string from an API ?
Okay, I am SO ashamed.
So, I printed the error in my "FAILURE..." section, and I got something like :
got jsonPrimitive but expected jsonObject
As the API was just returning me a string, and NOT an object, it is call a Json Primitive.
So I just changed the return of my functions in OrderService :
#GET("api/order/checkout/{id}/status")
fun getOrderStatus(#Path("id") id: String): Observable<JsonPrimitive>
Thanks for your helpful comments.
interface ServiceInterFace {
#POST("api/order/checkout/{id}/status")
fun getOrderStatus(#Path("id") id: String): Call<String>
}
// calling from your main class
val id: String? = null
val retrofit = Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.baseUrl("----Your link here-----")
.build()
val scalarService = retrofit.create(ServiceInterFace::class.java!!)
val stringCall = scalarService.getOrderStatus(id)
stringCall.enqueue(object : Callback<String> {
override fun onResponse(call: Call<String>, response: Response<String>) {
if (response.isSuccessful) {
val responseString = response.body()//get response here
}
}
override fun onFailure(call: Call<String>, t: Throwable) {
Toast.makeText(this#Main2Activity, "Failed to connect server",
Toast.LENGTH_SHORT).show()
}
})
//import
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.3.0'