Home > Software design >  What is the simplest way to make a post request in Kotlin for Android app
What is the simplest way to make a post request in Kotlin for Android app

Time:05-18

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?

CodePudding user response:

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.

CodePudding user response:

Hej T-34,

I would recommend the Retrofit library: https://square.github.io/retrofit/

  • Related