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

Book Listing Coding Challenger Implemented - MVP #21

Open
wants to merge 1 commit 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
114 changes: 94 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,100 @@
# The Challenge:
# Book App

The challenge is to create a simple Android app that exercises a REST-ful API. The API endpoint `https://api.nytimes.com/svc/books/v3/lists/current/hardcover-fiction.json?api-key=KoRB4K5LRHygfjCL2AH6iQ7NeUqDAGAB&offset=0` returns a JSON object which is a list of different books published by the New York Times.
Demonstrated the Model-View-Presenter (MVP) architecture along with Retrofit for networking,
Dagger for dependency injection, Glide for image loading, and data binding.

Using this endpoint, show a list of these items, with each row displaying at least the following data:
## Project Structure

- Image
- Title
- Description
The project is organized into the following main layers:

### Technical Instructions:
- MVP architecture, no ViewModel
- XML Layouts (no Compose)
- Demonstrate use of Dagger, Retrofit and Glide (for the images)
- No database needed
- Feel free to make any assumptions you want along the way or use any third party libraries as needed and document why you used them.
- **Data Layer**
- Repositories and API interactions.
- Retrofit for API calls.
- Error handling and result processing.

### General Instructions:
- This isn't a visual design exercise. For example, if you set random background colors to clearly differentiate the views when debugging, pick Comic Sans or Papyrus, we won't hold that against you. Well, maybe a little bit if you use Comic Sans :)
- This is also most of the code you'll be showing us – don't understimate the difficulty of the task, or the importance of this exercise in our process, and rush your PR. Put up your best professional game.
- This isn't just about handling the happy path. Think slow network (or no network at all), supporting different device sizes, ease of build and run of the project. If we can't check out and click the run button in Android Studio, you're off to a bad start (we've had PRs without a graddle for instance).
- Explanations on any choice you've made are welcome.
- We appreciate there's a lot that is asked in this exercise. If you need more time, feel free to ask. If you need to de-prioritize something, apply the same judgement you would on a professional project, argument your decision.
- **Domain Layer**
- Use cases to handle business logic, decoupled from UI or data source implementations.

Bonus Points:
- Unit tests
- **Presentation Layer**
- Activities, presenters, and views for UI.
- Adapters for RecyclerView.
- Loading indicators and error messages.

## Components and Architecture

### Components

- **BooksRepository Interface**
- Defines the contract for the repository handling API calls.

- **BooksRepositoryImpl Implementation**
- Implements `BooksRepository`.
- Manages API calls and error handling for `getBooksList`.

- **GetBooksUseCase**
- Executes the `getBooksList` call from `BooksRepository`.
- Returns `Result<BookResults?>` for handling success or failure.

- **BooksPresenter**
- Orchestrates the flow between `BooksView` and `GetBooksUseCase`.
- Calls view methods to update UI based on API call results.

- **BooksView Interface**
- Defines the contract for views to implement, e.g., `showLoading`, `displayBooks`, and `showError`.

- **BooksAdapter**
- Manages book list display in a RecyclerView.

- **MyApplication**
- Initializes `AppComponent` for Dagger dependency injection.

- **Constants**
- Holds API keys and other constants.

### Architecture Flow

1. **View Layer (Activity)**
- `BooksListActivity` initializes the UI and presenter, and manages swipe-to-refresh behavior.
- Handles user interactions and updates based on presenter calls.

2. **Presentation Layer (Presenter)**
- `BooksPresenter`:
- Starts by calling `showLoading(true)`.
- Asynchronously invokes `GetBooksUseCase` to fetch books.
- Based on the result:
- Calls `displayBooks` if successful.
- Calls `showError` if it encounters an error.
- Hides the loading indicator after receiving a result.

3. **Domain Layer (Use Case)**
- `GetBooksUseCase` interacts with `BooksRepository` to execute the API call.
- Wraps the repository result in a `Result` type to signal success or failure.

4. **Data Layer (Repository)**
- `BooksRepositoryImpl` calls `ApiService` to fetch data.
- On receiving a response:
- Returns `Result.success(data)` if successful.
- Returns `Result.failure(exception)` on failure.
### Flow Diagram

