Skip to content

Android Fast Networking with Kotlin Coroutines

Published: at 12:00 AM

Android Fast Networking is a great library for networking I am using since 2016, It has many features that are not available in similar networking libraries.

AFN provides direct support for RxJava but not for Coroutines, so we create by ourself

Adding AFN and Kotlin Coroutines to the project

// GSON
implementation 'com.google.code.gson:gson:2.8.6'

// Android Fast Networking
implementation 'com.amitshekhar.android:android-networking:1.0.2'

// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'

In this project, we are using the State Management approach class Resource

The resource is a wrapper class that contains status (Loading, Success, Error), message, Data

// Resource.kt
/**
 * @author Yogesh Paliyal
 * Created Date : 15 October 2020
 */
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {

    companion object {

        fun <T> success(data: T?, message: String?= ""): Resource<T> {
            return Resource(Status.SUCCESS, data, message)
        }

        fun <T> create(status: Status,data: T?, message: String?= ""): Resource<T> {
            return Resource(status, data, message)
        }


        fun <T> error(msg: String?, data: T? = null): Resource<T> {
            return Resource(Status.ERROR, data, msg)
        }

        fun <T> loading(data: T? = null): Resource<T> {
            return Resource(Status.LOADING, data, null)
        }

    }
}
//Status.kt
enum class Status {
    SUCCESS,
    ERROR,
    LOADING
}

Creating a BaseCaller, every API call will be going through this class, this is the class where the magic happens.

// AuthCaller.kt
class AuthCaller : BaseCaller(){
  fun login(email: String, password: String): Resource<BaseApiModel>{
    return hitApi(Apis.LOGIN, HashMap<String,String>().apply{
      put("email",email)
      put("password",password)
    })
  }
}
// BaseCaller.kt
open class BaseCaller {

    val params: HashMap<String, String> by lazy {
        HashMap<String, String>()
    }

    val files: HashMap<String, File?> by lazy {
        HashMap<String, File?>()
    }

    suspend fun hitApiGet(
        api: String,
        params: HashMap<String, String>? = null, checkAuthentication: Boolean = true
    ): Resource<BaseApiModel> {
        return hitApiGet(api, params, BaseApiModel::class.java,checkAuthentication)
    }

    suspend fun <T> hitApiGet(
        api: String,
        params: HashMap<String, String>? = null,
        objectType: Class<T>, checkAuthentication: Boolean = true
    ): Resource<T> {
        val request = AndroidNetworking.get(api)
            .addQueryParameter(params)
            .addHeaders(getHeader())
            .build()

        return request.fetch(objectType,checkAuthentication)
    }

    suspend fun hitApiUpload(
        api: String,
        params: HashMap<String, String>? = null,
        files: HashMap<String, File?>? = null, checkAuthentication: Boolean = true
    ): Resource<BaseApiModel> {
        return hitApiUpload(api, params, files, BaseApiModel::class.java, checkAuthentication)
    }

    suspend fun <T> hitApiUpload(
        api: String,
        params: HashMap<String, String>? = null,
        files: HashMap<String, File?>? = null,
        objectType: Class<T>, checkAuthentication: Boolean = true
    ): Resource<T> {
        val request = AndroidNetworking.upload(api)
            .addMultipartParameter(params)
            .addMultipartFile(files)
            .setOkHttpClient(OkHttpClient())
            .addHeaders(getHeader())
            .build()

        return request.fetch(objectType,checkAuthentication)
    }

