diff --git a/README.md b/README.md
new file mode 100755
index 0000000..799783d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,78 @@
+# MVVM Clean Architecture with RxJava3+Coroutines Flow, Static Code Analysis, Dagger Hilt, Dynamic Features
+
+[![ktlint](https://img.shields.io/badge/code%20style-%E2%9D%A4-FF4081.svg)](https://ktlint.github.io/)
+[![Kotlin Version](https://img.shields.io/badge/kotlin-1.4.0-blue.svg)](https://kotlinlang.org)
+[![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=21)
+[![codecov](https://codecov.io/gh/andremion/Theatre/graph/badge.svg)](https://codecov.io/gh/andremion/Theatre)
+
+
+## About
+
+Sample project that build with MVVM clean architure and various cool techs including RxJava3 and Coroutines Flow, Dynamic Feature modules as base of BottomNavigationView or ViewPager2, with both OfflineFirst and OfflineLast approaches as database Single Source of Truth and TDD.
+
+Unit tests are written with JUnit4, JUnit5, MockK, Truth, MockWebServer.
+
+| Flow | RxJava3 | Pagination | Favorites
+| ------------------|-------------| -----|--------------|
+| | | | |
+
+
+## Overview
+* Gradle Kotlin DSL is used for setting up gradle files with ```buildSrc``` folder and extensions.
+* KtLint, Detekt, and Git Hooks is used for checking, and formatting code and validating code before commits.
+* Dagger Hilt, Dynamic Feature Modules with Navigation Components, ViewModel, Retrofit, Room, RxJava, Coroutines libraries adn dependencies are set up.
+* ```features``` and ```libraries``` folders are used to include android libraries and dynamic feature modules
+* In core module dagger hilt dependencies and ```@EntryPoint``` is created
+* Data module uses Retrofit and Room to provide Local and Remote data sources
+* Repository provides offline and remote fetch function with mapping and local save, delete and fetch functions
+* Domain module uses useCase classes to implment business logic to fetch and forward data
+* ViewModel uses LiveData with data-binding to display LOADING, and ERROR or SUCCESS states.
+
+## Built With 🛠
+
+Some of the popular libraries and MVVM clean architecture used with offline-first and offline-last with Room database and Retrofit as data source
+
+* [Kotlin](https://kotlinlang.org/) - First class and official programming language for Android development.
+
+* [Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html) - Threads on steroids for Kotlin
+* [Flow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/) - A cold asynchronous data stream that sequentially emits values and completes normally or with an exception.
+* [RxJava3](https://github.com/ReactiveX/RxJava) - Newest version of famous reactive programming library for Java, and other languages
+* [Android JetPack](https://developer.android.com/jetpack) - Collection of libraries that help you design robust, testable, and maintainable apps.
+ * [LiveData](https://developer.android.com/topic/libraries/architecture/livedata) - Data objects that notify views when the underlying database changes.
+ * [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel) - Stores UI-related data that isn't destroyed on UI changes.
+ * [DataBinding](https://developer.android.com/topic/libraries/data-binding) - Generates a binding class for each XML layout file present in that module and allows you to more easily write code that interacts with views.
+ * [Navigation Components](https://developer.android.com/guide/navigation/navigation-getting-started) Navigate fragments as never easier before
+ * [Dynamic Feature Modules](https://developer.android.com/guide/playcore/dynamic-delivery) Dynamic modules for adding or removing based on preference
+* [Material Components for Android](https://github.com/material-components/material-components-android) - Modular and customizable Material Design UI components for Android.
+* [Dependency Injection](https://developer.android.com/training/dependency-injection) -
+ * [Hilt-Dagger](https://dagger.dev/hilt/) - Standard library to incorporate Dagger dependency injection into an Android application.
+ * [Hilt-ViewModel](https://developer.android.com/training/dependency-injection/hilt-jetpack) - DI for injecting `ViewModel`.
+* [Retrofit](https://square.github.io/retrofit/) - A type-safe HTTP client for Android and Java.
+* [Glide](https://github.com/bumptech/glide) - Image loading library.
+* [Lottie](http://airbnb.io/lottie) - animation library
+
+* Architecture
+ * Clean Architecture
+ * MVVM + MVI
+ * Offline first/last with Room an Retrofit
+ * [Dynamic feature modules](https://developer.android.com/studio/projects/dynamic-delivery)
+* Tests
+ * [Unit Tests](https://en.wikipedia.org/wiki/Unit_testing) ([JUnit5](https://junit.org/junit5/)) ([JUnit4](https://junit.org/junit4/))
+ * [MockWebServer](https://github.com/square/okhttp/tree/master/mockwebserver) Mock server for testing Api requests with OkHttp and Retrofit
+ * [Mockk](https://mockk.io/) Mockking library for Kotlin
+ * [Truth](https://truth.dev) Assertion library
+* Gradle
+ * [Gradle Kotlin DSL](https://docs.gradle.org/current/userguide/kotlin_dsl.html)
+ * Custom tasks
+ * Plugins ([Ktlint](https://github.com/JLLeitschuh/ktlint-gradle), [Detekt](https://github.com/arturbosch/detekt#with-gradle), [SafeArgs](https://developer.android.com/guide/navigation/navigation-pass-data#Safe-args)), [Git Hooks](https://githooks.com)
+
+
+### Modularaization, Library and Feature Modules
+
+Uses both library modules and dynamic feature modules
+
+## Architecture
+
+Uses concepts of clean architecture
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 057036e..961cdeb 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,5 +1,5 @@
- PropertyFindAR
+ Property FindAR
Home
Favorites
Notification
diff --git a/features/account/src/main/res/layout/fragment_account.xml b/features/account/src/main/res/layout/fragment_account.xml
index 572ed67..aecbe2f 100644
--- a/features/account/src/main/res/layout/fragment_account.xml
+++ b/features/account/src/main/res/layout/fragment_account.xml
@@ -13,23 +13,20 @@
android:text="ACCOUNT\n Under Construction"
android:textSize="30dp"
android:textStyle="bold"
- app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/lavUnderConstruction"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintVertical_bias="0.25" />
+ app:layout_constraintTop_toTopOf="parent" />
diff --git a/features/favorites/src/main/res/layout/fragment_favorites.xml b/features/favorites/src/main/res/layout/fragment_favorites.xml
index 605e4fa..ce72408 100644
--- a/features/favorites/src/main/res/layout/fragment_favorites.xml
+++ b/features/favorites/src/main/res/layout/fragment_favorites.xml
@@ -1,6 +1,7 @@
+
@@ -14,22 +15,20 @@
android:textSize="30dp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/lavUnderConstruction"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintVertical_bias="0.25" />
+ app:layout_constraintTop_toTopOf="parent" />
diff --git a/features/home/src/main/java/com/smarttoolfactory/home/adapter/HomeViewPager2FragmentStateAdapter.kt b/features/home/src/main/java/com/smarttoolfactory/home/adapter/HomeViewPager2FragmentStateAdapter.kt
index 31eeeec..fbe7651 100644
--- a/features/home/src/main/java/com/smarttoolfactory/home/adapter/HomeViewPager2FragmentStateAdapter.kt
+++ b/features/home/src/main/java/com/smarttoolfactory/home/adapter/HomeViewPager2FragmentStateAdapter.kt
@@ -23,18 +23,22 @@ class HomeViewPager2FragmentStateAdapter(fragmentManager: FragmentManager, lifec
override fun createFragment(position: Int): Fragment {
return when (position) {
-
- // Fragment with Flow
0 -> NavHostContainerFragment.createNavHostContainerFragment(
R.layout.fragment_navhost_property_list_flow,
R.id.nested_nav_host_fragment_property_list
)
- // Fragment with Pagination
- else -> NavHostContainerFragment.createNavHostContainerFragment(
+ // Fragment with RxJava3
+ 1 -> NavHostContainerFragment.createNavHostContainerFragment(
R.layout.fragment_navhost_property_list_rxjava3,
R.id.nested_nav_host_fragment_property_list
)
+
+ // Fragment with Flow + Pagination
+ else -> NavHostContainerFragment.createNavHostContainerFragment(
+ R.layout.fragment_navhost_property_list_paged,
+ R.id.nested_nav_host_fragment_property_list
+ )
}
}
}
diff --git a/features/home/src/main/java/com/smarttoolfactory/home/adapter/LoadingAdapter.kt b/features/home/src/main/java/com/smarttoolfactory/home/adapter/LoadingAdapter.kt
new file mode 100644
index 0000000..209dd93
--- /dev/null
+++ b/features/home/src/main/java/com/smarttoolfactory/home/adapter/LoadingAdapter.kt
@@ -0,0 +1,3 @@
+package com.smarttoolfactory.home.adapter
+
+class LoadingAdapter
diff --git a/features/home/src/main/java/com/smarttoolfactory/home/adapter/PropertyListAdapter.kt b/features/home/src/main/java/com/smarttoolfactory/home/adapter/PropertyListAdapter.kt
index 3326cd7..89b36f9 100644
--- a/features/home/src/main/java/com/smarttoolfactory/home/adapter/PropertyListAdapter.kt
+++ b/features/home/src/main/java/com/smarttoolfactory/home/adapter/PropertyListAdapter.kt
@@ -1,5 +1,6 @@
package com.smarttoolfactory.home.adapter
+import android.graphics.Color
import android.widget.ImageButton
import androidx.annotation.LayoutRes
import androidx.databinding.ViewDataBinding
@@ -57,12 +58,15 @@ class PropertyItemListAdapter(
onLikeButtonClick(this)
// Set image source of like button
+ val likeImageButton = (likeButton as? ImageButton)
val imageResource = if (isFavorite) {
- R.drawable.ic_baseline_favorite_24
+ likeImageButton?.setColorFilter(Color.rgb(244, 81, 30))
+ R.drawable.ic_baseline_favorite_30
} else {
- R.drawable.ic_baseline_favorite_border_24
+ likeImageButton?.setColorFilter(Color.rgb(41, 182, 246))
+ R.drawable.ic_baseline_favorite_border_30
}
- (likeButton as? ImageButton)?.setImageResource(imageResource)
+ likeImageButton?.setImageResource(imageResource)
}
}
}
@@ -78,17 +82,11 @@ class PropertyItemListAdapter(
*/
class PropertyItemDiffCallback : DiffUtil.ItemCallback() {
- override fun areItemsTheSame(
- oldItem: PropertyItem,
- newItem: PropertyItem
- ): Boolean {
- return oldItem === newItem
+ override fun areItemsTheSame(oldItem: PropertyItem, newItem: PropertyItem): Boolean {
+ return oldItem.id == newItem.id
}
- override fun areContentsTheSame(
- oldItem: PropertyItem,
- newItem: PropertyItem
- ): Boolean {
- return oldItem.id == newItem.id
+ override fun areContentsTheSame(oldItem: PropertyItem, newItem: PropertyItem): Boolean {
+ return oldItem == newItem
}
}
diff --git a/features/home/src/main/java/com/smarttoolfactory/home/di/HomeComponent.kt b/features/home/src/main/java/com/smarttoolfactory/home/di/HomeComponent.kt
index c18a031..f323e14 100644
--- a/features/home/src/main/java/com/smarttoolfactory/home/di/HomeComponent.kt
+++ b/features/home/src/main/java/com/smarttoolfactory/home/di/HomeComponent.kt
@@ -2,8 +2,9 @@ package com.smarttoolfactory.home.di
import androidx.fragment.app.Fragment
import com.smarttoolfactory.core.di.CoreModuleDependencies
-import com.smarttoolfactory.home.propertylist.PropertyListFlowFragment
-import com.smarttoolfactory.home.propertylist.PropertyListRxjava3Fragment
+import com.smarttoolfactory.home.propertylist.flow.PropertyListFlowFragment
+import com.smarttoolfactory.home.propertylist.paged.PagedPropertyListFragment
+import com.smarttoolfactory.home.propertylist.rxjava.PropertyListRxjava3Fragment
import dagger.BindsInstance
import dagger.Component
@@ -15,6 +16,7 @@ interface HomeComponent {
fun inject(fragment: PropertyListFlowFragment)
fun inject(fragment: PropertyListRxjava3Fragment)
+ fun inject(fragment: PagedPropertyListFragment)
@Component.Factory
interface Factory {
diff --git a/features/home/src/main/java/com/smarttoolfactory/home/di/HomeModule.kt b/features/home/src/main/java/com/smarttoolfactory/home/di/HomeModule.kt
index 5081a1e..da89f75 100644
--- a/features/home/src/main/java/com/smarttoolfactory/home/di/HomeModule.kt
+++ b/features/home/src/main/java/com/smarttoolfactory/home/di/HomeModule.kt
@@ -2,10 +2,12 @@ package com.smarttoolfactory.home.di
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
+import com.smarttoolfactory.home.propertylist.flow.PropertyListViewModelFlow
+import com.smarttoolfactory.home.propertylist.paged.PagedPropertyListViewModel
+import com.smarttoolfactory.home.propertylist.rxjava.PropertyListViewModelRxJava3
+import com.smarttoolfactory.home.viewmodel.PagedPropertyListViewModelFactory
import com.smarttoolfactory.home.viewmodel.PropertyListFlowViewModelFactory
import com.smarttoolfactory.home.viewmodel.PropertyListRxJava3ViewModelFactory
-import com.smarttoolfactory.home.viewmodel.PropertyListViewModelFlow
-import com.smarttoolfactory.home.viewmodel.PropertyListViewModelRxJava3
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -18,6 +20,9 @@ import kotlinx.coroutines.SupervisorJob
@Module
class HomeModule {
+ /**
+ * Property ViewModel that uses Flow for data operation
+ */
@Provides
fun providePropertyListViewModelFlow(
fragment: Fragment,
@@ -25,6 +30,19 @@ class HomeModule {
) =
ViewModelProvider(fragment, factory).get(PropertyListViewModelFlow::class.java)
+ /**
+ * Property ViewModel that uses Flow for data operation with Pagaination
+ */
+ @Provides
+ fun providePagedPropertyListViewModel(
+ fragment: Fragment,
+ factory: PagedPropertyListViewModelFactory
+ ) =
+ ViewModelProvider(fragment, factory).get(PagedPropertyListViewModel::class.java)
+
+ /**
+ * Property ViewModel that uses Rxjava for data operations
+ */
@Provides
fun providePropertyListViewModelRxJava3(
fragment: Fragment,
diff --git a/features/home/src/main/java/com/smarttoolfactory/home/viewmodel/AbstractPropertyListVM.kt b/features/home/src/main/java/com/smarttoolfactory/home/propertylist/AbstractPropertyListVM.kt
similarity index 94%
rename from features/home/src/main/java/com/smarttoolfactory/home/viewmodel/AbstractPropertyListVM.kt
rename to features/home/src/main/java/com/smarttoolfactory/home/propertylist/AbstractPropertyListVM.kt
index 7ef4617..ec20cbb 100644
--- a/features/home/src/main/java/com/smarttoolfactory/home/viewmodel/AbstractPropertyListVM.kt
+++ b/features/home/src/main/java/com/smarttoolfactory/home/propertylist/AbstractPropertyListVM.kt
@@ -1,4 +1,4 @@
-package com.smarttoolfactory.home.viewmodel
+package com.smarttoolfactory.home.propertylist
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
diff --git a/features/home/src/main/java/com/smarttoolfactory/home/propertylist/PropertyListFlowFragment.kt b/features/home/src/main/java/com/smarttoolfactory/home/propertylist/flow/PropertyListFlowFragment.kt
similarity index 96%
rename from features/home/src/main/java/com/smarttoolfactory/home/propertylist/PropertyListFlowFragment.kt
rename to features/home/src/main/java/com/smarttoolfactory/home/propertylist/flow/PropertyListFlowFragment.kt
index 806403c..0b04fd3 100644
--- a/features/home/src/main/java/com/smarttoolfactory/home/propertylist/PropertyListFlowFragment.kt
+++ b/features/home/src/main/java/com/smarttoolfactory/home/propertylist/flow/PropertyListFlowFragment.kt
@@ -1,4 +1,4 @@
-package com.smarttoolfactory.home.propertylist
+package com.smarttoolfactory.home.propertylist.flow
import android.os.Bundle
import androidx.core.os.bundleOf
@@ -12,7 +12,6 @@ import com.smarttoolfactory.home.adapter.PropertyItemListAdapter
import com.smarttoolfactory.home.databinding.FragmentPropertyListBinding
import com.smarttoolfactory.home.di.DaggerHomeComponent
import com.smarttoolfactory.home.viewmodel.HomeToolbarVM
-import com.smarttoolfactory.home.viewmodel.PropertyListViewModelFlow
import dagger.hilt.android.EntryPointAccessors
import javax.inject.Inject
diff --git a/features/home/src/main/java/com/smarttoolfactory/home/viewmodel/PropertyListViewModelFlow.kt b/features/home/src/main/java/com/smarttoolfactory/home/propertylist/flow/PropertyListViewModelFlow.kt
similarity index 74%
rename from features/home/src/main/java/com/smarttoolfactory/home/viewmodel/PropertyListViewModelFlow.kt
rename to features/home/src/main/java/com/smarttoolfactory/home/propertylist/flow/PropertyListViewModelFlow.kt
index 84ab745..59900aa 100644
--- a/features/home/src/main/java/com/smarttoolfactory/home/viewmodel/PropertyListViewModelFlow.kt
+++ b/features/home/src/main/java/com/smarttoolfactory/home/propertylist/flow/PropertyListViewModelFlow.kt
@@ -1,4 +1,4 @@
-package com.smarttoolfactory.home.viewmodel
+package com.smarttoolfactory.home.propertylist.flow
import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.LiveData
@@ -10,7 +10,11 @@ import com.smarttoolfactory.core.viewstate.ViewState
import com.smarttoolfactory.domain.ORDER_BY_NONE
import com.smarttoolfactory.domain.model.PropertyItem
import com.smarttoolfactory.domain.usecase.GetPropertiesUseCaseFlow
+import com.smarttoolfactory.home.propertylist.AbstractPropertyListVM
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
@@ -34,17 +38,17 @@ class PropertyListViewModelFlow @ViewModelInject constructor(
var orderKey = MutableLiveData().apply { value = _orderByKey }
- init {
- updateOrderByKey()
- }
-
- fun updateOrderByKey() {
- getPropertiesUseCase.getCurrentSortKey()
+ private fun getOrderByKey(): Flow {
+ return getPropertiesUseCase.getCurrentSortKey()
.onEach {
- _orderByKey = it
- orderKey.value = _orderByKey
+ println("🍏 AbstractPropertyListVM init orderKey: $it")
+ _orderByKey = it ?: _orderByKey
+ orderKey.postValue(_orderByKey)
+ }
+ .catch {
+ orderKey.postValue(_orderByKey)
+ println("❌ AbstractPropertyListVM init error: $it")
}
- .launchIn(coroutineScope)
}
/**
@@ -60,12 +64,18 @@ class PropertyListViewModelFlow @ViewModelInject constructor(
*/
override fun getPropertyList() {
- getPropertiesUseCase.getPropertiesOfflineFirst(_orderByKey)
+ getOrderByKey()
+ .flatMapConcat {
+ getPropertiesUseCase
+ .getPropertiesOfflineFirst(_orderByKey)
+ }
.convertToFlowViewState()
.onStart {
+ println("🍏 FlowViewModel getPropertyList() START")
_propertyViewState.value = ViewState(status = Status.LOADING)
}
.onEach {
+ println("🍎 FlowViewModel getPropertyList() RES: $it")
_propertyViewState.value = it
}
.launchIn(coroutineScope)
diff --git a/features/home/src/main/java/com/smarttoolfactory/home/propertylist/paged/PagedPropertyListFragment.kt b/features/home/src/main/java/com/smarttoolfactory/home/propertylist/paged/PagedPropertyListFragment.kt
new file mode 100644
index 0000000..9d1b785
--- /dev/null
+++ b/features/home/src/main/java/com/smarttoolfactory/home/propertylist/paged/PagedPropertyListFragment.kt
@@ -0,0 +1,145 @@
+package com.smarttoolfactory.home.propertylist.paged
+
+import android.os.Bundle
+import androidx.core.os.bundleOf
+import androidx.fragment.app.activityViewModels
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.smarttoolfactory.core.di.CoreModuleDependencies
+import com.smarttoolfactory.core.ui.fragment.DynamicNavigationFragment
+import com.smarttoolfactory.core.util.EndlessScrollListener
+import com.smarttoolfactory.core.util.observe
+import com.smarttoolfactory.home.R
+import com.smarttoolfactory.home.adapter.PropertyItemListAdapter
+import com.smarttoolfactory.home.databinding.FragmentPropertyListPagedBinding
+import com.smarttoolfactory.home.di.DaggerHomeComponent
+import com.smarttoolfactory.home.viewmodel.HomeToolbarVM
+import dagger.hilt.android.EntryPointAccessors
+import javax.inject.Inject
+
+class PagedPropertyListFragment :
+ DynamicNavigationFragment(),
+ EndlessScrollListener.ScrollToBottomListener {
+
+ @Inject
+ lateinit var viewModel: PagedPropertyListViewModel
+
+ lateinit var itemListAdapter: PropertyItemListAdapter
+
+ /**
+ * Listener for listening scrolling to last item of RecyclerView
+ */
+ private lateinit var endlessScrollListener: EndlessScrollListener
+
+ /**
+ * ViewModel for setting sort filter on top menu and property list fragments
+ */
+ private val toolbarVM by activityViewModels()
+
+ override fun getLayoutRes(): Int = R.layout.fragment_property_list_paged
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ initCoreDependentInjection()
+ super.onCreate(savedInstanceState)
+ viewModel.refreshPropertyList()
+ }
+
+ override fun bindViews() {
+ dataBinding.viewModel = viewModel
+
+ dataBinding.recyclerView.apply {
+
+ // Set Layout manager
+ val linearLayoutManager =
+ LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)
+
+ this.layoutManager = linearLayoutManager
+
+ // Set RecyclerViewAdapter
+ itemListAdapter = PropertyItemListAdapter(
+ R.layout.row_property,
+ viewModel::onClick,
+ viewModel::onLikeButtonClick
+
+ )
+
+ // Set Adapter
+ this.adapter = itemListAdapter
+
+ // Set RecyclerView layout manager, adapter, and scroll listener for infinite scrolling
+ endlessScrollListener =
+ EndlessScrollListener(linearLayoutManager, this@PagedPropertyListFragment)
+ this.addOnScrollListener(endlessScrollListener)
+ }
+
+ val swipeRefreshLayout = dataBinding.swipeRefreshLayout
+
+ swipeRefreshLayout.setOnRefreshListener {
+ swipeRefreshLayout.isRefreshing = false
+ viewModel.refreshPropertyList()
+ }
+
+ subscribeViewModelSortChange()
+
+ subscribeGoToDetailScreen()
+ }
+
+ /**
+ * When sort key is fetched from database change the one belong to Toolbar
+ */
+ private fun subscribeViewModelSortChange() {
+ viewLifecycleOwner.observe(viewModel.orderKey) {
+ toolbarVM.currentSortFilter = it
+ }
+ }
+
+ private fun subscribeToolbarSortChange() {
+
+ viewLifecycleOwner.observe(toolbarVM.queryBySort) {
+ it.getContentIfNotHandled()?.let { orderBy ->
+ viewModel.refreshPropertyList(orderBy)
+ toolbarVM.currentSortFilter = orderBy
+ }
+ }
+ }
+
+ private fun subscribeGoToDetailScreen() {
+
+ viewModel.goToDetailScreen.observe(
+ viewLifecycleOwner,
+ {
+
+ it.getContentIfNotHandled()?.let { propertyItem ->
+ val bundle = bundleOf("property" to propertyItem)
+ }
+ }
+ )
+ }
+
+ private fun initCoreDependentInjection() {
+
+ val coreModuleDependencies = EntryPointAccessors.fromApplication(
+ requireActivity().applicationContext,
+ CoreModuleDependencies::class.java
+ )
+
+ DaggerHomeComponent.factory().create(
+ dependentModule = coreModuleDependencies,
+ fragment = this
+ )
+ .inject(this)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ subscribeToolbarSortChange()
+ }
+
+ override fun onPause() {
+ super.onPause()
+ toolbarVM.queryBySort.removeObservers(viewLifecycleOwner)
+ }
+
+ override fun onScrollToBottom() {
+ viewModel.getPropertyList()
+ }
+}
diff --git a/features/home/src/main/java/com/smarttoolfactory/home/propertylist/paged/PagedPropertyListViewModel.kt b/features/home/src/main/java/com/smarttoolfactory/home/propertylist/paged/PagedPropertyListViewModel.kt
new file mode 100644
index 0000000..fd5a7cd
--- /dev/null
+++ b/features/home/src/main/java/com/smarttoolfactory/home/propertylist/paged/PagedPropertyListViewModel.kt
@@ -0,0 +1,95 @@
+package com.smarttoolfactory.home.propertylist.paged
+
+import androidx.hilt.lifecycle.ViewModelInject
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import com.smarttoolfactory.core.util.Event
+import com.smarttoolfactory.core.util.convertToFlowViewState
+import com.smarttoolfactory.core.viewstate.Status
+import com.smarttoolfactory.core.viewstate.ViewState
+import com.smarttoolfactory.domain.ORDER_BY_NONE
+import com.smarttoolfactory.domain.model.PropertyItem
+import com.smarttoolfactory.domain.usecase.GetPropertiesUseCasePaged
+import com.smarttoolfactory.home.propertylist.AbstractPropertyListVM
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.flatMapConcat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+
+class PagedPropertyListViewModel @ViewModelInject constructor(
+ private val coroutineScope: CoroutineScope,
+ private val getPropertiesUseCase: GetPropertiesUseCasePaged
+) : AbstractPropertyListVM() {
+
+ private val _goToDetailScreen = MutableLiveData>()
+
+ override val goToDetailScreen: LiveData>
+ get() = _goToDetailScreen
+
+ private val _propertyViewState = MutableLiveData>>()
+
+ override val propertyListViewState: LiveData>>
+ get() = _propertyViewState
+
+ private var _orderByKey = ORDER_BY_NONE
+
+ var orderKey = MutableLiveData().apply { value = _orderByKey }
+
+ private fun getOrderByKey(): Flow {
+ return getPropertiesUseCase.getCurrentSortKey()
+ .onEach {
+ println("🍏 AbstractPropertyListVM init orderKey: $it")
+ _orderByKey = it ?: _orderByKey
+ orderKey.postValue(_orderByKey)
+ }
+ .catch {
+ orderKey.postValue(_orderByKey)
+ println("❌ AbstractPropertyListVM init error: $it")
+ }
+ }
+
+ override fun getPropertyList() {
+
+ getOrderByKey()
+ .flatMapConcat {
+ println("🔥 refreshPropertyList: $it")
+ getPropertiesUseCase.getPagedOfflineLast(_orderByKey)
+ }
+ .convertToFlowViewState()
+ .onStart {
+ _propertyViewState.value = ViewState(status = Status.LOADING)
+ }
+ .onEach {
+ _propertyViewState.value = it
+ }
+ .launchIn(coroutineScope)
+ }
+
+ override fun refreshPropertyList(orderBy: String?) {
+
+ getOrderByKey()
+ .flatMapConcat {
+ println("🔥 refreshPropertyList: $it")
+ getPropertiesUseCase.refreshData(orderBy ?: _orderByKey)
+ }
+ .convertToFlowViewState()
+ .onStart {
+ _propertyViewState.value = ViewState(status = Status.LOADING)
+ }
+ .onEach {
+ _propertyViewState.value = it
+ }
+ .launchIn(coroutineScope)
+ }
+
+ override fun onClick(item: PropertyItem) {
+ _goToDetailScreen.value = Event(item)
+ }
+
+ fun onLikeButtonClick(item: PropertyItem) {
+ println("🔥 Like: $item")
+ }
+}
diff --git a/features/home/src/main/java/com/smarttoolfactory/home/propertylist/PropertyListRxjava3Fragment.kt b/features/home/src/main/java/com/smarttoolfactory/home/propertylist/rxjava/PropertyListRxjava3Fragment.kt
similarity index 96%
rename from features/home/src/main/java/com/smarttoolfactory/home/propertylist/PropertyListRxjava3Fragment.kt
rename to features/home/src/main/java/com/smarttoolfactory/home/propertylist/rxjava/PropertyListRxjava3Fragment.kt
index 83992a4..32dd303 100644
--- a/features/home/src/main/java/com/smarttoolfactory/home/propertylist/PropertyListRxjava3Fragment.kt
+++ b/features/home/src/main/java/com/smarttoolfactory/home/propertylist/rxjava/PropertyListRxjava3Fragment.kt
@@ -1,4 +1,4 @@
-package com.smarttoolfactory.home.propertylist
+package com.smarttoolfactory.home.propertylist.rxjava
import android.os.Bundle
import androidx.core.os.bundleOf
@@ -12,7 +12,6 @@ import com.smarttoolfactory.home.adapter.PropertyItemListAdapter
import com.smarttoolfactory.home.databinding.FragmentPropertyListBinding
import com.smarttoolfactory.home.di.DaggerHomeComponent
import com.smarttoolfactory.home.viewmodel.HomeToolbarVM
-import com.smarttoolfactory.home.viewmodel.PropertyListViewModelRxJava3
import dagger.hilt.android.EntryPointAccessors
import javax.inject.Inject
diff --git a/features/home/src/main/java/com/smarttoolfactory/home/viewmodel/PropertyListViewModelRxJava3.kt b/features/home/src/main/java/com/smarttoolfactory/home/propertylist/rxjava/PropertyListViewModelRxJava3.kt
similarity index 80%
rename from features/home/src/main/java/com/smarttoolfactory/home/viewmodel/PropertyListViewModelRxJava3.kt
rename to features/home/src/main/java/com/smarttoolfactory/home/propertylist/rxjava/PropertyListViewModelRxJava3.kt
index fa36400..9674206 100644
--- a/features/home/src/main/java/com/smarttoolfactory/home/viewmodel/PropertyListViewModelRxJava3.kt
+++ b/features/home/src/main/java/com/smarttoolfactory/home/propertylist/rxjava/PropertyListViewModelRxJava3.kt
@@ -1,4 +1,4 @@
-package com.smarttoolfactory.home.viewmodel
+package com.smarttoolfactory.home.propertylist.rxjava
import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.LiveData
@@ -10,7 +10,9 @@ import com.smarttoolfactory.core.viewstate.ViewState
import com.smarttoolfactory.domain.ORDER_BY_NONE
import com.smarttoolfactory.domain.model.PropertyItem
import com.smarttoolfactory.domain.usecase.GetPropertiesUseCaseRxJava3
+import com.smarttoolfactory.home.propertylist.AbstractPropertyListVM
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
class PropertyListViewModelRxJava3 @ViewModelInject constructor(
@@ -31,28 +33,25 @@ class PropertyListViewModelRxJava3 @ViewModelInject constructor(
var orderKey = MutableLiveData().apply { value = _orderByKey }
- init {
- updateOrderByKey()
- }
-
- private fun updateOrderByKey() {
- getPropertiesUseCase.getCurrentSortKey()
+ private fun getOrderByKey(): Single {
+ return getPropertiesUseCase.getCurrentSortKey()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
- .subscribe(
- {
- _orderByKey = it
-
- orderKey.value = _orderByKey
- },
- {
- println("PropertyListViewModelRxJava3 init error: $it")
- }
- )
+ .doOnSuccess {
+ _orderByKey = it ?: _orderByKey
+ orderKey.postValue(_orderByKey)
+ }
+ .onErrorResumeNext {
+ Single.just(_orderByKey)
+ }
}
override fun getPropertyList() {
- getPropertiesUseCase.getPropertiesOfflineFirst(_orderByKey)
+
+ getOrderByKey()
+ .flatMap {
+ getPropertiesUseCase.getPropertiesOfflineFirst(_orderByKey)
+ }
.convertFromSingleToObservableViewStateWithLoading()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
diff --git a/features/home/src/main/java/com/smarttoolfactory/home/viewbindings/ViewBindings.kt b/features/home/src/main/java/com/smarttoolfactory/home/viewbindings/ViewBindings.kt
index 7fd2e25..74d6267 100755
--- a/features/home/src/main/java/com/smarttoolfactory/home/viewbindings/ViewBindings.kt
+++ b/features/home/src/main/java/com/smarttoolfactory/home/viewbindings/ViewBindings.kt
@@ -1,5 +1,6 @@
package com.smarttoolfactory.home.viewbindings
+import android.graphics.Color
import android.view.View
import android.widget.ImageButton
import android.widget.ImageView
@@ -64,6 +65,12 @@ fun View.visibilityBasedOn(condition: Boolean) {
@BindingAdapter("favoriteImageSrc")
fun ImageButton.setFavoriteImageSrc(favorite: Boolean) {
+ if (favorite) {
+ setColorFilter(Color.rgb(244, 81, 30))
+ } else {
+ setColorFilter(Color.rgb(41, 182, 246))
+ }
+
val imageResource = if (favorite) R.drawable.ic_baseline_favorite_30
else R.drawable.ic_baseline_favorite_border_30
diff --git a/features/home/src/main/java/com/smarttoolfactory/home/viewmodel/ViewModelFactory.kt b/features/home/src/main/java/com/smarttoolfactory/home/viewmodel/ViewModelFactory.kt
index 6f7f2cb..d5e6ca5 100644
--- a/features/home/src/main/java/com/smarttoolfactory/home/viewmodel/ViewModelFactory.kt
+++ b/features/home/src/main/java/com/smarttoolfactory/home/viewmodel/ViewModelFactory.kt
@@ -3,7 +3,11 @@ package com.smarttoolfactory.home.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.smarttoolfactory.domain.usecase.GetPropertiesUseCaseFlow
+import com.smarttoolfactory.domain.usecase.GetPropertiesUseCasePaged
import com.smarttoolfactory.domain.usecase.GetPropertiesUseCaseRxJava3
+import com.smarttoolfactory.home.propertylist.flow.PropertyListViewModelFlow
+import com.smarttoolfactory.home.propertylist.paged.PagedPropertyListViewModel
+import com.smarttoolfactory.home.propertylist.rxjava.PropertyListViewModelRxJava3
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -26,6 +30,25 @@ class PropertyListFlowViewModelFactory @Inject constructor(
}
}
+class PagedPropertyListViewModelFactory @Inject constructor(
+ private val coroutineScope: CoroutineScope,
+ private val getPropertiesUseCase: GetPropertiesUseCasePaged
+) : ViewModelProvider.Factory {
+
+ @Suppress("UNCHECKED_CAST")
+ override fun create(modelClass: Class): T {
+
+ if (modelClass != PagedPropertyListViewModel::class.java) {
+ throw IllegalArgumentException("Unknown ViewModel class")
+ }
+
+ return PagedPropertyListViewModel(
+ coroutineScope,
+ getPropertiesUseCase
+ ) as T
+ }
+}
+
class PropertyListRxJava3ViewModelFactory @Inject constructor(
private val getPropertiesUseCase: GetPropertiesUseCaseRxJava3
) : ViewModelProvider.Factory {
diff --git a/features/home/src/main/res/layout/fragment_navhost_property_list_paged.xml b/features/home/src/main/res/layout/fragment_navhost_property_list_paged.xml
new file mode 100644
index 0000000..a7cd307
--- /dev/null
+++ b/features/home/src/main/res/layout/fragment_navhost_property_list_paged.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/features/home/src/main/res/layout/fragment_property_list.xml b/features/home/src/main/res/layout/fragment_property_list.xml
index 2199fc8..095f0a4 100644
--- a/features/home/src/main/res/layout/fragment_property_list.xml
+++ b/features/home/src/main/res/layout/fragment_property_list.xml
@@ -9,7 +9,7 @@
+ type="com.smarttoolfactory.home.propertylist.AbstractPropertyListVM" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/features/home/src/main/res/layout/row_property.xml b/features/home/src/main/res/layout/row_property.xml
index 2710b0b..4a391c6 100644
--- a/features/home/src/main/res/layout/row_property.xml
+++ b/features/home/src/main/res/layout/row_property.xml
@@ -14,16 +14,15 @@
diff --git a/features/home/src/main/res/navigation/nav_graph_property_list_paged.xml b/features/home/src/main/res/navigation/nav_graph_property_list_paged.xml
new file mode 100644
index 0000000..a5528db
--- /dev/null
+++ b/features/home/src/main/res/navigation/nav_graph_property_list_paged.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/features/home/src/main/res/navigation/nav_graph_property_list_rxjava3.xml b/features/home/src/main/res/navigation/nav_graph_property_list_rxjava3.xml
index 15b0d89..91287d4 100644
--- a/features/home/src/main/res/navigation/nav_graph_property_list_rxjava3.xml
+++ b/features/home/src/main/res/navigation/nav_graph_property_list_rxjava3.xml
@@ -7,7 +7,7 @@
diff --git a/features/home/src/test/java/com/smarttoolfactory/home/viewmodel/PropertyListViewModelFlowTest.kt b/features/home/src/test/java/com/smarttoolfactory/home/viewmodel/PropertyListViewModelFlowTest.kt
index 39fef61..e49370e 100644
--- a/features/home/src/test/java/com/smarttoolfactory/home/viewmodel/PropertyListViewModelFlowTest.kt
+++ b/features/home/src/test/java/com/smarttoolfactory/home/viewmodel/PropertyListViewModelFlowTest.kt
@@ -3,8 +3,10 @@ package com.smarttoolfactory.home.viewmodel
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.google.common.truth.Truth
import com.smarttoolfactory.core.viewstate.Status
+import com.smarttoolfactory.domain.ORDER_BY_NONE
import com.smarttoolfactory.domain.model.PropertyItem
import com.smarttoolfactory.domain.usecase.GetPropertiesUseCaseFlow
+import com.smarttoolfactory.home.propertylist.flow.PropertyListViewModelFlow
import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH
import com.smarttoolfactory.test_utils.rule.TestCoroutineRule
import com.smarttoolfactory.test_utils.test_observer.test
@@ -20,6 +22,9 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
+/**
+ * ❌ FIXME Either [LiveDataTestObserver] or Flow is bugged with tests, solve the issue
+ */
class PropertyListViewModelFlowTest {
// Run tasks synchronously
@@ -72,12 +77,20 @@ class PropertyListViewModelFlowTest {
emit(throw Exception("Network Exception"))
}
+ every {
+ useCase.getCurrentSortKey()
+ } returns flow {
+ emit((ORDER_BY_NONE))
+ }
+
val testObserver = viewModel.propertyListViewState.test()
// WHEN
+
viewModel.getPropertyList()
// THEN
+ println("💀 THEN")
testObserver
.assertValue { states ->
(
@@ -92,9 +105,6 @@ class PropertyListViewModelFlowTest {
verify(atMost = 1) { useCase.getPropertiesOfflineFirst() }
}
- /**
- * ❌ FIXME This test is flaky, find out the cause, sometimes null is returned
- */
@Test
fun `given useCase fetched data, should have ViewState SUCCESS and data offlineFirst`() =
testCoroutineRule.runBlockingTest {
@@ -104,14 +114,19 @@ class PropertyListViewModelFlowTest {
emit(itemList)
}
+ every {
+ useCase.getCurrentSortKey()
+ } returns flow {
+ emit((ORDER_BY_NONE))
+ }
+
val testObserver = viewModel.propertyListViewState.test()
// WHEN
viewModel.getPropertyList()
- advanceUntilIdle()
-
// THEN
+ println("💀 THEN")
val viewStates = testObserver.values()
Truth.assertThat(viewStates.first().status).isEqualTo(Status.LOADING)
@@ -128,7 +143,7 @@ class PropertyListViewModelFlowTest {
// GIVEN
every {
- useCase.getPropertiesOfflineLast()
+ useCase.getPropertiesOfflineLast(ORDER_BY_NONE)
} returns flow> {
emit(throw Exception("Network Exception"))
}
@@ -137,6 +152,7 @@ class PropertyListViewModelFlowTest {
// WHEN
viewModel.refreshPropertyList()
+ advanceUntilIdle()
// THEN
testObserver
@@ -151,7 +167,7 @@ class PropertyListViewModelFlowTest {
val finalState = testObserver.values()[1]
Truth.assertThat(finalState.error?.message).isEqualTo("Network Exception")
Truth.assertThat(finalState.error).isInstanceOf(Exception::class.java)
- verify(atMost = 1) { useCase.getPropertiesOfflineLast() }
+ verify(atMost = 1) { useCase.getPropertiesOfflineLast(ORDER_BY_NONE) }
}
/**
@@ -163,7 +179,7 @@ class PropertyListViewModelFlowTest {
// GIVEN
every {
- useCase.getPropertiesOfflineLast()
+ useCase.getPropertiesOfflineLast(ORDER_BY_NONE)
} returns flow {
emit(itemList)
}
@@ -172,6 +188,7 @@ class PropertyListViewModelFlowTest {
// WHEN
viewModel.refreshPropertyList()
+ advanceUntilIdle()
// THEN
val viewStates = testObserver.values()
@@ -179,7 +196,7 @@ class PropertyListViewModelFlowTest {
val actual = viewStates.last().data
Truth.assertThat(actual?.size).isEqualTo(itemList.size)
- verify(exactly = 1) { useCase.getPropertiesOfflineLast() }
+ verify(exactly = 1) { useCase.getPropertiesOfflineLast(ORDER_BY_NONE) }
testObserver.dispose()
}
diff --git a/features/home/src/test/java/com/smarttoolfactory/home/viewmodel/PropertyListViewModelRxJava3Test.kt b/features/home/src/test/java/com/smarttoolfactory/home/viewmodel/PropertyListViewModelRxJava3Test.kt
index 483f198..16f414d 100644
--- a/features/home/src/test/java/com/smarttoolfactory/home/viewmodel/PropertyListViewModelRxJava3Test.kt
+++ b/features/home/src/test/java/com/smarttoolfactory/home/viewmodel/PropertyListViewModelRxJava3Test.kt
@@ -3,9 +3,10 @@ package com.smarttoolfactory.home.viewmodel
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.google.common.truth.Truth
import com.smarttoolfactory.core.viewstate.Status
+import com.smarttoolfactory.domain.ORDER_BY_NONE
import com.smarttoolfactory.domain.model.PropertyItem
import com.smarttoolfactory.domain.usecase.GetPropertiesUseCaseRxJava3
-import com.smarttoolfactory.home.viewmodel.AbstractPropertyListVM.Companion.ORDER_BY_NONE
+import com.smarttoolfactory.home.propertylist.rxjava.PropertyListViewModelRxJava3
import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH
import com.smarttoolfactory.test_utils.rule.RxImmediateSchedulerRule
import com.smarttoolfactory.test_utils.test_observer.test
@@ -71,6 +72,10 @@ class PropertyListViewModelRxJava3Test {
useCase.getPropertiesOfflineFirst(ORDER_BY_NONE)
} returns Single.error(Exception("Network Exception"))
+ every {
+ useCase.getCurrentSortKey()
+ } returns Single.just(ORDER_BY_NONE)
+
val testObserver = viewModel.propertyListViewState.test()
// WHEN
@@ -96,6 +101,9 @@ class PropertyListViewModelRxJava3Test {
// GIVEN
every { useCase.getPropertiesOfflineFirst(ORDER_BY_NONE) } returns Single.just(itemList)
+ every {
+ useCase.getCurrentSortKey()
+ } returns Single.just(ORDER_BY_NONE)
val testObserver = viewModel.propertyListViewState.test()
@@ -114,7 +122,9 @@ class PropertyListViewModelRxJava3Test {
val finalState = testObserver.values()[1]
val actual = finalState.data
Truth.assertThat(actual?.size).isEqualTo(itemList.size)
+
verify(exactly = 1) { useCase.getPropertiesOfflineFirst(ORDER_BY_NONE) }
+ verify(exactly = 1) { useCase.getCurrentSortKey(ORDER_BY_NONE) }
testObserver.dispose()
}
diff --git a/features/notification/src/main/res/layout/fragment_notification.xml b/features/notification/src/main/res/layout/fragment_notification.xml
index 650549f..268c085 100644
--- a/features/notification/src/main/res/layout/fragment_notification.xml
+++ b/features/notification/src/main/res/layout/fragment_notification.xml
@@ -1,6 +1,7 @@
+
@@ -14,22 +15,20 @@
android:textSize="30dp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/lavUnderConstruction"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintVertical_bias="0.25" />
+ app:layout_constraintTop_toTopOf="parent" />
diff --git a/libraries/core/src/main/java/com/smarttoolfactory/core/di/CoreModuleDependencies.kt b/libraries/core/src/main/java/com/smarttoolfactory/core/di/CoreModuleDependencies.kt
index 5f12a3a..e2d77d2 100644
--- a/libraries/core/src/main/java/com/smarttoolfactory/core/di/CoreModuleDependencies.kt
+++ b/libraries/core/src/main/java/com/smarttoolfactory/core/di/CoreModuleDependencies.kt
@@ -1,13 +1,14 @@
package com.smarttoolfactory.core.di
import com.smarttoolfactory.domain.usecase.GetPropertiesUseCaseFlow
+import com.smarttoolfactory.domain.usecase.GetPropertiesUseCasePaged
import com.smarttoolfactory.domain.usecase.GetPropertiesUseCaseRxJava3
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ApplicationComponent
/**
- * This component is required for adding component to Dynamic Feature Module dependencies
+ * This component is required for adding dependencies to Dy
*/
@EntryPoint
@InstallIn(ApplicationComponent::class)
@@ -18,4 +19,5 @@ interface CoreModuleDependencies {
*/
fun getPropertiesUseCaseFlow(): GetPropertiesUseCaseFlow
fun getPropertiesUseCaseRxJava3(): GetPropertiesUseCaseRxJava3
+ fun getPropertiesUseCasePaged(): GetPropertiesUseCasePaged
}
diff --git a/libraries/core/src/main/java/com/smarttoolfactory/core/di/DataModule.kt b/libraries/core/src/main/java/com/smarttoolfactory/core/di/DataModule.kt
index e697b93..a973749 100644
--- a/libraries/core/src/main/java/com/smarttoolfactory/core/di/DataModule.kt
+++ b/libraries/core/src/main/java/com/smarttoolfactory/core/di/DataModule.kt
@@ -3,11 +3,15 @@ package com.smarttoolfactory.core.di
import com.smarttoolfactory.data.di.DatabaseModule
import com.smarttoolfactory.data.di.NetworkModule
import com.smarttoolfactory.data.mapper.PropertyDTOtoEntityListMapper
+import com.smarttoolfactory.data.repository.PagedPropertyRepository
+import com.smarttoolfactory.data.repository.PagedPropertyRepositoryImpl
import com.smarttoolfactory.data.repository.PropertyRepositoryCoroutines
import com.smarttoolfactory.data.repository.PropertyRepositoryImlRxJava3
import com.smarttoolfactory.data.repository.PropertyRepositoryImplCoroutines
import com.smarttoolfactory.data.repository.PropertyRepositoryRxJava3
import com.smarttoolfactory.data.source.LocalDataSourceRxJava3Impl
+import com.smarttoolfactory.data.source.LocalPagedPropertyDataSource
+import com.smarttoolfactory.data.source.LocalPagedPropertySourceImpl
import com.smarttoolfactory.data.source.LocalPropertyDataSourceCoroutines
import com.smarttoolfactory.data.source.LocalPropertyDataSourceImpl
import com.smarttoolfactory.data.source.LocalPropertyDataSourceRxJava3
@@ -43,6 +47,21 @@ interface DataModule {
@Binds
fun bindRepositoryCoroutines(repository: PropertyRepositoryImplCoroutines):
PropertyRepositoryCoroutines
+
+ /*
+ Coroutines + Pagination
+ */
+
+ @Singleton
+ @Binds
+ fun bindPagedLocalDataSource(localDataSource: LocalPagedPropertySourceImpl):
+ LocalPagedPropertyDataSource
+
+ @Singleton
+ @Binds
+ fun bindPagedRepository(repository: PagedPropertyRepositoryImpl):
+ PagedPropertyRepository
+
/*
RxJava
*/
diff --git a/libraries/core/src/main/java/com/smarttoolfactory/core/util/EndlessScrollListener.kt b/libraries/core/src/main/java/com/smarttoolfactory/core/util/EndlessScrollListener.kt
new file mode 100644
index 0000000..c241977
--- /dev/null
+++ b/libraries/core/src/main/java/com/smarttoolfactory/core/util/EndlessScrollListener.kt
@@ -0,0 +1,46 @@
+package com.smarttoolfactory.core.util
+
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+
+class EndlessScrollListener(
+ private val linearLayoutManager: LinearLayoutManager,
+ private val listener: ScrollToBottomListener
+) : RecyclerView.OnScrollListener() {
+
+ private var previousTotal = 0
+ private var loading = true
+ private val visibleThreshold = 8
+ private var firstVisibleItem = 0
+ private var visibleItemCount = 0
+ private var totalItemCount = 0
+
+ fun onRefresh() {
+ previousTotal = 0
+ }
+
+ override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
+ super.onScrolled(recyclerView, dx, dy)
+
+ visibleItemCount = recyclerView.childCount
+ totalItemCount = linearLayoutManager.itemCount
+ firstVisibleItem = linearLayoutManager.findFirstVisibleItemPosition()
+
+ if (loading) {
+ if (totalItemCount > previousTotal) {
+ loading = false
+ previousTotal = totalItemCount
+ }
+ }
+ if (!loading && totalItemCount - visibleItemCount
+ <= firstVisibleItem + visibleThreshold
+ ) {
+ listener.onScrollToBottom()
+ loading = true
+ }
+ }
+
+ interface ScrollToBottomListener {
+ fun onScrollToBottom()
+ }
+}
diff --git a/libraries/data/schemas/com.smarttoolfactory.data.db.PropertyDatabase/3.json b/libraries/data/schemas/com.smarttoolfactory.data.db.PropertyDatabase/3.json
new file mode 100644
index 0000000..2eb8412
--- /dev/null
+++ b/libraries/data/schemas/com.smarttoolfactory.data.db.PropertyDatabase/3.json
@@ -0,0 +1,597 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 3,
+ "identityHash": "4e4855a4623740f1eebc51d968f31cb3",
+ "entities": [
+ {
+ "tableName": "property",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`insert_order` INTEGER NOT NULL, `id` INTEGER NOT NULL, `update` INTEGER NOT NULL, `category_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `type` TEXT NOT NULL, `type_id` INTEGER NOT NULL, `thumbnail` TEXT, `thumbnail_big` TEXT, `image_count` INTEGER NOT NULL, `price` TEXT NOT NULL, `price_period` TEXT, `price_period_raw` TEXT NOT NULL, `price_label` TEXT, `price_value` TEXT, `price_value_raw` INTEGER NOT NULL, `currency` TEXT NOT NULL, `featured` INTEGER NOT NULL, `location` TEXT NOT NULL, `area` TEXT NOT NULL, `poa` INTEGER NOT NULL, `rera_permit` TEXT, `bathrooms` TEXT NOT NULL, `bedrooms` TEXT NOT NULL, `date_insert` TEXT NOT NULL, `date_update` TEXT NOT NULL, `agent_name` TEXT NOT NULL, `broker_name` TEXT NOT NULL, `agent_license` TEXT, `location_id` INTEGER NOT NULL, `hide_location` INTEGER NOT NULL, `broker` TEXT NOT NULL, `amenities` TEXT NOT NULL, `amenities_keys` TEXT NOT NULL, `latitude` REAL NOT NULL, `longitude` REAL NOT NULL, `premium` INTEGER NOT NULL, `livingrooms` TEXT NOT NULL, `verified` INTEGER NOT NULL, `gallery` TEXT, `phone` TEXT NOT NULL, `lead_email_receivers` TEXT NOT NULL, `reference` TEXT NOT NULL, PRIMARY KEY(`insert_order`))",
+ "fields": [
+ {
+ "fieldPath": "insertOrder",
+ "columnName": "insert_order",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "update",
+ "columnName": "update",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "categoryId",
+ "columnName": "category_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeId",
+ "columnName": "type_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "thumbnail",
+ "columnName": "thumbnail",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "thumbnailBig",
+ "columnName": "thumbnail_big",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "imageCount",
+ "columnName": "image_count",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "price",
+ "columnName": "price",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pricePeriod",
+ "columnName": "price_period",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "pricePeriodRaw",
+ "columnName": "price_period_raw",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "priceLabel",
+ "columnName": "price_label",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "priceValue",
+ "columnName": "price_value",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "priceValueRaw",
+ "columnName": "price_value_raw",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "currency",
+ "columnName": "currency",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "featured",
+ "columnName": "featured",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "location",
+ "columnName": "location",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "area",
+ "columnName": "area",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "poa",
+ "columnName": "poa",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "reraPermit",
+ "columnName": "rera_permit",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "bathrooms",
+ "columnName": "bathrooms",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "bedrooms",
+ "columnName": "bedrooms",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dateInsert",
+ "columnName": "date_insert",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dateUpdate",
+ "columnName": "date_update",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "agentName",
+ "columnName": "agent_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "brokerName",
+ "columnName": "broker_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "agentLicense",
+ "columnName": "agent_license",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "locationId",
+ "columnName": "location_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hideLocation",
+ "columnName": "hide_location",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "broker",
+ "columnName": "broker",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "amenities",
+ "columnName": "amenities",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "amenitiesKeys",
+ "columnName": "amenities_keys",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "latitude",
+ "columnName": "latitude",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "longitude",
+ "columnName": "longitude",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "premium",
+ "columnName": "premium",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "livingrooms",
+ "columnName": "livingrooms",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "verified",
+ "columnName": "verified",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "gallery",
+ "columnName": "gallery",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "phone",
+ "columnName": "phone",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "leadEmailReceivers",
+ "columnName": "lead_email_receivers",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "reference",
+ "columnName": "reference",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "insert_order"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "paged_property",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`insert_order` INTEGER NOT NULL, `id` INTEGER NOT NULL, `update` INTEGER NOT NULL, `category_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `type` TEXT NOT NULL, `type_id` INTEGER NOT NULL, `thumbnail` TEXT, `thumbnail_big` TEXT, `image_count` INTEGER NOT NULL, `price` TEXT NOT NULL, `price_period` TEXT, `price_period_raw` TEXT NOT NULL, `price_label` TEXT, `price_value` TEXT, `price_value_raw` INTEGER NOT NULL, `currency` TEXT NOT NULL, `featured` INTEGER NOT NULL, `location` TEXT NOT NULL, `area` TEXT NOT NULL, `poa` INTEGER NOT NULL, `rera_permit` TEXT, `bathrooms` TEXT NOT NULL, `bedrooms` TEXT NOT NULL, `date_insert` TEXT NOT NULL, `date_update` TEXT NOT NULL, `agent_name` TEXT NOT NULL, `broker_name` TEXT NOT NULL, `agent_license` TEXT, `location_id` INTEGER NOT NULL, `hide_location` INTEGER NOT NULL, `broker` TEXT NOT NULL, `amenities` TEXT NOT NULL, `amenities_keys` TEXT NOT NULL, `latitude` REAL NOT NULL, `longitude` REAL NOT NULL, `premium` INTEGER NOT NULL, `livingrooms` TEXT NOT NULL, `verified` INTEGER NOT NULL, `gallery` TEXT, `phone` TEXT NOT NULL, `lead_email_receivers` TEXT NOT NULL, `reference` TEXT NOT NULL, PRIMARY KEY(`insert_order`))",
+ "fields": [
+ {
+ "fieldPath": "insertOrder",
+ "columnName": "insert_order",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "update",
+ "columnName": "update",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "categoryId",
+ "columnName": "category_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeId",
+ "columnName": "type_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "thumbnail",
+ "columnName": "thumbnail",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "thumbnailBig",
+ "columnName": "thumbnail_big",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "imageCount",
+ "columnName": "image_count",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "price",
+ "columnName": "price",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pricePeriod",
+ "columnName": "price_period",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "pricePeriodRaw",
+ "columnName": "price_period_raw",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "priceLabel",
+ "columnName": "price_label",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "priceValue",
+ "columnName": "price_value",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "priceValueRaw",
+ "columnName": "price_value_raw",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "currency",
+ "columnName": "currency",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "featured",
+ "columnName": "featured",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "location",
+ "columnName": "location",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "area",
+ "columnName": "area",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "poa",
+ "columnName": "poa",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "reraPermit",
+ "columnName": "rera_permit",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "bathrooms",
+ "columnName": "bathrooms",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "bedrooms",
+ "columnName": "bedrooms",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dateInsert",
+ "columnName": "date_insert",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dateUpdate",
+ "columnName": "date_update",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "agentName",
+ "columnName": "agent_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "brokerName",
+ "columnName": "broker_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "agentLicense",
+ "columnName": "agent_license",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "locationId",
+ "columnName": "location_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hideLocation",
+ "columnName": "hide_location",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "broker",
+ "columnName": "broker",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "amenities",
+ "columnName": "amenities",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "amenitiesKeys",
+ "columnName": "amenities_keys",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "latitude",
+ "columnName": "latitude",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "longitude",
+ "columnName": "longitude",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "premium",
+ "columnName": "premium",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "livingrooms",
+ "columnName": "livingrooms",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "verified",
+ "columnName": "verified",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "gallery",
+ "columnName": "gallery",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "phone",
+ "columnName": "phone",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "leadEmailReceivers",
+ "columnName": "lead_email_receivers",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "reference",
+ "columnName": "reference",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "insert_order"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "sort_order",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `order_by` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "orderBy",
+ "columnName": "order_by",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "''"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4e4855a4623740f1eebc51d968f31cb3')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/libraries/data/src/main/java/com/smarttoolfactory/data/constant/Constants.kt b/libraries/data/src/main/java/com/smarttoolfactory/data/constant/Constants.kt
index b4f1d33..3485236 100644
--- a/libraries/data/src/main/java/com/smarttoolfactory/data/constant/Constants.kt
+++ b/libraries/data/src/main/java/com/smarttoolfactory/data/constant/Constants.kt
@@ -19,4 +19,4 @@ const val ORDER_BY_DES_DESCENDING = "bd"
DBConstants
*/
const val DATABASE_NAME = "property.db"
-const val DATABASE_VERSION = 2
+const val DATABASE_VERSION = 3
diff --git a/libraries/data/src/main/java/com/smarttoolfactory/data/db/PagedPropertyDao.kt b/libraries/data/src/main/java/com/smarttoolfactory/data/db/PagedPropertyDao.kt
new file mode 100755
index 0000000..65a36d7
--- /dev/null
+++ b/libraries/data/src/main/java/com/smarttoolfactory/data/db/PagedPropertyDao.kt
@@ -0,0 +1,38 @@
+package com.smarttoolfactory.data.db
+
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import com.smarttoolfactory.data.model.local.PagedPropertyEntity
+
+@Dao
+interface PagedPropertyDao {
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insert(entity: PagedPropertyEntity): Long
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insert(entities: List): List
+
+ @Delete
+ suspend fun deletePagedPropertyEntity(entity: PagedPropertyEntity): Int
+
+ @Query("DELETE FROM paged_property")
+ suspend fun deleteAll()
+
+ /**
+ * Get number of properties in db
+ */
+ @Query("SELECT COUNT(*) FROM paged_property")
+ suspend fun getPropertyCount(): Int
+
+ /**
+ * Get properties from database.
+ *
+ * *If database is empty returns empty list []
+ */
+ @Query("SELECT * FROM paged_property")
+ suspend fun getPropertyList(): List
+}
diff --git a/libraries/data/src/main/java/com/smarttoolfactory/data/db/PropertyDatabase.kt b/libraries/data/src/main/java/com/smarttoolfactory/data/db/PropertyDatabase.kt
index 154d905..40eafcf 100644
--- a/libraries/data/src/main/java/com/smarttoolfactory/data/db/PropertyDatabase.kt
+++ b/libraries/data/src/main/java/com/smarttoolfactory/data/db/PropertyDatabase.kt
@@ -6,11 +6,16 @@ import androidx.room.TypeConverters
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.smarttoolfactory.data.constant.DATABASE_VERSION
+import com.smarttoolfactory.data.model.local.PagedPropertyEntity
import com.smarttoolfactory.data.model.local.PropertyEntity
import com.smarttoolfactory.data.model.local.SortOrderEntity
@Database(
- entities = [PropertyEntity::class, SortOrderEntity::class],
+ entities = [
+ PropertyEntity::class,
+ PagedPropertyEntity::class,
+ SortOrderEntity::class
+ ],
version = DATABASE_VERSION,
exportSchema = true
)
@@ -24,6 +29,8 @@ abstract class PropertyDatabase : RoomDatabase() {
abstract fun propertySortDaoCoroutines(): SortOrderDaoCoroutines
abstract fun propertySortDaoRxJava(): SortOrderDaoRxJava3
+
+ abstract fun pagedPropertyDao(): PagedPropertyDao
}
/**
@@ -40,3 +47,61 @@ val MIGRATION_1_2: Migration = object : Migration(1, 2) {
)
}
}
+
+/**
+ * Add new Property table for paging, this could have been done with [PropertyEntity] but
+ * used this as another sample.
+ */
+val MIGRATION_2_3: Migration = object : Migration(2, 3) {
+
+ override fun migrate(database: SupportSQLiteDatabase) {
+
+ database.execSQL(
+ "CREATE TABLE IF NOT EXISTS `paged_property` (" +
+ "`insert_order` INTEGER NOT NULL, " +
+ "`id` INTEGER NOT NULL, " +
+ "`update` INTEGER NOT NULL, " +
+ "`category_id` INTEGER NOT NULL, " +
+ "`title` TEXT NOT NULL, " +
+ "`subject` TEXT NOT NULL, " +
+ "`type` TEXT NOT NULL, " +
+ "`type_id` INTEGER NOT NULL, " +
+ "`thumbnail` TEXT, " +
+ "`thumbnail_big` TEXT, " +
+ "`image_count` INTEGER NOT NULL, " +
+ "`price` TEXT NOT NULL, " +
+ "`price_period` TEXT, " +
+ "`price_period_raw` TEXT NOT NULL, " +
+ "`price_label` TEXT, " +
+ "`price_value` TEXT, " +
+ "`price_value_raw` INTEGER NOT NULL, " +
+ "`currency` TEXT NOT NULL, " +
+ "`featured` INTEGER NOT NULL, " +
+ "`location` TEXT NOT NULL, " +
+ "`area` TEXT NOT NULL, " +
+ "`poa` INTEGER NOT NULL, " +
+ "`rera_permit` TEXT, " +
+ "`bathrooms` TEXT NOT NULL, " +
+ "`bedrooms` TEXT NOT NULL, " +
+ "`date_insert` TEXT NOT NULL, " +
+ "`date_update` TEXT NOT NULL, " +
+ "`agent_name` TEXT NOT NULL, " +
+ "`broker_name` TEXT NOT NULL, " +
+ "`agent_license` TEXT, " +
+ "`location_id` INTEGER NOT NULL, " +
+ "`hide_location` INTEGER NOT NULL, " +
+ "`broker` TEXT NOT NULL, " +
+ "`amenities` TEXT NOT NULL, " +
+ "`amenities_keys` TEXT NOT NULL, " +
+ "`latitude` REAL NOT NULL, " +
+ "`longitude` REAL NOT NULL, " +
+ "`premium` INTEGER NOT NULL, " +
+ "`livingrooms` TEXT NOT NULL, " +
+ "`verified` INTEGER NOT NULL, " +
+ "`gallery` TEXT, " +
+ "`phone` TEXT NOT NULL, " +
+ "`lead_email_receivers` TEXT NOT NULL, " +
+ "`reference` TEXT NOT NULL, PRIMARY KEY(`insert_order`))"
+ )
+ }
+}
diff --git a/libraries/data/src/main/java/com/smarttoolfactory/data/di/DatabaseModule.kt b/libraries/data/src/main/java/com/smarttoolfactory/data/di/DatabaseModule.kt
index c260655..38ecaa3 100644
--- a/libraries/data/src/main/java/com/smarttoolfactory/data/di/DatabaseModule.kt
+++ b/libraries/data/src/main/java/com/smarttoolfactory/data/di/DatabaseModule.kt
@@ -4,6 +4,8 @@ import android.app.Application
import androidx.room.Room
import com.smarttoolfactory.data.constant.DATABASE_NAME
import com.smarttoolfactory.data.db.MIGRATION_1_2
+import com.smarttoolfactory.data.db.MIGRATION_2_3
+import com.smarttoolfactory.data.db.PagedPropertyDao
import com.smarttoolfactory.data.db.PropertyDaoCoroutines
import com.smarttoolfactory.data.db.PropertyDaoRxJava3
import com.smarttoolfactory.data.db.PropertyDatabase
@@ -27,7 +29,7 @@ class DatabaseModule {
PropertyDatabase::class.java,
DATABASE_NAME
)
- .addMigrations(MIGRATION_1_2)
+ .addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.build()
}
@@ -50,4 +52,9 @@ class DatabaseModule {
@Provides
fun provideSortOrderDaoRxJava3(appDatabase: PropertyDatabase): SortOrderDaoRxJava3 =
appDatabase.propertySortDaoRxJava()
+
+ @Singleton
+ @Provides
+ fun providePagedPropertyDao(appDatabase: PropertyDatabase): PagedPropertyDao =
+ appDatabase.pagedPropertyDao()
}
diff --git a/libraries/data/src/main/java/com/smarttoolfactory/data/mapper/MappingFactory.kt b/libraries/data/src/main/java/com/smarttoolfactory/data/mapper/MappingFactory.kt
index 6424d2f..345cc6e 100755
--- a/libraries/data/src/main/java/com/smarttoolfactory/data/mapper/MappingFactory.kt
+++ b/libraries/data/src/main/java/com/smarttoolfactory/data/mapper/MappingFactory.kt
@@ -3,6 +3,7 @@ package com.smarttoolfactory.data.mapper
import com.smarttoolfactory.data.model.IEntity
import com.smarttoolfactory.data.model.Mappable
import com.smarttoolfactory.data.model.local.BrokerEntity
+import com.smarttoolfactory.data.model.local.PagedPropertyEntity
import com.smarttoolfactory.data.model.local.PropertyEntity
import com.smarttoolfactory.data.model.remote.BrokerDTO
import com.smarttoolfactory.data.model.remote.PropertyDTO
@@ -110,6 +111,70 @@ class PropertyDTOtoEntityListMapper @Inject constructor() :
}
}
+class PropertyDTOtoPagedEntityListMapper @Inject constructor() :
+ ListMapper {
+
+ override fun map(input: List): List {
+
+ return input.map { input ->
+ PagedPropertyEntity(
+ id = input.id,
+ update = input.update,
+ categoryId = input.categoryId,
+ title = input.title,
+ subject = input.subject,
+ type = input.type,
+ typeId = input.typeId,
+ thumbnail = input.thumbnail,
+ thumbnailBig = input.thumbnailBig,
+ imageCount = input.imageCount,
+ price = input.price,
+ pricePeriod = input.pricePeriod,
+ pricePeriodRaw = input.pricePeriodRaw,
+ priceLabel = input.priceLabel,
+ priceValue = input.priceValue,
+ priceValueRaw = input.priceValueRaw,
+ currency = input.currency,
+ featured = input.featured,
+ location = input.location,
+ area = input.area,
+ poa = input.poa,
+ reraPermit = input.reraPermit,
+ bathrooms = input.bathrooms,
+ bedrooms = input.bedrooms,
+ dateInsert = input.dateInsert,
+ dateUpdate = input.dateUpdate,
+ agentName = input.agentName,
+ brokerName = input.brokerName,
+ agentLicense = input.agentLicense,
+ locationId = input.locationId,
+ hideLocation = input.hideLocation,
+
+ // Maps BrokerEntity
+ broker =
+ MapperFactory.createMapper().map(input.broker),
+ // Maps List
+ amenities = input.amenities,
+ amenitiesKeys = input.amenitiesKeys,
+
+ latitude = input.lat,
+ longitude = input.long,
+ premium = input.premium,
+ livingrooms = input.livingrooms,
+ verified = input.verified,
+
+ // Maps List
+ gallery = input.gallery,
+ phone = input.phone,
+
+ // Maps List
+ leadEmailReceivers = input.leadEmailReceivers,
+ reference = input.reference,
+ )
+ }
+ }
+}
+
/**
* Create [Mapper] or [ListMapper] using Reflection api and factory pattern
*/
diff --git a/libraries/data/src/main/java/com/smarttoolfactory/data/model/IEntity.kt b/libraries/data/src/main/java/com/smarttoolfactory/data/model/Mappables.kt
similarity index 100%
rename from libraries/data/src/main/java/com/smarttoolfactory/data/model/IEntity.kt
rename to libraries/data/src/main/java/com/smarttoolfactory/data/model/Mappables.kt
diff --git a/libraries/data/src/main/java/com/smarttoolfactory/data/model/local/PagedPropertyEntity.kt b/libraries/data/src/main/java/com/smarttoolfactory/data/model/local/PagedPropertyEntity.kt
new file mode 100644
index 0000000..5224e58
--- /dev/null
+++ b/libraries/data/src/main/java/com/smarttoolfactory/data/model/local/PagedPropertyEntity.kt
@@ -0,0 +1,111 @@
+package com.smarttoolfactory.data.model.local
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import com.smarttoolfactory.data.model.IEntity
+
+/**
+ * Properties for PAGING which are retrieved via REST is converted
+ * to [PagedPropertyEntity] to store in database for offline-first or online-first.
+ *
+ * * Implements [IEntity] marker interface for mapping this database items from REST DTOs or
+ * to UI items
+ */
+@Entity(tableName = "paged_property", primaryKeys = ["insert_order"])
+data class PagedPropertyEntity(
+
+ @ColumnInfo(name = "insert_order")
+ var insertOrder: Int = 0,
+
+ @ColumnInfo(name = "id")
+ val id: Int,
+ @ColumnInfo(name = "update")
+ val update: Int,
+ @ColumnInfo(name = "category_id")
+ val categoryId: Int,
+ @ColumnInfo(name = "title")
+ val title: String,
+ @ColumnInfo(name = "subject")
+ val subject: String,
+ @ColumnInfo(name = "type")
+ val type: String,
+ @ColumnInfo(name = "type_id")
+ val typeId: Int,
+ @ColumnInfo(name = "thumbnail")
+ val thumbnail: String?,
+ @ColumnInfo(name = "thumbnail_big")
+ val thumbnailBig: String?,
+ @ColumnInfo(name = "image_count")
+ val imageCount: Int,
+ @ColumnInfo(name = "price")
+ val price: String,
+ @ColumnInfo(name = "price_period")
+ val pricePeriod: String?,
+ @ColumnInfo(name = "price_period_raw")
+ val pricePeriodRaw: String,
+ @ColumnInfo(name = "price_label")
+ val priceLabel: String?,
+ @ColumnInfo(name = "price_value")
+ val priceValue: String?,
+ @ColumnInfo(name = "price_value_raw")
+ val priceValueRaw: Int,
+ @ColumnInfo(name = "currency")
+ val currency: String,
+ @ColumnInfo(name = "featured")
+ val featured: Boolean,
+ @ColumnInfo(name = "location")
+ val location: String,
+ @ColumnInfo(name = "area")
+ val area: String,
+ @ColumnInfo(name = "poa")
+ val poa: Boolean,
+ @ColumnInfo(name = "rera_permit")
+ val reraPermit: String?,
+ @ColumnInfo(name = "bathrooms")
+ val bathrooms: String,
+ @ColumnInfo(name = "bedrooms")
+ val bedrooms: String,
+ @ColumnInfo(name = "date_insert")
+ val dateInsert: String,
+ @ColumnInfo(name = "date_update")
+ val dateUpdate: String,
+ @ColumnInfo(name = "agent_name")
+ val agentName: String,
+ @ColumnInfo(name = "broker_name")
+ val brokerName: String,
+ @ColumnInfo(name = "agent_license")
+ val agentLicense: String?,
+ @ColumnInfo(name = "location_id")
+ val locationId: Int,
+ @ColumnInfo(name = "hide_location")
+ val hideLocation: Boolean,
+ @ColumnInfo(name = "broker")
+
+ val broker: BrokerEntity,
+ @ColumnInfo(name = "amenities")
+ val amenities: List,
+ @ColumnInfo(name = "amenities_keys")
+ val amenitiesKeys: List,
+
+ @ColumnInfo(name = "latitude")
+ val latitude: Double,
+ @ColumnInfo(name = "longitude")
+ val longitude: Double,
+ @ColumnInfo(name = "premium")
+ val premium: Boolean,
+ @ColumnInfo(name = "livingrooms")
+ val livingrooms: String,
+ @ColumnInfo(name = "verified")
+ val verified: Boolean,
+
+ @ColumnInfo(name = "gallery")
+ val gallery: List?,
+ @ColumnInfo(name = "phone")
+ val phone: String,
+
+ @ColumnInfo(name = "lead_email_receivers")
+ val leadEmailReceivers: List,
+
+ @ColumnInfo(name = "reference")
+ val reference: String
+) : IEntity
diff --git a/libraries/data/src/main/java/com/smarttoolfactory/data/model/local/PropertyEntity.kt b/libraries/data/src/main/java/com/smarttoolfactory/data/model/local/PropertyEntity.kt
index 4d9211b..9dac095 100644
--- a/libraries/data/src/main/java/com/smarttoolfactory/data/model/local/PropertyEntity.kt
+++ b/libraries/data/src/main/java/com/smarttoolfactory/data/model/local/PropertyEntity.kt
@@ -10,6 +10,9 @@ import com.smarttoolfactory.data.model.IEntity
*
* * Implements [IEntity] marker interface for mapping this database items from REST DTOs or
* to UI items
+ *
+ * * Insert order is required for getting data same order they are inserted since ordering is
+ * done in server side.
*/
@Entity(tableName = "property", primaryKeys = ["insert_order"])
data class PropertyEntity(
diff --git a/libraries/data/src/main/java/com/smarttoolfactory/data/model/local/SavedProperty.kt b/libraries/data/src/main/java/com/smarttoolfactory/data/model/local/SavedProperty.kt
new file mode 100644
index 0000000..16ff648
--- /dev/null
+++ b/libraries/data/src/main/java/com/smarttoolfactory/data/model/local/SavedProperty.kt
@@ -0,0 +1,72 @@
+package com.smarttoolfactory.data.model.local
+
+import androidx.room.Embedded
+import androidx.room.Entity
+import androidx.room.ForeignKey
+import androidx.room.Index
+import androidx.room.PrimaryKey
+import androidx.room.Relation
+
+@Entity(tableName = "post")
+data class PostEntity(
+ @PrimaryKey
+ val id: Int,
+ val userId: Int,
+ val title: String,
+ val body: String
+)
+
+/**
+ * * Data class that contains [PostStatus] data.
+ * [PostEntity.id] is in [PostEntity] class, [PostStatus.postId] is in [PostStatus]
+ * both points to same value.
+ *
+ * * [PostStatus.id] is auto generated by insertion to table.
+ *
+ * * Index let's this table to be sorted by postId which makes all
+ * rows with same postId to be found faster.
+ *
+ * * Status of the [PostEntity] with [PostEntity.id] or [PostStatus.postId] belong to current user
+ * logged in with [PostStatus.userAccountId] or -1 if any user hasn't logged in
+ */
+@Entity(
+ tableName = "post_status",
+ indices = [Index(value = ["userAccountId", "postId"])],
+ foreignKeys = [
+ ForeignKey(
+ entity = PostEntity::class,
+ parentColumns = ["id"],
+ childColumns = ["postId"],
+ onDelete = ForeignKey.NO_ACTION
+ )
+ ]
+)
+data class PostStatus(
+ @PrimaryKey(autoGenerate = true)
+ val id: Int = 0,
+ val userAccountId: Int = -1,
+ val postId: Int,
+ val displayCount: Int = 0,
+ val isFavorite: Boolean = false
+)
+
+/**
+ * @Embedded tag is for having nested entities that are contained inside another entity. For
+ * instance Songs are embedded inside an Album.
+ *
+ * @Relation is for having relation between entities based on pairing one or more properties,
+ * such as ids. For instance Person with id, having Pets that has userId that is exactly same
+ * with each other.
+ *
+ * * ParentColumn name from [PostEntity] class is matched with entityColumn
+ * from [PostStatus.postId]
+ */
+data class PostAndStatus(
+
+ @Embedded
+ val postEntity: PostEntity,
+
+ // 🔥 'id' comes from Post, 'postId' comes from Post. Both are the same ids
+ @Relation(parentColumn = "id", entityColumn = "postId")
+ var postStatus: PostStatus? = null
+)
diff --git a/libraries/data/src/main/java/com/smarttoolfactory/data/repository/Repository.kt b/libraries/data/src/main/java/com/smarttoolfactory/data/repository/Repository.kt
index 3e3df0b..0d3c49a 100644
--- a/libraries/data/src/main/java/com/smarttoolfactory/data/repository/Repository.kt
+++ b/libraries/data/src/main/java/com/smarttoolfactory/data/repository/Repository.kt
@@ -1,6 +1,7 @@
package com.smarttoolfactory.data.repository
import com.smarttoolfactory.data.constant.ORDER_BY_NONE
+import com.smarttoolfactory.data.model.local.PagedPropertyEntity
import com.smarttoolfactory.data.model.local.PropertyEntity
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Single
@@ -12,15 +13,8 @@ import io.reactivex.rxjava3.core.Single
*/
interface PropertyRepositoryCoroutines {
- fun getCurrentPageNumber(): Int
-
suspend fun fetchEntitiesFromRemote(orderBy: String = ORDER_BY_NONE): List
- suspend fun fetchEntitiesFromRemoteByPage(
- page: Int,
- orderBy: String = ORDER_BY_NONE
- ): List
-
suspend fun getPropertyEntitiesFromLocal(): List
suspend fun savePropertyEntities(propertyEntities: List)
@@ -38,15 +32,8 @@ interface PropertyRepositoryCoroutines {
*/
interface PropertyRepositoryRxJava3 {
- fun getCurrentPageNumber(): Int
-
fun fetchEntitiesFromRemote(orderBy: String = ORDER_BY_NONE): Single>
- fun fetchEntitiesFromRemoteByPage(
- page: Int,
- orderBy: String = ORDER_BY_NONE
- ): Single>
-
fun getPropertyEntitiesFromLocal(): Single>
fun savePropertyEntities(propertyEntities: List): Completable
@@ -56,3 +43,26 @@ interface PropertyRepositoryRxJava3 {
fun saveSortOrderKey(orderBy: String): Completable
fun getSortOrderKey(): Single
}
+
+// TODO This can be single interface with generic input and output types
+interface PagedPropertyRepository {
+
+ fun getCurrentPageNumber(): Int
+
+ suspend fun fetchEntitiesFromRemoteByPage(
+ orderBy: String = ORDER_BY_NONE
+ ): List
+
+ suspend fun getPropertyCount(): Int
+
+ fun resetPageCount()
+
+ suspend fun getPropertyEntitiesFromLocal(): List
+
+ suspend fun savePropertyEntities(propertyEntities: List)
+
+ suspend fun deletePropertyEntities()
+
+ suspend fun saveSortOrderKey(orderBy: String)
+ suspend fun getSortOrderKey(): String
+}
diff --git a/libraries/data/src/main/java/com/smarttoolfactory/data/repository/RepositoryImpl.kt b/libraries/data/src/main/java/com/smarttoolfactory/data/repository/RepositoryImpl.kt
index 1c43533..6915c02 100644
--- a/libraries/data/src/main/java/com/smarttoolfactory/data/repository/RepositoryImpl.kt
+++ b/libraries/data/src/main/java/com/smarttoolfactory/data/repository/RepositoryImpl.kt
@@ -1,7 +1,10 @@
package com.smarttoolfactory.data.repository
import com.smarttoolfactory.data.mapper.PropertyDTOtoEntityListMapper
+import com.smarttoolfactory.data.mapper.PropertyDTOtoPagedEntityListMapper
+import com.smarttoolfactory.data.model.local.PagedPropertyEntity
import com.smarttoolfactory.data.model.local.PropertyEntity
+import com.smarttoolfactory.data.source.LocalPagedPropertyDataSource
import com.smarttoolfactory.data.source.LocalPropertyDataSourceCoroutines
import com.smarttoolfactory.data.source.LocalPropertyDataSourceRxJava3
import com.smarttoolfactory.data.source.RemotePropertyDataSourceCoroutines
@@ -16,24 +19,10 @@ class PropertyRepositoryImplCoroutines @Inject constructor(
private val mapper: PropertyDTOtoEntityListMapper
) : PropertyRepositoryCoroutines {
- private var currentPageNumber = 0
-
- override fun getCurrentPageNumber(): Int {
- return currentPageNumber
- }
-
override suspend fun fetchEntitiesFromRemote(orderBy: String): List {
+ val data = remoteDataSource.getPropertyDTOs(orderBy)
saveSortOrderKey(orderBy)
- return mapper.map(remoteDataSource.getPropertyDTOs(orderBy))
- }
-
- override suspend fun fetchEntitiesFromRemoteByPage(
- page: Int,
- orderBy: String
- ): List {
- currentPageNumber = page
- saveSortOrderKey(orderBy)
- return mapper.map(remoteDataSource.getPropertyDTOsWithPagination(page, orderBy))
+ return mapper.map(data)
}
override suspend fun getPropertyEntitiesFromLocal(): List {
@@ -63,12 +52,6 @@ class PropertyRepositoryImlRxJava3 @Inject constructor(
private val mapper: PropertyDTOtoEntityListMapper
) : PropertyRepositoryRxJava3 {
- private var currentPageNumber = 0
-
- override fun getCurrentPageNumber(): Int {
- return currentPageNumber
- }
-
override fun fetchEntitiesFromRemote(orderBy: String): Single> {
return remoteDataSource.getPropertyDTOs(orderBy)
@@ -78,17 +61,6 @@ class PropertyRepositoryImlRxJava3 @Inject constructor(
}
}
- override fun fetchEntitiesFromRemoteByPage(
- page: Int,
- orderBy: String
- ): Single> {
- return remoteDataSource.getPropertyDTOsWithPagination(page, orderBy).map {
- this.currentPageNumber = page
- saveSortOrderKey(orderBy)
- mapper.map(it)
- }
- }
-
override fun getPropertyEntitiesFromLocal(): Single> {
return localDataSource.getPropertyEntities()
}
@@ -106,6 +78,59 @@ class PropertyRepositoryImlRxJava3 @Inject constructor(
}
override fun getSortOrderKey(): Single {
- return localDataSource.getOrderkey()
+ return localDataSource.getOrderKey()
+ }
+}
+
+class PagedPropertyRepositoryImpl @Inject constructor(
+ private val localDataSource: LocalPagedPropertyDataSource,
+ private val remoteDataSource: RemotePropertyDataSourceCoroutines,
+ private val mapper: PropertyDTOtoPagedEntityListMapper
+) : PagedPropertyRepository {
+
+ private var currentPageNumber = 1
+
+ override fun getCurrentPageNumber(): Int {
+ return currentPageNumber
+ }
+
+ override suspend fun fetchEntitiesFromRemoteByPage(
+ orderBy: String
+ ): List {
+
+ val data = remoteDataSource.getPropertyDTOsWithPagination(
+ currentPageNumber++,
+ orderBy
+ )
+ saveSortOrderKey(orderBy)
+ return mapper.map(data)
+ }
+
+ override suspend fun getPropertyCount(): Int {
+ return localDataSource.getPropertyCount()
+ }
+
+ override fun resetPageCount() {
+ currentPageNumber = 1
+ }
+
+ override suspend fun getPropertyEntitiesFromLocal(): List {
+ return localDataSource.getPropertyEntities()
+ }
+
+ override suspend fun savePropertyEntities(propertyEntities: List) {
+ localDataSource.saveEntities(propertyEntities)
+ }
+
+ override suspend fun deletePropertyEntities() {
+ localDataSource.deletePropertyEntities()
+ }
+
+ override suspend fun saveSortOrderKey(orderBy: String) {
+ localDataSource.saveOrderKey(orderBy)
+ }
+
+ override suspend fun getSortOrderKey(): String {
+ return localDataSource.getOrderKey()
}
}
diff --git a/libraries/data/src/main/java/com/smarttoolfactory/data/source/PropertyDataSource.kt b/libraries/data/src/main/java/com/smarttoolfactory/data/source/PropertyDataSource.kt
index 14100b4..d03fb1f 100644
--- a/libraries/data/src/main/java/com/smarttoolfactory/data/source/PropertyDataSource.kt
+++ b/libraries/data/src/main/java/com/smarttoolfactory/data/source/PropertyDataSource.kt
@@ -1,6 +1,7 @@
package com.smarttoolfactory.data.source
import com.smarttoolfactory.data.constant.ORDER_BY_NONE
+import com.smarttoolfactory.data.model.local.PagedPropertyEntity
import com.smarttoolfactory.data.model.local.PropertyEntity
import com.smarttoolfactory.data.model.remote.PropertyDTO
import io.reactivex.rxjava3.core.Completable
@@ -27,6 +28,18 @@ interface LocalPropertyDataSourceCoroutines : PropertyDataSource {
suspend fun getOrderKey(): String
}
+/*
+ Pagination + Coroutines
+ */
+interface LocalPagedPropertyDataSource : PropertyDataSource {
+ suspend fun getPropertyEntities(): List
+ suspend fun saveEntities(properties: List): List
+ suspend fun deletePropertyEntities()
+ suspend fun getPropertyCount(): Int
+ suspend fun saveOrderKey(orderBy: String)
+ suspend fun getOrderKey(): String
+}
+
/*
RxJava3
*/
@@ -43,5 +56,5 @@ interface LocalPropertyDataSourceRxJava3 : PropertyDataSource {
fun saveEntities(properties: List): Completable
fun deletePropertyEntities(): Completable
fun saveOrderKey(orderBy: String): Completable
- fun getOrderkey(): Single
+ fun getOrderKey(): Single
}
diff --git a/libraries/data/src/main/java/com/smarttoolfactory/data/source/PropertyDataSourceImpl.kt b/libraries/data/src/main/java/com/smarttoolfactory/data/source/PropertyDataSourceImpl.kt
index 07b8ec2..63525c0 100644
--- a/libraries/data/src/main/java/com/smarttoolfactory/data/source/PropertyDataSourceImpl.kt
+++ b/libraries/data/src/main/java/com/smarttoolfactory/data/source/PropertyDataSourceImpl.kt
@@ -2,10 +2,12 @@ package com.smarttoolfactory.data.source
import com.smarttoolfactory.data.api.PropertyApiCoroutines
import com.smarttoolfactory.data.api.PropertyApiRxJava
+import com.smarttoolfactory.data.db.PagedPropertyDao
import com.smarttoolfactory.data.db.PropertyDaoCoroutines
import com.smarttoolfactory.data.db.PropertyDaoRxJava3
import com.smarttoolfactory.data.db.SortOrderDaoCoroutines
import com.smarttoolfactory.data.db.SortOrderDaoRxJava3
+import com.smarttoolfactory.data.model.local.PagedPropertyEntity
import com.smarttoolfactory.data.model.local.PropertyEntity
import com.smarttoolfactory.data.model.local.SortOrderEntity
import com.smarttoolfactory.data.model.remote.PropertyDTO
@@ -98,7 +100,40 @@ class LocalDataSourceRxJava3Impl @Inject constructor(
return sortDao.insert(SortOrderEntity(orderBy = orderBy))
}
- override fun getOrderkey(): Single {
+ override fun getOrderKey(): Single {
return sortDao.getSortOrderSingle()
}
}
+
+/*
+ Paged Local Data Source
+ */
+class LocalPagedPropertySourceImpl @Inject constructor(
+ private val dao: PagedPropertyDao,
+ private val sortDao: SortOrderDaoCoroutines
+) : LocalPagedPropertyDataSource {
+
+ override suspend fun getPropertyEntities(): List {
+ return dao.getPropertyList()
+ }
+
+ override suspend fun saveEntities(properties: List): List {
+ return dao.insert(properties)
+ }
+
+ override suspend fun deletePropertyEntities() {
+ return dao.deleteAll()
+ }
+
+ override suspend fun getPropertyCount(): Int {
+ return dao.getPropertyCount()
+ }
+
+ override suspend fun saveOrderKey(orderBy: String) {
+ sortDao.insert(SortOrderEntity(orderBy = orderBy))
+ }
+
+ override suspend fun getOrderKey(): String {
+ return sortDao.getSortOrderEntity()
+ }
+}
diff --git a/libraries/data/src/test/java/com/smarttoolfactory/data/repository/PagedPropertyRepositoryImplTest.kt b/libraries/data/src/test/java/com/smarttoolfactory/data/repository/PagedPropertyRepositoryImplTest.kt
new file mode 100644
index 0000000..4b68f0b
--- /dev/null
+++ b/libraries/data/src/test/java/com/smarttoolfactory/data/repository/PagedPropertyRepositoryImplTest.kt
@@ -0,0 +1,205 @@
+package com.smarttoolfactory.data.repository
+
+import com.google.common.truth.Truth
+import com.smarttoolfactory.data.constant.ORDER_BY_NONE
+import com.smarttoolfactory.data.mapper.MapperFactory
+import com.smarttoolfactory.data.mapper.PropertyDTOtoPagedEntityListMapper
+import com.smarttoolfactory.data.model.local.PagedPropertyEntity
+import com.smarttoolfactory.data.model.remote.PropertyResponse
+import com.smarttoolfactory.data.source.LocalPagedPropertyDataSource
+import com.smarttoolfactory.data.source.RemotePropertyDataSourceCoroutines
+import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH
+import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH_PAGE_1
+import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH_PAGE_2
+import com.smarttoolfactory.test_utils.util.convertToObjectFromJson
+import com.smarttoolfactory.test_utils.util.getResourceAsText
+import io.mockk.clearMocks
+import io.mockk.coEvery
+import io.mockk.coVerify
+import io.mockk.coVerifyOrder
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.runs
+import io.mockk.slot
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+
+internal class PagedPropertyRepositoryImplTest {
+
+ private lateinit var repository: PagedPropertyRepository
+
+ private val localDataSource: LocalPagedPropertyDataSource = mockk()
+ private val remoteDataSource: RemotePropertyDataSourceCoroutines = mockk()
+ private val mapper: PropertyDTOtoPagedEntityListMapper = mockk()
+
+ companion object {
+
+ private val propertyResponse = convertToObjectFromJson(
+ getResourceAsText(RESPONSE_JSON_PATH)
+ )!!
+
+ private val propertyDTOList = propertyResponse.res
+
+ private val entityList =
+ MapperFactory.createListMapper()
+ .map(propertyDTOList)
+
+ // FIXME Cannot convert from Json to Entity even with wrapper, check out Moshi or Jackson
+
+ private val propertyResponsePage1 = convertToObjectFromJson(
+ getResourceAsText(RESPONSE_JSON_PATH_PAGE_1)
+ )!!
+
+ private val propertyResponsePage2 = convertToObjectFromJson(
+ getResourceAsText(RESPONSE_JSON_PATH_PAGE_2)
+ )!!
+
+ private val propertyDTOListPage1 = propertyResponsePage1.res
+ private val propertyDTOListPage2 = propertyResponsePage2.res
+
+ private val entityListPage1 =
+ MapperFactory.createListMapper()
+ .map(
+ convertToObjectFromJson(
+ getResourceAsText(RESPONSE_JSON_PATH_PAGE_1)
+ )!!.res
+ )
+
+ private val entityListPage2 =
+ MapperFactory.createListMapper()
+ .map(
+ convertToObjectFromJson(
+ getResourceAsText(RESPONSE_JSON_PATH_PAGE_1)
+ )!!.res
+ )
+ }
+
+ @Test
+ fun `given page 2 returned data returned should have current page number 2 with Pagination`() =
+ runBlockingTest {
+
+ // GIVEN
+ val slot = slot()
+
+ // Page 1 Pagination
+ val page1DTO = propertyDTOListPage1
+ val page1Data = entityListPage1
+
+ coEvery {
+ remoteDataSource.getPropertyDTOsWithPagination(1)
+ } returns page1DTO
+
+ every { mapper.map(page1DTO) } returns page1Data
+ coEvery { localDataSource.saveOrderKey(capture(slot)) } just runs
+
+ // Page 2 Pagination
+ val page2DTO = propertyDTOListPage2
+ val page2Data = entityListPage2
+
+ coEvery {
+ remoteDataSource.getPropertyDTOsWithPagination(2)
+ } returns page2DTO
+
+ every { mapper.map(page2DTO) } returns page2Data
+
+ // WHEN
+ val page1 = repository.getCurrentPageNumber()
+ val expected1 = repository.fetchEntitiesFromRemoteByPage()
+
+ val page2 = repository.getCurrentPageNumber()
+ val expected2 = repository.fetchEntitiesFromRemoteByPage()
+
+ // THEN
+ Truth.assertThat(expected1).isEqualTo(page1Data)
+ Truth.assertThat(page1).isEqualTo(1)
+
+ Truth.assertThat(expected2).isEqualTo(page2Data)
+ Truth.assertThat(page2).isEqualTo(2)
+
+ coVerifyOrder {
+ remoteDataSource.getPropertyDTOsWithPagination(1)
+ localDataSource.saveOrderKey(ORDER_BY_NONE)
+ mapper.map(page1DTO)
+ remoteDataSource.getPropertyDTOsWithPagination(2)
+ localDataSource.saveOrderKey(ORDER_BY_NONE)
+ mapper.map(page2DTO)
+ }
+ }
+
+ @Test
+ fun `given DB is empty should return an empty list`() = runBlockingTest {
+
+ // GIVEN
+ val expected = listOf()
+ coEvery { localDataSource.getPropertyEntities() } returns expected
+
+ // WHEN
+ val actual = repository.getPropertyEntitiesFromLocal()
+
+ // THEN
+ Truth.assertThat(actual).isEmpty()
+ coVerify(exactly = 1) { localDataSource.getPropertyEntities() }
+ }
+
+ @Test
+ fun `given DB is populated should return data list`() = runBlockingTest {
+
+ // GIVEN
+ coEvery { localDataSource.getPropertyEntities() } returns entityList
+
+ // WHEN
+ val actual = repository.getPropertyEntitiesFromLocal()
+
+ // THEN
+ Truth.assertThat(actual)
+ .containsExactlyElementsIn(entityList)
+ coVerify(exactly = 1) { localDataSource.getPropertyEntities() }
+ }
+
+ @Test
+ fun `given entities, should save entities`() = runBlockingTest {
+
+ // GIVEN
+ val idList = entityList.map {
+ it.id.toLong()
+ }
+
+ coEvery {
+ localDataSource.saveEntities(entityList)
+ } returns idList
+
+ // WHEN
+ repository.savePropertyEntities(entityList)
+
+ // THEN
+ coVerify(exactly = 1) { localDataSource.saveEntities(entityList) }
+ }
+
+ @Test
+ fun `given no error should delete entities`() = runBlockingTest {
+
+ // GIVEN
+ coEvery { localDataSource.deletePropertyEntities() } just runs
+
+ // WHEN
+ repository.deletePropertyEntities()
+
+ // THEN
+ coVerify(exactly = 1) {
+ localDataSource.deletePropertyEntities()
+ }
+ }
+
+ @BeforeEach
+ fun setUp() {
+ repository = PagedPropertyRepositoryImpl(localDataSource, remoteDataSource, mapper)
+ }
+
+ @AfterEach
+ fun tearDown() {
+ clearMocks(localDataSource, remoteDataSource, mapper)
+ }
+}
diff --git a/libraries/data/src/test/java/com/smarttoolfactory/data/repository/PropertyRepositoryCoroutinesTest.kt b/libraries/data/src/test/java/com/smarttoolfactory/data/repository/PropertyRepositoryCoroutinesTest.kt
index 2d85bd0..4fd4c72 100644
--- a/libraries/data/src/test/java/com/smarttoolfactory/data/repository/PropertyRepositoryCoroutinesTest.kt
+++ b/libraries/data/src/test/java/com/smarttoolfactory/data/repository/PropertyRepositoryCoroutinesTest.kt
@@ -9,8 +9,6 @@ import com.smarttoolfactory.data.model.remote.PropertyResponse
import com.smarttoolfactory.data.source.LocalPropertyDataSourceCoroutines
import com.smarttoolfactory.data.source.RemotePropertyDataSourceCoroutines
import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH
-import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH_PAGE_1
-import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH_PAGE_2
import com.smarttoolfactory.test_utils.util.convertToObjectFromJson
import com.smarttoolfactory.test_utils.util.getResourceAsText
import io.mockk.clearMocks
@@ -21,6 +19,7 @@ import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
+import io.mockk.slot
import io.mockk.verify
import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.AfterEach
@@ -54,35 +53,6 @@ internal class PropertyRepositoryCoroutinesTest {
private val entityList =
MapperFactory.createListMapper()
.map(propertyDTOList)
-
- // FIXME Cannot convert from Json to Entity even with wrapper, check out Moshi or Jackson
-
- private val propertyResponsePage1 = convertToObjectFromJson(
- getResourceAsText(RESPONSE_JSON_PATH_PAGE_1)
- )!!
-
- private val propertyResponsePage2 = convertToObjectFromJson(
- getResourceAsText(RESPONSE_JSON_PATH_PAGE_2)
- )!!
-
- private val propertyDTOListPage1 = propertyResponsePage1.res
- private val propertyDTOListPage2 = propertyResponsePage2.res
-
- private val entityListPage1 =
- MapperFactory.createListMapper()
- .map(
- convertToObjectFromJson(
- getResourceAsText(RESPONSE_JSON_PATH_PAGE_1)
- )!!.res
- )
-
- private val entityListPage2 =
- MapperFactory.createListMapper()
- .map(
- convertToObjectFromJson(
- getResourceAsText(RESPONSE_JSON_PATH_PAGE_1)
- )!!.res
- )
}
@Test
@@ -110,69 +80,27 @@ internal class PropertyRepositoryCoroutinesTest {
runBlockingTest {
// GIVEN
+ val slot = slot()
+
val actual = entityList
coEvery { remoteDataSource.getPropertyDTOs() } returns propertyDTOList
every { mapper.map(propertyDTOList) } returns entityList
+ coEvery { localDataSource.saveOrderKey(capture(slot)) } just runs
// WHEN
val expected = repository.fetchEntitiesFromRemote()
// THEN
Truth.assertThat(expected).isEqualTo(actual)
+ Truth.assertThat(slot.captured).isEqualTo(ORDER_BY_NONE)
+
coVerifyOrder {
remoteDataSource.getPropertyDTOs()
+ localDataSource.saveOrderKey(ORDER_BY_NONE)
mapper.map(propertyDTOList)
}
}
- @Test
- fun `given page 2 returned data returned should have current page number 2 with Pagination`() =
- runBlockingTest {
-
- // GIVEN
-
- // Page 1 Pagination
- val page1DTO = propertyDTOListPage1
- val page1Data = entityListPage1
-
- coEvery {
- remoteDataSource.getPropertyDTOsWithPagination(1)
- } returns page1DTO
-
- every { mapper.map(page1DTO) } returns page1Data
-
- // Page 2 Pagination
- val page2DTO = propertyDTOListPage2
- val page2Data = entityListPage2
-
- coEvery {
- remoteDataSource.getPropertyDTOsWithPagination(2)
- } returns page2DTO
-
- every { mapper.map(page2DTO) } returns page2Data
-
- // WHEN
- val expected1 = repository.fetchEntitiesFromRemoteByPage(1)
- val page1 = repository.getCurrentPageNumber()
-
- val expected2 = repository.fetchEntitiesFromRemoteByPage(2)
- val page2 = repository.getCurrentPageNumber()
-
- // THEN
- Truth.assertThat(expected1).isEqualTo(page1Data)
- Truth.assertThat(page1).isEqualTo(1)
-
- Truth.assertThat(expected2).isEqualTo(page2Data)
- Truth.assertThat(page2).isEqualTo(2)
-
- coVerifyOrder {
- remoteDataSource.getPropertyDTOsWithPagination(1)
- mapper.map(page1DTO)
- remoteDataSource.getPropertyDTOsWithPagination(2)
- mapper.map(page2DTO)
- }
- }
-
@Test
fun `given DB is empty should return an empty list`() = runBlockingTest {
diff --git a/libraries/data/src/test/java/com/smarttoolfactory/data/repository/PropertyRepositoryRxJava3Test.kt b/libraries/data/src/test/java/com/smarttoolfactory/data/repository/PropertyRepositoryRxJava3Test.kt
index eea813f..c94033b 100644
--- a/libraries/data/src/test/java/com/smarttoolfactory/data/repository/PropertyRepositoryRxJava3Test.kt
+++ b/libraries/data/src/test/java/com/smarttoolfactory/data/repository/PropertyRepositoryRxJava3Test.kt
@@ -1,6 +1,7 @@
package com.smarttoolfactory.data.repository
import com.google.common.truth.Truth
+import com.smarttoolfactory.data.constant.ORDER_BY_NONE
import com.smarttoolfactory.data.mapper.MapperFactory
import com.smarttoolfactory.data.mapper.PropertyDTOtoEntityListMapper
import com.smarttoolfactory.data.model.local.PropertyEntity
@@ -8,15 +9,12 @@ import com.smarttoolfactory.data.model.remote.PropertyResponse
import com.smarttoolfactory.data.source.LocalPropertyDataSourceRxJava3
import com.smarttoolfactory.data.source.RemotePropertyDataSourceRxJava3
import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH
-import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH_PAGE_1
-import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH_PAGE_2
import com.smarttoolfactory.test_utils.util.convertToObjectFromJson
import com.smarttoolfactory.test_utils.util.getResourceAsText
import io.mockk.clearMocks
-import io.mockk.coEvery
-import io.mockk.coVerifyOrder
import io.mockk.every
import io.mockk.mockk
+import io.mockk.slot
import io.mockk.verify
import io.mockk.verifyOrder
import io.reactivex.rxjava3.core.Completable
@@ -52,35 +50,6 @@ class PropertyRepositoryRxJava3Test {
private val entityList =
MapperFactory.createListMapper()
.map(propertyDTOList)
-
- // FIXME Cannot convert from Json to Entity even with wrapper, check out Moshi or Jackson
-
- private val propertyResponsePage1 = convertToObjectFromJson(
- getResourceAsText(RESPONSE_JSON_PATH_PAGE_1)
- )!!
-
- private val propertyResponsePage2 = convertToObjectFromJson(
- getResourceAsText(RESPONSE_JSON_PATH_PAGE_2)
- )!!
-
- private val propertyDTOListPage1 = propertyResponsePage1.res
- private val propertyDTOListPage2 = propertyResponsePage2.res
-
- private val entityListPage1 =
- MapperFactory.createListMapper()
- .map(
- convertToObjectFromJson(
- getResourceAsText(RESPONSE_JSON_PATH_PAGE_1)
- )!!.res
- )
-
- private val entityListPage2 =
- MapperFactory.createListMapper()
- .map(
- convertToObjectFromJson(
- getResourceAsText(RESPONSE_JSON_PATH_PAGE_1)
- )!!.res
- )
}
@Test
@@ -112,72 +81,26 @@ class PropertyRepositoryRxJava3Test {
fun `given remote data source return PropertyDTO list, should return PropertyEntity list`() {
// GIVEN
+ val slot = slot()
+
every { remoteDataSource.getPropertyDTOs() } returns Single.just(propertyDTOList)
every { mapper.map(propertyDTOList) } returns entityList
+ every { localDataSource.saveOrderKey(capture(slot)) } returns Completable.complete()
// WHEN
val expected = repository.fetchEntitiesFromRemote().blockingGet()
// THEN
Truth.assertThat(expected).containsExactlyElementsIn(entityList)
+ Truth.assertThat(slot.captured).isEqualTo(ORDER_BY_NONE)
+
verifyOrder {
remoteDataSource.getPropertyDTOs()
+ localDataSource.saveOrderKey(ORDER_BY_NONE)
mapper.map(propertyDTOList)
}
}
- @Test
- fun `given page 2 returned data returned should have current page number 2 with Pagination`() {
-
- // GIVEN
-
- // Page 1 Pagination
- val page1DTO = propertyDTOListPage1
- val page1Data = entityListPage1
-
- coEvery {
- remoteDataSource.getPropertyDTOsWithPagination(1)
- } returns Single.just(page1DTO)
-
- every { mapper.map(page1DTO) } returns page1Data
-
- // Page 2 Pagination
- val page2DTO = propertyDTOListPage2
- val page2Data = entityListPage2
-
- coEvery {
- remoteDataSource.getPropertyDTOsWithPagination(2)
- } returns Single.just(page2DTO)
-
- every { mapper.map(page2DTO) } returns page2Data
-
- // WHEN
- val expected1 = repository
- .fetchEntitiesFromRemoteByPage(1)
- .blockingGet()
-
- val page1 = repository.getCurrentPageNumber()
-
- val expected2 = repository
- .fetchEntitiesFromRemoteByPage(2)
- .blockingGet()
- val page2 = repository.getCurrentPageNumber()
-
- // THEN
- Truth.assertThat(expected1).isEqualTo(page1Data)
- Truth.assertThat(page1).isEqualTo(1)
-
- Truth.assertThat(expected2).isEqualTo(page2Data)
- Truth.assertThat(page2).isEqualTo(2)
-
- coVerifyOrder {
- remoteDataSource.getPropertyDTOsWithPagination(1)
- mapper.map(page1DTO)
- remoteDataSource.getPropertyDTOsWithPagination(2)
- mapper.map(page2DTO)
- }
- }
-
@Test
fun `given DB is empty should return an empty list`() {
diff --git a/libraries/data/src/test/java/com/smarttoolfactory/data/source/PropertyDataSourceCoroutinesTest.kt b/libraries/data/src/test/java/com/smarttoolfactory/data/source/PropertyDataSourceCoroutinesTest.kt
index 5227a0f..5a2291c 100644
--- a/libraries/data/src/test/java/com/smarttoolfactory/data/source/PropertyDataSourceCoroutinesTest.kt
+++ b/libraries/data/src/test/java/com/smarttoolfactory/data/source/PropertyDataSourceCoroutinesTest.kt
@@ -3,6 +3,7 @@ package com.smarttoolfactory.data.source
import com.google.common.truth.Truth
import com.smarttoolfactory.data.api.PropertyApiCoroutines
import com.smarttoolfactory.data.db.PropertyDaoCoroutines
+import com.smarttoolfactory.data.db.SortOrderDaoCoroutines
import com.smarttoolfactory.data.mapper.MapperFactory
import com.smarttoolfactory.data.mapper.PropertyDTOtoEntityListMapper
import com.smarttoolfactory.data.model.local.PropertyEntity
@@ -100,6 +101,7 @@ class PropertyDataSourceCoroutinesTest {
private val dao = mockk()
private lateinit var localDataSource: LocalPropertyDataSourceCoroutines
+ private val sortDao = mockk()
@Test
fun `given DB is empty should return an empty list`() = runBlockingTest {
@@ -163,12 +165,12 @@ class PropertyDataSourceCoroutinesTest {
@BeforeEach
fun setUp() {
- localDataSource = LocalPropertyDataSourceImpl(dao)
+ localDataSource = LocalPropertyDataSourceImpl(dao, sortDao)
}
@AfterEach
fun tearDown() {
- clearMocks(dao)
+ clearMocks(dao, sortDao)
}
}
}
diff --git a/libraries/data/src/test/java/com/smarttoolfactory/data/source/PropertyDataSourceRxJava3Test.kt b/libraries/data/src/test/java/com/smarttoolfactory/data/source/PropertyDataSourceRxJava3Test.kt
index 08fcd1e..4f8d34e 100644
--- a/libraries/data/src/test/java/com/smarttoolfactory/data/source/PropertyDataSourceRxJava3Test.kt
+++ b/libraries/data/src/test/java/com/smarttoolfactory/data/source/PropertyDataSourceRxJava3Test.kt
@@ -3,6 +3,7 @@ package com.smarttoolfactory.data.source
import com.google.common.truth.Truth
import com.smarttoolfactory.data.api.PropertyApiRxJava
import com.smarttoolfactory.data.db.PropertyDaoRxJava3
+import com.smarttoolfactory.data.db.SortOrderDaoRxJava3
import com.smarttoolfactory.data.mapper.MapperFactory
import com.smarttoolfactory.data.mapper.PropertyDTOtoEntityListMapper
import com.smarttoolfactory.data.model.local.PropertyEntity
@@ -100,6 +101,7 @@ class PropertyDataSourceRxJava3Test {
inner class LocalDataSourceTest {
private val dao = mockk()
+ private val sortDao = mockk()
private lateinit var localDataSource: LocalDataSourceRxJava3Impl
@@ -167,12 +169,12 @@ class PropertyDataSourceRxJava3Test {
@BeforeEach
fun setUp() {
- localDataSource = LocalDataSourceRxJava3Impl(dao)
+ localDataSource = LocalDataSourceRxJava3Impl(dao, sortDao)
}
@AfterEach
fun tearDown() {
- clearMocks(dao)
+ clearMocks(dao, sortDao)
}
}
}
diff --git a/libraries/domain/src/main/java/com/smarttoolfactory/domain/mapper/Mappers.kt b/libraries/domain/src/main/java/com/smarttoolfactory/domain/mapper/Mappers.kt
index f3b6c6e..d08bb1c 100755
--- a/libraries/domain/src/main/java/com/smarttoolfactory/domain/mapper/Mappers.kt
+++ b/libraries/domain/src/main/java/com/smarttoolfactory/domain/mapper/Mappers.kt
@@ -4,6 +4,7 @@ import com.smarttoolfactory.data.mapper.ListMapper
import com.smarttoolfactory.data.mapper.Mapper
import com.smarttoolfactory.data.mapper.MapperFactory
import com.smarttoolfactory.data.model.local.BrokerEntity
+import com.smarttoolfactory.data.model.local.PagedPropertyEntity
import com.smarttoolfactory.data.model.local.PropertyEntity
import com.smarttoolfactory.domain.model.BrokerItem
import com.smarttoolfactory.domain.model.PropertyItem
@@ -96,3 +97,68 @@ class PropertyEntityToItemListMapper @Inject constructor() :
}
}
}
+
+class PagedEntityToItemListMapper @Inject constructor() :
+ ListMapper {
+
+ override fun map(input: List): List {
+
+ return input.map { input ->
+
+ PropertyItem(
+ id = input.id,
+ update = input.update,
+ categoryId = input.categoryId,
+ title = input.title,
+ subject = input.subject,
+ type = input.type,
+ typeId = input.typeId,
+ thumbnail = input.thumbnail,
+ thumbnailBig = input.thumbnailBig,
+ imageCount = input.imageCount,
+ price = input.price,
+ pricePeriod = input.pricePeriod,
+ pricePeriodRaw = input.pricePeriodRaw,
+ priceLabel = input.priceLabel,
+ priceValue = input.priceValue,
+ priceValueRaw = input.priceValueRaw,
+ currency = input.currency,
+ featured = input.featured,
+ location = input.location,
+ area = input.area,
+ poa = input.poa,
+ reraPermit = input.reraPermit,
+ bathrooms = input.bathrooms,
+ bedrooms = input.bedrooms,
+ dateInsert = input.dateInsert,
+ dateUpdate = input.dateUpdate,
+ agentName = input.agentName,
+ brokerName = input.brokerName,
+ agentLicense = input.agentLicense,
+ locationId = input.locationId,
+ hideLocation = input.hideLocation,
+
+ // Maps BrokerEntity
+ broker =
+ MapperFactory.createMapper().map(input.broker),
+ // Maps List
+ amenities = input.amenities,
+ amenitiesKeys = input.amenitiesKeys,
+
+ latitude = input.latitude,
+ longitude = input.longitude,
+ premium = input.premium,
+ livingrooms = input.livingrooms,
+ verified = input.verified,
+
+ // Maps List
+ gallery = input.gallery,
+ phone = input.phone,
+
+ // Maps List
+ leadEmailReceivers = input.leadEmailReceivers,
+ reference = input.reference,
+ )
+ }
+ }
+}
diff --git a/libraries/domain/src/main/java/com/smarttoolfactory/domain/usecase/GetPropertiesUseCaseFlow.kt b/libraries/domain/src/main/java/com/smarttoolfactory/domain/usecase/GetPropertiesUseCaseFlow.kt
index fbb4291..dd57e47 100644
--- a/libraries/domain/src/main/java/com/smarttoolfactory/domain/usecase/GetPropertiesUseCaseFlow.kt
+++ b/libraries/domain/src/main/java/com/smarttoolfactory/domain/usecase/GetPropertiesUseCaseFlow.kt
@@ -109,49 +109,7 @@ class GetPropertiesUseCaseFlow @Inject constructor(
data.forEachIndexed { index, propertyEntity ->
propertyEntity.insertOrder = index
}
-
- data
- }
- } else {
- it
- }
- }
- .flowOn(dispatcherProvider.ioDispatcher)
- .catch { throwable ->
- emitAll(flowOf(listOf()))
- }
- .map {
- toPropertyListOrError(it)
- }
-
-// return flow { emit((repository.getSortOrderKey() ?: ORDER_BY_NONE) == orderBy) }
-// .flatMapConcat { sameFilter ->
-//
-// if (sameFilter) {
-// getOfflineFirstPropertyListFlow()
-// } else {
-// getPropertiesOfflineLast(orderBy)
-// }
-// }
- }
-
- private fun getOfflineFirstPropertyListFlow(): Flow> {
- return flow {
- emit(repository.getPropertyEntitiesFromLocal())
- }
- .catch { throwable ->
- emitAll(flowOf(listOf()))
- }
- .map {
- if (it.isEmpty()) {
- repository.run {
- val data = fetchEntitiesFromRemote()
- deletePropertyEntities()
-
- // 🔥 Add an insert order since we are not using Room's ORDER BY
- data.forEachIndexed { index, propertyEntity ->
- propertyEntity.insertOrder = index
- }
+ savePropertyEntities(data)
data
}
@@ -176,7 +134,10 @@ class GetPropertiesUseCaseFlow @Inject constructor(
}
}
- fun getCurrentSortKey(defaultKey: String = ORDER_BY_NONE): Flow {
+ /**
+ * Get current sort key from db
+ */
+ fun getCurrentSortKey(defaultKey: String = ORDER_BY_NONE): Flow {
return flow { emit(repository.getSortOrderKey()) }
.catch {
emitAll(flowOf(defaultKey))
diff --git a/libraries/domain/src/main/java/com/smarttoolfactory/domain/usecase/GetPropertiesUseCasePaged.kt b/libraries/domain/src/main/java/com/smarttoolfactory/domain/usecase/GetPropertiesUseCasePaged.kt
index 9eab3b5..d055228 100644
--- a/libraries/domain/src/main/java/com/smarttoolfactory/domain/usecase/GetPropertiesUseCasePaged.kt
+++ b/libraries/domain/src/main/java/com/smarttoolfactory/domain/usecase/GetPropertiesUseCasePaged.kt
@@ -1,70 +1,54 @@
package com.smarttoolfactory.domain.usecase
import com.smarttoolfactory.data.constant.ORDER_BY_NONE
-import com.smarttoolfactory.data.model.local.PropertyEntity
-import com.smarttoolfactory.data.repository.PropertyRepositoryCoroutines
+import com.smarttoolfactory.data.model.local.PagedPropertyEntity
+import com.smarttoolfactory.data.repository.PagedPropertyRepository
import com.smarttoolfactory.domain.dispatcher.UseCaseDispatchers
import com.smarttoolfactory.domain.error.EmptyDataException
-import com.smarttoolfactory.domain.mapper.PropertyEntityToItemListMapper
+import com.smarttoolfactory.domain.mapper.PagedEntityToItemListMapper
import com.smarttoolfactory.domain.model.PropertyItem
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.emitAll
+import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
-/**
- * UseCase for getting UI Item list with offline first or offline last approach.
- *
- * * *Offline-first* first source to look for data is local data source, or database,
- * if database is empty or if caching is used it's expiry date is over, looks for remote source
- * for data. If both of the sources are empty, either return empty list or error to notify UI.
- *
- * This approach is good for when user is offline or have no internet connection, additional logic
- * can be added to check if user is offline to not deleted cached data.
- *
- * * *Offline-last* always checks remote data source for data and applies to database or offline
- * source as last resort. Offline-last is used especially when user refreshes data with a UI
- * element to get the latest data or new data is always first preference.
- */
class GetPropertiesUseCasePaged @Inject constructor(
- private val repository: PropertyRepositoryCoroutines,
- private val mapper: PropertyEntityToItemListMapper,
+ private val repository: PagedPropertyRepository,
+ private val mapper: PagedEntityToItemListMapper,
private val dispatcherProvider: UseCaseDispatchers
) {
/**
- * Function to retrieve data from repository with offline-last which checks
- * REMOTE data source first.
- *
- * * Check out Remote Source first
- * * If empty data or null returned throw empty set exception
- * * If error occurred while fetching data from remote: Try to fetch data from db
- * * If data is fetched from remote source: delete old data, save new data and return new data
- * * If both network and db don't have any data throw empty set exception
+ * Fetches data from REMOTE source with PAGINATION
+ * using specified parameter parameter. Page number i s kept in [PagedPropertyRepository].
*
+ * * Since [PagedPropertyEntity] insert order is not kept
+ * it's required to add an index for insertion or to db to get valid data which is ordered
+ * in server side
*/
- fun getPagedOfflineLast(page: Int, orderBy: String): Flow> {
- return flow { emit(repository.fetchEntitiesFromRemote(orderBy)) }
+ fun getPagedOfflineLast(orderBy: String): Flow> {
+
+ return flow { emit(repository.fetchEntitiesFromRemoteByPage(orderBy)) }
.map {
if (it.isNullOrEmpty()) {
throw EmptyDataException("No Data is available in Remote source!")
} else {
repository.run {
- if (page == 1) {
- deletePropertyEntities()
- }
+// deletePropertyEntities()
+
+ val propertyCount = repository.getPropertyCount()
// 🔥 Add an insert order since we are not using Room's ORDER BY
it.forEachIndexed { index, propertyEntity ->
- propertyEntity.insertOrder = index
+ propertyEntity.insertOrder = propertyCount + index
}
savePropertyEntities(it)
-
getPropertyEntitiesFromLocal()
}
}
@@ -79,7 +63,19 @@ class GetPropertiesUseCasePaged @Inject constructor(
}
}
- private fun toPropertyListOrError(entityList: List): List {
+ /**
+ * Resets page number to 1, clears previous data from database and fetches new data
+ * from REMOTE source with given param
+ */
+ fun refreshData(orderBy: String): Flow> {
+ return flow { emit(repository.resetPageCount()) }
+ .flatMapConcat {
+ repository.deletePropertyEntities()
+ getPagedOfflineLast(orderBy)
+ }
+ }
+
+ private fun toPropertyListOrError(entityList: List): List {
return if (!entityList.isNullOrEmpty()) {
mapper.map(entityList)
} else {
@@ -87,7 +83,10 @@ class GetPropertiesUseCasePaged @Inject constructor(
}
}
- fun getCurrentSortKey(defaultKey: String = ORDER_BY_NONE): Flow {
+ /**
+ * Get current sort key from db
+ */
+ fun getCurrentSortKey(defaultKey: String = ORDER_BY_NONE): Flow {
return flow { emit(repository.getSortOrderKey()) }
.catch {
emitAll(flowOf(defaultKey))
diff --git a/libraries/domain/src/main/java/com/smarttoolfactory/domain/usecase/GetPropertiesUseCaseRxJava3.kt b/libraries/domain/src/main/java/com/smarttoolfactory/domain/usecase/GetPropertiesUseCaseRxJava3.kt
index 23ffc9a..5bba88d 100644
--- a/libraries/domain/src/main/java/com/smarttoolfactory/domain/usecase/GetPropertiesUseCaseRxJava3.kt
+++ b/libraries/domain/src/main/java/com/smarttoolfactory/domain/usecase/GetPropertiesUseCaseRxJava3.kt
@@ -121,7 +121,10 @@ class GetPropertiesUseCaseRxJava3 @Inject constructor(
)
}
- fun getCurrentSortKey(defaultKey: String = ORDER_BY_NONE): Single {
+ /**
+ * Get current sort key from db
+ */
+ fun getCurrentSortKey(defaultKey: String = ORDER_BY_NONE): Single {
return repository.getSortOrderKey()
.onErrorResumeNext {
Single.just(defaultKey)
diff --git a/libraries/domain/src/test/java/com/smarttoolfactory/domain/mapper/PropertyEntityToItemListMapperTest.kt b/libraries/domain/src/test/java/com/smarttoolfactory/domain/mapper/PropertyEntityToItemListMapperTest.kt
index 745294e..1c2af4a 100644
--- a/libraries/domain/src/test/java/com/smarttoolfactory/domain/mapper/PropertyEntityToItemListMapperTest.kt
+++ b/libraries/domain/src/test/java/com/smarttoolfactory/domain/mapper/PropertyEntityToItemListMapperTest.kt
@@ -1,13 +1,11 @@
package com.smarttoolfactory.domain.mapper
-import com.google.common.truth.Truth
import com.smarttoolfactory.data.mapper.MapperFactory
import com.smarttoolfactory.data.mapper.PropertyDTOtoEntityListMapper
import com.smarttoolfactory.data.model.remote.PropertyResponse
import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH
import com.smarttoolfactory.test_utils.util.convertToObjectFromJson
import com.smarttoolfactory.test_utils.util.getResourceAsText
-import org.junit.jupiter.api.Test
class PropertyEntityToItemListMapperTest {
@@ -22,21 +20,21 @@ class PropertyEntityToItemListMapperTest {
.map(propertyDTOList)
}
- @Test
- fun `Given a valid propertyEntity list, should map to propertyItem list`() {
-
- // GIVEN
- val mapper =
- MapperFactory.createListMapper()
-
- // WHEN
- val actual = mapper.map(entityList)
-
- // THEN
- actual.forEachIndexed { index, propertyItem ->
- Truth.assertThat(propertyItem.id).isEqualTo(entityList[index].id)
- Truth.assertThat(propertyItem.update).isEqualTo(entityList[index].update)
- Truth.assertThat(propertyItem.price).isEqualTo(entityList[index].price)
- }
- }
+// @Test
+// fun `Given a valid propertyEntity list, should map to propertyItem list`() {
+//
+// // GIVEN
+// val mapper =
+// MapperFactory.createListMapper()
+//
+// // WHEN
+// val actual = mapper.map(entityList)
+//
+// // THEN
+// actual.forEachIndexed { index, propertyItem ->
+// Truth.assertThat(propertyItem.id).isEqualTo(entityList[index].id)
+// Truth.assertThat(propertyItem.update).isEqualTo(entityList[index].update)
+// Truth.assertThat(propertyItem.price).isEqualTo(entityList[index].price)
+// }
+// }
}
diff --git a/libraries/domain/src/test/java/com/smarttoolfactory/domain/usecase/GetPropertiesUseCaseFlowTest.kt b/libraries/domain/src/test/java/com/smarttoolfactory/domain/usecase/GetPropertiesUseCaseFlowTest.kt
index 560503a..9412e7d 100644
--- a/libraries/domain/src/test/java/com/smarttoolfactory/domain/usecase/GetPropertiesUseCaseFlowTest.kt
+++ b/libraries/domain/src/test/java/com/smarttoolfactory/domain/usecase/GetPropertiesUseCaseFlowTest.kt
@@ -252,7 +252,6 @@ class GetPropertiesUseCaseFlowTest {
testCoroutineExtension.runBlockingTest {
// GIVEN
- coEvery { repository.getOrderFilter() } returns ORDER_BY_NONE
coEvery { repository.getPropertyEntitiesFromLocal() } returns entityList
coEvery { mapper.map(entityList) } returns itemList
// WHEN
@@ -268,7 +267,6 @@ class GetPropertiesUseCaseFlowTest {
.dispose()
coVerifySequence {
- repository.getOrderFilter()
repository.getPropertyEntitiesFromLocal()
mapper.map(entityList)
}
@@ -279,7 +277,6 @@ class GetPropertiesUseCaseFlowTest {
testCoroutineExtension.runBlockingTest {
// GIVEN
- coEvery { repository.getOrderFilter() } returns ORDER_BY_NONE
coEvery { repository.getPropertyEntitiesFromLocal() } returns listOf()
coEvery { repository.fetchEntitiesFromRemote() } returns entityList
coEvery { repository.deletePropertyEntities() } just runs
@@ -298,7 +295,6 @@ class GetPropertiesUseCaseFlowTest {
.dispose()
coVerifySequence {
- repository.getOrderFilter()
repository.getPropertyEntitiesFromLocal()
repository.fetchEntitiesFromRemote()
repository.deletePropertyEntities()
@@ -312,7 +308,6 @@ class GetPropertiesUseCaseFlowTest {
testCoroutineExtension.runBlockingTest {
// GIVEN
- coEvery { repository.getOrderFilter() } returns ORDER_BY_NONE
coEvery {
repository.getPropertyEntitiesFromLocal()
} throws SQLException("Database Exception")
@@ -334,7 +329,6 @@ class GetPropertiesUseCaseFlowTest {
.dispose()
coVerifySequence {
- repository.getOrderFilter()
repository.getPropertyEntitiesFromLocal()
repository.fetchEntitiesFromRemote()
repository.deletePropertyEntities()
@@ -348,7 +342,6 @@ class GetPropertiesUseCaseFlowTest {
testCoroutineExtension.runBlockingTest {
// GIVEN
- coEvery { repository.getOrderFilter() } returns ORDER_BY_NONE
coEvery { repository.getPropertyEntitiesFromLocal() } returns listOf()
coEvery {
@@ -367,7 +360,6 @@ class GetPropertiesUseCaseFlowTest {
.dispose()
coVerifySequence {
- repository.getOrderFilter()
repository.getPropertyEntitiesFromLocal()
repository.fetchEntitiesFromRemote()
}
diff --git a/libraries/domain/src/test/java/com/smarttoolfactory/domain/usecase/GetPropertiesUseCaseRxJava3Test.kt b/libraries/domain/src/test/java/com/smarttoolfactory/domain/usecase/GetPropertiesUseCaseRxJava3Test.kt
index 01d7b77..c7b931c 100644
--- a/libraries/domain/src/test/java/com/smarttoolfactory/domain/usecase/GetPropertiesUseCaseRxJava3Test.kt
+++ b/libraries/domain/src/test/java/com/smarttoolfactory/domain/usecase/GetPropertiesUseCaseRxJava3Test.kt
@@ -211,7 +211,6 @@ class GetPropertiesUseCaseRxJava3Test {
fun `given Local source has data, should return data`() {
// GIVEN
- every { repository.getOrderFilter() } returns ORDER_BY_NONE
every { repository.getPropertyEntitiesFromLocal() } returns Single.just(entityList)
every { mapper.map(entityList) } returns itemList
@@ -228,7 +227,6 @@ class GetPropertiesUseCaseRxJava3Test {
.dispose()
verifySequence {
- repository.getOrderFilter()
repository.getPropertyEntitiesFromLocal()
mapper.map(entityList)
}
@@ -238,7 +236,6 @@ class GetPropertiesUseCaseRxJava3Test {
fun `given Local source is empty, should fetch data from Remote`() {
// GIVEN
- every { repository.getOrderFilter() } returns ORDER_BY_NONE
every { repository.getPropertyEntitiesFromLocal() } returns Single.just(listOf())
every { repository.fetchEntitiesFromRemote() } returns Single.just(entityList)
every { repository.deletePropertyEntities() } returns Completable.complete()
@@ -260,7 +257,6 @@ class GetPropertiesUseCaseRxJava3Test {
.dispose()
verifySequence {
- repository.getOrderFilter()
repository.getPropertyEntitiesFromLocal()
repository.fetchEntitiesFromRemote()
repository.deletePropertyEntities()
@@ -273,7 +269,6 @@ class GetPropertiesUseCaseRxJava3Test {
fun `given exception returned from Local source should fetch data from Remote`() {
// GIVEN
- every { repository.getOrderFilter() } returns ORDER_BY_NONE
every {
repository.getPropertyEntitiesFromLocal()
} returns Single.error(SQLException("Database Exception"))
@@ -297,7 +292,6 @@ class GetPropertiesUseCaseRxJava3Test {
.dispose()
verifySequence {
- repository.getOrderFilter()
repository.getPropertyEntitiesFromLocal()
repository.fetchEntitiesFromRemote()
repository.deletePropertyEntities()
@@ -310,7 +304,6 @@ class GetPropertiesUseCaseRxJava3Test {
fun `given Local source is empty and Remote returned error, should throw exception`() {
// GIVEN
- every { repository.getOrderFilter() } returns ORDER_BY_NONE
every { repository.getPropertyEntitiesFromLocal() } returns Single.just(listOf())
every {
@@ -329,7 +322,6 @@ class GetPropertiesUseCaseRxJava3Test {
.dispose()
verifySequence {
- repository.getOrderFilter()
repository.getPropertyEntitiesFromLocal()
repository.fetchEntitiesFromRemote()
}
diff --git a/screenshots/favorites.png b/screenshots/favorites.png
new file mode 100644
index 0000000..8d56f9e
Binary files /dev/null and b/screenshots/favorites.png differ
diff --git a/screenshots/modules.png b/screenshots/modules.png
new file mode 100644
index 0000000..b7c4595
Binary files /dev/null and b/screenshots/modules.png differ
diff --git a/screenshots/property_flow.png b/screenshots/property_flow.png
new file mode 100644
index 0000000..29fbdbd
Binary files /dev/null and b/screenshots/property_flow.png differ
diff --git a/screenshots/property_pagination.png b/screenshots/property_pagination.png
new file mode 100644
index 0000000..77d6457
Binary files /dev/null and b/screenshots/property_pagination.png differ
diff --git a/screenshots/property_rxjava3.png b/screenshots/property_rxjava3.png
new file mode 100644
index 0000000..92f5dfe
Binary files /dev/null and b/screenshots/property_rxjava3.png differ