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

EC / MVP books list displayer #18

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
11 changes: 10 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ android {
kotlinOptions {
jvmTarget = '1.8'
}

viewBinding {
enabled = true
}
}

dependencies {
Expand All @@ -40,13 +44,16 @@ dependencies {
implementation libs.material
implementation libs.androidx.activity
implementation libs.androidx.constraintlayout
implementation libs.androidx.recyclerview
implementation libs.swipe.refresh.layout
// dagger
implementation libs.dagger
kapt libs.dagger.compiler

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

//glide
implementation libs.glide
Expand All @@ -57,6 +64,8 @@ dependencies {
implementation libs.rx.kotlin

testImplementation libs.junit
testImplementation libs.mockito.core
testImplementation libs.mockito.kotlin
androidTestImplementation libs.androidx.junit
androidTestImplementation libs.androidx.espresso.core
}
}
45 changes: 24 additions & 21 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
<?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.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=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

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

</manifest>
3 changes: 3 additions & 0 deletions app/src/main/java/com/example/otchallenge/Constants.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.example.otchallenge

const val BOOKS_API_KEY="KoRB4K5LRHygfjCL2AH6iQ7NeUqDAGAB"
91 changes: 74 additions & 17 deletions app/src/main/java/com/example/otchallenge/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,22 +1,79 @@
package com.example.otchallenge

import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import android.view.LayoutInflater
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
(application as MyApplication).appComponent.inject(this)
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
}
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.otchallenge.databinding.ActivityMainBinding
import com.example.otchallenge.modules.DataProvider
import com.example.otchallenge.model.BookModel
import com.example.otchallenge.modules.ImageProvider
import com.example.otchallenge.presenter.BooksPresenter
import com.example.otchallenge.presenter.BooksPresenterInterface
import com.example.otchallenge.ui.BooksAdapter
import javax.inject.Inject

class MainActivity : AppCompatActivity(), BooksPresenterInterface {

@Inject
lateinit var imageProvider: ImageProvider

@Inject
lateinit var dataProvider: DataProvider

private lateinit var binding: ActivityMainBinding

private lateinit var presenter: BooksPresenter

private lateinit var adapter: BooksAdapter

override fun onCreate(savedInstanceState: Bundle?) {
(application as MyApplication).appComponent.inject(this)
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(LayoutInflater.from(this))
setContentView(binding.root)
initUi()
}

private fun initUi() {
binding.recyclerView.layoutManager = LinearLayoutManager(binding.root.context)
adapter = BooksAdapter(imageProvider)
binding.recyclerView.adapter = adapter
binding.retryButton.setOnClickListener {
// retry to get the information
dataProvider.getBooks()
}
binding.swipeRefreshLayout.setOnRefreshListener {
// refresh the information
dataProvider.getBooks()
}
// due the strong reference between ui and presenter, presenter should live
// only in the lifecycle scope with the view
presenter = BooksPresenter(this@MainActivity)
dataProvider.booksData.observe(this@MainActivity) {
// display the information with the presenter
presenter.presentUi(it)
}
// as view has been created we can request the initial data
dataProvider.getBooks()
}

override fun displayBooks(books: List<BookModel>) {
adapter.updateData(books)
binding.retryButton.isVisible = false
}

override fun displayLoader(showLoader: Boolean) {
binding.swipeRefreshLayout.isRefreshing = showLoader
}

override fun displayError(message: String?) {
binding.swipeRefreshLayout.isRefreshing = false
binding.retryButton.isVisible = true
message?.let {
Toast.makeText(this@MainActivity, it, Toast.LENGTH_LONG).show()
}
}
}
17 changes: 12 additions & 5 deletions app/src/main/java/com/example/otchallenge/MyApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@ package com.example.otchallenge
import android.app.Application
import com.example.otchallenge.di.AppComponent
import com.example.otchallenge.di.DaggerAppComponent
import com.example.otchallenge.di.DataProviderModule
import com.example.otchallenge.di.ImageProviderModule
import com.example.otchallenge.di.NetworkProviderModule

class MyApplication : Application() {

lateinit var appComponent: AppComponent
lateinit var appComponent: AppComponent

override fun onCreate() {
super.onCreate()
appComponent = DaggerAppComponent.builder().build()
}
override fun onCreate() {
super.onCreate()
appComponent = DaggerAppComponent.builder()
.networkProviderModule(NetworkProviderModule())
.imageProviderModule(ImageProviderModule(this))
.dataProviderModule(DataProviderModule())
.build()
}
}
12 changes: 12 additions & 0 deletions app/src/main/java/com/example/otchallenge/api/BooksApi.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example.otchallenge.api

import com.example.otchallenge.BOOKS_API_KEY
import com.example.otchallenge.model.BooksResponseModel
import io.reactivex.Single
import retrofit2.http.GET
import retrofit2.http.Query

interface BooksApi {
@GET("svc/books/v3/lists/current/hardcover-fiction.json?&api-key=${BOOKS_API_KEY}")
fun getBooks(@Query("offset") offset: Int = 0): Single<BooksResponseModel>
}
10 changes: 8 additions & 2 deletions app/src/main/java/com/example/otchallenge/di/AppComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import dagger.Component
import javax.inject.Singleton

@Singleton
@Component
@Component(
modules = [
NetworkProviderModule::class,
DataProviderModule::class,
ImageProviderModule::class
]
)
interface AppComponent {
fun inject(activity: MainActivity)
fun inject(activity: MainActivity)
}
17 changes: 17 additions & 0 deletions app/src/main/java/com/example/otchallenge/di/DataProviderModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.example.otchallenge.di

import com.example.otchallenge.api.BooksApi
import com.example.otchallenge.modules.DataProvider
import dagger.Module
import dagger.Provides
import javax.inject.Singleton

@Module
class DataProviderModule {

@Provides
@Singleton
fun provideDataProvider(api: BooksApi): DataProvider {
return DataProvider(api)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.otchallenge.di

import android.content.Context
import com.example.otchallenge.modules.ImageProvider
import dagger.Module
import dagger.Provides

@Module
class ImageProviderModule(private val context: Context) {

@Provides
fun provideImageProvider(): ImageProvider {
return ImageProvider(context)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.example.otchallenge.di

import com.example.otchallenge.api.BooksApi
import dagger.Module
import dagger.Provides
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Singleton

@Module
class NetworkProviderModule {

@Provides
@Singleton
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.nytimes.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}

@Provides
@Singleton
fun provideApi(retrofit: Retrofit): BooksApi {
return retrofit.create(BooksApi::class.java)
}
}
10 changes: 10 additions & 0 deletions app/src/main/java/com/example/otchallenge/model/BookModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.example.otchallenge.model

import com.google.gson.annotations.SerializedName

data class BookModel(
val title: String? = null,
@SerializedName("book_image")
val image: String? = null,
val description: String? = null
)
9 changes: 9 additions & 0 deletions app/src/main/java/com/example/otchallenge/model/BooksData.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.otchallenge.model

data class BooksData(
val isLoading: Boolean = false,
val shouldLoadMore: Boolean = false,
val books: List<BookModel> = emptyList(),
val showError: Boolean = false,
val errorMessage: String? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example.otchallenge.model

import com.google.gson.annotations.SerializedName

data class BooksResponseModel(
@SerializedName("num_results")
val numResults: Int? = 0,
val results: BooksResultModel? = null,
)

data class BooksResultModel(val books: List<BookModel>)
Loading