With code along the lines of the following, how can I access the HTTP response headers? I cannot find an API in Apollo that lets me access the HTTP response.
fun getUser(result: AuthenticatedResult<Boolean>): Disposable {
val query = FetchUserQuery.builder().build()
val call = ApolloManager.client.query(query).httpCachePolicy(HttpCachePolicy.NETWORK_ONLY)
val disposable = object : DisposableSingleObserver<Response<FetchUserQuery.Data>>() {
override fun onSuccess(data: Response<FetchUserQuery.Data>) {
if (!data.hasErrors()) {
persistUser(data)
result.success(true)
} else {
result.authenticationFailed()
}
}
override fun onError(e: Throwable) {
...
}
}
Rx2Apollo.from(call)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.singleOrError()
.subscribe(disposable)
return disposable
}
Related
The question about post requests in android has been asked before, but all the solutions I've tried have not worked properly. On top of that, a lot of them seem to be overly complicated as well. All I wish to do is make a post to a specific sight with a few body parameters. Is there any simple way to do that?
Let me explain my request calling structure using Retrofit.
build.gradle(app)
// Retrofit + GSON
implementation 'com.squareup.okhttp3:logging-interceptor:4.4.0'
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
ApiClient.kt
object ApiClient {
private const val baseUrl = ApiInterface.BASE_URL
private var retrofit: Retrofit? = null
private val dispatcher = Dispatcher()
fun getClient(): Retrofit? {
val logging = HttpLoggingInterceptor()
if (BuildConfig.DEBUG)
logging.level = HttpLoggingInterceptor.Level.BODY
else
logging.level = HttpLoggingInterceptor.Level.NONE
if (retrofit == null) {
retrofit = Retrofit.Builder()
.client(OkHttpClient().newBuilder().readTimeout(120, TimeUnit.SECONDS)
.connectTimeout(120, TimeUnit.SECONDS).retryOnConnectionFailure(false)
.dispatcher(
dispatcher
).addInterceptor(Interceptor { chain: Interceptor.Chain? ->
val newRequest = chain?.request()!!.newBuilder()
return#Interceptor chain.proceed(newRequest.build())
}).addInterceptor(logging).build()
)
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return retrofit
}
}
ApiClient will be used to initialize Retrofit singleton object, also initialize logging interceptors so you can keep track of the requests and responses in the logcat by using the keyword 'okhttp'.
SingleEnqueueCall.kt
object SingleEnqueueCall {
var retryCount = 0
lateinit var snackbar: Snackbar
fun <T> callRetrofit(
activity: Activity,
call: Call<T>,
apiName: String,
isLoaderShown: Boolean,
apiListener: IGenericCallBack
) {
snackbar = Snackbar.make(
activity.findViewById(android.R.id.content),
Constants.CONST_NO_INTERNET_CONNECTION, Snackbar.LENGTH_INDEFINITE
)
if (isLoaderShown)
activity.showAppLoader()
snackbar.dismiss()
call.enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
hideAppLoader()
if (response.isSuccessful) {
retryCount = 0
apiListener.success(apiName, response.body())
} else {
when {
response.errorBody() != null -> try {
val json = JSONObject(response.errorBody()!!.string())
Log.e("TEGD", "JSON==> " + response.errorBody())
Log.e("TEGD", "Response Code==> " + response.code())
val error = json.get("message") as String
apiListener.failure(apiName, error)
} catch (e: Exception) {
e.printStackTrace()
Log.e("TGED", "JSON==> " + e.message)
Log.e("TGED", "Response Code==> " + response.code())
apiListener.failure(apiName, Constants.CONST_SERVER_NOT_RESPONDING)
}
else -> {
apiListener.failure(apiName, Constants.CONST_SERVER_NOT_RESPONDING)
return
}
}
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
hideAppLoader()
val callBack = this
if (t.message != "Canceled") {
Log.e("TGED", "Fail==> " + t.localizedMessage)
if (t is UnknownHostException || t is IOException) {
snackbar.setAction("Retry") {
snackbar.dismiss()
enqueueWithRetry(activity, call, callBack, isLoaderShown)
}
snackbar.show()
apiListener.failure(apiName, Constants.CONST_NO_INTERNET_CONNECTION)
} else {
retryCount = 0
apiListener.failure(apiName, t.toString())
}
} else {
retryCount = 0
}
}
})
}
fun <T> enqueueWithRetry(
activity: Activity,
call: Call<T>,
callback: Callback<T>,
isLoaderShown: Boolean
) {
activity.showAppLoader()
call.clone().enqueue(callback)
}
}
SingleEnqueueCall will be used for calling the retrofit, it is quite versatile, written with onFailure() functions and by passing Call to it, we can call an API along with ApiName parameter so this function can be used for any possible calls and by ApiName, we can distinguish in the response that which API the result came from.
Constants.kt
object Constants {
const val CONST_NO_INTERNET_CONNECTION = "Please check your internet
connection"
const val CONST_SERVER_NOT_RESPONDING = "Server not responding!
Please try again later"
const val USER_REGISTER = "/api/User/register"
}
ApiInterface.kt
interface ApiInterface {
companion object {
const val BASE_URL = "URL_LINK"
}
#POST(Constants.USER_REGISTER)
fun userRegister(#Body userRegisterRequest: UserRegisterRequest):
Call<UserRegisterResponse>
}
UserRegisterRequest.kt
data class UserRegisterRequest(
val Email: String,
val Password: String
)
UserRegisterResponse.kt
data class UserRegisterResponse(
val Message: String,
val Code: Int
)
IGenericCallBack.kt
interface IGenericCallBack {
fun success(apiName: String, response: Any?)
fun failure(apiName: String, message: String?)
}
MyApplication.kt
class MyApplication : Application() {
companion object {
lateinit var apiService: ApiInterface
}
override fun onCreate() {
super.onCreate()
apiService = ApiClient.getClient()!!.create(ApiInterface::class.java)
}
}
MyApplication is the application class to initialize Retrofit at the launch of the app.
AndroidManifest.xml
android:name=".MyApplication"
You have to write above tag in AndroidManifest inside Application tag.
MainActivity.kt
class MainActivity : AppCompatActivity(), IGenericCallBack {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val call = MyApplication.apiService.userRegister(UserRegisterRequest(email, password))
SingleEnqueueCall.callRetrofit(this, call, Constants.USER_REGISTER, true, this)
}
override fun success(apiName: String, response: Any?) {
val model = response as UserRegisterResponse
}
override fun failure(apiName: String, message: String?) {
if (message != null) {
showToastMessage(message)
}
}
}
Firstly, we create a call object by using the API defined in ApiInterface and passing the parameters (if any). Then using SingleEnqueueCall, we pass the call to the retrofit along with ApiName and the interface listener IGenericCallBack by using this. Remember to implement it to respective activity or fragment as above.
Secondly, you will have the response of the API whether in success() or failure() function overriden by IGenericCallBack
P.S: You can differentiate which API got the response by using the ApiName parameter inside success() function.
override fun success(apiName: String, response: Any?) {
when(ApiName) {
Constants.USER_REGISTER -> {
val model = response as UserRegisterResponse
}
}
}
The whole concept is to focus on reusability, now every API call has to create a call variable by using the API's inside ApiInterface then call that API by SingleEnqueueCall and get the response inside success() or failure() functions.
According to google, Repository should be the single source of truth provider for a piece of data but when using retrofit, we enqueue the request which is async, so I am using Livedata to observe the changes to result of the request. But we cannot observe livedata in a repository. So how do I listen to the result in repository so that when results are fetched, I can reflect the changes in the repo.
Below are the codes of my Datasource and Repository
// repository functions to fetch masters and save them to database
fun getAllMaster(): LiveData<List<Master>> {
// save response from datasource to database
// call
// I want to observe the changes to request here
val masters = MasterHook().fetchMasters()
executor.execute{
for(master in masters){
masterDao.insertMaster(master)
}
}
return masterDao.getAllMasters()
}
// Datasource function
fun fetchMasters():LiveData<List<Master>>{
val responseLiveData= MutableLiveData<List<Master>>()
val masterRequest: Call<List<Master>> = dashboardApi.fetchMasters(token =
"Bearer tokenHere")
masterRequest.enqueue(object : Callback<List<Master>>{
override fun onResponse(
call: Call<List<Master>>,
response: Response<List<Master>>
) {
Log.d("Dashboard API", "Response Received")
val masters = response.body()
if (masters != null) {
Log.d("Fetcher", masters.toString())
responseLiveData.value = masters
}
}
override fun onFailure(call: Call<List<Master>>, t: Throwable) {
Log.e("Dashboard API","Failed to fetch masters",t)
}
})
return responseLiveData
}
The best approach there is using the suspend function and coroutines as follows
create a suspend function to request data
#GET
suspend fun fetMasters(#Query("token") apiToken : String) : Response
To save data coming from the Rest API to a local database (Room) there are several ways to achieve this task, you can use Paging Library (version is simplified one) you can use MediatorLiveData or even using Work Library for simplicity in the repository you can fetch data and insert to the Room db directly as follows
class ApiHelper( private val masterService: MasterService) {
suspend fun getMaster() : Response =
masterService.fetchMasters(tokenKey)
}
class Repository(private val apiHelper : ApiHelper) {
suspend fun callDataFromDb()
CoroutineScope(Dispatchers.IO).launch {
val masterResponse = apiHelper.getMaster
withContext(Dispatchers.Main) {
try {
if (masterResponse.isSuccessful) {
masterDao.insertMaster(masterResponse.body)
} else {
Log.d(TAG, "Error: ${masterResponseresponse.code()}")
}
} catch (e: HttpException) {
Log.d(TAG, "Exception ${e.message}")
} catch (e: Throwable) {
Log.d(TAG, "Ooops: Something else went wrong")
}
}
}
}
}
3.In the ViewModel you call the method callDataFromDb as follows
class MasterViewModel(val repository) : ViewModel {
init {
calldata()
}
fun calldata() = viewModelScope.launch {
repository.callDataFromDb
}
}
From there you can functions that retrieve data from the database.
I am new android developer, how can I get result form this snippet, what way does exist, because it doesn't return anything, because of I'm adding element inside onResponse, but using only kotlin module:
private fun foo(list: ArrayList<CurrencyModel> = ArrayList()): ArrayList<CurrencyModel> {
val request = Request.Builder().url(BASE_URL_YESTERDAY).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
val body = response.body?.string()
val data = Gson().fromJson(body, Currencies::class.java)
list.add(CurrencyModel("USD", data.rates.USD, 0.0))
list.add(CurrencyModel("SEK", data.rates.SEK, 0.0))
list.add(CurrencyModel("EUR", data.rates.EUR, 0.0))
}
})
return list
}
}
You can give your function a callback parameter that's called when the response is receieved. And you shouldn't have an input list in this case, because if you have multiple sources modifying it at unpredictable future moments, it will be difficult to track.
The function can look like this:
private fun getCurrencyModels(callback: (ArrayList<CurrencyModel>) {
val request = Request.Builder().url(BASE_URL_YESTERDAY).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
val body = response.body?.string()
val data = Gson().fromJson(body, Currencies::class.java)
val list = arrayListOf(
CurrencyModel("USD", data.rates.USD, 0.0)),
CurrencyModel("SEK", data.rates.SEK, 0.0)),
CurrencyModel("EUR", data.rates.EUR, 0.0))
)
callback(list)
}
})
}
And then to use it:
getCurrencyModels { modelsList ->
// do something with modelsList when it arrives
}
An alternative is to use coroutines, which allow you to do asynchronous actions without callbacks. Someone has already created a library that lets you use OkHttp requests in coroutines here. You could write your function as a suspend function like this:
private suspend fun getCurrencyModels(): ArrayList<CurrencyModel> {
val request = Request.Builder().url(BASE_URL_YESTERDAY).build()
val client = OkHttpClient()
val response = client.newCall(request).await()
val body = response.body?.string()
val data = Gson().fromJson(body, Currencies::class.java)
return arrayListOf(
CurrencyModel("USD", data.rates.USD, 0.0)),
CurrencyModel("SEK", data.rates.SEK, 0.0)),
CurrencyModel("EUR", data.rates.EUR, 0.0))
)
}
and then use it like this:
lifecycleScope.launch {
try {
val currencyModels = getCurrencyModels()
// do something with currencyModels
} catch (e: IOException) {
// request failed
}
}
Coroutines make it really easy to avoid leaking memory when your asynchronous calls outlive your Activity or Fragment. In this case, if your Activity closes while the request is going, it will be cancelled automatically and references to your Activity will be removed so the garbage collector can release your Activity.
The onResponse() function is only called when the HTTP response is successfully returned by the remote server. Since this response doesn't happen immediately, you can't use the result in your code immediately. What you could do is use a ViewModel and LiveData variable and add the values to that variable in onResponse(). Something like:
private fun foo(list: ArrayList<CurrencyModel> = ArrayList()) {
val request = Request.Builder().url(BASE_URL_YESTERDAY).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
val body = response.body?.string()
val data = Gson().fromJson(body, Currencies::class.java)
val list: ArrayList<CurrencyModel> = arrayListOf()
list.add(CurrencyModel("USD", data.rates.USD, 0.0))
list.add(CurrencyModel("SEK", data.rates.SEK, 0.0))
list.add(CurrencyModel("EUR", data.rates.EUR, 0.0))
viewModel.list.postValue(list)
}
})
}
As the title implies, I'm currently working with an API that has an async callback method. I'd like to be able to WAIT for the result before proceeding with the rest of the code (making an existing async call somewhat synchronous, if that makes sense). Is this possible with coroutines? This is what my code looks like right now, but the request is still called multiple times (I'm new to coroutines).
class TokenAuthenticator #Inject constructor(val prefs: AppPrefs) : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
if (response.code() == ApiErrorCode.UNAUTHORIZED) {
Timber.w("Unauthorized. Refreshing token...")
val token: String? = runBlocking(Dispatchers.Main) { refreshToken() }
token?.let {
return response
.request()
.newBuilder()
.header("Authorization", "Bearer $token")
.build()
}
}
when (response.code()) {
ApiErrorCode.UNAUTHORIZED -> Timber.w("Tried to refresh token. Failed?")
else -> Timber.d("NOT refreshing token, response code was: ${response.code()}")
}
return response.request()
}
private suspend fun refreshToken() = suspendCoroutine<String?> {
SomeApi.getValidAccessToken(object : TokenCallback {
override fun onSuccess(accessToken: String?) {
Timber.d("Token successfully retrieved. Storing to prefs...")
prefs.userAuthToken = accessToken
it.resume(accessToken)
}
override fun onError(errorData: Any?) {
Timber.e("Error retrieving token")
it.resume(null)
}
})
}
}
I am trying to write an Android Chess client using websockets. I chose the okhttp3 library. I can make a successful connection and can send data and receive. However, I am not sure how to return the data to LiveData for the ViewModel. I am somewhat familiar with Kotlin coroutines but I am not sure how I would get the data out of the listener.
I have tried trying to return from the function but as it is an overridden function I cannot return from it.
Here is the current WebSocketListener:
class EchoWebSocketListener : WebSocketListener() {
private val NORMAL_CLOSURE_STATUS = 1000
override fun onOpen(webSocket: WebSocket, response: Response) {
super.onOpen(webSocket, response)
webSocket.send("Hello It is me")
webSocket.send("test 3!")
}
override fun onMessage(webSocket: WebSocket, text: String){
super.onMessage(webSocket, text)
outputData("Receiving $text")
}
override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
super.onMessage(webSocket, bytes)
outputData("Receiving bytes : " + bytes.hex())
}
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
super.onClosing(webSocket, code, reason)
outputData("$code $reason")
}
private fun outputData(outputString: String) {
d("web socket", outputString)
}
}
And here is the setup code in the repository
fun startChat() {
httpClient = OkHttpClient()
val request = Request.Builder()
.url("ws://echo.websocket.org")
.build()
val listener = EchoWebSocketListener()
val webSocket = httpClient.newWebSocket(request, listener)
//webSocket.
httpClient.dispatcher.executorService.shutdown()
}
I would like to be able to run the repository with a Kotlin coroutine and return LiveData for the fragment to consume.
In your EchoWebSocketistener you could create a private MutableLiveData like so
class EchoWebSocketListener : WebSocketListener() {
private val _liveData = MutableLiveData<String>()
val liveData: LiveData<String> get() = _liveData
// Overridden methods
private fun outputData(string: String) {
_liveData.postValue(string)
}
}
Then you return the live data from the listener like so in a Coroutine
fun startChat(): LiveData<String> {
val listener = EchoWebSocketListener()
GlobalScope.launch(Dispatchers.IO) {
httpClient = OkHttpClient()
val request = Request.Builder()
.url("ws://echo.websocket.org")
.build()
val webSocket = httpClient.newWebSocket(request, listener)
//webSocket.
httpClient.dispatcher.executorService.shutdown()
}
return listener.liveData
}