Skip to content

Commit 28eb321

Browse files
committed
Add special use case for determining if a BlogPost exists in both the cache and the server.
So if a blog is cached, and it does not exist on server, it will be deleted from the cache.
1 parent 83c8aa0 commit 28eb321

File tree

11 files changed

+215
-17
lines changed

11 files changed

+215
-17
lines changed

app/src/main/java/com/codingwithmitch/openapi/business/datasource/cache/blog/BlogPostDao.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ interface BlogPostDao {
1212
@Delete
1313
suspend fun deleteBlogPost(blogPost: BlogPostEntity)
1414

15+
@Query("DELETE FROM blog_post WHERE pk = :pk")
16+
suspend fun deleteBlogPost(pk: Int)
17+
1518
@Query("""
1619
UPDATE blog_post SET title = :title, body = :body, image = :image
1720
WHERE pk = :pk

app/src/main/java/com/codingwithmitch/openapi/business/datasource/network/main/OpenApiMainService.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ interface OpenApiMainService {
7373
@Part("body") body: RequestBody,
7474
@Part image: MultipartBody.Part?
7575
): BlogCreateUpdateResponse
76+
77+
@GET("blog/{slug}")
78+
suspend fun getBlog(
79+
@Header("Authorization") authorization: String,
80+
@Path("slug") slug: String,
81+
): BlogPostDto?
7682
}
7783

7884

app/src/main/java/com/codingwithmitch/openapi/business/domain/util/ErrorHandling.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class ErrorHandling{
4040
const val ERROR_SOMETHING_WENT_WRONG = "Something went wrong."
4141
const val ERROR_DELETE_BLOG_DOES_NOT_EXIST = "That blog post does not exist."
4242
const val ERROR_DELETE_BLOG_NEED_PERMISSION = "You don't have permission to delete that."
43+
const val ERROR_BLOG_DOES_NOT_EXIST = "That BlogPost does not exist on the server."
4344
const val INVALID_STATE_EVENT = "Invalid state event"
4445
const val CANNOT_BE_UNDONE = "This can't be undone."
4546
const val NETWORK_ERROR = "Network error"

app/src/main/java/com/codingwithmitch/openapi/business/domain/util/SuccessHandling.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ class SuccessHandling {
1313
const val SUCCESS_BLOG_DELETED = "deleted"
1414
const val SUCCESS_BLOG_UPDATED = "updated"
1515

16+
const val SUCCESS_BLOG_DOES_NOT_EXIST_IN_CACHE = "Blog does not exist in the cache."
17+
const val SUCCESS_BLOG_EXISTS_ON_SERVER = "Blog exists on the server and in the cache."
18+
1619
const val SUCCESS_ACCOUNT_UPDATED = "Account update success"
1720
const val SUCCESS_PASSWORD_UPDATED = "successfully changed password"
1821

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package com.codingwithmitch.openapi.business.interactors.blog
2+
3+
import com.codingwithmitch.openapi.api.handleUseCaseException
4+
import com.codingwithmitch.openapi.business.datasource.cache.blog.BlogPostDao
5+
import com.codingwithmitch.openapi.business.datasource.cache.blog.toBlogPost
6+
import com.codingwithmitch.openapi.business.datasource.network.main.OpenApiMainService
7+
import com.codingwithmitch.openapi.business.domain.models.AuthToken
8+
import com.codingwithmitch.openapi.business.domain.models.BlogPost
9+
import com.codingwithmitch.openapi.business.domain.util.*
10+
import com.codingwithmitch.openapi.business.domain.util.SuccessHandling.Companion.SUCCESS_BLOG_DOES_NOT_EXIST_IN_CACHE
11+
import kotlinx.coroutines.flow.Flow
12+
import kotlinx.coroutines.flow.catch
13+
import kotlinx.coroutines.flow.flow
14+
import java.lang.Exception
15+
16+
/**
17+
* If a blog exist in the cache but does not exist on server, we need to delete from cache.
18+
*/
19+
class ConfirmBlogExistsOnServer(
20+
private val service: OpenApiMainService,
21+
private val cache: BlogPostDao
22+
) {
23+
24+
fun execute(
25+
authToken: AuthToken?,
26+
pk: Int,
27+
slug: String,
28+
): Flow<DataState<Response>> = flow {
29+
emit(DataState.loading<Response>())
30+
val cachedBlog = cache.getBlogPost(pk)
31+
if(cachedBlog == null){
32+
// It doesn't exist in cache. Finish.
33+
emit(DataState.data<Response>(
34+
response = Response(
35+
message = SUCCESS_BLOG_DOES_NOT_EXIST_IN_CACHE,
36+
uiComponentType = UIComponentType.None(),
37+
messageType = MessageType.Success()
38+
)
39+
))
40+
}else{
41+
if(authToken == null){
42+
throw Exception(ErrorHandling.ERROR_AUTH_TOKEN_INVALID)
43+
}
44+
// confirm it exists on server
45+
val blogPost = try {
46+
service.getBlog(
47+
authorization = "Token ${authToken.token}",
48+
slug = slug,
49+
)
50+
}catch (e1: Exception){
51+
e1.printStackTrace()
52+
null
53+
}
54+
// if it exists on server but not in cache. Delete from cache and emit error.
55+
if(blogPost == null){
56+
cache.deleteBlogPost(pk)
57+
emit(DataState.error<Response>(
58+
response = Response(
59+
message = ErrorHandling.ERROR_BLOG_DOES_NOT_EXIST,
60+
uiComponentType = UIComponentType.Dialog(),
61+
messageType = MessageType.Error()
62+
)
63+
))
64+
}else{ // if it exists in the cache and on the server. Everything is fine.
65+
emit(DataState.data<Response>(
66+
data = Response(
67+
message = SuccessHandling.SUCCESS_BLOG_EXISTS_ON_SERVER,
68+
uiComponentType = UIComponentType.None(),
69+
messageType = MessageType.Success()
70+
),
71+
response = null,
72+
))
73+
}
74+
}
75+
76+
}.catch { e ->
77+
emit(handleUseCaseException(e))
78+
}
79+
}
80+
81+
82+
83+
84+
85+
86+
87+
88+
89+
90+
91+
92+
93+
94+
95+

app/src/main/java/com/codingwithmitch/openapi/di/blog/BlogModule.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,15 @@ object BlogModule {
7373
): GetOrderAndFilter{
7474
return GetOrderAndFilter(appDataStoreManager)
7575
}
76+
77+
@Singleton
78+
@Provides
79+
fun provideConfirmBlogExistsOnServer(
80+
service: OpenApiMainService,
81+
cache: BlogPostDao,
82+
): ConfirmBlogExistsOnServer{
83+
return ConfirmBlogExistsOnServer(service = service, cache = cache)
84+
}
7685
}
7786

7887

app/src/main/java/com/codingwithmitch/openapi/presentation/main/blog/detail/ViewBlogEvents.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@ import com.codingwithmitch.openapi.business.domain.util.StateMessage
55

66
sealed class ViewBlogEvents {
77

8-
data class isAuthor(val slug: String): ViewBlogEvents()
8+
data class IsAuthor(val slug: String): ViewBlogEvents()
99

10-
data class getBlog(val pk: Int): ViewBlogEvents()
10+
data class GetBlog(val pk: Int): ViewBlogEvents()
11+
12+
data class ConfirmBlogExistsOnServer(
13+
val pk: Int,
14+
val slug: String
15+
): ViewBlogEvents()
1116

1217
object DeleteBlog: ViewBlogEvents()
1318

app/src/main/java/com/codingwithmitch/openapi/presentation/main/blog/detail/ViewBlogFragment.kt

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.bumptech.glide.request.RequestOptions
1010
import com.codingwithmitch.openapi.R
1111
import com.codingwithmitch.openapi.business.domain.models.BlogPost
1212
import com.codingwithmitch.openapi.business.domain.util.*
13+
import com.codingwithmitch.openapi.business.domain.util.ErrorHandling.Companion.ERROR_BLOG_DOES_NOT_EXIST
1314
import com.codingwithmitch.openapi.databinding.FragmentViewBlogBinding
1415
import com.codingwithmitch.openapi.presentation.main.blog.BaseBlogFragment
1516
import com.codingwithmitch.openapi.presentation.util.processQueue
@@ -50,14 +51,18 @@ class ViewBlogFragment : BaseBlogFragment()
5051

5152
uiCommunicationListener.displayProgressBar(state.isLoading)
5253

53-
processQueue(
54-
context = context,
55-
queue = state.queue,
56-
stateMessageCallback = object: StateMessageCallback {
57-
override fun removeMessageFromStack() {
58-
viewModel.onTriggerEvent(ViewBlogEvents.OnRemoveHeadFromQueue)
59-
}
60-
})
54+
if(state.queue.peek()?.response?.message == ERROR_BLOG_DOES_NOT_EXIST){
55+
findNavController().popBackStack(R.id.blogFragment, false)
56+
}else{
57+
processQueue(
58+
context = context,
59+
queue = state.queue,
60+
stateMessageCallback = object: StateMessageCallback {
61+
override fun removeMessageFromStack() {
62+
viewModel.onTriggerEvent(ViewBlogEvents.OnRemoveHeadFromQueue)
63+
}
64+
})
65+
}
6166

6267
state.blogPost?.let { setBlogProperties(it) }
6368

app/src/main/java/com/codingwithmitch/openapi/presentation/main/blog/detail/ViewBlogViewModel.kt

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import androidx.lifecycle.ViewModel
77
import androidx.lifecycle.viewModelScope
88
import com.codingwithmitch.openapi.business.domain.util.StateMessage
99
import com.codingwithmitch.openapi.business.domain.util.SuccessHandling
10+
import com.codingwithmitch.openapi.business.domain.util.UIComponentType
1011
import com.codingwithmitch.openapi.business.domain.util.doesMessageAlreadyExistInQueue
12+
import com.codingwithmitch.openapi.business.interactors.blog.ConfirmBlogExistsOnServer
1113
import com.codingwithmitch.openapi.business.interactors.blog.DeleteBlogPost
1214
import com.codingwithmitch.openapi.business.interactors.blog.GetBlogFromCache
1315
import com.codingwithmitch.openapi.business.interactors.blog.IsAuthorOfBlogPost
@@ -23,6 +25,7 @@ class ViewBlogViewModel
2325
constructor(
2426
private val sessionManager: SessionManager,
2527
private val getBlogFromCache: GetBlogFromCache,
28+
private val confirmBlogExistsOnServer: ConfirmBlogExistsOnServer,
2629
private val isAuthorOfBlogPost: IsAuthorOfBlogPost,
2730
private val deleteBlogPost: DeleteBlogPost,
2831
private val savedStateHandle: SavedStateHandle,
@@ -34,27 +37,42 @@ constructor(
3437

3538
init {
3639
savedStateHandle.get<Int>("blogPostPk")?.let { blogPostPk ->
37-
onTriggerEvent(ViewBlogEvents.getBlog(blogPostPk))
40+
onTriggerEvent(ViewBlogEvents.GetBlog(blogPostPk))
3841
}
3942
}
4043

4144
fun onTriggerEvent(event: ViewBlogEvents){
4245
when(event){
43-
is ViewBlogEvents.getBlog -> {
46+
is ViewBlogEvents.GetBlog -> {
4447
getBlog(
4548
event.pk,
49+
object: OnCompleteCallback { // Determine if blog exists on server
50+
override fun done() {
51+
state.value?.let { state ->
52+
state.blogPost?.let { blog ->
53+
onTriggerEvent(ViewBlogEvents.ConfirmBlogExistsOnServer(pk = event.pk, blog.slug))
54+
}
55+
}
56+
}
57+
}
58+
)
59+
}
60+
is ViewBlogEvents.ConfirmBlogExistsOnServer -> {
61+
confirmBlogExistsOnServer(
62+
event.pk,
63+
event.slug,
4664
object: OnCompleteCallback { // Determine if they are the author
4765
override fun done() {
4866
state.value?.let { state ->
4967
state.blogPost?.let { blog ->
50-
onTriggerEvent(ViewBlogEvents.isAuthor(slug = blog.slug))
68+
onTriggerEvent(ViewBlogEvents.IsAuthor(slug = blog.slug))
5169
}
5270
}
5371
}
5472
}
5573
)
5674
}
57-
is ViewBlogEvents.isAuthor -> {
75+
is ViewBlogEvents.IsAuthor -> {
5876
isAuthor(event.slug)
5977
}
6078
is ViewBlogEvents.DeleteBlog -> {
@@ -94,12 +112,45 @@ constructor(
94112
state.value?.let { state ->
95113
val queue = state.queue
96114
if(!stateMessage.doesMessageAlreadyExistInQueue(queue = queue)){
97-
queue.add(stateMessage)
98-
this.state.value = state.copy(queue = queue)
115+
if(!(stateMessage.response.uiComponentType is UIComponentType.None)){
116+
queue.add(stateMessage)
117+
this.state.value = state.copy(queue = queue)
118+
}
99119
}
100120
}
101121
}
102122

123+
private fun confirmBlogExistsOnServer(pk: Int, slug: String, callback: OnCompleteCallback){
124+
state.value?.let { state ->
125+
confirmBlogExistsOnServer.execute(
126+
authToken = sessionManager.state.value?.authToken,
127+
pk = pk,
128+
slug = slug,
129+
).onEach { dataState ->
130+
this.state.value = state.copy(isLoading = dataState.isLoading)
131+
132+
dataState.data?.let { response ->
133+
if(response.message == SuccessHandling.SUCCESS_BLOG_DOES_NOT_EXIST_IN_CACHE
134+
|| response.message == SuccessHandling.SUCCESS_BLOG_EXISTS_ON_SERVER
135+
){
136+
// Blog exists in cache and on server. All is good.
137+
callback.done()
138+
}else{
139+
appendToMessageQueue(
140+
stateMessage = StateMessage(
141+
response = response
142+
)
143+
)
144+
}
145+
}
146+
147+
dataState.stateMessage?.let { stateMessage ->
148+
appendToMessageQueue(stateMessage)
149+
}
150+
}.launchIn(viewModelScope)
151+
}
152+
}
153+
103154
private fun deleteBlog(){
104155
state.value?.let { state ->
105156
state.blogPost?.let { blogPost ->

app/src/test/java/com/codingwithmitch/openapi/datasource/network/blog/CreateResponses.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,18 @@ object CreateResponses {
2020
val dateUpdated = "2021-07-09T16:26:23.121544Z"
2121

2222
val createSuccess = "{ \"response\": \"${SuccessHandling.SUCCESS_BLOG_CREATED}\", \"pk\": ${blogPk}, \"title\": \"${title}\", \"body\": \"${body}\", \"slug\": \"${slug}\", \"date_updated\": \"${dateUpdated}\", \"image\": \"${image}\", \"username\": \"${username}\" }"
23-
}
23+
}
24+
25+
26+
27+
28+
29+
30+
31+
32+
33+
34+
35+
36+
37+

0 commit comments

Comments
 (0)