From 985406051a0cb198665b9fe1487949d3b9f76a33 Mon Sep 17 00:00:00 2001 From: Kenneth Soh Date: Tue, 30 Jan 2024 02:03:34 +0800 Subject: [PATCH 1/5] feat: Implemented a basic API handler with Volley This is a extremely basic version and is subjected to changes Part of #194 --- README.md | 17 +-- build.gradle | 1 + helperlib/build.gradle | 3 + .../helperlib/helpers/ApiCallsHelper.kt | 107 ++++++++++++++++++ .../helperlib/objects/ApiResponse.kt | 13 +++ 5 files changed, 128 insertions(+), 13 deletions(-) create mode 100644 helperlib/src/main/java/com/itachi1706/helperlib/helpers/ApiCallsHelper.kt create mode 100644 helperlib/src/main/java/com/itachi1706/helperlib/objects/ApiResponse.kt diff --git a/README.md b/README.md index 1276400..f974929 100644 --- a/README.md +++ b/README.md @@ -38,20 +38,11 @@ dependencies { } ``` -## Usage - Artifactory (DEPRECATED) -To use this library in an Android Project, add the following lines into your app-level build.gradle file -Note: If you are using artifactory, Move to GitHub Packages or Maven Central ASAP. This repo will be removed soon +## API Helper Usage +To use the API Helper, you will need to add the following lines into your `AndroidManifest.xml` file -```gradle -repositories { - maven { - url "https://itachi1706.jfrog.io/artifactory/ccn-android-libs/" - } -} -… -dependencies { - implementation 'com.itachi1706.helpers:helperlib:' // See badge for latest version -} +```xml + ``` ## Helper Classes in the library diff --git a/build.gradle b/build.gradle index 538142c..2d1d362 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'com.android.library' version '8.2.1' apply false id 'org.jetbrains.kotlin.android' version '1.9.22' apply false + id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.22' apply false id "org.sonarqube" version "4.4.1.3373" } diff --git a/helperlib/build.gradle b/helperlib/build.gradle index 9a67617..6171e0d 100644 --- a/helperlib/build.gradle +++ b/helperlib/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.library' id 'org.jetbrains.kotlin.android' + id 'org.jetbrains.kotlin.plugin.serialization' } android { @@ -61,6 +62,8 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' implementation 'com.google.android.material:material:1.11.0' + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2' + implementation 'com.android.volley:volley:1.2.1' } apply from: 'publish.gradle' diff --git a/helperlib/src/main/java/com/itachi1706/helperlib/helpers/ApiCallsHelper.kt b/helperlib/src/main/java/com/itachi1706/helperlib/helpers/ApiCallsHelper.kt new file mode 100644 index 0000000..3977448 --- /dev/null +++ b/helperlib/src/main/java/com/itachi1706/helperlib/helpers/ApiCallsHelper.kt @@ -0,0 +1,107 @@ +package com.itachi1706.helperlib.helpers + +import android.content.Context +import com.android.volley.Request +import com.android.volley.RequestQueue +import com.android.volley.toolbox.StringRequest +import com.android.volley.toolbox.Volley +import com.itachi1706.helperlib.objects.ApiResponse +import kotlinx.serialization.json.Json + +@Suppress("unused") +class ApiCallsHelper(context: Context, private val baseUrl: String = DEFAULT_URL, private val tag: String = "DEFAULT") { + + // Set up volley request queue + private val queue: RequestQueue = Volley.newRequestQueue(context) + + fun cancelAllRequests() { + queue.cancelAll(tag) + } + + fun getSequenceNumber(): Int { + return queue.sequenceNumber + } + + fun makeGetCall(path: String, listener: ApiCallListener) { + // Make a HTTP GET Call to the URL and path with Volley + val url = "$baseUrl/$path" + + val request = StringRequest(Request.Method.GET, url, { response -> + listener.onApiCallSuccess(serializeResponse(response)) + }, { error -> + listener.onApiCallError(error.message ?: UNKNOWN_ERROR) + }) + request.tag = tag + queue.add(request) + } + + fun makePostCall(path: String, data: String, listener: ApiCallListener) { + // Make a HTTP POST Call to the URL and path + val url = "$baseUrl/$path" + + val request = object : StringRequest(Method.POST, url, { response -> + listener.onApiCallSuccess(serializeResponse(response)) + }, { error -> + listener.onApiCallError(error.message ?: UNKNOWN_ERROR) + }) { + override fun getBody(): ByteArray { + return data.toByteArray() + } + + override fun getBodyContentType(): String { + return "application/json" + } + } + request.tag = tag + queue.add(request) + } + + fun makePutCall(path: String, data: String, listener: ApiCallListener) { + // Make a HTTP PUT Call to the URL and path + val url = "$baseUrl/$path" + + val request = object : StringRequest(Method.PUT, url, { response -> + listener.onApiCallSuccess(serializeResponse(response)) + }, { error -> + listener.onApiCallError(error.message ?: UNKNOWN_ERROR) + }) { + override fun getBody(): ByteArray { + return data.toByteArray() + } + + override fun getBodyContentType(): String { + return "application/json" + } + } + request.tag = tag + queue.add(request) + } + + fun makeDeleteCall(path: String, listener: ApiCallListener) { + // Make a HTTP DELETE Call to the URL and path + val url = "$baseUrl/$path" + + val request = StringRequest(Request.Method.DELETE, url, { response -> + listener.onApiCallSuccess(serializeResponse(response)) + }, { error -> + listener.onApiCallError(error.message ?: UNKNOWN_ERROR) + }) + request.tag = tag + queue.add(request) + } + + private fun serializeResponse(response: String): ApiResponse { + return Json.decodeFromString(response) + } + + interface ApiCallListener { + fun onApiCallSuccess(response: ApiResponse) + fun onApiCallError(error: String) + } + + companion object { + const val LOG_TAG = "ApiCallsHelper" + const val UNKNOWN_ERROR = "Unknown Error" + const val DEFAULT_URL = "https://api.itachi1706.com" + } +} \ No newline at end of file diff --git a/helperlib/src/main/java/com/itachi1706/helperlib/objects/ApiResponse.kt b/helperlib/src/main/java/com/itachi1706/helperlib/objects/ApiResponse.kt new file mode 100644 index 0000000..b9cec64 --- /dev/null +++ b/helperlib/src/main/java/com/itachi1706/helperlib/objects/ApiResponse.kt @@ -0,0 +1,13 @@ +package com.itachi1706.helperlib.objects + +import java.io.Serializable +import java.util.Date + +data class ApiResponse( + val date: Date, + val status: Int, + val message: String, + val success: Boolean, + val data: Any? = null, + val error: String? = null +) : Serializable From 1f62e498889968cf1d78885106a7190da105faa8 Mon Sep 17 00:00:00 2001 From: Kenneth Soh Date: Tue, 30 Jan 2024 02:29:17 +0800 Subject: [PATCH 2/5] fix: Disable HTTP traffic --- helperlib/src/main/AndroidManifest.xml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/helperlib/src/main/AndroidManifest.xml b/helperlib/src/main/AndroidManifest.xml index cc6c666..ccbb399 100644 --- a/helperlib/src/main/AndroidManifest.xml +++ b/helperlib/src/main/AndroidManifest.xml @@ -1,11 +1,16 @@ - + + + + android:theme="@style/AppTheme" + tools:ignore="UnusedAttribute" /> From ab49d00a276a967b0b6037b6e005bba95d52a629 Mon Sep 17 00:00:00 2001 From: Kenneth Soh Date: Tue, 30 Jan 2024 02:29:27 +0800 Subject: [PATCH 3/5] refactor: Formatting --- helperlib/src/main/AndroidManifest.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/helperlib/src/main/AndroidManifest.xml b/helperlib/src/main/AndroidManifest.xml index ccbb399..9bc8fe6 100644 --- a/helperlib/src/main/AndroidManifest.xml +++ b/helperlib/src/main/AndroidManifest.xml @@ -1,16 +1,16 @@ - + From 3bbf9c945da4c2ab1dfcd3f12c7ee87e6af134ec Mon Sep 17 00:00:00 2001 From: Kenneth Soh Date: Wed, 31 Jan 2024 01:21:41 +0800 Subject: [PATCH 4/5] refactor: Simply codebase --- .../helperlib/helpers/ApiCallsHelper.kt | 91 +++++++++---------- 1 file changed, 42 insertions(+), 49 deletions(-) diff --git a/helperlib/src/main/java/com/itachi1706/helperlib/helpers/ApiCallsHelper.kt b/helperlib/src/main/java/com/itachi1706/helperlib/helpers/ApiCallsHelper.kt index 3977448..7fd1aa0 100644 --- a/helperlib/src/main/java/com/itachi1706/helperlib/helpers/ApiCallsHelper.kt +++ b/helperlib/src/main/java/com/itachi1706/helperlib/helpers/ApiCallsHelper.kt @@ -3,13 +3,18 @@ package com.itachi1706.helperlib.helpers import android.content.Context import com.android.volley.Request import com.android.volley.RequestQueue +import com.android.volley.Response import com.android.volley.toolbox.StringRequest import com.android.volley.toolbox.Volley import com.itachi1706.helperlib.objects.ApiResponse import kotlinx.serialization.json.Json @Suppress("unused") -class ApiCallsHelper(context: Context, private val baseUrl: String = DEFAULT_URL, private val tag: String = "DEFAULT") { +class ApiCallsHelper( + context: Context, + private val baseUrl: String = DEFAULT_URL, + private val tag: String = "DEFAULT" +) { // Set up volley request queue private val queue: RequestQueue = Volley.newRequestQueue(context) @@ -24,68 +29,56 @@ class ApiCallsHelper(context: Context, private val baseUrl: String = DEFAULT_URL fun makeGetCall(path: String, listener: ApiCallListener) { // Make a HTTP GET Call to the URL and path with Volley - val url = "$baseUrl/$path" - - val request = StringRequest(Request.Method.GET, url, { response -> - listener.onApiCallSuccess(serializeResponse(response)) - }, { error -> - listener.onApiCallError(error.message ?: UNKNOWN_ERROR) - }) - request.tag = tag - queue.add(request) + internalCallHandling(Request.Method.GET, path, null, listener) } - fun makePostCall(path: String, data: String, listener: ApiCallListener) { + fun makePostCall(path: String, data: String?, listener: ApiCallListener) { // Make a HTTP POST Call to the URL and path - val url = "$baseUrl/$path" + internalCallHandling(Request.Method.POST, path, data, listener) + } - val request = object : StringRequest(Method.POST, url, { response -> - listener.onApiCallSuccess(serializeResponse(response)) - }, { error -> - listener.onApiCallError(error.message ?: UNKNOWN_ERROR) - }) { - override fun getBody(): ByteArray { - return data.toByteArray() - } + fun makePutCall(path: String, data: String?, listener: ApiCallListener) { + // Make a HTTP PUT Call to the URL and path + internalCallHandling(Request.Method.PUT, path, data, listener) + } - override fun getBodyContentType(): String { - return "application/json" - } - } - request.tag = tag - queue.add(request) + fun makeDeleteCall(path: String, listener: ApiCallListener) { + // Make a HTTP DELETE Call to the URL and path + internalCallHandling(Request.Method.DELETE, path, null, listener) } - fun makePutCall(path: String, data: String, listener: ApiCallListener) { - // Make a HTTP PUT Call to the URL and path + private fun internalCallHandling( + method: Int, + path: String, + data: String?, + listener: ApiCallListener + ) { + // Make a HTTP DELETE Call to the URL and path val url = "$baseUrl/$path" - val request = object : StringRequest(Method.PUT, url, { response -> + val successListener = Response.Listener { response: String -> listener.onApiCallSuccess(serializeResponse(response)) - }, { error -> - listener.onApiCallError(error.message ?: UNKNOWN_ERROR) - }) { - override fun getBody(): ByteArray { - return data.toByteArray() - } + } + val failedListener = Response.ErrorListener { error -> + listener.onApiCallError( + error?.message ?: UNKNOWN_ERROR + ) + } - override fun getBodyContentType(): String { - return "application/json" + val request: StringRequest = if (data != null) { + object : StringRequest(method, url, successListener, failedListener) { + override fun getBody(): ByteArray { + return data.toByteArray() + } + + override fun getBodyContentType(): String { + return "application/json" + } } + } else { + StringRequest(method, url, successListener, failedListener) } - request.tag = tag - queue.add(request) - } - fun makeDeleteCall(path: String, listener: ApiCallListener) { - // Make a HTTP DELETE Call to the URL and path - val url = "$baseUrl/$path" - - val request = StringRequest(Request.Method.DELETE, url, { response -> - listener.onApiCallSuccess(serializeResponse(response)) - }, { error -> - listener.onApiCallError(error.message ?: UNKNOWN_ERROR) - }) request.tag = tag queue.add(request) } From 1346ba308f35d9c6569b21e0d3f22e98abbea6c9 Mon Sep 17 00:00:00 2001 From: Kenneth Soh Date: Wed, 31 Jan 2024 01:28:41 +0800 Subject: [PATCH 5/5] docs: Add documentation for new code --- .../helperlib/helpers/ApiCallsHelper.kt | 95 +++++++++++++++++-- .../helperlib/objects/ApiResponse.kt | 13 +++ 2 files changed, 99 insertions(+), 9 deletions(-) diff --git a/helperlib/src/main/java/com/itachi1706/helperlib/helpers/ApiCallsHelper.kt b/helperlib/src/main/java/com/itachi1706/helperlib/helpers/ApiCallsHelper.kt index 7fd1aa0..37afa20 100644 --- a/helperlib/src/main/java/com/itachi1706/helperlib/helpers/ApiCallsHelper.kt +++ b/helperlib/src/main/java/com/itachi1706/helperlib/helpers/ApiCallsHelper.kt @@ -10,50 +10,110 @@ import com.itachi1706.helperlib.objects.ApiResponse import kotlinx.serialization.json.Json @Suppress("unused") +/** + * API Call Handling class + * @param context Context + * @param baseUrl Base URL to use (Default: https://api.itachi1706.com) + * @param tag Tag to use for Volley Request Queue (Default: DEFAULT) + */ class ApiCallsHelper( context: Context, private val baseUrl: String = DEFAULT_URL, private val tag: String = "DEFAULT" ) { - // Set up volley request queue + /** + * Volley Request Queue + */ private val queue: RequestQueue = Volley.newRequestQueue(context) + /** + * Cancels all requests with the tag + */ fun cancelAllRequests() { queue.cancelAll(tag) } + /** + * Gets the current sequence number of the request queue + * @return Sequence Number + */ fun getSequenceNumber(): Int { return queue.sequenceNumber } + /** + * Makes a GET Call to the specified path + * @param path Path to call + * @param listener Listener to handle the response on + * @see ApiCallListener + * @see ApiResponse + * @see StringRequest + * @see internalCallHandling + */ fun makeGetCall(path: String, listener: ApiCallListener) { - // Make a HTTP GET Call to the URL and path with Volley internalCallHandling(Request.Method.GET, path, null, listener) } + /** + * Makes a POST Call to the specified path + * @param path Path to call + * @param data Data to send + * @param listener Listener to handle the response on + * @see ApiCallListener + * @see ApiResponse + * @see StringRequest + * @see internalCallHandling + */ fun makePostCall(path: String, data: String?, listener: ApiCallListener) { - // Make a HTTP POST Call to the URL and path internalCallHandling(Request.Method.POST, path, data, listener) } + /** + * Makes a PUT Call to the specified path + * @param path Path to call + * @param data Data to send + * @param listener Listener to handle the response on + * @see ApiCallListener + * @see ApiResponse + * @see StringRequest + * @see internalCallHandling + */ fun makePutCall(path: String, data: String?, listener: ApiCallListener) { - // Make a HTTP PUT Call to the URL and path internalCallHandling(Request.Method.PUT, path, data, listener) } + /** + * Makes a DELETE Call to the specified path + * @param path Path to call + * @param listener Listener to handle the response on + * @see ApiCallListener + * @see ApiResponse + * @see StringRequest + * @see internalCallHandling + */ fun makeDeleteCall(path: String, listener: ApiCallListener) { - // Make a HTTP DELETE Call to the URL and path internalCallHandling(Request.Method.DELETE, path, null, listener) } + /** + * Makes a HTTP Call to the specified path + * @param method HTTP Method to use + * @param path Path to call + * @param data Data to send if any + * @param listener Listener to handle the response on + * @see ApiCallListener + * @see ApiResponse + * @see StringRequest + * @see internalCallHandling + */ private fun internalCallHandling( method: Int, path: String, data: String?, listener: ApiCallListener ) { - // Make a HTTP DELETE Call to the URL and path + // Make a HTTP Call to the URL and path val url = "$baseUrl/$path" val successListener = Response.Listener { response: String -> @@ -83,18 +143,35 @@ class ApiCallsHelper( queue.add(request) } + /** + * Serializes the response into an ApiResponse object + * @param response Response to serialize + * @return ApiResponse object + */ private fun serializeResponse(response: String): ApiResponse { return Json.decodeFromString(response) } + /** + * Interface to handle API Calls + */ interface ApiCallListener { + /** + * Called when API Call is successful + * @param response ApiResponse object + */ fun onApiCallSuccess(response: ApiResponse) + + /** + * Called when API Call fails + * @param error Error message + */ fun onApiCallError(error: String) } companion object { - const val LOG_TAG = "ApiCallsHelper" - const val UNKNOWN_ERROR = "Unknown Error" - const val DEFAULT_URL = "https://api.itachi1706.com" + private const val LOG_TAG = "ApiCallsHelper" + private const val UNKNOWN_ERROR = "Unknown Error" + private const val DEFAULT_URL = "https://api.itachi1706.com" } } \ No newline at end of file diff --git a/helperlib/src/main/java/com/itachi1706/helperlib/objects/ApiResponse.kt b/helperlib/src/main/java/com/itachi1706/helperlib/objects/ApiResponse.kt index b9cec64..f20819a 100644 --- a/helperlib/src/main/java/com/itachi1706/helperlib/objects/ApiResponse.kt +++ b/helperlib/src/main/java/com/itachi1706/helperlib/objects/ApiResponse.kt @@ -3,6 +3,19 @@ package com.itachi1706.helperlib.objects import java.io.Serializable import java.util.Date +/** + * API Response Object + * @constructor Creates an API Response Object + * @since 1.0.0 + * @property date Date of the response + * @property status Status Code of the response + * @property message Message of the response + * @property success Success of the response + * @property data Data of the response + * @property error Error of the response + * @see Serializable + * @see Date + */ data class ApiResponse( val date: Date, val status: Int,