Setup data layer
This commit is contained in:
@@ -36,6 +36,8 @@ dependencies {
|
||||
api(platform(Libs.kotlin_coroutine_bom))
|
||||
api(Libs.kotlin_coroutine_core)
|
||||
|
||||
api(Libs.ktor_client_android)
|
||||
|
||||
testImplementation(Libs.junit_jupiter_api)
|
||||
testImplementation(Libs.junit_jupiter_engine)
|
||||
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.ericampire.android.androidstudycase.data.datasource.animator
|
||||
|
||||
import com.ericampire.android.androidstudycase.domain.entity.Animator
|
||||
|
||||
|
||||
interface AnimatorDataSource {
|
||||
suspend fun findAll(): List<Animator>
|
||||
suspend fun save(animator: Animator)
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.ericampire.android.androidstudycase.data.datasource.animator
|
||||
|
||||
import com.ericampire.android.androidstudycase.data.room.AnimatorDao
|
||||
import com.ericampire.android.androidstudycase.domain.entity.Animator
|
||||
import javax.inject.Inject
|
||||
|
||||
class LocalAnimatorDataSource @Inject constructor(
|
||||
private val animatorDao: AnimatorDao
|
||||
) : AnimatorDataSource {
|
||||
|
||||
override suspend fun findAll(): List<Animator> {
|
||||
return animatorDao.findAll()
|
||||
}
|
||||
|
||||
override suspend fun save(animator: Animator) {
|
||||
animatorDao.save(animator)
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.ericampire.android.androidstudycase.data.datasource.animator
|
||||
|
||||
import com.ericampire.android.androidstudycase.domain.entity.Animator
|
||||
import com.ericampire.android.androidstudycase.domain.entity.AnimatorApiResponse
|
||||
import com.ericampire.android.androidstudycase.util.ApiUrl
|
||||
import com.ericampire.android.androidstudycase.util.Result
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.request.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import javax.inject.Inject
|
||||
|
||||
class RemoteAnimatorDataSource @Inject constructor(
|
||||
private val httpClient: HttpClient
|
||||
) : AnimatorDataSource {
|
||||
override suspend fun findAll(): List<Animator> {
|
||||
val data = httpClient.get<AnimatorApiResponse>(ApiUrl.Animator.featured)
|
||||
return data.animatorAnimatorData.featuredAnimators.results
|
||||
}
|
||||
|
||||
override suspend fun save(animator: Animator) {
|
||||
TODO("Save animator online, not supported by the API")
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.ericampire.android.androidstudycase.data.datasource.blog
|
||||
|
||||
|
||||
import com.ericampire.android.androidstudycase.domain.entity.Blog
|
||||
|
||||
interface BlogDataSource {
|
||||
suspend fun findAll(): List<Blog>
|
||||
suspend fun save(blog: Blog)
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.ericampire.android.androidstudycase.data.datasource.blog
|
||||
|
||||
import com.ericampire.android.androidstudycase.data.room.BlogDao
|
||||
import com.ericampire.android.androidstudycase.domain.entity.Blog
|
||||
import javax.inject.Inject
|
||||
|
||||
class LocalBlogDataSource @Inject constructor(
|
||||
private val dao: BlogDao
|
||||
) : BlogDataSource {
|
||||
override suspend fun findAll(): List<Blog> {
|
||||
return dao.findAll()
|
||||
}
|
||||
|
||||
override suspend fun save(blog: Blog) {
|
||||
dao.save(blog)
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.ericampire.android.androidstudycase.data.datasource.blog
|
||||
|
||||
import com.ericampire.android.androidstudycase.domain.entity.Blog
|
||||
import com.ericampire.android.androidstudycase.domain.entity.BlogApiResponse
|
||||
import com.ericampire.android.androidstudycase.util.ApiUrl
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.request.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class RemoteBlogDataSource @Inject constructor(
|
||||
private val httpClient: HttpClient
|
||||
) : BlogDataSource {
|
||||
override suspend fun findAll(): List<Blog> {
|
||||
val data = httpClient.get<BlogApiResponse>(ApiUrl.Blog.latest)
|
||||
return data.blogBlogData.blogPage.blogs
|
||||
}
|
||||
|
||||
override suspend fun save(blog: Blog) {
|
||||
TODO("Not supported yet by the API")
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.ericampire.android.androidstudycase.data.datasource.lottiefiles
|
||||
|
||||
import com.ericampire.android.androidstudycase.data.room.LottieFilesDao
|
||||
import com.ericampire.android.androidstudycase.domain.entity.Lottiefile
|
||||
import javax.inject.Inject
|
||||
|
||||
class LocalLottieFileDataSource @Inject constructor(
|
||||
private val lottieFileDao: LottieFilesDao
|
||||
) : LottieFileDataSource {
|
||||
override suspend fun findRecent(): List<Lottiefile> {
|
||||
return lottieFileDao.findRecent()
|
||||
}
|
||||
|
||||
override suspend fun findPopular(): List<Lottiefile> {
|
||||
return lottieFileDao.findPopular()
|
||||
}
|
||||
|
||||
override suspend fun findFeatured(): List<Lottiefile> {
|
||||
return lottieFileDao.findFeatured()
|
||||
}
|
||||
|
||||
override suspend fun save(lottiefile: Lottiefile) {
|
||||
lottieFileDao.save(lottiefile)
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.ericampire.android.androidstudycase.data.datasource.lottiefiles
|
||||
|
||||
import com.ericampire.android.androidstudycase.domain.entity.Blog
|
||||
import com.ericampire.android.androidstudycase.domain.entity.Lottiefile
|
||||
|
||||
interface LottieFileDataSource {
|
||||
suspend fun findRecent(): List<Lottiefile>
|
||||
suspend fun findPopular(): List<Lottiefile>
|
||||
suspend fun findFeatured(): List<Lottiefile>
|
||||
suspend fun save(lottiefile: Lottiefile)
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.ericampire.android.androidstudycase.data.datasource.lottiefiles
|
||||
|
||||
import com.ericampire.android.androidstudycase.domain.entity.LottieFilesApiResponse
|
||||
import com.ericampire.android.androidstudycase.domain.entity.Lottiefile
|
||||
import com.ericampire.android.androidstudycase.util.ApiUrl
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.request.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class RemoteLottieFileDataSource @Inject constructor(
|
||||
private val httpClient: HttpClient
|
||||
) : LottieFileDataSource {
|
||||
|
||||
private suspend fun find(url: String): List<Lottiefile> {
|
||||
val data = httpClient.get<LottieFilesApiResponse>(url)
|
||||
return data.lottieFilesLottieFilesData.page.results
|
||||
}
|
||||
|
||||
override suspend fun findRecent(): List<Lottiefile> {
|
||||
return find(ApiUrl.LottieFile.recent)
|
||||
}
|
||||
|
||||
override suspend fun findPopular(): List<Lottiefile> {
|
||||
return find(ApiUrl.LottieFile.popular)
|
||||
}
|
||||
|
||||
override suspend fun findFeatured(): List<Lottiefile> {
|
||||
return find(ApiUrl.LottieFile.featured)
|
||||
}
|
||||
|
||||
override suspend fun save(lottiefile: Lottiefile) {
|
||||
TODO("Not supported by the API")
|
||||
}
|
||||
}
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
package com.ericampire.android.androidstudycase.data.datasource.user
|
||||
|
||||
|
||||
interface UserDataSource {
|
||||
}
|
||||
+79
@@ -1,6 +1,85 @@
|
||||
package com.ericampire.android.androidstudycase.data.di
|
||||
|
||||
import com.ericampire.android.androidstudycase.data.datasource.animator.AnimatorDataSource
|
||||
import com.ericampire.android.androidstudycase.data.datasource.animator.LocalAnimatorDataSource
|
||||
import com.ericampire.android.androidstudycase.data.datasource.animator.RemoteAnimatorDataSource
|
||||
import com.ericampire.android.androidstudycase.data.datasource.blog.BlogDataSource
|
||||
import com.ericampire.android.androidstudycase.data.datasource.blog.LocalBlogDataSource
|
||||
import com.ericampire.android.androidstudycase.data.datasource.blog.RemoteBlogDataSource
|
||||
import com.ericampire.android.androidstudycase.data.datasource.lottiefiles.LocalLottieFileDataSource
|
||||
import com.ericampire.android.androidstudycase.data.datasource.lottiefiles.LottieFileDataSource
|
||||
import com.ericampire.android.androidstudycase.data.datasource.lottiefiles.RemoteLottieFileDataSource
|
||||
import com.ericampire.android.androidstudycase.data.room.AnimatorDao
|
||||
import com.ericampire.android.androidstudycase.data.room.BlogDao
|
||||
import com.ericampire.android.androidstudycase.data.room.LottieFilesDao
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.engine.android.*
|
||||
import io.ktor.client.features.json.*
|
||||
import io.ktor.client.features.json.serializer.*
|
||||
import kotlinx.serialization.json.Json as KotlinJson
|
||||
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object DataSourceModule {
|
||||
|
||||
@Provides
|
||||
fun provideHttpClient(): HttpClient {
|
||||
return HttpClient(Android) {
|
||||
install(JsonFeature) {
|
||||
val jsonSetup = KotlinJson {
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
prettyPrint = true
|
||||
}
|
||||
serializer = KotlinxSerializer(jsonSetup)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideLocalAnimatorDataSource(
|
||||
animatorDao: AnimatorDao
|
||||
): AnimatorDataSource {
|
||||
return LocalAnimatorDataSource(animatorDao)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideRemoteBlogDataSource(
|
||||
httpClient: HttpClient
|
||||
): BlogDataSource {
|
||||
return RemoteBlogDataSource(httpClient)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideLocalBlogDataSource(
|
||||
dao: BlogDao
|
||||
): BlogDataSource {
|
||||
return LocalBlogDataSource(dao)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideRemoteAnimatorDataSource(
|
||||
httpClient: HttpClient
|
||||
): AnimatorDataSource {
|
||||
return RemoteAnimatorDataSource(httpClient)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideRemoteLottieFileDataSource(
|
||||
httpClient: HttpClient
|
||||
): LottieFileDataSource {
|
||||
return RemoteLottieFileDataSource(httpClient)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideLocalLottieFileDataSource(
|
||||
dao: LottieFilesDao
|
||||
): LottieFileDataSource {
|
||||
return LocalLottieFileDataSource(dao)
|
||||
}
|
||||
}
|
||||
+52
@@ -1,6 +1,58 @@
|
||||
package com.ericampire.android.androidstudycase.data.di
|
||||
|
||||
import com.ericampire.android.androidstudycase.data.datasource.animator.AnimatorDataSource
|
||||
import com.ericampire.android.androidstudycase.data.datasource.animator.LocalAnimatorDataSource
|
||||
import com.ericampire.android.androidstudycase.data.datasource.animator.RemoteAnimatorDataSource
|
||||
import com.ericampire.android.androidstudycase.data.datasource.blog.LocalBlogDataSource
|
||||
import com.ericampire.android.androidstudycase.data.datasource.blog.RemoteBlogDataSource
|
||||
import com.ericampire.android.androidstudycase.data.datasource.lottiefiles.LocalLottieFileDataSource
|
||||
import com.ericampire.android.androidstudycase.data.datasource.lottiefiles.RemoteLottieFileDataSource
|
||||
import com.ericampire.android.androidstudycase.data.repository.AnimatorRepositoryImpl
|
||||
import com.ericampire.android.androidstudycase.data.repository.BlogRepositoryImpl
|
||||
import com.ericampire.android.androidstudycase.data.repository.LottieFileRepositoryImpl
|
||||
import com.ericampire.android.androidstudycase.domain.repository.AnimatorRepository
|
||||
import com.ericampire.android.androidstudycase.domain.repository.BlogRepository
|
||||
import com.ericampire.android.androidstudycase.domain.repository.LottieFileRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object RepositoryModule {
|
||||
|
||||
@Provides
|
||||
fun provideAnimatorRepository(
|
||||
localDataSource: LocalAnimatorDataSource,
|
||||
remoteDataSource: RemoteAnimatorDataSource,
|
||||
) : AnimatorRepository {
|
||||
return AnimatorRepositoryImpl(
|
||||
localDataSource = localDataSource,
|
||||
remoteDataSource = remoteDataSource
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideLottieFileRepository(
|
||||
localDataSource: LocalLottieFileDataSource,
|
||||
remoteDataSource: RemoteLottieFileDataSource,
|
||||
) : LottieFileRepository {
|
||||
return LottieFileRepositoryImpl(
|
||||
localDataSource = localDataSource,
|
||||
remoteDataSource = remoteDataSource
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideBlogRepository(
|
||||
localDataSource: LocalBlogDataSource,
|
||||
remoteDataSource: RemoteBlogDataSource,
|
||||
) : BlogRepository {
|
||||
return BlogRepositoryImpl(
|
||||
localDataSource = localDataSource,
|
||||
remoteDataSource = remoteDataSource
|
||||
)
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.ericampire.android.androidstudycase.data.repository
|
||||
|
||||
import com.ericampire.android.androidstudycase.data.datasource.animator.AnimatorDataSource
|
||||
import com.ericampire.android.androidstudycase.domain.entity.Animator
|
||||
import com.ericampire.android.androidstudycase.domain.repository.AnimatorRepository
|
||||
import com.ericampire.android.androidstudycase.util.Result
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class AnimatorRepositoryImpl @Inject constructor(
|
||||
private val remoteDataSource: AnimatorDataSource,
|
||||
private val localDataSource: AnimatorDataSource,
|
||||
) : AnimatorRepository {
|
||||
override fun findAll(): Flow<Result<List<Animator>>> {
|
||||
return flow {
|
||||
try {
|
||||
refreshData()
|
||||
emit(Result.Success(localDataSource.findAll()))
|
||||
} catch (e: Exception) {
|
||||
emit(Result.Error(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun refreshData() {
|
||||
remoteDataSource.findAll().forEach {
|
||||
localDataSource.save(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.ericampire.android.androidstudycase.data.repository
|
||||
|
||||
import com.ericampire.android.androidstudycase.data.datasource.animator.AnimatorDataSource
|
||||
import com.ericampire.android.androidstudycase.data.datasource.blog.BlogDataSource
|
||||
import com.ericampire.android.androidstudycase.domain.entity.Animator
|
||||
import com.ericampire.android.androidstudycase.domain.entity.Blog
|
||||
import com.ericampire.android.androidstudycase.domain.repository.AnimatorRepository
|
||||
import com.ericampire.android.androidstudycase.domain.repository.BlogRepository
|
||||
import com.ericampire.android.androidstudycase.util.Result
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import javax.inject.Inject
|
||||
|
||||
class BlogRepositoryImpl @Inject constructor(
|
||||
private val remoteDataSource: BlogDataSource,
|
||||
private val localDataSource: BlogDataSource,
|
||||
) : BlogRepository {
|
||||
override fun findAll(): Flow<Result<List<Blog>>> {
|
||||
return flow {
|
||||
try {
|
||||
refreshData()
|
||||
emit(Result.Success(localDataSource.findAll()))
|
||||
} catch (e: Exception) {
|
||||
emit(Result.Error(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun refreshData() {
|
||||
remoteDataSource.findAll().forEach {
|
||||
localDataSource.save(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package com.ericampire.android.androidstudycase.data.repository
|
||||
|
||||
import com.ericampire.android.androidstudycase.data.datasource.lottiefiles.LottieFileDataSource
|
||||
import com.ericampire.android.androidstudycase.domain.entity.Lottiefile
|
||||
import com.ericampire.android.androidstudycase.domain.repository.LottieFileRepository
|
||||
import com.ericampire.android.androidstudycase.util.Result
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import javax.inject.Inject
|
||||
|
||||
class LottieFileRepositoryImpl @Inject constructor(
|
||||
private val localDataSource: LottieFileDataSource,
|
||||
private val remoteDataSource: LottieFileDataSource,
|
||||
) : LottieFileRepository {
|
||||
override suspend fun findRecent(): Flow<Result<List<Lottiefile>>> {
|
||||
return flow {
|
||||
try {
|
||||
val recentFiles = remoteDataSource.findRecent()
|
||||
refreshData(recentFiles)
|
||||
emit(Result.Success(localDataSource.findRecent()))
|
||||
} catch (e: Exception) {
|
||||
emit(Result.Error(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun refreshData(data: List<Lottiefile>) {
|
||||
data.forEach {
|
||||
localDataSource.save(it)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun findPopular(): Flow<Result<List<Lottiefile>>> {
|
||||
return flow {
|
||||
try {
|
||||
val recentFiles = remoteDataSource.findPopular()
|
||||
refreshData(recentFiles)
|
||||
emit(Result.Success(localDataSource.findPopular()))
|
||||
} catch (e: Exception) {
|
||||
emit(Result.Error(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun findFeatured(): Flow<Result<List<Lottiefile>>> {
|
||||
return flow {
|
||||
try {
|
||||
val recentFiles = remoteDataSource.findFeatured()
|
||||
refreshData(recentFiles)
|
||||
emit(Result.Success(localDataSource.findFeatured()))
|
||||
} catch (e: Exception) {
|
||||
emit(Result.Error(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
package com.ericampire.android.androidstudycase.data.repository
|
||||
|
||||
import com.ericampire.android.androidstudycase.data.datasource.user.UserDataSource
|
||||
import com.ericampire.android.androidstudycase.domain.repository.UserRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class UserRepositoryImpl @Inject constructor(
|
||||
private val dataSource: UserDataSource
|
||||
) : UserRepository {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.ericampire.android.androidstudycase.data.room
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.ericampire.android.androidstudycase.domain.entity.Animator
|
||||
|
||||
@Dao
|
||||
interface AnimatorDao {
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun save(animator: Animator)
|
||||
|
||||
@Query("SELECT * FROM Animator")
|
||||
suspend fun findAll(): List<Animator>
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.ericampire.android.androidstudycase.data.room
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.ericampire.android.androidstudycase.domain.entity.Blog
|
||||
|
||||
@Dao
|
||||
interface BlogDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun save(blog: Blog)
|
||||
|
||||
@Query("SELECT * FROM Blog")
|
||||
suspend fun findAll(): List<Blog>
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.ericampire.android.androidstudycase.data.room
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.ericampire.android.androidstudycase.domain.entity.Lottiefile
|
||||
|
||||
@Dao
|
||||
interface LottieFilesDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun save(lottiefile: Lottiefile)
|
||||
|
||||
@Query("SELECT * FROM Lottiefile")
|
||||
suspend fun findPopular(): List<Lottiefile>
|
||||
|
||||
@Query("SELECT * FROM Lottiefile")
|
||||
suspend fun findFeatured(): List<Lottiefile>
|
||||
|
||||
@Query("SELECT * FROM Lottiefile")
|
||||
suspend fun findRecent(): List<Lottiefile>
|
||||
}
|
||||
Reference in New Issue
Block a user