As an Android developer, I understand the importance of efficient and reliable network communication in mobile applications. Retrofit is a versatile and widely adopted library that simplifies the process of making HTTP requests and handling responses in Android apps
One of the standout features of Retrofit is its ability to handle multiple API calls seamlessly. Whether you're fetching data from a single endpoint or multiple endpoints, Retrofit streamlines the process and makes it easy to manage these requests. What sets my implementation apart is the added flexibility to cancel ongoing calls if the user decides to do so. This empowers users to control their app's behavior and optimize network usage according to their preferences.
To provide practical insight into the implementation, I've included code snippets on this page for reference. These snippets demonstrate how to integrate my solution into your Android projects, offering a hands-on approach to leveraging the power of Retrofit for API calls
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.squareup.okhttp:okhttp-urlconnection:2.7.5'
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2'
}
Create a file named "RetrofitClientFactory" in your project.
object RetrofitClientFactory {
private var retrofit: Retrofit? = null
fun getRetroFitClient(bearer: String? = null): Retrofit {
if (retrofit == null) {
retrofit = Retrofit.Builder()
.baseUrl("${BuildConfig.API}api/")
.addConverterFactory(GsonConverterFactory.create())
.client(provideOkHttpClient(false, bearer ?: ""))
.build()
}
return retrofit ?: Retrofit.Builder()
.baseUrl("${BuildConfig.API}api/")
.addConverterFactory(GsonConverterFactory.create())
.client(provideOkHttpClient(false, bearer ?: ""))
.build()
}
private fun provideOkHttpClient(isLargerUpload: Boolean, bearer: String? = null): OkHttpClient {
val loggingInterceptor = HttpLoggingInterceptor()
val okhttpClientBuilder = OkHttpClient.Builder()
if (BuildConfig.HTTP_LOG) {
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
} else {
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.NONE)
}
okhttpClientBuilder.addInterceptor(Interceptor { chain ->
val original: Request = chain.request()
val request: Request = original.newBuilder()
.header("Authorization", "Bearer ${"IConstants.JwtKeys.token_key"}")
.build()
chain.proceed(request)
})
var timeOutSec = 45
if (isLargerUpload) {
timeOutSec = 300
}
okhttpClientBuilder.connectTimeout(timeOutSec.toLong(), TimeUnit.SECONDS)
okhttpClientBuilder.readTimeout(timeOutSec.toLong(), TimeUnit.SECONDS)
okhttpClientBuilder.writeTimeout(timeOutSec.toLong(), TimeUnit.SECONDS)
okhttpClientBuilder.addInterceptor(loggingInterceptor)
return okhttpClientBuilder.build()
}
}
In this interface, include all the suspend functions you want to use in the project.
interface RestApis {
@FormUrlEncoded
@POST(IConstants.Apis.milkMainPageData)
suspend fun getMainPage(@FieldMap request: HashMap): Response>>>
@FormUrlEncoded
@POST(IConstants.Apis.MilkProductDetailView)
suspend fun milkProductDetailView(@FieldMap request: HashMap): Response>
}
Create a file named "NetworkHelpers" in your project.
fun getRestApis(useSignature: String? = null): RestApis {
return RetrofitClientFactory.getRetroFitClient(useSignature).create(RestApis::class.java)
}
fun Context.getApiCalls(): BaseActivity.ApiCalls {
return (this as BaseActivity).ApiCalls()
}
data class ErrorResponse(val type: String? = null, val message: String? = null)
Create an interface named "NetworkResponse" with a generic type variable in your project.
interface NetworkResponse {
fun onApiSuccess(response: BaseResponses?)
fun onApiError(error: ErrorResponse)
fun onNetworkError() {}
}
open class BaseActivity : AppCompatActivity() {
private lateinit var progressDialogue: AlertDialog
private val dialogFragment = NoInternetFragmentDialogue()
val connectionStateMonitor = ConnectionMonitor()
private val dialogueBinding by lazy {
InternetRetryDialogueBinding.inflate(layoutInflater)
}
private val dialogue by lazy {
AlertDialog.Builder(this@BaseActivity).setCancelable(false)
.setView(dialogueBinding.root).create().also {
it.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
}
}
var internetObserve = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
progressDialogue =
AlertDialog.Builder(this).setView(R.layout.progress_bar_layout).setCancelable(false)
.create().also {
it.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
}
if (!isNetworkAvailable()) {
dialogFragment.show(supportFragmentManager, "noInternet")
dialogFragment.postponeEnterTransition()
}
if (internetObserve) {
connectionStateMonitor.enable(this)
connectionStateMonitor.observer.observe(this) {
if (it) {
if (dialogFragment.isVisible) {
dialogFragment.dismiss()
}
} else {
dialogFragment.show(supportFragmentManager, "noInternet")
dialogFragment.postponeEnterTransition()
}
}
}
}
fun showProgressDialogue(show: Boolean) {
if (show) {
progressDialogue.show()
} else {
progressDialogue.dismiss()
}
}
open inner class ApiCalls() {
private var params=HashMap()
private var job: Job? = null
private var cancelingCall = true
private val restApi = getRestApis()
fun getRestCall() = restApi
fun launch(
work: suspend () -> Response>,
callback: NetworkResponse,
) {
if (cancelingCall) {
cancel()
}
if (connectionStateMonitor.observer.value == true) {
job = CoroutineScope(Dispatchers.IO).launch {
try {
work().also { response ->
withContext(Dispatchers.Main) {
if (response.isSuccessful) {
callback.onApiSuccess(response.body())
// onApiSuccess(response.body())
} else {
callback.onApiError(
ErrorResponse(
"ApiError",
response.errorBody()?.string().toString()
)
)
}
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
if (e is SocketException || e is UnknownHostException) {
dialogueBinding.tilte.text = getString(R.string.socket_exception)
dialogueBinding.message.text = e.message
dialogue.show()
dialogueBinding.retry.setOnClickListener {
dialogue.dismiss()
launch(work, callback)
}
}
callback.onApiError(
ErrorResponse(
message =
e.message.toString()
)
)
}
}
}
} else {
connectionStateMonitor.observer.observe(this@BaseActivity) {
if (it) {
launch(work, callback)
}
}
}
}
fun cancel() {
if (dialogue.isShowing) {
dialogue.dismiss()
}
job?.cancel()
}
fun setCancelingCall(cancel: Boolean) {
this.cancelingCall = cancel
}
fun setParams(params: HashMap) {
this.params = params
}
}
}
Now, extend your Activities with BaseActivity instead of AppCompatActivity.
Wherever you need to call the API, create a variable like this:
private val getDataCall by lazy {
getApiCalls<Pagination<ArrayList<MilkProductsModel>>>()
}
Here's how you perform an API call:
fun getProductsData() {
val requestMap = HashMap<String, String>()
requestMap[IConstants.Params.seo_url] = "milk-store8"
requestMap[IConstants.Params.page] = currentPage.toString()
getDataCall.setParams(requestMap)
getDataCall.launch(
{ getDataCall.getRestCall().getMainPage(requestMap) },
object : NetworkResponse<Pagination<ArrayList<MilkProductsModel>>> {
override fun onApiSuccess(response: BaseResponses<Pagination<ArrayList<MilkProductsModel>>>?) {
binding.bottomProgress.visible(false)
if (response?.err_code == IConstants.ApiResponses.valid) {
binding.progressBar.visible(false)
totalPage = response.data?.total_pages ?: 1
configureRecycler(response.data?.results)
}
}
override fun onApiError(error: ErrorResponse) {
}
})
}
Furthermore, my implementation of Retrofit prioritizes robustness in handling network connectivity issues. In scenarios where the internet connection is lost during an ongoing API call, the app gracefully handles the situation without crashing or freezing. Instead, it intelligently detects the connectivity issue and displays a user-friendly screen, informing the user about the problem. Once the internet connection is restored, the app automatically retries the failed API call, ensuring a seamless user experience.
The implementation of these features is designed to be as user-friendly as possible. With straightforward integration and intuitive controls, developers can easily incorporate this functionality into their Android apps. I'm continuously conducting further research to optimize and refine this solution, aiming to make it even more efficient and reliable for developers worldwide
I'm committed to advancing the field of Android development and providing innovative solutions that improve app performance and user satisfaction. I invite you to explore the code examples and learn more about my approach to optimizing API calls with Retrofit.