From 9ce33bba750e6d0b56957c805371b47b140fc317 Mon Sep 17 00:00:00 2001 From: Aditya Wasan Date: Sun, 31 Jan 2021 23:02:16 +0530 Subject: [PATCH 1/2] src: add isCacheReady to let users know cache status Signed-off-by: Aditya Wasan --- .../data/remote/LobstersPagingSource.kt | 9 +++-- .../lobsters/data/repo/LobstersRepository.kt | 36 ++++++++++++------- .../ui/viewmodel/LobstersViewModel.kt | 11 +++++- 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/dev/msfjarvis/lobsters/data/remote/LobstersPagingSource.kt b/app/src/main/java/dev/msfjarvis/lobsters/data/remote/LobstersPagingSource.kt index 77b48b65..619e39ed 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/data/remote/LobstersPagingSource.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/data/remote/LobstersPagingSource.kt @@ -1,18 +1,21 @@ package dev.msfjarvis.lobsters.data.remote import androidx.paging.PagingSource -import dev.msfjarvis.lobsters.data.api.LobstersApi import dev.msfjarvis.lobsters.data.local.LobstersPost +import dev.msfjarvis.lobsters.data.repo.LobstersRepository import javax.inject.Inject class LobstersPagingSource @Inject constructor( - private val lobstersApi: LobstersApi, + private val lobstersRepository: LobstersRepository, ) : PagingSource() { override suspend fun load(params: LoadParams): LoadResult { return try { val page = params.key ?: 1 - val posts = lobstersApi.getHottestPosts(page) + // Update cache before fetching a list. + // This is done to make sure that we can update the isSaved status of incoming posts. + lobstersRepository.updateCache() + val posts = lobstersRepository.fetchPosts(page) LoadResult.Page( data = posts, diff --git a/app/src/main/java/dev/msfjarvis/lobsters/data/repo/LobstersRepository.kt b/app/src/main/java/dev/msfjarvis/lobsters/data/repo/LobstersRepository.kt index c9450a21..86d75bb1 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/data/repo/LobstersRepository.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/data/repo/LobstersRepository.kt @@ -1,26 +1,22 @@ package dev.msfjarvis.lobsters.data.repo +import dev.msfjarvis.lobsters.data.api.LobstersApi import dev.msfjarvis.lobsters.data.local.LobstersPost import dev.msfjarvis.lobsters.database.LobstersDatabase -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.withContext import javax.inject.Inject -class LobstersRepository @Inject constructor(private val lobstersDatabase: LobstersDatabase) { +class LobstersRepository @Inject constructor( + private val lobstersApi: LobstersApi, + private val lobstersDatabase: LobstersDatabase, +) { private val savedPostsCache: MutableMap = mutableMapOf() - private val coroutineScope = CoroutineScope(Job() + Dispatchers.IO) - - init { - coroutineScope.launch { - getAllPosts().forEach { - savedPostsCache.putIfAbsent(it.short_id, it) - } - } - } + private val _isCacheReady = MutableStateFlow(false) + val isCacheReady = _isCacheReady.asStateFlow() fun isPostSaved(postId: String): Boolean { return savedPostsCache.containsKey(postId) @@ -34,6 +30,20 @@ class LobstersRepository @Inject constructor(private val lobstersDatabase: Lobst return savedPostsCache.values.toList() } + suspend fun fetchPosts(page: Int): List = withContext(Dispatchers.IO) { + return@withContext lobstersApi.getHottestPosts(page) + } + + suspend fun updateCache() { + if (_isCacheReady.value) return + val posts = getAllPosts() + + posts.forEach { + savedPostsCache.putIfAbsent(it.short_id, it) + } + _isCacheReady.value = true + } + private suspend fun getPost(postId: String): LobstersPost? = withContext(Dispatchers.IO) { return@withContext lobstersDatabase.postQueries.selectPost(postId).executeAsOneOrNull() } diff --git a/app/src/main/java/dev/msfjarvis/lobsters/ui/viewmodel/LobstersViewModel.kt b/app/src/main/java/dev/msfjarvis/lobsters/ui/viewmodel/LobstersViewModel.kt index 76ebbb25..290f4fde 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/ui/viewmodel/LobstersViewModel.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/ui/viewmodel/LobstersViewModel.kt @@ -11,6 +11,7 @@ import dev.msfjarvis.lobsters.data.remote.LobstersPagingSource import dev.msfjarvis.lobsters.data.repo.LobstersRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import javax.inject.Inject @@ -19,12 +20,20 @@ class LobstersViewModel @Inject constructor( private val lobstersRepository: LobstersRepository, private val pagingSource: LobstersPagingSource, ) : ViewModel() { - private val _savedPosts = MutableStateFlow(lobstersRepository.getAllPostsFromCache()) + private val _savedPosts = MutableStateFlow>(emptyList()) val savedPosts = _savedPosts.asStateFlow() val posts = Pager(PagingConfig(25)) { pagingSource }.flow.cachedIn(viewModelScope) + init { + viewModelScope.launch { + lobstersRepository.isCacheReady.onEach { + _savedPosts.value = lobstersRepository.getAllPostsFromCache() + } + } + } + fun toggleSave(post: LobstersPost) { viewModelScope.launch { val isSaved = lobstersRepository.isPostSaved(post.short_id) From 0fcf584b01b6221513a0fbd11981d280b2628ae8 Mon Sep 17 00:00:00 2001 From: Aditya Wasan Date: Fri, 5 Feb 2021 15:00:23 +0530 Subject: [PATCH 2/2] src: make LobstersRepository a singleton Earlier both LobstersViewModel and LobstersPagingSource were getting a different instance of LobstersRepository. This lead to cache issues where LobstersPagingSource filled the cache but it was not available to the LobstersViewModel. Signed-off-by: Aditya Wasan --- .../msfjarvis/lobsters/injection/ApiModule.kt | 4 ++-- .../lobsters/data/repo/LobstersRepository.kt | 5 ++-- .../lobsters/injection/RepositoryModule.kt | 24 +++++++++++++++++++ .../ui/viewmodel/LobstersViewModel.kt | 7 +++--- 4 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/dev/msfjarvis/lobsters/injection/RepositoryModule.kt diff --git a/api/src/main/java/dev/msfjarvis/lobsters/injection/ApiModule.kt b/api/src/main/java/dev/msfjarvis/lobsters/injection/ApiModule.kt index 92a002c0..061b2f09 100644 --- a/api/src/main/java/dev/msfjarvis/lobsters/injection/ApiModule.kt +++ b/api/src/main/java/dev/msfjarvis/lobsters/injection/ApiModule.kt @@ -5,7 +5,7 @@ import dagger.Lazy import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.components.ViewModelComponent +import dagger.hilt.components.SingletonComponent import dev.msfjarvis.lobsters.data.api.LobstersApi import okhttp3.OkHttpClient import retrofit2.Retrofit @@ -13,7 +13,7 @@ import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.create @Module -@InstallIn(ViewModelComponent::class) +@InstallIn(SingletonComponent::class) object ApiModule { @Provides diff --git a/app/src/main/java/dev/msfjarvis/lobsters/data/repo/LobstersRepository.kt b/app/src/main/java/dev/msfjarvis/lobsters/data/repo/LobstersRepository.kt index 86d75bb1..a9ff9f68 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/data/repo/LobstersRepository.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/data/repo/LobstersRepository.kt @@ -7,9 +7,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.withContext -import javax.inject.Inject -class LobstersRepository @Inject constructor( +class LobstersRepository constructor( private val lobstersApi: LobstersApi, private val lobstersDatabase: LobstersDatabase, ) { @@ -39,7 +38,7 @@ class LobstersRepository @Inject constructor( val posts = getAllPosts() posts.forEach { - savedPostsCache.putIfAbsent(it.short_id, it) + savedPostsCache[it.short_id] = it } _isCacheReady.value = true } diff --git a/app/src/main/java/dev/msfjarvis/lobsters/injection/RepositoryModule.kt b/app/src/main/java/dev/msfjarvis/lobsters/injection/RepositoryModule.kt new file mode 100644 index 00000000..afed15c3 --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/lobsters/injection/RepositoryModule.kt @@ -0,0 +1,24 @@ +package dev.msfjarvis.lobsters.injection + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dev.msfjarvis.lobsters.data.api.LobstersApi +import dev.msfjarvis.lobsters.data.repo.LobstersRepository +import dev.msfjarvis.lobsters.database.LobstersDatabase +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object RepositoryModule { + + @Singleton + @Provides + fun provideLobstersRepository( + lobstersApi: LobstersApi, + lobstersDatabase: LobstersDatabase + ): LobstersRepository { + return LobstersRepository(lobstersApi, lobstersDatabase) + } +} diff --git a/app/src/main/java/dev/msfjarvis/lobsters/ui/viewmodel/LobstersViewModel.kt b/app/src/main/java/dev/msfjarvis/lobsters/ui/viewmodel/LobstersViewModel.kt index 290f4fde..e63564fc 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/ui/viewmodel/LobstersViewModel.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/ui/viewmodel/LobstersViewModel.kt @@ -11,6 +11,7 @@ import dev.msfjarvis.lobsters.data.remote.LobstersPagingSource import dev.msfjarvis.lobsters.data.repo.LobstersRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import javax.inject.Inject @@ -27,11 +28,11 @@ class LobstersViewModel @Inject constructor( }.flow.cachedIn(viewModelScope) init { - viewModelScope.launch { - lobstersRepository.isCacheReady.onEach { + lobstersRepository.isCacheReady.onEach { ready -> + if (ready) { _savedPosts.value = lobstersRepository.getAllPostsFromCache() } - } + }.launchIn(viewModelScope) } fun toggleSave(post: LobstersPost) {