Skip to content
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

New York Times Books Application #6

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 34 additions & 15 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
# Gradle files
.gradle/
build/

# Local configuration file (sdk path, etc)
local.properties
.idea

# Log/OS Files
*.log

# Android Studio generated files and folders
captures/
.externalNativeBuild/
.cxx/
*.apk
output.json

# IntelliJ
*.iml
.idea/
misc.xml
deploymentTargetDropDown.xml
render.experimental.xml

# Keystore files
*.jks
*.keystore

# Google Services (e.g. APIs or Firebase)
google-services.json

# Android Profiling
*.hprof

**/ApiKeys.kt
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,71 @@ Using this endpoint, show a list of these items, with each row displaying at lea

Bonus Points:
- Unit tests

---

### Project description

This is an approach to the requested requirement of an application that shows the list of elements (books).
The general idea is to have an Android application with three screens, a splash screen, a screen with the list of elements and a last screen with the details of each element.
The app uses a clean architecture with MVP (Model-View-Presenter) design pattern and demonstrates various technologies including Retrofit, Dagger, Room, RxJava, and Navigation Architecture Component.

#### Architecture
##### Clean Architecture Layers
1. Presentation Layer
- **View (Fragment):** Displays data and handles user interactions.
- **Presenter:** Contains the presentation logic and communicates with the Use Cases to fetch data.

2. Domain Layer
- **Use Cases:** Encapsulate the application's business logic. Example: GetBooksUseCase, GetBookDetailsUseCase.
- **Models:** Business models used within the application logic.

3. Data Layer
- **Repositories**: Responsible for fetching data from data sources (API, database) and converting it to domain models.
- **Data Sources**: Include API service interfaces and DAOs for database access.
- **Mappers**: Convert data models to domain models and vice versa.

##### MVP Design Pattern

1. **Model**: Represents the data and business logic.
2. **View**: Displays data and routes user commands to the Presenter.
3. **Presenter**: Retrieves data from the Model, applies presentation logic, and updates the View.

#### Technologies Used

- **Kotlin**: Programming language for Android development.
- **Retrofit**: For making network requests to the New York Times Books API.
- **Dagger**: For dependency injection.
- **Room**: For local database storage.
- **RxJava**: For handling asynchronous operations.
- **Glide**: For image loading and caching.
- **JUnit & Mockito**: For unit testing.
- **Navigation Architecture Component**: For managing navigation and fragment transitions.

#### Project Structure

├───data
│ ├───api
│ ├───database
│ ├───di
│ ├───mapper
│ ├───model
│ └───repository
├───di
├───domain
│ ├───executor
│ ├───mapper
│ ├───model
│ ├───repository
│ └───usecase
├───presentation
│ ├───model
│ ├───presenter
│ └───view
└───utils

#### Known Issues
There is an unresolved bug where the loadBookDetails method in BookDetailPresenter does not always reach the subscribe block. This might be related to threading issues or improper disposal of RxJava subscriptions. This bug prevents the detail information from being painted in the book detail fragment.
Further debugging is required to pinpoint the exact cause and resolve the issue.


47 changes: 42 additions & 5 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.kapt)
alias(libs.plugins.navigation.safeargs)
}

