I am new to Android development with Kotlin and I am struggling on finding any useful documentation on how to create a simple GET and POST requests with the best current practices as possible. I am coming from an Angular development and there we used a reactive development using RxJS.
Normally I would create a service file that would hold all my request functions, then I would use this service in whichever component and subscribe to the observable.
How would you do this in Android? Is there a good started example of things that have to be created. From the first look, everything looks so complicated and over-engineered
I suggest you to use the official recommendation of OkHttp, or the Fuel library for easier side and it also has bindings for deserialization of response into objects using popular Json / ProtoBuf libraries.
Fuel example:
// Coroutines way:
// both are equivalent
val (request, response, result) = Fuel.get("https://httpbin.org/ip").awaitStringResponseResult()
val (request, response, result) = "https://httpbin.org/ip".httpGet().awaitStringResponseResult()
// process the response further:
result.fold(
{ data -> println(data) /* "{"origin":"127.0.0.1"}" */ },
{ error -> println("An error of type ${error.exception} happened: ${error.message}") }
)
// Or coroutines way + no callback style:
try {
println(Fuel.get("https://httpbin.org/ip").awaitString()) // "{"origin":"127.0.0.1"}"
} catch(exception: Exception) {
println("A network request exception was thrown: ${exception.message}")
}
// Or non-coroutine way / callback style:
val httpAsync = "https://httpbin.org/get"
.httpGet()
.responseString { request, response, result ->
when (result) {
is Result.Failure -> {
val ex = result.getException()
println(ex)
}
is Result.Success -> {
val data = result.get()
println(data)
}
}
}
httpAsync.join()
OkHttp example:
val request = Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build()
// Coroutines not supported directly, use the basic Callback way:
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
response.use {
if (!response.isSuccessful) throw IOException("Unexpected code $response")
for ((name, value) in response.headers) {
println("$name: $value")
}
println(response.body!!.string())
}
}
})
you can use something like that:
internal inner class RequestTask : AsyncTask<String?, String?, String?>() {
override fun doInBackground(vararg params: String?): String? {
val httpclient: HttpClient = DefaultHttpClient()
val response: HttpResponse
var responseString: String? = null
try {
response = httpclient.execute(HttpGet(uri[0]))
val statusLine = response.statusLine
if (statusLine.statusCode == HttpStatus.SC_OK) {
val out = ByteArrayOutputStream()
response.entity.writeTo(out)
responseString = out.toString()
out.close()
} else {
//Closes the connection.
response.entity.content.close()
throw IOException(statusLine.reasonPhrase)
}
} catch (e: ClientProtocolException) {
//TODO Handle problems..
} catch (e: IOException) {
//TODO Handle problems..
}
return responseString
}
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
//Do anything with response..
}
}
and for call:
RequestTask().execute("https://v6.exchangerate-api.com/v6/")
HttpClient is not supported any more in sdk 23. You have to use URLConnection or downgrade to sdk 22 (compile 'com.android.support:appcompat-v7:22.2.0')
If you need sdk 23, add this to your gradle:
android {
useLibrary 'org.apache.http.legacy'
}
You also may try to download and include HttpClient.jar directly into your project or use OkHttp instead
The best practice you ever get to just go through the basics of networking call and create some demo applications using Android Studio.
If you want to click start then follow this tutorial
Simplet netwroking call in Kotlin
https://www.androidhire.com/retrofit-tutorial-in-kotlin/
Also, I would like to suggest Please create some demo application for GET and POST request and then merge these examples into your project.
Related
I'm trying to make a GET request to my server from my Android application using Retrofit, OKHttp, and Kotlin Flow w/ MVVM architecture.
For some reason whenever I try to invoke my GET request from a try-catch scope the program always enters catch, but the value of Throwable is always null. It's as if there was a crash but no exception being thrown for me to examine in the logs/debugger.
Filtering logcat for OKHTTP logs I can see that the network request never occurs/reaches the server. It seems to be failing locally, somewhere in my device's app process, before triggering the catch block.
Other network calls execute fine in this same project, so something about my specific implementation for this one must be incorrect. What am I missing?
RemoteDataSource.kt
suspend fun getProductData(skuId: String): Result<ProductLookupResponse>{
return getResponse(
request = {
pickingAPI.lookupProductBySku(
"Test User",
"A place",
skuId
)
},
defaultErrorMessage = "Error looking up product"
)
}
private suspend fun <T> getResponse(
request: suspend () -> Response<T>,
defaultErrorMessage: String
): Result<T> {
return try {
val result = request.invoke() //Always Crashes here for this request
if (result.isSuccessful) {
Result.success(result.body())
} else {
val networkError = NetworkError(code = result.code(), message = result.message())
Result.error(message = networkError.message ?: "", error = networkError)
}
} catch (e: Exception) {
Result.error(defaultErrorMessage, null)
}
}
Repo.kt
suspend fun getProductData(
skuId: String
): Flow<Result<ProductLookupResponse>> {
return flow {
emit(Result.loading())
emit(RemoteDataSource.getProductData(skuId))
}.flowOn(Dispatchers.IO)
}
API.kt
#GET("garments/sku/{skuId}")
fun lookupProductBySku(
#Header(HEADER_ASSOCIATE_ID) userUniqueId: String,
#Header(HEADER_LOCATION_ID) dcId: String,
#Path("skuId") sku: String
): Response<ProductLookupResponse>
In my app, I get a set of urls to some images from an api and need to create Bitmap objects out of those urls to be able do display the images in the UI. I saw that the android docs recommend using corutines for performing such async tasks, but I am not sure how to do it properly.
Using OkHttp for my http client, I tried the following approach:
GlobalScope.launch {
val gson = Gson();
val parsedRes = gson.fromJson(
response.body?.charStream(),
Array<GoodreadsBook>::class.java
);
// Create the bitmap from the imageUrl
for (i in 0 until parsedRes.size) {
val bitmap =
GlobalScope.async { createBitmapFromUrl(parsedRes[i].best_book.image_url) }
parsedRes[i].best_book.imageBitmap = bitmap.await();
}
searchResults.postValue(parsedRes)
}
Where response is what I get back from my API, and searchResults is a LiveData that hold the parsed response.
Also, here is how I am getting the images from those urls:
suspend fun createBitmapFromUrl(url: String): Bitmap? {
val client = OkHttpClient();
val req = Request.Builder().url(url).build();
val res = client.newCall(req).execute();
return BitmapFactory.decodeStream(res.body?.byteStream())
}
Even though every fetch action is done on a separate coroutine, it's still too slow. Is there a better way of doing it? I can use any other http client if there is one out there optimized for use with coroutines, although I am new to Kotlin so I don't know any.
First of all the createBitmapFromUrl(url: String) does everything synchronously, you've to first stop them from blocking the coroutine thread, you may want to use Dispatchers.IO for that because callback isn't the most idomatic thing ever in coroutines.
val client = OkHttpClient() // preinitialize the client
suspend fun createBitmapFromUrl(url: String): Bitmap? = withContext(Dispatchers.IO) {
val req = Request.Builder().url(url).build()
val res = client.newCall(req).execute()
BitmapFactory.decodeStream(res.body?.byteStream())
}
Now, when you are calling bitmap.await() you are simply saying that "Hey, wait for the deferred bitmap and once it is finished resume the loop for next iteration"
So you may want to do the assignment in the coroutine itself to stop it from suspending the loop, otherwise create another loop for that. I'd go for first option.
scope.launch {
val gson = Gson();
val parsedRes = gson.fromJson(
response.body?.charStream(),
Array<GoodreadsBook>::class.java
);
// Create the bitmap from the imageUrl
for (i in 0 until parsedRes.size) {
launch {
parsedRes[i].best_book.imageBitmap = createBitmapFromUrl(parsedRes[i].best_book.image_url)
}
}
}
Use a library like the following that doesn't use the blocking execute method and instead bridges from the async enqueue.
https://github.com/gildor/kotlin-coroutines-okhttp
suspend fun main() {
// Do call and await() for result from any suspend function
val result = client.newCall(request).await()
println("${result.code()}: ${result.message()}")
}
What this basically does is the following
public suspend fun Call.await(): Response {
return suspendCancellableCoroutine { continuation ->
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
continuation.resume(response)
}
override fun onFailure(call: Call, e: IOException) {
if (continuation.isCancelled) return
continuation.resumeWithException(e)
}
})
continuation.invokeOnCancellation {
try {
cancel()
} catch (ex: Throwable) {
//Ignore cancel exception
}
}
}
}
I am building an app based off of the Android Clean Architecture Kotlin version (https://github.com/android10/Android-CleanArchitecture-Kotlin).
Using this architecture, each time you want to invoke a use case, a Kotlin coroutine is launched and the result is posted in the main thread. This is achieved by this code:
abstract class UseCase<out Type, in Params> where Type : Any {
abstract suspend fun run(params: Params): Either<Failure, Type>
fun execute(onResult: (Either<Failure, Type>) -> Unit, params: Params) {
val job = async(CommonPool) { run(params) }
launch(UI) { onResult.invoke(job.await()) }
}
In his example architecture, Mr. Android10 uses Retrofit to make a synchronous api call inside the kotlin couroutine. For example:
override fun movies(): Either<Failure, List<Movie>> {
return when (networkHandler.isConnected) {
true -> request(service.movies(), { it.map { it.toMovie() } }, emptyList())
false, null -> Left(NetworkConnection())
}
}
private fun <T, R> request(call: Call<T>, transform: (T) -> R, default: T): Either<Failure, R> {
return try {
val response = call.execute()
when (response.isSuccessful) {
true -> Right(transform((response.body() ?: default)))
false -> Left(ServerError())
}
} catch (exception: Throwable) {
Left(ServerError())
}
}
'Either' represents a disjoint type, meaning the result will either be a Failure or the object of type T you want.
His service.movies() method is implemented like so (using retrofit)
#GET(MOVIES) fun movies(): Call<List<MovieEntity>>
Now here is my question. I am replacing retrofit with Google Cloud Firestore. I know that currently, Firebase/Firestore is an all async library. I want to know if anyone knows of a method more elegant way of making a synchronous API call to Firebase.
I implemented my own version of Call:
interface Call<T: Any> {
fun execute(): Response<T>
data class Response<T>(var isSuccessful: Boolean, var body: T?, var failure: Failure?)
}
and my API call is implemented here
override fun movieList(): Call<List<MovieEntity>> = object : Call<List<MovieEntity>> {
override fun execute(): Call.Response<List<MovieEntity>> {
return movieListResponse()
}
}
private fun movieListResponse(): Call.Response<List<MovieEntity>> {
var response: Call.Response<List<MovieEntity>>? = null
FirebaseFirestore.getInstance().collection(DataConfig.databasePath + MOVIES_PATH).get().addOnCompleteListener { task ->
response = when {
!task.isSuccessful -> Call.Response(false, null, Failure.ServerError())
task.result.isEmpty -> Call.Response(false, null, MovieFailure.ListNotAvailable())
else -> Call.Response(true, task.result.mapTo(ArrayList()) { MovieEntity.fromSnapshot(it) }, null)
}
}
while (response == null)
Thread.sleep(50)
return response as Call.Response<List<MovieEntity>>
}
Of course, the while loop at the end bothers me. Is there any other, more elegant ways, to wait for the response to be assigned before returning from the movieListResponse method?
I tried calling await() on the Task that is returned from the Firebase get() method, but the movieListResponse method would return immediately anyway. Thanks for the help!
So I found what I was looking for in the Google Tasks API: "If your program is already executing in a background thread you can block a task to get the result synchronously and avoid callbacks" https://developers.google.com/android/guides/tasks#blocking
So my previous problematic code becomes:
private fun movieListResponse(): Call.Response<List<MovieEntity>> {
return try {
val taskResult = Tasks.await(FirebaseFirestore.getInstance().
collection(DataConfig.databasePath + MOVIES_PATH).get(), 2, TimeUnit.SECONDS)
Call.Response(true, taskResult.mapTo(ArrayList()) { MovieEntity.fromSnapshot(it) }, null)
} catch (e: ExecutionException) {
Call.Response(false, null, Failure.ServerError())
} catch (e: InterruptedException) {
Call.Response(false, null, Failure.InterruptedError())
} catch (e: TimeoutException) {
Call.Response(false, null, Failure.TimeoutError())
}
}
Note I no longer need my Thread.sleep while loop.
This code should only be run in a background thread/kotlin coroutine.
This is overengineered, there are several layers trying to do the same thing. I suggest you go back a few steps, undo the abstractions and get into the mood of using coroutines directly. Implement a suspend fun according to this template. You don't need the crutches of Either, handle exceptions in the most natural way: a try-catch around a suspend fun call.
You should end up with a signature as follows:
suspend fun movieList(): List<MovieEntity>
Call site:
launch(UI) {
try {
val list = movieList()
...
} catch (e: FireException) {
// handle
}
}
That's is not the way how firebase works. Firebase is based on callback.
I recommend architecture component's livedata.
Please check the following example.
here is a link: https://android.jlelse.eu/android-architecture-components-with-firebase-907b7699f6a0
I'm trying to create generic architecture to consume complex json structure as follows:
Json Format
{
"type": "success",
"code": "s-groups-0006",
"description": "Index List successfully",
"result": {
"asOnDate": 1505457095278,
"indexList": [
{
"change": "22.35",
"changePercent": "0.27",
"isDefault": true,
"isEditable": false
}
]
}
}
Dagger Format
#Singleton
fun provideGson(): Gson =
GsonBuilder()
.setLenient()
// .registerTypeAdapter(BaseResponse::class.java, RestDeserializer<BaseResponse<T>>())
.create()
Rest Deseralizers
class RestDeserializer<T> : JsonDeserializer<T> {
#Throws(JsonParseException::class)
override fun deserialize(je: JsonElement, type: Type, jdc: JsonDeserializationContext): T? {
val content = je.asJsonObject
// Deserialize it. You use a new instance of Gson to avoid infinite recursion
// to this deserializer
return Gson().fromJson<T>(content, type)
}
}
Error callback
abstract class ErrorCallBack<T : BaseResponse<T>> : DisposableObserver<T>() {
protected abstract fun onSuccess(t: T)
override fun onNext(t: T) {
//You can return StatusCodes of different cases from your API and handle it here. I usually include these cases on BaseResponse and iherit it from every Response
onSuccess(t)
}
override fun onError(e: Throwable) {
when (e) {
is HttpException -> {
val responseBody = (e).response().errorBody()
responseBody?.let {
L.e("Error in call htttp exception")
}
}
is SocketTimeoutException -> {
// todo
L.e("Error in Socket time out")
}
is IOException -> {
// todo
L.e("Error in IO Exception")
}
else -> {
e.message?.let {
// todo
}
}
}
}
override fun onComplete() {
}
private fun getErrorMessage(responseBody: ResponseBody): String {
return try {
val jsonObject = JSONObject(responseBody.string())
jsonObject.getString("message")
} catch (e: Exception) {
e.message!!
}
}
}
Repository
override fun getValidateUser(validateUser: ValidateUser): LiveData<ValidateUserResponse> {
val mutableLiveData = MutableLiveData<ValidateUserResponse>()
remoteServices.requestValidateUser(validateUser)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : ErrorCallBack<BaseResponse<ValidateUserResponse>>() {
override fun onSuccess(t: BaseResponse<ValidateUserResponse>) {
if (t.type == CommonContents.SUCCESS) {
L.d("Success in validate user")
mutableLiveData.value = transform(t)
} else {
L.e("Validate User Error")
}
}
})
return mutableLiveData
}
Data Class
data class BaseResponse<out T>(
#SerializedName(CommonContents.TYPE) val type: String,
#SerializedName(CommonContents.CODE) val Code: String,
#SerializedName(CommonContents.DESCRIPTION) val Description: String,
#SerializedName(CommonContents.RESULT)val result: T? = null)
These are my structures and I try to make a generic structure but am facing an issue when invoking the Error callback.
Please guide me how to achieve this. Can i call generic method inside generic Response?
.subscribeWith(object : ErrorCallBack<BaseResponse<ValidateUserResponse>>() {
Guide To Achieving Working Code
This is my guide to making some code work. It is based on the principles of Test Driven Development.
Set up your unit test environment in your IDE. Personally, I have been using JUnit 4 with Eclipse, but you may prefer JUnit 5 with JetBrains' IDE.
Write a unit test for your ErrorCallback class constructor. Make it pass. Next, write unit tests for each of the methods, to see that it behaves the way that you expect.
Write unit tests where your test fixture is a PublishSubject<BaseResponse<Integer>> for some number of different cases: normal data, sequence of normal data, normal data followed by error, normal data followed by completion.
From there, add some more tests so that you can test the entire observable chain.
This scene takes place in an Android app using Retrofit2 and Moshi for JSON deserialization.
In a case where you don't have control over the server's implementation, and this said server have an inconsistent behavior in how it answers requests (also know as "a bad case"):
Is there a way to handle com.squareup.moshi.JsonDataException without crashing?
For example you expected a JSONArray, and here comes a JSONObject. Crash. Is there another way to handle this than having the app crashing?
Also in the case the server's implementation is updated, wouldn't it be better to display an error message to the user, instead of having it to crash / be totally out of service, even for one wrong request?
Make the call with Retrofit and use try and catch to handle exceptions, something similar to:
class NetworkCardDataSource(
private val networkApi: NetworkCardAPI,
private val mapper: CardResponseMapper,
private val networkExceptionMapper: RetrofitExceptionMapper,
private val parserExceptionMapper: MoshiExceptionMapper
) : RemoteCardDataSource {
override suspend fun getCard(id: String): Outcome<Card, Throwable> = withContext(Dispatchers.IO) {
val response: Response<CardResponseJson>
return#withContext try {
response = networkApi.getCard(id)
handleResponse(
response,
data = response.body(),
transform = { mapper.mapFromRemote(it.card) }
)
} catch (e: JsonDataException) {
// Moshi parsing error
Outcome.Failure(parserExceptionMapper.getException(e))
} catch (e: Exception) {
// Retrofit error
Outcome.Failure(networkExceptionMapper.getException(e))
}
}
private fun <Json, D, L> handleResponse(response: Response<Json>, data: D?, transform: (D) -> L): Outcome<L, Throwable> {
return if (response.isSuccessful) {
data?.let {
Outcome.Success(transform(it))
} ?: Outcome.Failure(RuntimeException("JSON cannot be deserialized"))
} else {
Outcome.Failure(
HTTPException(
response.message(),
Exception(response.raw().message),
response.code(),
response.body().toString()
)
)
}
}
}
where:
networkApi is your Retrofit object,
mapper is a class for mapping the received object to another one used in your app (if needed),
networkExceptionMapper and parserExceptionMapper map Retrofit and Moshi exceptions, respectively, to your own exceptions so that Retrofit and Moshi exceptions do not spread all over your app (if needed),
Outcome is just a iOS Result enum copy to return either a Success or a Failure result but not both,
HTTPException is a custom Runtime exception to return unsuccessful request.
This a snippet from a clean architecture example project.