Skip to content

Commit

Permalink
Added core-network & core-preference implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
AshuTyagi16 committed Jan 1, 2024
1 parent 1e06699 commit 381c4d1
Show file tree
Hide file tree
Showing 21 changed files with 545 additions and 34 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@
local.properties
shared/build
shared/core-logger/build
shared/core-network/build
shared/core-network/build
shared/core-preferences/build
1 change: 1 addition & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,12 @@ plugins {
alias(libs.plugins.com.android.library) apply false
alias(libs.plugins.org.jetbrains.kotlin.android) apply false
alias(libs.plugins.kotlin.multiplatform) apply false
alias(libs.plugins.kotlin.serialization) apply false
}
buildscript {
dependencies {
classpath(libs.build.konfig)
}
}

true // Needed to make the Suppress annotation work for the plugins block
13 changes: 13 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ compose-bom = "2023.10.01"
coroutines = "1.7.3"
kermit = "2.0.2"
ktor = "2.3.7"
prferences = "1.1.1"
build-konfig = "0.15.1"

[libraries]
## Core-Ktx
Expand All @@ -26,6 +28,10 @@ core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx
## Lifecycle-Ktx
lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle-runtime-ktx" }

## Multiplatform preferences
multiplatform-settings = { module = "com.russhwolf:multiplatform-settings-no-arg", version.ref = "prferences" }
multiplatform-settings-couroutine = { module = "com.russhwolf:multiplatform-settings-coroutines", version.ref = "prferences" }

## Compose
activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" }
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
Expand Down Expand Up @@ -54,6 +60,7 @@ coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", ve

## Logger
touchlab-kermit = { module = "co.touchlab:kermit", version.ref = "kermit" }
build-konfig = { module = "com.codingfeline.buildkonfig:buildkonfig-gradle-plugin", version.ref = "build-konfig" }

## Testing
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
Expand All @@ -66,6 +73,8 @@ com-android-application = { id = "com.android.application", version.ref = "agp"
com-android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
org-jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
build-konfig = { id = "com.codingfeline.buildkonfig" }

[bundles]
ktor-android = [
Expand All @@ -82,3 +91,7 @@ ktor-common = [
"ktor-client-auth",
"ktor-client-encoding"
]
multiplatform-preferences = [
"multiplatform-settings",
"multiplatform-settings-couroutine"
]
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ include(":app")
include(":shared")
include(":shared:core-network")
include(":shared:core-logger")
include(":shared:core-preferences")
25 changes: 24 additions & 1 deletion shared/core-network/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import com.codingfeline.buildkonfig.compiler.FieldSpec.Type.STRING

@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.com.android.library)
alias(libs.plugins.kotlin.serialization)
id(libs.plugins.build.konfig.get().toString())
}

