Smoother User Experience: Retrofit for Reliable API Calls in Android

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.

#Empowering Android Development: Hands-On Integration of Retrofit for Optimized API Calls

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

Step 1: Add Dependencies to build.gradle

  • Firstly, add dependencies to the build.gradle file:
  • 
                            
                    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'
                    }
                
                  

    Step 2: Create RetrofitClientFactory Class

    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()
                        }
                
                    }
                
                  

    Step 3: Create RestApis Interface

    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>
                  }
                
                    

    Step 4: Create NetworkHelpers Class

    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)
                
                    

    Step 5: Create NetworkResponse Interface

    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() {}
                  }
                  
                
                    

    Step 6: Create BaseActivity Class

  • Then, create a BaseActivity class so that you can extend your activities with this class:
  • 
                
                      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
                            }
                        }
                
                
                    }
                
                    
    You can customize this class according to your needs.

    Step 7: Extend Activities with BaseActivity

    Now, extend your Activities with BaseActivity instead of AppCompatActivity.

    Step 8: Call API with Retrofit

    Wherever you need to call the API, create a variable like this:

    
                
                    private val getDataCall by lazy {
                        getApiCalls<Pagination<ArrayList<MilkProductsModel>>>()
                    }
                
                  

    Step 9: Perform API Call

    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.