class Service{
interface RedditApi {
#GET("/top.json")
fun getTop(#Query("after") after: String,
#Query("limit") limit: String)
: Deferred<Response<News>>;
}
}
val okHttpClient = OkHttpClient.Builder()
.readTimeout(40, TimeUnit.SECONDS)
.addInterceptor { chain ->
val ongoing = chain.request().newBuilder()
ongoing.addHeader("Cache-Control", "no-cache")
ongoing.addHeader("User-Agent", System.getProperty("http.agent"))
//ongoing.addHeader("Authorization", val.trim());
chain.proceed(ongoing.build())
}
.connectTimeout(40, TimeUnit.SECONDS)
.build()
val retrofit = Retrofit.Builder()
.baseUrl( "/rest/s1/mobile/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.client(okHttpClient)
.build()
redditApi = retrofit.create(Service.RedditApi::class.java)
Okey I have that, am trying to use retrofit with Coroutine. I go to my activity and implement it like below.I get error dispatchers.main unresolved reference main.I am using kotlin 1.3.21. Also my other question is, what if user clicks back on the activity how can I cancel the coroutine operation?Like In Java I used to do call.cancel() with retrofit.It cancelled the call.
class MainActivity : AppCompatActivity(), Fightable {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CoroutineScope(Dispatchers.IO).launch {
val request = App.redditApi.getTop("after", "limit")
withContext(Dispatchers.Main) {
try {
val response = request.await()
if (response.isSuccessful) {
val news: News? = response.body()
//Do something with response e.g show to the UI.
} else {
}
} catch (e: HttpException) {
} catch (e: Throwable) {
}
}
}}}
You need to create a single instance of coroutine context and also have a job defined to it.
val job = Job()
val coroutineScope = CoroutineContext(Dispatchers.Main+job)
And start the work using the declared scope and when you want to cancel the work, you can simply call job.cancel() which cancels all current and upcoming works.
Related
I've been developing an Android Q&A app using Jetpack Compose. I've been trying to make Post requests in Retrofit but the data I send isn't on my API website. I've succeeded in making Get requests though. I've read many documents but I cannot find out what is wrong with this code.
This is data class.
data class UsersEntity(
val id: Int? = null,
val name: String? = null,
val uid: String? = null
)
This is Service interface.
interface UserService {
#POST("createusers")
fun createUsers(#Body usersinfo: UsersEntity): Call<Unit>
}
When I click a button, I'd like to send data to the server. I get the log "Hi, good job" but I cannot see the data on my API.
Button(
onClick = {
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.*****.com/")
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
val service: UserService = retrofit.create(UserService::class.java)
val usersInfo = UsersEntity(
3, "Alex", "164E92FC-D37A")
service.createUsers(usersInfo).enqueue(object: Callback<Unit> {
override fun onResponse(call: Call<Unit>, response: Response<Unit>) {
Log.d("Hi", "good job")
}
override fun onFailure(call: Call<Unit>, t: Throwable) {
Log.d("Hi", "error")
}
})
}
I changed the code like this.
Button(
onClick = {
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.*****.com/")
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
thread {
try {
val service: UserService = retrofit.create(UserService::class.java)
val usersInfo = UsersEntity(
3, "Alex", "164E92FC-D37A")
service.createUsers(usersInfo).enqueue(object: Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
Log.d("Response", "${response.body()}")
}
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
Log.d("Hi", "error")
}
})
} catch (e: Exception) {
Log.d("response", "debug $e")
}
}
},
Could someone help me? Thank you.
I think your baseurl shouldn't end with a slash. Try this.
.baseUrl("https://api.*****.com")
And for your interface (also the Call<ResponseBody>):
interface UserService {
#POST("/createusers/")
fun createUsers(#Body usersinfo: UsersEntity): Call<ResponseBody>
}
Got some issues with this in the past so this might help. If not it atleasts cleans the code a bit :p
Also you can use ProxyMan to intercept your request and read what your application is actually sending to the server, might be a issue to find there!
Proxyman.io
I'm making cryptocurrency information viewer using Retrofit. Everything is okay, but have a problem.
One exchange demands me execute retrofit twice.
https://api.upbit.com/v1/market/all
https://api.upbit.com/v1/ticker?markets=KRW-BTC,KRW-ETH,KRW-BTG,KRW-BSV,KRW-ETC ..., KRW-(Some cryptocurrency)
Base url is "https://api.upbit.com/v1/". And I have to get 'markets' from first api, and get cryptocurrency's information by using 'markets' as query value.
But there are problem. I thought first and second execution. Get markets from first execution, and get infomations by using markets. But Retrofit basically runs asynchronously, So usually skip first execution and there are no markets in second executions. (markets = "")
How can I run Retrofit in Retrofit? Did I approach wrong? Or there are solution for this problem?
This is Retrofit parts in my code.
var markets = ""
val publishSubject: PublishSubject<String> = PublishSubject.create()
init {
publishSubject.subscribe {
markets = it
}
}
fun getData(exchange: String) {
// var markets = "Basic"
val url = when(exchange) {
coinone -> "https://api.coinone.co.kr/"
bithumb -> "https://api.bithumb.com/"
upbit -> {
getMarketsUpbit()
"https://api.upbit.com/v1/"
}
else -> "https://api-cloud.huobi.co.kr/"
}
val parser = DataParser()
val builder = Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(RetrofitService::class.java)
val call: retrofit2.Call<Any> =
when (exchange) {
coinone ->
builder.getTickersCoinone("all")
bithumb ->
builder.getTickersBithumb()
upbit ->
builder.getTickersUpbit(markets)
else ->
builder.getTickersHuobi()
}
call.enqueue(object : retrofit2.Callback<Any> {
override fun onResponse(call: retrofit2.Call<Any>, response: retrofit2.Response<Any>) {
coinInfos.value = parser.getParsedData(
if (exchange != "upbit") exchange
else markets
,
response.body().toString()
)
}
override fun onFailure(call: retrofit2.Call<Any>, t: Throwable) {
println("Retrofit process is failed.")
}
})
}
private fun getMarketsUpbit() {
val parser = DataParser()
var markets = ""
val builder = Retrofit.Builder()
.baseUrl("https://api.upbit.com/v1/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(RetrofitService::class.java)
val call: retrofit2.Call<Any> = builder.getMarketsUpbit()
call.enqueue(object : retrofit2.Callback<Any> {
override fun onResponse(call: retrofit2.Call<Any>, response: retrofit2.Response<Any>) {
publishSubject.onNext(parser.parseUpbitMarkets(response.body().toString()))
}
override fun onFailure(call: retrofit2.Call<Any>, t: Throwable) {
println("Retrofit process is failed.")
}
})
}
I'm trying to implement application, which will work with websocket. So I choose scarlet. I can see in logs a response from a server, but I cant consume a data in my viewModel. How to do that? I am using Koin + viewModel + coroutine
Module for Koin
val networkModule = module {
single { createScarlet() }
single <ChatSocketRepository> {
ChatSocketRepositoryImpl(get())
}
}
private fun createScarlet(): ChatSocketApi {
val client = OkHttpClient.Builder()
.readTimeout(DataProviderImplementation.TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DataProviderImplementation.TIMEOUT, TimeUnit.SECONDS)
.connectTimeout(DataProviderImplementation.TIMEOUT, TimeUnit.SECONDS)
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.build()
return Scarlet.Builder()
.webSocketFactory(client.newWebSocketFactory("wss://demos.kaazing.com/echo"))
.addMessageAdapterFactory(GsonMessageAdapter.Factory())
.addStreamAdapterFactory(CoroutinesStreamAdapterFactory())
.build()
.create()
}
ChatSocketApi
interface ChatSocketApi {
#Receive
fun observeText(): ReceiveChannel<String>
}
ChatSocketRepository
interface ChatSocketRepository {
fun observeTest(): ReceiveChannel<String>
}
ChatSocketRepositoryImpl:
class ChatSocketRepositoryImpl(private val api: ChatSocketApi) : ChatSocketRepository {
override fun observeTest(): ReceiveChannel<String> {
return api.observeText()
}
}
ViewModel
class MyViewModel(private val chatSocketRepository: ChatSocketRepository) : BaseViewModel() {
init {
viewModelScope.launch {
val text = chatSocketRepository.observeTest().consumeEach {
Log.d("SOCKET", it.toString())
}
}
}
you already get the data, and your data is String because you declare ReceiveChannel<String> you can use the received data and put it in a LiveData. change the value of livedata in the .consumeEach{} method instead.
I am developing a new android app but I am getting the following exception Unable to invoke no-args constructor for kotlinx.coroutines.Deferred>. Registering an InstanceCreator with Gson for this type may fix this problem.
below my MainViewModel.kt
#Suppress("UNCHECKED_CAST")
class MainViewModel(val newsRepository: NewsRepository) : ViewModel(), CoroutineScope {
// Coroutine's background job
val job = Job()
// Define default thread for Coroutine as Main and add job
override val coroutineContext: CoroutineContext = Dispatchers.Main + job
val showLoading = MutableLiveData<Boolean>()
val sportList = MutableLiveData <List<Article>>()
val showError = SingleLiveEvent<String>()
fun loadNews() {
// Show progressBar during the operation on the MAIN (default) thread
showLoading.value = true
// launch the Coroutine
launch {
// Switching from MAIN to IO thread for API operation
// Update our data list with the new one from API
val result = withContext(Dispatchers.IO) {
newsRepository?.getNewsList()
}
// Hide progressBar once the operation is done on the MAIN (default) thread
showLoading.value = false
when (result) {
is UseCaseResult.Success<*> -> {
sportList.value = result.data as List<Article>
}
is Error -> showError.value = result.message
}
}
}
override fun onCleared() {
super.onCleared()
// Clear our job when the linked activity is destroyed to avoid memory leaks
job.cancel()
}
}
below appmodules.kt my network logic implementation
const val BASE_URL = "https://newsapi.org/"
val appModules = module {
// The Retrofit service using our custom HTTP client instance as a singleton
single {
createWebService<SportNewsInterface>(
okHttpClient = createHttpClient(),
factory = RxJava2CallAdapterFactory.create(),
baseUrl = BASE_URL
)
}
// Tells Koin how to create an instance of CatRepository
factory<NewsRepository> { (NewsRepositoryImpl(sportsNewsApi = get())) }
// Specific viewModel pattern to tell Koin how to build MainViewModel
viewModel { MainViewModel (newsRepository = get ()) }
}
/* Returns a custom OkHttpClient instance with interceptor. Used for building Retrofit service */
fun createHttpClient(): OkHttpClient {
val client = OkHttpClient.Builder()
client.readTimeout(5 * 60, TimeUnit.SECONDS)
return client.addInterceptor {
val original = it.request()
val requestBuilder = original.newBuilder()
requestBuilder.header("Content-Type", "application/json")
val request = requestBuilder.method(original.method, original.body).build()
return#addInterceptor it.proceed(request)
}.build()
}
/* function to build our Retrofit service */
inline fun <reified T> createWebService(
okHttpClient: OkHttpClient,
factory: CallAdapter.Factory, baseUrl: String
): T {
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.addCallAdapterFactory(factory)
.client(okHttpClient)
.build()
return retrofit.create(T::class.java)
}
below SportNewsResponse.kt
data class SportNewsResponse(
val articles: List<Article>,
val status: String,
val totalResults: Int
)
below SportNewsInterface where I have implemented my ending points
interface SportNewsInterface {
#GET("v2/top-headlines?country=us&apiKey=da331087e3f3462bb534b3b0917cbee9")
suspend fun getNews(): Deferred<List<SportNewsResponse>>
#GET("/v2/top-headlines?sources=espn&apiKey=da331087e3f3462bb534b3b0917cbee9")
fun getEspn(): Deferred<List<SportNewsResponse>>
#GET("/v2/top-headlines?sources=football-italia&apiKey=da331087e3f3462bb534b3b0917cbee9")
fun getFootballItalia(): Deferred<List<SportNewsResponse>>
#GET("/v2/top-headlines?sources=bbc-sport&apiKey=da331087e3f3462bb534b3b0917cbee9")
fun getBBCSport(): Deferred<List<SportNewsResponse>>
}
I am trying to post data on server using retrofit2 and rxjava2 after data posted successfully on server I want to get response from server.I am using kotlin so how can I get server response in my app.
This is what I have done so far:
AddHero.kt
class AddHero : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_hero)
addHero.setOnClickListener {
if(hero.text.toString().equals("")){
Toast.makeText(applicationContext,"Enter superhero name",Toast.LENGTH_SHORT).show()
}
else if(movie.text.toString().equals("")){
Toast.makeText(applicationContext,"Enter movie name",Toast.LENGTH_SHORT).show()
}
else{
saveData()
}
}
}
private fun saveData() {
RetrofitClient.create().saveHero(hero.text.toString(),movie.text.toString())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe()
}
}
RetrofitClient.kt
object RetrofitClient {
fun create():ApiService{
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(12,TimeUnit.SECONDS)
.readTimeout(12,TimeUnit.SECONDS)
.writeTimeout(12,TimeUnit.SECONDS)
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://www.example.com")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
.client(okHttpClient)
.build()
val service = retrofit.create(ApiService::class.java)
return service
}
}
ApiService.kt
interface ApiService {
#POST("createHero")
#FormUrlEncoded
fun saveHero(#Field("name") name:String,
#Field("movie") movie:String):Observable<Hero>
}
Hero.kt
data class Hero (
#SerializedName("name")
val name:String,
#SerializedName("movie")
val movie:String
)
Someone please let me know what I am doing wrong or missing. Any help would be appreciated.
THANKS
Your ApiService saveHero function returns Observable<Hero>, you should get your response in subscribe(onNext, onError), like this:
RetrofitClient.create().saveHero(hero.text.toString(), movie.text.toString())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ hero -> Log.d("AddHeroTag", hero.toString()) },
{ error -> Log.e("AddHero", error.message, error) })
And don't forget to check if your object is non-null