kotlin {
Expand Down Expand Up @@ -38,6 +42,9 @@ kotlin {

// Core-Logger Module
implementation(project(":shared:core-logger"))

// Core-Preferences Module
implementation(project(":shared:core-preferences"))
}

commonTest.dependencies {
Expand All @@ -57,10 +64,26 @@ kotlin {
}
}

val modulePackageName = "com.spotify.app.core_network.shared"

buildkonfig {
packageName = modulePackageName
exposeObjectWithName = "CoreNetworkBuildKonfig"

defaultConfigs {
buildConfigField(STRING, "BASE_URL", "api.spotify.com")
buildConfigField(STRING, "BASE_URL_AUTH", "accounts.spotify.com")
}
}

android {
namespace = "com.spotify.app.core_network.shared"
namespace = modulePackageName
compileSdk = libs.versions.compileSdkVersion.get().toInt()
defaultConfig {
minSdk = libs.versions.minSdkVersion.get().toInt()
}
}

task("testClasses").doLast {
println("This is a dummy testClasses task")
}

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.spotify.app.core_network.shared.api

import com.spotify.app.core_network.shared.impl.HttpEngineProvider
import io.ktor.client.HttpClient


interface HttpClientApi {
fun getHttpClient(httpEngineProvider: HttpEngineProvider): HttpClient

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package com.spotify.app.core_network.shared.impl

import com.spotify.app.core_logger.shared.api.LoggerApi
import com.spotify.app.core_network.shared.CoreNetworkBuildKonfig
import com.spotify.app.core_network.shared.api.HttpClientApi
import com.spotify.app.core_preferences.shared.impl.util.PreferenceUtil
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.auth.Auth
import io.ktor.client.plugins.auth.providers.BearerTokens
import io.ktor.client.plugins.auth.providers.bearer
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.defaultRequest
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.plugins.observer.ResponseObserver
import com.spotify.app.core_network.shared.impl.util.NetworkConstants.Endpoints
import com.spotify.app.core_network.shared.impl.model.RefreshTokenRequest
import com.spotify.app.core_network.shared.impl.model.RefreshTokenResponse
import io.ktor.client.request.host
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.client.request.url
import io.ktor.client.statement.bodyAsText
import io.ktor.http.ContentType
import io.ktor.http.URLProtocol
import io.ktor.http.contentType
import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json

class HttpClientApiImpl(
private val shouldEnableLogging: Boolean,
private val loggerApi: LoggerApi,
private val preferenceUtil: PreferenceUtil
) : HttpClientApi {

companion object {
private const val SOCKET_TIMEOUT_MILLIS = 60_000L
private const val CONNECT_TIMEOUT_MILLIS = 60_000L
private const val REQUEST_TIMEOUT_MILLIS = 60_000L
}

@OptIn(ExperimentalSerializationApi::class)
private val json by lazy {
Json {
ignoreUnknownKeys = true
prettyPrint = false
isLenient = true
useAlternativeNames = true
encodeDefaults = true
explicitNulls = false
}
}

override fun getHttpClient(httpEngineProvider: HttpEngineProvider): HttpClient {
return HttpClient(httpEngineProvider.clientEngine(shouldEnableLogging)) {
expectSuccess = true

//Default Request
defaultRequest {
host = CoreNetworkBuildKonfig.BASE_URL
url {
protocol = URLProtocol.HTTPS
}
contentType(ContentType.Application.Json)
}

//Authenticator
install(Auth) {
bearer {
loadTokens {
BearerTokens(
accessToken = preferenceUtil.getAccessToken()!!,
refreshToken = preferenceUtil.getRefreshToken()!!
)
}
refreshTokens {
val refreshTokenResponse = client.post {
host = CoreNetworkBuildKonfig.BASE_URL_AUTH
url(Endpoints.REFRESH_TOKEN)
contentType(ContentType.parse("application/x-www-form-urlencoded"))
setBody(
RefreshTokenRequest(
grantType = "",
refreshToken = preferenceUtil.getRefreshToken()!!,
clientId = ""
)
)
markAsRefreshTokenRequest()
}.body<RefreshTokenResponse>()

val accessToken = refreshTokenResponse.accessToken
val refreshToken = refreshTokenResponse.refreshToken

runBlocking {
preferenceUtil.setAccessToken(accessToken)
preferenceUtil.setRefreshToken(refreshToken)
}

BearerTokens(
accessToken = accessToken,
refreshToken = refreshToken
)
}
}
}

//Timeout
install(HttpTimeout) {
requestTimeoutMillis = REQUEST_TIMEOUT_MILLIS
socketTimeoutMillis = SOCKET_TIMEOUT_MILLIS
connectTimeoutMillis = CONNECT_TIMEOUT_MILLIS
}

//Logging
if (shouldEnableLogging) {
install(Logging) {
logger = object : Logger {
override fun log(message: String) {
loggerApi.logD(message)
}
}
level = LogLevel.ALL
}
}

//Response Observer
if (shouldEnableLogging) {
install(ResponseObserver) {
onResponse {
loggerApi.logD(it.bodyAsText())
}
}
}

//Serialization
install(ContentNegotiation) {
json(json)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.spotify.app.core_network.shared.impl

import io.ktor.client.engine.HttpClientEngine

interface HttpEngineProvider {
fun clientEngine(shouldEnableLogging: Boolean): HttpClientEngine
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.spotify.app.core_network.shared.impl.model

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class RefreshTokenRequest(
@SerialName("grant_type")
val grantType: String,

@SerialName("refresh_token")
val refreshToken: String,

@SerialName("client_id")
val clientId: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.spotify.app.core_network.shared.impl.model

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class RefreshTokenResponse(
@SerialName("access_token")
val accessToken: String,

@SerialName("refresh_token")
val refreshToken: String,

@SerialName("token_type")
val tokenType: String,

@SerialName("expires_in")
val expiresIn: Int
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.spotify.app.core_network.shared.impl.util

object NetworkConstants {
object Endpoints {
const val REFRESH_TOKEN = "/api/token"
}
}

This file was deleted.

Loading

0 comments on commit 381c4d1

Please sign in to comment.