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

Add product list repository #321

Open
wants to merge 3 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
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ buildscript {
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
classpath 'com.google.gms:google-services:3.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28.3-alpha'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/in/testpress/database/ProductDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface ProductDao: BaseDao<ProductCourseEntity> {

@Transaction
@Query("SELECT * FROM productentity")
fun getAll(): LiveData<List<ProductWithCoursesAndPrices>>
fun getAll(): List<ProductWithCoursesAndPrices>

@Delete
fun deleteProduct(product: ProductEntity)
Expand Down
9 changes: 9 additions & 0 deletions store/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'

android {
compileSdkVersion rootProject.compileSdkVersion
Expand All @@ -22,6 +23,10 @@ android {
lintOptions {
abortOnError false
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

dependencies {
Expand All @@ -37,6 +42,10 @@ dependencies {
testImplementation rootProject.androidxCore
testImplementation rootProject.mockServer
testImplementation "androidx.arch.core:core-testing:2.1.0"

//DAGGER_HILT
implementation "com.google.dagger:hilt-android:2.28-alpha"
kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}

apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
7 changes: 7 additions & 0 deletions store/src/main/java/in/testpress/store/di/StoreApplication.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package `in`.testpress.store.di

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class StoreApplication : Application()
29 changes: 29 additions & 0 deletions store/src/main/java/in/testpress/store/di/StoreModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package `in`.testpress.store.di

import `in`.testpress.database.ProductDao
import `in`.testpress.database.TestpressDatabase
import `in`.testpress.store.network.StoreApiClient
import android.content.Context
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ApplicationComponent
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Singleton

@Module
@InstallIn(ApplicationComponent::class)
object StoreModule {

@Singleton
@Provides
fun provideProductDao(@ApplicationContext context: Context): ProductDao {
return TestpressDatabase(context).productDao()
}

@Singleton
@Provides
fun provideStoreApiClient(@ApplicationContext context: Context): StoreApiClient {
return StoreApiClient(context)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package `in`.testpress.store.repository

import `in`.testpress.database.ProductCourseEntity
import `in`.testpress.database.ProductDao
import `in`.testpress.database.ProductPriceEntity
import `in`.testpress.database.ProductWithCoursesAndPrices
import `in`.testpress.network.NetworkBoundResource
import `in`.testpress.network.Resource
import `in`.testpress.network.RetrofitCall
import `in`.testpress.store.domain.DomainProductWithCoursesAndPrices
import `in`.testpress.store.domain.asDomainContent
import `in`.testpress.store.network.NetworkProductResponse
import `in`.testpress.store.network.StoreApiClient
import `in`.testpress.store.network.asDatabaseModel
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import javax.inject.Inject

open class ProductsRepository @Inject constructor(
val productDao: ProductDao,
val storeApiClient: StoreApiClient
) {

fun fetch(forceFetch: Boolean = true): LiveData<Resource<List<DomainProductWithCoursesAndPrices>>> {
return object : NetworkBoundResource<List<DomainProductWithCoursesAndPrices>, NetworkProductResponse>() {
override fun saveNetworkResponseToDB(item: NetworkProductResponse) {
saveProducts(item)
saveCourses(item)
savePrices(item)
saveRelationalData(item)
}

override fun shouldFetch(data: List<DomainProductWithCoursesAndPrices>?): Boolean {
return forceFetch || data.isNullOrEmpty()
}

override fun loadFromDb(): LiveData<List<DomainProductWithCoursesAndPrices>> {
val liveData = MutableLiveData<List<DomainProductWithCoursesAndPrices>>()
liveData.postValue(getProductFromDB()?.asDomainContent())
return liveData
}

override fun createCall(): RetrofitCall<NetworkProductResponse> {
return storeApiClient.productsList
}

}.asLiveData()
}

private fun saveProducts(item: NetworkProductResponse) {
item.products?.forEach {
it?.asDatabaseModel()?.let { it1 -> productDao.insertProduct(it1) }
}
}

private fun saveCourses(item: NetworkProductResponse) {
item.courses?.forEach {
it?.asDatabaseModel()?.let { it1 -> productDao.insertCourse(it1) }
}
}

private fun savePrices(item: NetworkProductResponse) {
item.prices?.forEach {
it?.asDatabaseModel()?.let { it1 -> productDao.insertPrice(it1) }
}
}

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun saveRelationalData(item: NetworkProductResponse) {
item.products?.forEach { product ->
product?.courses?.forEach { courseId ->
productDao.insert(ProductCourseEntity(product.id, courseId))
}
product?.prices?.forEach { priceId ->
productDao.insertProductPrice(ProductPriceEntity(product.id, priceId))
}
}
}

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun getProductFromDB(): List<ProductWithCoursesAndPrices>? = runBlocking {
val job = CoroutineScope(Dispatchers.IO).async {
productDao.getAll()
}
return@runBlocking job.await()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package `in`.testpress.store.repository

import `in`.testpress.database.ProductDao
import `in`.testpress.store.network.NetworkProductResponse
import `in`.testpress.store.network.StoreApiClient
import android.content.Context
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class ProductsRepositoryTest {

@Mock
lateinit var productDao: ProductDao

@Mock
lateinit var storeApiClient: StoreApiClient

@Mock
lateinit var context: Context

private lateinit var repository: ProductsRepository

private lateinit var spy: ProductsRepository

@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()

@Before
fun setup() {
MockitoAnnotations.initMocks(this)
repository = ProductsRepository(productDao, storeApiClient)
spy = Mockito.spy(repository)
}

@Test
fun testWhenDbDataIsAvailableReturnDataImmediately() {
repository.fetch(false)
verify(spy).getProductFromDB()
}

@Test
fun testWhenNetworkFetchedSaveResponseToDb() {
repository.fetch(true)
verify(spy).saveRelationalData(item = NetworkProductResponse())
}

@Test
fun testWhenForceFetchFalseReturnDataImmediately() {
repository.fetch(false)
verify(spy).getProductFromDB()
}
}