Setup data layer

This commit is contained in:
2021-09-04 03:21:28 +02:00
parent e9efc3f2ab
commit 356ad13003
20 changed files with 480 additions and 16 deletions
+2
View File
@@ -36,6 +36,8 @@ dependencies {
api(platform(Libs.kotlin_coroutine_bom)) api(platform(Libs.kotlin_coroutine_bom))
api(Libs.kotlin_coroutine_core) api(Libs.kotlin_coroutine_core)
api(Libs.ktor_client_android)
testImplementation(Libs.junit_jupiter_api) testImplementation(Libs.junit_jupiter_api)
testImplementation(Libs.junit_jupiter_engine) testImplementation(Libs.junit_jupiter_engine)
@@ -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)
}
@@ -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)
}
}
@@ -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")
}
}
@@ -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)
}
@@ -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)
}
}
@@ -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")
}
}
@@ -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)
}
}
@@ -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)
}
@@ -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")
}
}
@@ -1,5 +0,0 @@
package com.ericampire.android.androidstudycase.data.datasource.user
interface UserDataSource {
}
@@ -1,6 +1,85 @@
package com.ericampire.android.androidstudycase.data.di 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 { 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)
}
} }
@@ -1,6 +1,58 @@
package com.ericampire.android.androidstudycase.data.di 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 { 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
)
}
} }
@@ -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)
}
}
}
@@ -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)
}
}
}
@@ -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))
}
}
}
}
@@ -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>
}
@@ -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>
}