BooksListActivity
|
| --> Presenter.loadBooks()
|
BooksPresenter
|
| --> GetBooksUseCase()
|
GetBooksUseCase
|
| --> BooksRepository.getBooksList()
|
BooksRepositoryImpl
|
| --> ApiService.getBooksList()
|
Retrofit API Call
15 changes: 14 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ android {

buildTypes {
release {
minifyEnabled false
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
Expand All @@ -31,6 +31,12 @@ android {
kotlinOptions {
jvmTarget = '1.8'
}
viewBinding {
enable = true
}
buildFeatures {
dataBinding true
}
}

dependencies {
Expand All @@ -47,6 +53,7 @@ dependencies {
//retrofit
implementation libs.retrofit
implementation libs.retrofit.rx.adapter
implementation libs.retrofit.rx.converter

//glide
implementation libs.glide
Expand All @@ -56,7 +63,13 @@ dependencies {
implementation libs.rx.java
implementation libs.rx.kotlin

// Swipe to refresh layout
implementation libs.androidx.swiperefreshlayout

testImplementation libs.junit
androidTestImplementation libs.androidx.junit
androidTestImplementation libs.androidx.espresso.core
testImplementation libs.mockito.core
testImplementation libs.mockito.inline
testImplementation libs.kotlinx.coroutines.test
}
6 changes: 4 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".MyApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand All @@ -13,7 +15,7 @@
android:theme="@style/Theme.AndroidOTChallenge"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:name=".ui.books.BooksListActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand Down
22 changes: 0 additions & 22 deletions app/src/main/java/com/example/otchallenge/MainActivity.kt

This file was deleted.

15 changes: 13 additions & 2 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,25 @@ package com.example.otchallenge

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

/**
* Custom Application class that serves as the entry point for the app.
* This class initializes the Dagger dependency injection framework
* by creating the `AppComponent`, which is used to provide application-level
* dependencies throughout the app.
*
* The `onCreate` method is overridden to set up the Dagger component
* using the `ApplicationModule`, ensuring that all necessary dependencies
* are properly injected before any activity or service is launched.
*/
class MyApplication : Application() {

lateinit var appComponent: AppComponent

override fun onCreate() {
super.onCreate()
appComponent = DaggerAppComponent.builder().build()
appComponent = DaggerAppComponent.builder()
.applicationModule(ApplicationModule(this)).build()
}
}
19 changes: 16 additions & 3 deletions app/src/main/java/com/example/otchallenge/di/AppComponent.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
package com.example.otchallenge.di

import com.example.otchallenge.MainActivity
import com.example.otchallenge.ui.books.BooksListActivity
import com.example.otchallenge.usecase.GetBooksUseCase
import dagger.Component
import javax.inject.Singleton


/**
* Dagger Component interface responsible for providing dependencies across the application.
* It specifies the modules used for dependency injection (`ApplicationModule`, `AppModule`, and `NetworkModule`)
* and declares the injection target (`BooksListActivity`).
* Additionally, it exposes `getBooksUseCase` as a provision function for dependency retrieval, allowing it to be injected wherever required.
*/

@Singleton
@Component
@Component(modules = [ApplicationModule::class,AppModule::class, NetworkModule::class])
interface AppComponent {
fun inject(activity: MainActivity)

fun inject(activity: BooksListActivity)

fun getBooksUseCase(): GetBooksUseCase

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

import com.example.otchallenge.network.ApiService
import com.example.otchallenge.repository.BooksRepository
import com.example.otchallenge.repository.BooksRepositoryImpl
import com.example.otchallenge.usecase.GetBooksUseCase
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
/**
* Dagger Module that provides dependencies related to the application's domain layer.
* This object module offers singleton instances of `BooksRepository` and `GetBooksUseCase`, ensuring
* that these dependencies are shared across the application. The repository relies on `ApiService` for data
* access, while `GetBooksUseCase` utilizes the repository to fetch book data as a use case.
*/
@Module
object AppModule {

@Provides
@Singleton
fun provideBooksRepository(apiService: ApiService): BooksRepository {
return BooksRepositoryImpl(apiService)
}

@Provides
@Singleton
fun provideGetBooksUseCase(booksRepository: BooksRepository): GetBooksUseCase {
return GetBooksUseCase(booksRepository)
}
}
18 changes: 18 additions & 0 deletions app/src/main/java/com/example/otchallenge/di/ApplicationModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.example.otchallenge.di

import android.app.Application
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
/**
* Dagger Module that provides application-level dependencies.
* This module offers a singleton instance of `Application`, allowing it to be injected wherever needed
* within the application's dependency graph.
*/
@Module
class ApplicationModule(private val application: Application) {

@Provides
@Singleton
fun provideApplication(): Application = application
}
Loading