-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: 추억 생성 및 수정 썸네일 사진 업로드 오류 수정 #535 #542
Changes from 10 commits
01e24e9
fe9f38d
a33ba04
993e26d
97e73b1
8ec98e1
1e86b27
7172173
9ccacec
0b9dff5
d08dcca
499b185
863b9be
7a1bebc
27ecb45
c0f5458
382d063
9e028d2
e35c751
fa613e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.on.staccato.presentation.memorycreation | ||
|
||
import android.net.Uri | ||
|
||
data class ThumbnailUiModel( | ||
val uri: Uri? = null, | ||
val url: String? = null, | ||
) { | ||
fun updateUrl(newUrl: String?): ThumbnailUiModel = this.copy(url = newUrl) | ||
|
||
fun isEqualUri(newUri: Uri?): Boolean = uri == newUri | ||
|
||
fun delete() = this.copy(uri = null, url = null) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 추억 썸네일의 이미지 uri 와 url 을 가지고 있는 UiModel을 새로 만들어주신 덕분에 보일러 플레이트 코드가 많이 줄어들었네요! 무척 좋은 구현인 것 같습니다 😄 |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -21,12 +21,16 @@ import com.on.staccato.presentation.common.MutableSingleLiveData | |||||||||||||||||||||||||||||||||||||||||||
import com.on.staccato.presentation.common.SingleLiveData | ||||||||||||||||||||||||||||||||||||||||||||
import com.on.staccato.presentation.memorycreation.DateConverter.convertLongToLocalDate | ||||||||||||||||||||||||||||||||||||||||||||
import com.on.staccato.presentation.memorycreation.MemoryCreationError | ||||||||||||||||||||||||||||||||||||||||||||
import com.on.staccato.presentation.memorycreation.ThumbnailUiModel | ||||||||||||||||||||||||||||||||||||||||||||
import com.on.staccato.presentation.util.convertMemoryUriToFile | ||||||||||||||||||||||||||||||||||||||||||||
import dagger.hilt.android.lifecycle.HiltViewModel | ||||||||||||||||||||||||||||||||||||||||||||
import kotlinx.coroutines.Job | ||||||||||||||||||||||||||||||||||||||||||||
import kotlinx.coroutines.launch | ||||||||||||||||||||||||||||||||||||||||||||
import java.time.LocalDate | ||||||||||||||||||||||||||||||||||||||||||||
import javax.inject.Inject | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
private typealias URI = Uri | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 추억 생성, 수정 ViewModel에서 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
위 코드도 그런 이유로
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
@HiltViewModel | ||||||||||||||||||||||||||||||||||||||||||||
class MemoryCreationViewModel | ||||||||||||||||||||||||||||||||||||||||||||
@Inject | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -36,6 +40,7 @@ class MemoryCreationViewModel | |||||||||||||||||||||||||||||||||||||||||||
) : ViewModel() { | ||||||||||||||||||||||||||||||||||||||||||||
val title = ObservableField<String>() | ||||||||||||||||||||||||||||||||||||||||||||
val description = ObservableField<String>() | ||||||||||||||||||||||||||||||||||||||||||||
val isPeriodActive = MutableLiveData<Boolean>(false) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
private val _startDate = MutableLiveData<LocalDate?>(null) | ||||||||||||||||||||||||||||||||||||||||||||
val startDate: LiveData<LocalDate?> get() = _startDate | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -46,51 +51,34 @@ class MemoryCreationViewModel | |||||||||||||||||||||||||||||||||||||||||||
private val _createdMemoryId = MutableLiveData<Long>() | ||||||||||||||||||||||||||||||||||||||||||||
val createdMemoryId: LiveData<Long> get() = _createdMemoryId | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
private val _thumbnailUri = MutableLiveData<Uri?>(null) | ||||||||||||||||||||||||||||||||||||||||||||
val thumbnailUri: LiveData<Uri?> get() = _thumbnailUri | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
private val _thumbnailUrl = MutableLiveData<String?>(null) | ||||||||||||||||||||||||||||||||||||||||||||
val thumbnailUrl: LiveData<String?> get() = _thumbnailUrl | ||||||||||||||||||||||||||||||||||||||||||||
private val _thumbnail = MutableLiveData<ThumbnailUiModel>(ThumbnailUiModel()) | ||||||||||||||||||||||||||||||||||||||||||||
val thumbnail: LiveData<ThumbnailUiModel> get() = _thumbnail | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
private val _isPosting = MutableLiveData<Boolean>(false) | ||||||||||||||||||||||||||||||||||||||||||||
val isPosting: LiveData<Boolean> get() = _isPosting | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
private val _isPhotoPosting = MutableLiveData<Boolean>(false) | ||||||||||||||||||||||||||||||||||||||||||||
val isPhotoPosting: LiveData<Boolean> get() = _isPhotoPosting | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
val isPeriodActive = MutableLiveData<Boolean>(false) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
private val _errorMessage = MutableLiveData<String>() | ||||||||||||||||||||||||||||||||||||||||||||
val errorMessage: LiveData<String> get() = _errorMessage | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
private val _error = MutableSingleLiveData<MemoryCreationError>() | ||||||||||||||||||||||||||||||||||||||||||||
val error: SingleLiveData<MemoryCreationError> get() = _error | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
private val thumbnailJobs = mutableMapOf<URI, Job>() | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
fun createThumbnailUrl( | ||||||||||||||||||||||||||||||||||||||||||||
context: Context, | ||||||||||||||||||||||||||||||||||||||||||||
thumbnailUri: Uri, | ||||||||||||||||||||||||||||||||||||||||||||
uri: Uri, | ||||||||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||||||||
_thumbnailUri.value = thumbnailUri | ||||||||||||||||||||||||||||||||||||||||||||
_isPhotoPosting.value = true | ||||||||||||||||||||||||||||||||||||||||||||
val thumbnailFile = convertMemoryUriToFile(context, thumbnailUri, name = MEMORY_FILE_NAME) | ||||||||||||||||||||||||||||||||||||||||||||
viewModelScope.launch { | ||||||||||||||||||||||||||||||||||||||||||||
val result: ResponseResult<ImageResponse> = | ||||||||||||||||||||||||||||||||||||||||||||
imageRepository.convertImageFileToUrl(thumbnailFile) | ||||||||||||||||||||||||||||||||||||||||||||
result.onSuccess(::setThumbnailUrl) | ||||||||||||||||||||||||||||||||||||||||||||
.onServerError(::handlePhotoError) | ||||||||||||||||||||||||||||||||||||||||||||
.onException { e, message -> | ||||||||||||||||||||||||||||||||||||||||||||
handlePhotoException(e, message, thumbnailUri) | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
fun setThumbnailUri(thumbnailUri: Uri?) { | ||||||||||||||||||||||||||||||||||||||||||||
_thumbnailUri.value = thumbnailUri | ||||||||||||||||||||||||||||||||||||||||||||
createThumbnail(uri) | ||||||||||||||||||||||||||||||||||||||||||||
registerThumbnailJob(context, uri) | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
fun setThumbnailUrl(imageResponse: ImageResponse?) { | ||||||||||||||||||||||||||||||||||||||||||||
_thumbnailUrl.value = imageResponse?.imageUrl | ||||||||||||||||||||||||||||||||||||||||||||
_isPhotoPosting.value = false | ||||||||||||||||||||||||||||||||||||||||||||
fun deleteThumbnail() { | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 함수명도 마찬가지로 clearThumbnail이 어떨까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 반영했습니다!
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 추가로, 사진의 X 버튼이 눌렸을 때에도 리소스 관리를 위해 Job.cancel을 수행해야 한다는 생각이 드네요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 추억 생성 및 수정 화면에서는 사진이 업로드 중일 때 X 버튼이 보여지지 않도록 구현되어 있습니다! 따라서 X 버튼이 사용자에게 보여진 시점에는 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아하ㅋㅋ 제가 스타카토 생성/수정 화면이랑 헷갈렸네요! 좋습니다~ |
||||||||||||||||||||||||||||||||||||||||||||
_thumbnail.value = thumbnail.value?.delete() | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
fun setMemoryPeriod( | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -114,13 +102,58 @@ class MemoryCreationViewModel | |||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
private fun createThumbnail( | ||||||||||||||||||||||||||||||||||||||||||||
uri: Uri?, | ||||||||||||||||||||||||||||||||||||||||||||
url: String? = null, | ||||||||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||||||||
val isEqualUri: Boolean? = _thumbnail.value?.isEqualUri(uri) | ||||||||||||||||||||||||||||||||||||||||||||
if (isEqualUri == false) { | ||||||||||||||||||||||||||||||||||||||||||||
thumbnailJobs[_thumbnail.value?.uri]?.cancel() | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아래 두 경우, Job.cancel()을 호출해도 코루틴이 이를 수신할 수 없어 즉시 취소되지 않습니다!
이렇게 내부적으로 취소될 수 없는 상태인 경우를 고려해 저는 Job.cancel()을 호출하기 전에 isActive로 수동으로 상태를 확인해주곤 하는데요! if (photoJobs[deletedPhoto.uri.toString()]?.isActive == true) {
photoJobs[deletedPhoto.uri.toString()]?.cancel()
} Retrofit2 interface에서 지원하는 코루틴은 취소될 수 있는 코루틴일 것 같아 이 과정이 불필요한가? 싶기도 하네요 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 헉 이 부분을 놓쳤네요!! 감사합니다~ 빙빙 짱!
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
이와 관련해서 Retrofit2의 내부 구현을 찾아 봤는 데 아직 이해가 잘 안 되네요 ㅠㅠ |
||||||||||||||||||||||||||||||||||||||||||||
_thumbnail.value = ThumbnailUiModel(uri = uri, url = url) | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 덧붙여서, fun createThumbnailUrl(
context: Context,
uri: Uri,
) {
_isPhotoPosting.value = true
createThumbnail(uri) // uri만 넣어주고 있음. url은 항상 default인 null
registerThumbnailJob(context, uri)
} 그렇다면 url 매개변수를 제거하고 아래처럼 null을 직접 넣어주는 것이 더 명시적일 것 같아요~!
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 헉 그렇네요! 수정했습니다~
|
||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 업로드하려는 저도 마땅히 생각나지는 않지만, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 추가로 기존 Uri와 다른 Uri인지 확인하는 코드를 아래처럼 메서드로 분리하면 코드가 좀 더 깔끔해질 것 같아요!
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저도 호두의 의견에 동의합니다~
조금 더 구체적으로, Uri만 바꿔준다는 의미까지 포함해 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
해당 메서드는
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
변수로 분리하는 것보다 메서드로 분리하는 게 더 깔끔하겠네요! private fun setThumbnailUri(
uri: Uri?,
url: String? = null,
) {
if (isNewUri(uri)) {
...
}
}
private fun isNewUri(uri: Uri?): Boolean = _thumbnail.value?.isEqualUri(uri) == false 매개변수로 들어온 Uri가 새로운 Uri일 때 uri를 설정한다는 것을 명시적으로 나타내기 위해
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
private fun registerThumbnailJob( | ||||||||||||||||||||||||||||||||||||||||||||
context: Context, | ||||||||||||||||||||||||||||||||||||||||||||
uri: Uri, | ||||||||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||||||||
val job = fetchThumbnail(context, uri) | ||||||||||||||||||||||||||||||||||||||||||||
job.invokeOnCompletion { | ||||||||||||||||||||||||||||||||||||||||||||
thumbnailJobs.remove(uri) | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
thumbnailJobs[uri] = job | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
private fun fetchThumbnail( | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 추억 썸네일을 업로드하는 Job을 생성하고 반환해주는 메서드로 역할이 바뀌었으니, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 호두가 제안해주신 이름은 " 더해서, 함수 명의 구조를 동사 + 목적어 형태로 하기 위해 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 서버에서 데이터를 불러올 때 fetchXXX 형식을 사용하기로 해서
|
||||||||||||||||||||||||||||||||||||||||||||
context: Context, | ||||||||||||||||||||||||||||||||||||||||||||
uri: Uri, | ||||||||||||||||||||||||||||||||||||||||||||
): Job { | ||||||||||||||||||||||||||||||||||||||||||||
val thumbnailFile = convertMemoryUriToFile(context, uri, name = MEMORY_FILE_NAME) | ||||||||||||||||||||||||||||||||||||||||||||
return viewModelScope.launch { | ||||||||||||||||||||||||||||||||||||||||||||
val result: ResponseResult<ImageResponse> = | ||||||||||||||||||||||||||||||||||||||||||||
imageRepository.convertImageFileToUrl(thumbnailFile) | ||||||||||||||||||||||||||||||||||||||||||||
result | ||||||||||||||||||||||||||||||||||||||||||||
.onSuccess(::setThumbnailUrl) | ||||||||||||||||||||||||||||||||||||||||||||
.onServerError(::handlePhotoError) | ||||||||||||||||||||||||||||||||||||||||||||
.onException { e, message -> | ||||||||||||||||||||||||||||||||||||||||||||
handlePhotoException(e, message, uri) | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
private fun setThumbnailUrl(imageResponse: ImageResponse?) { | ||||||||||||||||||||||||||||||||||||||||||||
val newUrl = imageResponse?.imageUrl | ||||||||||||||||||||||||||||||||||||||||||||
_thumbnail.value = _thumbnail.value?.updateUrl(newUrl) | ||||||||||||||||||||||||||||||||||||||||||||
_isPhotoPosting.value = false | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이미지 삭제 버튼 클릭 시,
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||||||||||||||||||||
private fun setCreatedMemoryId(memoryCreationResponse: MemoryCreationResponse) { | ||||||||||||||||||||||||||||||||||||||||||||
_createdMemoryId.value = memoryCreationResponse.memoryId | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
private fun makeNewMemory() = | ||||||||||||||||||||||||||||||||||||||||||||
NewMemory( | ||||||||||||||||||||||||||||||||||||||||||||
memoryThumbnailUrl = thumbnailUrl.value, | ||||||||||||||||||||||||||||||||||||||||||||
memoryThumbnailUrl = _thumbnail.value?.url, | ||||||||||||||||||||||||||||||||||||||||||||
memoryTitle = title.get() ?: throw IllegalArgumentException(), | ||||||||||||||||||||||||||||||||||||||||||||
startAt = getDateByPeriodSetting(startDate), | ||||||||||||||||||||||||||||||||||||||||||||
endAt = getDateByPeriodSetting(endDate), | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -145,9 +178,11 @@ class MemoryCreationViewModel | |||||||||||||||||||||||||||||||||||||||||||
private fun handlePhotoException( | ||||||||||||||||||||||||||||||||||||||||||||
e: Throwable, | ||||||||||||||||||||||||||||||||||||||||||||
message: String, | ||||||||||||||||||||||||||||||||||||||||||||
thumbnailUri: Uri, | ||||||||||||||||||||||||||||||||||||||||||||
uri: Uri, | ||||||||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||||||||
_error.setValue(MemoryCreationError.Thumbnail(message, thumbnailUri)) | ||||||||||||||||||||||||||||||||||||||||||||
if (thumbnailJobs[uri]?.isActive == true) { | ||||||||||||||||||||||||||||||||||||||||||||
_error.setValue(MemoryCreationError.Thumbnail(message, uri)) | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
private fun handleCreateServerError( | ||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사소한 부분인데, delete는 컬렉션의 원소나 인스턴스 자체를 삭제하는 동작을 연상시킨다고 생각해요!
ThumbnailUiModel
의 uri와 url 정보를 모두 null로 설정해 값을 비운다는 의미를 더 명확하게 나타내기 위해clear
라는 함수명은 어떠신가요?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delete
보다clear
라는 네이밍이 더 적합하겠네요!clear
로 변경했습니다!반영 커밋
: 27ecb45