-
Notifications
You must be signed in to change notification settings - Fork 1
Fix #7: Setup Ktor and Fix Instrumented Tests Failure #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
22cd341
Implement network layer with Ktor and Result
theMr17 e7d71b2
Add notification data model and remote data source implementation
theMr17 23d20d8
Improve Error Handling, Update Dependencies, and Add Timeouts
theMr17 51f5bef
Introduce network layer testing and utility enhancements
theMr17 f90230b
Refactor ResponseToResultTest to use a mock base URL for consistency
theMr17 5f3179e
Set GitHub API Version and add Authorization header to HttpClient
theMr17 2049e42
Enhance network response handling and error processing
theMr17 30c6fa4
Improve HttpClientFactoryTest readability and maintainability
theMr17 9b9e771
Merge branch 'main' into feat/setup-ktor
theMr17 a8fec1f
Update test.yml workflow: Add cleanup step and enable debug logging
theMr17 a907714
Update Android test script in test.yml
theMr17 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
64 changes: 64 additions & 0 deletions
64
app/src/main/java/com/notifier/app/core/data/networking/HttpClientFactory.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| package com.notifier.app.core.data.networking | ||
|
|
||
| import io.ktor.client.HttpClient | ||
| import io.ktor.client.engine.HttpClientEngine | ||
| import io.ktor.client.plugins.HttpTimeout | ||
| import io.ktor.client.plugins.contentnegotiation.ContentNegotiation | ||
| import io.ktor.client.plugins.defaultRequest | ||
| import io.ktor.client.plugins.logging.ANDROID | ||
| import io.ktor.client.plugins.logging.LogLevel | ||
| import io.ktor.client.plugins.logging.Logger | ||
| import io.ktor.client.plugins.logging.Logging | ||
| import io.ktor.http.ContentType | ||
| import io.ktor.http.HttpHeaders | ||
| import io.ktor.http.contentType | ||
| import io.ktor.http.headers | ||
| import io.ktor.serialization.kotlinx.json.json | ||
| import kotlinx.serialization.json.Json | ||
|
|
||
| /** | ||
| * Factory object for creating an instance of [HttpClient] with predefined configurations. | ||
| */ | ||
| object HttpClientFactory { | ||
| /** | ||
| * Creates and configures an instance of [HttpClient] with logging, JSON serialization, | ||
| * and default request headers. | ||
| * | ||
| * @param engine The HTTP client engine to use for network requests. | ||
| * @return A configured instance of [HttpClient]. | ||
| */ | ||
| fun create(engine: HttpClientEngine): HttpClient = HttpClient(engine) { | ||
| // Enable logging for network requests and responses | ||
| install(Logging) { | ||
| logger = Logger.ANDROID | ||
| level = LogLevel.ALL | ||
| } | ||
|
|
||
| // Configure request timeouts | ||
| install(HttpTimeout) { | ||
| requestTimeoutMillis = 30000 // 30 seconds | ||
| connectTimeoutMillis = 15000 // 15 seconds | ||
| socketTimeoutMillis = 15000 // 15 seconds | ||
| } | ||
|
|
||
| // Configure JSON serialization/deserialization | ||
| install(ContentNegotiation) { | ||
| json( | ||
| Json { | ||
| ignoreUnknownKeys = true | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| // Set default request headers and properties | ||
| defaultRequest { | ||
| contentType(ContentType.Application.Json) | ||
| } | ||
|
|
||
| // TODO: Inject the token once dagger-hilt is setup. | ||
| headers { | ||
| append(HttpHeaders.Authorization, "Bearer ") | ||
| append("X-GitHub-Api-Version", "2022-11-28") | ||
| } | ||
| } | ||
| } | ||
theMr17 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
25 changes: 25 additions & 0 deletions
25
app/src/main/java/com/notifier/app/core/data/networking/constructUrl.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package com.notifier.app.core.data.networking | ||
|
|
||
| import com.notifier.app.BuildConfig | ||
|
|
||
| /** | ||
| * Constructs a complete URL by ensuring it is prefixed with the base URL. | ||
| * | ||
| * This function checks whether the provided [url] already contains the base URL. | ||
| * If not, it appends the base URL accordingly. | ||
| * | ||
| * @param url The relative or absolute URL to be processed. | ||
| * @return The fully constructed URL with the appropriate base URL. | ||
| */ | ||
| fun constructUrl(url: String): String { | ||
| return when { | ||
| // If the URL already contains the base URL, return it as is | ||
| url.contains(BuildConfig.BASE_URL) -> url | ||
|
|
||
| // If the URL starts with "/", append it to the base URL after removing the leading slash | ||
| url.startsWith("/") -> BuildConfig.BASE_URL + url.drop(1) | ||
|
|
||
| // If the URL is a relative path without a leading slash, append it directly to the base URL | ||
| else -> BuildConfig.BASE_URL + url | ||
| } | ||
| } |
35 changes: 35 additions & 0 deletions
35
app/src/main/java/com/notifier/app/core/data/networking/responseToResult.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package com.notifier.app.core.data.networking | ||
|
|
||
| import com.notifier.app.core.domain.util.NetworkError | ||
| import com.notifier.app.core.domain.util.Result | ||
| import io.ktor.client.call.body | ||
| import io.ktor.client.statement.HttpResponse | ||
| import io.ktor.serialization.JsonConvertException | ||
|
|
||
| /** | ||
| * Converts an [HttpResponse] to a [Result] object, handling different HTTP status codes | ||
| * and potential serialization errors. | ||
| * | ||
| * @param T The expected response type. | ||
| * @param response The [HttpResponse] received from the network request. | ||
| * @return A [Result] containing either the parsed response data or a [NetworkError]. | ||
| */ | ||
| suspend inline fun <reified T> responseToResult( | ||
| response: HttpResponse, | ||
| ): Result<T, NetworkError> { | ||
| return when (response.status.value) { | ||
| in 200..299 -> { | ||
| try { | ||
| Result.Success(response.body<T>()) | ||
| } catch (e: JsonConvertException) { | ||
| e.printStackTrace() | ||
| Result.Error(NetworkError.SERIALIZATION) | ||
| } | ||
| } | ||
|
|
||
| 408 -> Result.Error(NetworkError.REQUEST_TIMEOUT) | ||
| 429 -> Result.Error(NetworkError.TOO_MANY_REQUESTS) | ||
| in 500..599 -> Result.Error(NetworkError.SERVER_ERROR) | ||
| else -> Result.Error(NetworkError.UNKNOWN) | ||
| } | ||
| } |
37 changes: 37 additions & 0 deletions
37
app/src/main/java/com/notifier/app/core/data/networking/safeCall.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| package com.notifier.app.core.data.networking | ||
|
|
||
| import com.notifier.app.core.domain.util.NetworkError | ||
| import com.notifier.app.core.domain.util.Result | ||
| import io.ktor.client.statement.HttpResponse | ||
| import io.ktor.util.network.UnresolvedAddressException | ||
| import kotlinx.coroutines.ensureActive | ||
| import kotlinx.serialization.SerializationException | ||
| import kotlin.coroutines.coroutineContext | ||
|
|
||
| /** | ||
| * Executes a network call safely, handling exceptions and returning a [Result]. | ||
| * | ||
| * This function wraps a network call inside a try-catch block to handle common errors such as | ||
| * internet unavailability, serialization issues, and unknown errors. It ensures that the coroutine | ||
| * remains active before returning a result. | ||
| * | ||
| * @param T The expected response type. | ||
| * @param execute A lambda function that performs the network request and returns an [HttpResponse]. | ||
| * @return A [Result] containing either the successful response data or a [NetworkError]. | ||
| */ | ||
| suspend inline fun <reified T> safeCall( | ||
| execute: () -> HttpResponse, | ||
| ): Result<T, NetworkError> { | ||
| val response = try { | ||
| execute() | ||
| } catch (e: UnresolvedAddressException) { | ||
| return Result.Error(NetworkError.NO_INTERNET) | ||
| } catch (e: SerializationException) { | ||
| return Result.Error(NetworkError.SERIALIZATION) | ||
| } catch (e: Exception) { | ||
| coroutineContext.ensureActive() | ||
| return Result.Error(NetworkError.UNKNOWN) | ||
| } | ||
|
|
||
| return responseToResult(response) | ||
| } |
Empty file.
10 changes: 10 additions & 0 deletions
10
app/src/main/java/com/notifier/app/core/domain/util/Error.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.notifier.app.core.domain.util | ||
|
|
||
| /** | ||
| * A base interface for representing different types of errors in the application. | ||
| * | ||
| * Implementations of this interface should define specific error categories, | ||
| * such as network errors, validation errors, or business logic errors. | ||
| * This allows for a standardized approach to error handling throughout the application. | ||
| */ | ||
| interface Error |
24 changes: 24 additions & 0 deletions
24
app/src/main/java/com/notifier/app/core/domain/util/NetworkError.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package com.notifier.app.core.domain.util | ||
|
|
||
| /** | ||
| * Enum representing different types of network-related errors. | ||
| */ | ||
| enum class NetworkError : Error { | ||
| /** The request timed out before receiving a response. */ | ||
| REQUEST_TIMEOUT, | ||
|
|
||
| /** Too many requests were sent in a short period (rate limiting). */ | ||
| TOO_MANY_REQUESTS, | ||
|
|
||
| /** No internet connection is available. */ | ||
| NO_INTERNET, | ||
|
|
||
| /** A server error occurred (HTTP 5xx status codes). */ | ||
| SERVER_ERROR, | ||
|
|
||
| /** Serialization error occurred while parsing the response. */ | ||
| SERIALIZATION, | ||
|
|
||
| /** An unknown error occurred. */ | ||
| UNKNOWN, | ||
| } |
91 changes: 91 additions & 0 deletions
91
app/src/main/java/com/notifier/app/core/domain/util/Result.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| package com.notifier.app.core.domain.util | ||
|
|
||
| /** | ||
| * A type alias representing an error type in the domain layer. | ||
| * | ||
| * This alias is used to standardize error handling throughout the domain logic. | ||
| */ | ||
| typealias DomainError = Error | ||
theMr17 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * A sealed interface representing the result of an operation that can either be successful | ||
| * or return an error. | ||
| * | ||
| * @param D The type of successful data. | ||
| * @param E The type of error, which must extend [Error]. | ||
| */ | ||
| sealed interface Result<out D, out E : Error> { | ||
| /** | ||
| * Represents a successful operation result containing the expected data. | ||
| * | ||
| * @param data The successfully retrieved data. | ||
| */ | ||
| data class Success<out D>(val data: D) : Result<D, Nothing> | ||
|
|
||
| /** | ||
| * Represents an error result containing an error of type [E]. | ||
| * | ||
| * @param error The error that occurred. | ||
| */ | ||
| data class Error<out E : DomainError>(val error: E) : Result<Nothing, E> | ||
| } | ||
|
|
||
| /** | ||
| * Transforms the successful data inside a [Result] using the given mapping function. | ||
| * | ||
| * @param map A function to transform the success data. | ||
| * @return A new [Result] with the transformed data if successful; otherwise, returns the original error. | ||
| */ | ||
| inline fun <T, E : Error, R> Result<T, E>.map(map: (T) -> R): Result<R, E> { | ||
| return when (this) { | ||
| is Result.Error -> Result.Error(error) | ||
| is Result.Success -> Result.Success(map(data)) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Converts a [Result] into an [EmptyResult], which discards the success data but retains the error. | ||
| * | ||
| * @return A [Result] with `Unit` as its success type. | ||
| */ | ||
| fun <T, E : Error> Result<T, E>.asEmptyDataResult(): EmptyResult<E> { | ||
| return map { } | ||
| } | ||
|
|
||
| /** | ||
| * Executes the given action if the [Result] is successful. | ||
| * | ||
| * @param action A function to execute if the result is [Result.Success]. | ||
| * @return The original [Result] to allow method chaining. | ||
| */ | ||
| inline fun <T, E : Error> Result<T, E>.onSuccess(action: (T) -> Unit): Result<T, E> { | ||
| return when (this) { | ||
| is Result.Error -> this | ||
| is Result.Success -> { | ||
| action(data) | ||
| this | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Executes the given action if the [Result] contains an error. | ||
| * | ||
| * @param action A function to execute if the result is [Result.Error]. | ||
| * @return The original [Result] to allow method chaining. | ||
| */ | ||
| inline fun <T, E : Error> Result<T, E>.onError(action: (E) -> Unit): Result<T, E> { | ||
| return when (this) { | ||
| is Result.Error -> { | ||
| action(error) | ||
| this | ||
| } | ||
|
|
||
| is Result.Success -> this | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A type alias for a [Result] that contains no success data but may contain an error. | ||
| */ | ||
| typealias EmptyResult<E> = Result<Unit, E> | ||
Empty file.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.