    private suspend fun <T> ANRequest<*>.fetch(objectType: Class<T>,checkAuthentication: Boolean): Resource<T> = withContext(Dispatchers.IO){
        val response = executeForObject(BaseApiModel::class.java)
        try {
            if (response.isSuccess) {
                if (response.result is BaseApiModel) {
                    val baseApiModel = response.result as BaseApiModel
                    if (baseApiModel.code == 200) {
                        if (objectType.isAssignableFrom(BaseApiModel::class.java))
                            return@withContext Resource.success<T>(baseApiModel as T, baseApiModel.message)
                        val formatted = Gson().fromJson(baseApiModel.data, objectType)
                        return@withContext  Resource.success<T>(formatted, baseApiModel.message)
                    } else {
                        return@withContext  onGettingError(ANError().apply {
                            errorCode = baseApiModel.code
                            this.errorBody = Gson().toJson(baseApiModel).toString()
                        },checkAuthentication)
                    }
                } else {
                    return@withContext  Resource.error<T>("Some error occurred")
                }
            }
        }catch (e:Exception){
            e.printStackTrace()
            return@withContext  Resource.error<T>("Some error occurred")
        }
        return@withContext  onGettingError(response.error,checkAuthentication)
    }

    suspend fun hitApi(
        api: String,
        params: HashMap<String, String>? = null, checkAuthentication: Boolean = true,headers: HashMap<String, String>? = null
    ): Resource<BaseApiModel> {
        return hitApi(api, params, BaseApiModel::class.java, checkAuthentication,headers)
    }

    suspend fun <T> hitApi(
        api: String,
        params: HashMap<String, String>? = null,
        objectType: Class<T>, checkAuthentication: Boolean = true, headers: HashMap<String, String>? = null
    ): Resource<T> {

        val request = AndroidNetworking.post(api)
            .addBodyParameter(params)
            .addHeaders(getHeader())
            .addHeaders(headers)
            .build()

        return request.fetch(objectType,checkAuthentication)
    }


    private fun <T> onGettingError(anError: ANError, checkAuthentication: Boolean): Resource<T> {
         if (anError.errorCode == 401) {
             if (checkAuthentication)
                 MyApplication.getInstance().logout()
             return Resource.error<T>("User Logout")
         } else if (anError.errorCode != 0) {
            // received error from server
            // error.getErrorCode() - the error code from server
            // error.getErrorBody() - the error body from server
            // error.getErrorDetail() - just an error detail
            //Log.d(TAG, "onError errorCode : " + anError.getErrorCode());
            //Log.d(TAG, "onError errorBody : " + anError.getErrorBody());
            //Log.d(TAG, "onError errorDetail : " + anError.getErrorDetail());
            // get parsed error object (If ApiError is your class)
            try {
                LogHelper.logD("Error",anError.errorBody)
                val apiError = anError.getErrorAsObject(BaseApiModel::class.java)
                return Resource.error<T>(apiError.message)
            } catch (e: Exception) {
                return Resource.error<T>("Parsing Error")
                e.printStackTrace()
            }
        } else {
            // error.getErrorDetail() : connectionError, parseError, requestCancelledError
            //Log.d(TAG, "onError errorDetail : " + anError.getErrorDetail());
            return when (anError.errorDetail) {
                ANConstants.REQUEST_CANCELLED_ERROR -> Resource.error<T>("Request Cancelled")
                ANConstants.CONNECTION_ERROR -> Resource.error<T>("No internet connected")
                else -> Resource.error<T>("Some Error occurred")
            }
        }
    }


    protected fun getHeader(): HashMap<String, String> {
        val map = HashMap<String, String>()
        map.put("platform", "A")
        map.put("os_version", Build.VERSION.SDK_INT.toString())
       // map["device_id"] = getDeviceId()
       // map["X-localization"] = PreferenceLocaleStore(MyApplication.getInstance(), Locale(Languages.ENGLISH)).getLocale().language
        map["app_version"] = BuildConfig.VERSION_NAME
        map["time"] = System.currentTimeMillis().toString()
        map["timezone"] = TimeZone.getDefault().id
        map["Accept"] = "application/json"
        val token = MyApplication.getInstance().getLoginToken()
        if (!token.trim().isBlank())
            map["Authorization"] = "Bearer $token"
        return map
    }


}

BaseApiModel is an API structure that is used for all APIs like, change as per requirements

{
  "status" : 200,
  "message" : "Api hit success",
  "data" : {
      "name" : "Yogesh Paliyal"
  }
}

Previous Post
How I became a Software Engineer without a Degree
Next Post
Getting Started with Android Data Binding