11package com.patika.getir_lite.data
22
3- import android.util.Log
43import com.patika.getir_lite.data.di.AppDispatchers.IO
54import com.patika.getir_lite.data.di.Dispatcher
65import com.patika.getir_lite.data.local.ProductDao
6+ import com.patika.getir_lite.data.local.model.BasketWithProducts
77import com.patika.getir_lite.data.local.model.OrderEntity
88import com.patika.getir_lite.data.local.model.OrderStatus
99import com.patika.getir_lite.data.local.model.ProductEntity
10+ import com.patika.getir_lite.data.local.model.StatusEntity
1011import com.patika.getir_lite.data.local.model.toDomainModel
1112import com.patika.getir_lite.data.remote.RemoteRepository
1213import com.patika.getir_lite.data.remote.model.ProductDto
1314import com.patika.getir_lite.data.remote.model.SuggestedProductDto
1415import com.patika.getir_lite.data.remote.model.toProductEntity
1516import com.patika.getir_lite.model.BaseResponse
16- import com.patika.getir_lite.model.BasketWithProducts
1717import com.patika.getir_lite.model.CountType
1818import com.patika.getir_lite.model.CountType.MINUS_ONE
1919import com.patika.getir_lite.model.CountType.PLUS_ONE
@@ -24,56 +24,80 @@ import com.patika.getir_lite.util.TopLevelException
2424import kotlinx.coroutines.CoroutineDispatcher
2525import kotlinx.coroutines.async
2626import kotlinx.coroutines.flow.Flow
27- import kotlinx.coroutines.flow.MutableStateFlow
28- import kotlinx.coroutines.flow.asStateFlow
2927import kotlinx.coroutines.flow.map
30- import kotlinx.coroutines.flow.update
28+ import kotlinx.coroutines.joinAll
29+ import kotlinx.coroutines.launch
3130import kotlinx.coroutines.withContext
3231import javax.inject.Inject
3332
33+ /* *
34+ * A data source class that handles fetching, caching, and retrieving products and orders from both local and remote sources.
35+ * This class serves as an implementation of the [ProductRepository] to provide an interface for data operations.
36+ *
37+ * @property remoteRepository The backend API repository used for fetching products from a remote server.
38+ * @property productDao The local DAO (Data Access Object) used for querying and updating the local database.
39+ * @property ioDispatcher A [CoroutineDispatcher] specifically for I/O operations to ensure database and network operations do not block the main thread.
40+ */
3441class ProductDataSource @Inject constructor(
3542 private val remoteRepository : RemoteRepository ,
3643 private val productDao : ProductDao ,
3744 @Dispatcher(IO ) private val ioDispatcher : CoroutineDispatcher
3845) : ProductRepository {
39-
40- private val _dataSyncResult = MutableStateFlow (listOf (DataSyncResult .IDLE ))
41- override val dataSyncResult = _dataSyncResult .asStateFlow()
42-
4346 override fun getProductsAsFlow (): Flow <List <ProductWithCount >> =
4447 productDao.getProductsWithCounts(ProductType .PRODUCT )
4548
4649 override fun getSuggestedProductsAsFlow (): Flow <List <ProductWithCount >> =
4750 productDao.getProductsWithCounts(ProductType .SUGGESTED_PRODUCT )
4851
49- override suspend fun syncWithRemote (): BaseResponse <Unit > {
50- return try {
51- withContext(ioDispatcher) {
52- val remoteProductsDeferred = async { getProducts() }
53- val remoteSuggestedProductsDeferred = async { getSuggestedProducts() }
54- val localProductsDeferred = async { productDao.getAllItems() }
55-
56- val remoteProducts = remoteProductsDeferred.await()
57- val remoteSuggestedProducts = remoteSuggestedProductsDeferred.await()
58- val localProduct = localProductsDeferred.await()
59-
60- if (localProduct.isEmpty()) {
61- productDao.insertOrder(OrderEntity (- 1 ))
62- if (remoteProducts is BaseResponse .Success ) {
63- saveDataToLocalFirstTime(remoteProducts.data)
64- _dataSyncResult .update { it + DataSyncResult .PRODUCT_SYNCED }
65- }
66-
67- if (remoteSuggestedProducts is BaseResponse .Success ) {
68- saveDataToLocalFirstTime(remoteSuggestedProducts.data)
69- _dataSyncResult .update { it + DataSyncResult .SUGGESTED_PRODUCT_SYNCED }
70- }
71- }
72-
73- BaseResponse .Success (Unit )
52+ override suspend fun fetchDataFromRemote (): BaseResponse <Unit > = try {
53+ withContext(ioDispatcher) {
54+ val dbStatus = productDao.getStatus().firstOrNull() ? : run {
55+ val id = productDao.insertStatus(StatusEntity ())
56+ StatusEntity (id = id)
57+ }
58+
59+ if (dbStatus.isProductLoaded && dbStatus.isSuggestedProductLoaded)
60+ return @withContext BaseResponse .Success (Unit )
61+
62+ val proJob = launch {
63+ if (! dbStatus.isProductLoaded) insertProductToDb()
64+ }
65+
66+ val suggestedProJob = launch {
67+ if (! dbStatus.isSuggestedProductLoaded) insertSuggestedProductToDb()
68+ }
69+
70+ joinAll(proJob, suggestedProJob)
71+
72+ BaseResponse .Success (Unit )
73+ }
74+ } catch (e: Exception ) {
75+ BaseResponse .Error (TopLevelException .GenericException (e.message))
76+ }
77+
78+ private suspend fun insertProductToDb () {
79+ when (val remoteProducts = getProducts()) {
80+ is BaseResponse .Error -> throw remoteProducts.exception
81+ BaseResponse .Loading -> Unit
82+ is BaseResponse .Success -> {
83+ productDao.insertProductsFirstTime(
84+ data = remoteProducts.data,
85+ productType = ProductType .PRODUCT
86+ )
87+ }
88+ }
89+ }
90+
91+ private suspend fun insertSuggestedProductToDb () {
92+ when (val remoteSuggestedProducts = getSuggestedProducts()) {
93+ is BaseResponse .Error -> throw remoteSuggestedProducts.exception
94+ BaseResponse .Loading -> Unit
95+ is BaseResponse .Success -> {
96+ productDao.insertProductsFirstTime(
97+ data = remoteSuggestedProducts.data,
98+ productType = ProductType .SUGGESTED_PRODUCT
99+ )
74100 }
75- } catch (e: Exception ) {
76- BaseResponse .Error (TopLevelException .GenericException (e.message))
77101 }
78102 }
79103
@@ -110,31 +134,26 @@ class ProductDataSource @Inject constructor(
110134 BaseResponse .Loading -> BaseResponse .Loading
111135 }
112136
113- private suspend fun saveDataToLocalFirstTime (products : List <ProductEntity >) {
114- productDao.insertProducts(products)
115- }
116-
117- override suspend fun updateItemCount (productId : Long , countType : CountType ) = try {
137+ override suspend fun updateItemCount (productId : Long , countType : CountType ) = runCatching {
118138 withContext(ioDispatcher) {
119- val getActiveOrder = productDao.getActiveOrder(OrderStatus .ON_BASKET )
120- val productPrice = productDao.getProductById(productId).price
121- val orderId = getActiveOrder?.id ? : run {
139+ val getActiveOrder = async { productDao.getActiveOrder(OrderStatus .ON_BASKET ) }
140+ val productPrice = async { productDao.getProductById(productId).price }
141+ val orderId = getActiveOrder.await() ?.id ? : run {
122142 val id = productDao.insertOrder(
123143 OrderEntity (orderStatus = OrderStatus .ON_BASKET )
124144 )
125145 id
126146 }
127147
128148 when (countType) {
129- PLUS_ONE -> productDao.addItemToBasket(productId, orderId, productPrice)
130- MINUS_ONE -> productDao.decrementItemCount(productId, orderId, productPrice)
149+ PLUS_ONE -> productDao.addItemToBasket(productId, orderId, productPrice.await() )
150+ MINUS_ONE -> productDao.decrementItemCount(productId, orderId, productPrice.await() )
131151 }
132152 }
133- } catch (e: Exception ) {
134- Log .e(" ProductDataSource" , " updateItemCount: $e " )
135- Unit
136153 }
137154
155+ override suspend fun getStatus (): List <StatusEntity ?> = productDao.getStatus()
156+
138157 override suspend fun clearBasket (): Boolean = try {
139158 withContext(ioDispatcher) {
140159 productDao.cancelOrder()
@@ -144,7 +163,3 @@ class ProductDataSource @Inject constructor(
144163 false
145164 }
146165}
147-
148- enum class DataSyncResult {
149- PRODUCT_SYNCED , SUGGESTED_PRODUCT_SYNCED , IDLE
150- }
0 commit comments