android {
Expand All @@ -19,7 +20,17 @@ android {
}

buildTypes {

debug {
//This information should not be hardcoded. CI\/CD override and manage.
buildConfigField "String", "NYT_API_KEY", "\"KoRB4K5LRHygfjCL2AH6iQ7NeUqDAGAB\""
buildConfigField "String", "NYT_API_URL", "\"https://api.nytimes.com/svc/books/v3/\""
buildConfigField "String", "OFFSET", "\"0\""
}
release {
buildConfigField "String", "NYT_API_KEY", "\"KoRB4K5LRHygfjCL2AH6iQ7NeUqDAGAB\""
buildConfigField "String", "NYT_API_URL", "\"https://api.nytimes.com/svc/books/v3/\""
buildConfigField "String", "OFFSET", "\"0\""
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
Expand All @@ -31,32 +42,58 @@ android {
kotlinOptions {
jvmTarget = '1.8'
}

buildFeatures {
buildConfig = true
}
}

dependencies {

implementation libs.androidx.core.ktx
implementation libs.androidx.appcompat
implementation libs.material
implementation libs.androidx.activity
implementation libs.androidx.constraintlayout
// dagger
implementation libs.androidx.swiperefreshlayout

//Dagger
implementation libs.dagger
implementation libs.dagger.android
kapt libs.dagger.compiler

//retrofit
//Retrofit
implementation libs.retrofit
implementation libs.retrofit.gson
implementation libs.retrofit.rx.adapter

//glide
//Glide
implementation libs.glide
kapt libs.glide.compiler

//reactive x
//Reactive x
implementation libs.rx.android
implementation libs.rx.java
implementation libs.rx.kotlin

//Room database
implementation libs.androidx.room
kapt libs.androidx.room.compiler
implementation libs.androidx.room.rxjava

//Navigation Architecture Component
implementation libs.androidx.navigation
implementation libs.androidx.navigation.ui

//Testing
testImplementation libs.junit
testImplementation libs.mockito.core
testImplementation libs.mockito.inline
testImplementation libs.powermock.module
testImplementation libs.powermock.api
androidTestImplementation libs.androidx.junit
androidTestImplementation libs.androidx.espresso.core
}

kapt {
correctErrorTypes = true
}
44 changes: 24 additions & 20 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools">

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AndroidOTChallenge"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<application
android:name=".MyApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AndroidOTChallenge"
tools:targetApi="31">
<activity
android:name=".presentation.view.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
22 changes: 0 additions & 22 deletions app/src/main/java/com/example/otchallenge/MainActivity.kt

This file was deleted.

23 changes: 18 additions & 5 deletions app/src/main/java/com/example/otchallenge/MyApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,27 @@ package com.example.otchallenge

import android.app.Application
import com.example.otchallenge.di.AppComponent
import com.example.otchallenge.di.AppModule
import com.example.otchallenge.di.DaggerAppComponent

class MyApplication : Application() {

lateinit var appComponent: AppComponent

override fun onCreate() {
super.onCreate()
appComponent = DaggerAppComponent.builder().build()
}
lateinit var appComponent: AppComponent

override fun onCreate() {
super.onCreate()
appComponent = DaggerAppComponent.factory()
.create(AppModule(this))
appComponent.inject(this)
/*appComponent = DaggerAppComponent.builder()
.appModule(AppModule(this))
.networkModule(NetworkModule())
//.repositoryModule(RepositoryModule())
.databaseModule(DatabaseModule())
.presenterModule(PresenterModule())
.useCaseModule(UseCaseModule())
.build()
appComponent.inject(this)*/
}
}
15 changes: 15 additions & 0 deletions app/src/main/java/com/example/otchallenge/data/api/BooksService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.otchallenge.data.api

import com.example.otchallenge.BuildConfig
import com.example.otchallenge.data.model.OverviewResponse
import io.reactivex.Single
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Query

interface BooksService {
@GET("lists/current/hardcover-fiction.json")
fun getBooks(@Query("api-key") apiKey: String = BuildConfig.NYT_API_KEY):
Single<Response<OverviewResponse>>

}
20 changes: 20 additions & 0 deletions app/src/main/java/com/example/otchallenge/data/database/BookDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.example.otchallenge.data.database

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import io.reactivex.Completable
import io.reactivex.Single

@Dao
interface BookDao {
@Query("SELECT id, title, description, bookImage FROM books")
fun getBooks(): Single<List<BookSummaryEntity>>

@Query("SELECT * FROM books WHERE id = :bookId")
fun getBookById(bookId: Int): Single<BookEntity>

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertBooks(books: List<BookEntity>): Completable
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.otchallenge.data.database

import androidx.room.Database
import androidx.room.RoomDatabase

@Database(entities = [BookEntity::class], version = 1)
abstract class BookDatabase : RoomDatabase() {
abstract fun bookDao(): BookDao
}
Loading