Skip to content
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

refactor : EventDetail 코드 리팩토링 #282

Merged
merged 11 commits into from
Aug 11, 2023
2 changes: 1 addition & 1 deletion android/2023-emmsale/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
android:name=".presentation.ui.onboarding.OnboardingActivity"
android:exported="true" />
<activity
android:name=".presentation.eventdetail.EventDetailActivity"
android:name=".presentation.ui.eventdetail.EventDetailActivity"
android:exported="false" />
<activity
android:name=".presentation.ui.login.LoginActivity"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package com.emmsale.data.eventdetail

import java.time.LocalDateTime

data class EventDetail(
val id: Long,
val name: String,
val status: String,
val location: String,
val startDate: String,
val endDate: String,
val startDate: LocalDateTime,
val endDate: LocalDateTime,
Comment on lines -8 to +11
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

데이터 모델에서 날짜 표현을 위해 String에서 LocalDateTime으로 타입을 바꾼 것 좋네요 👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

감사요

val informationUrl: String,
val tags: List<String>,
val imageUrl: String?,
val postImageUrl: String?,
val remainingDays: Int,
val type: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ package com.emmsale.data.eventdetail
import com.emmsale.data.common.ApiResult

interface EventDetailRepository {
suspend fun fetchEventDetail(eventId: Long): ApiResult<EventDetail>
suspend fun getEventDetail(eventId: Long): ApiResult<EventDetail>
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ package com.emmsale.data.eventdetail
import com.emmsale.data.common.ApiResult
import com.emmsale.data.common.handleApi
import com.emmsale.data.eventdetail.dto.EventDetailApiModel
import com.emmsale.data.eventdetail.mapper.toData

class EventDetailRepositoryImpl(
private val eventDetailService: EventDetailService,
) : EventDetailRepository {

override suspend fun fetchEventDetail(eventId: Long): ApiResult<EventDetail> {
override suspend fun getEventDetail(eventId: Long): ApiResult<EventDetail> {
return handleApi(
execute = { eventDetailService.fetchEventDetail(eventId) },
execute = { eventDetailService.getEventDetail(eventId) },
mapToDomain = EventDetailApiModel::toData,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import retrofit2.http.Path

interface EventDetailService {
@GET("events/{eventId}")
suspend fun fetchEventDetail(
suspend fun getEventDetail(
@Path("eventId") eventId: Long,
): Response<EventDetailApiModel>
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.emmsale.data.eventdetail.dto

import com.emmsale.data.eventdetail.EventDetail
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

Expand All @@ -10,36 +9,30 @@ data class EventDetailApiModel(
val id: Long,
@SerialName("name")
val name: String,
@SerialName("status")
val status: String,
@SerialName("location")
val location: String,
@SerialName("informationUrl")
val informationUrl: String,
@SerialName("startDate")
val startDate: String,
@SerialName("endDate")
val endDate: String,
@SerialName("informationUrl")
val informationUrl: String,
@SerialName("applyStartDate")
val applyStartDate: String,
@SerialName("applyEndDate")
val applyEndDate: String,
@SerialName("location")
val location: String,
@SerialName("status")
val status: String,
@SerialName("applyStatus")
val applyStatus: String,
@SerialName("tags")
val tags: List<String>,
@SerialName("imageUrl")
val imageUrl: String?,
@SerialName("remainingDays")
val remainingDays: Int,
@SerialName("applyRemainingDays")
val applyRemainingDays: Int,
@SerialName("type")
val type: String,
) {
fun toData(): EventDetail = EventDetail(
id = id,
name = name,
status = status,
location = location,
startDate = startDate,
endDate = endDate,
informationUrl = informationUrl,
tags = tags,
imageUrl = imageUrl,
remainingDays = remainingDays,
type = type,
)
}
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.emmsale.data.eventdetail.mapper

import com.emmsale.data.eventdetail.EventDetail
import com.emmsale.data.eventdetail.dto.EventDetailApiModel
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

fun EventDetailApiModel.toData(): EventDetail = EventDetail(
id = id,
name = name,
status = status,
location = location,
startDate = startDate.toLocalDateTime(),
endDate = endDate.toLocalDateTime(),
informationUrl = informationUrl,
tags = tags,
postImageUrl = imageUrl,
remainingDays = remainingDays,
type = type,
)

private fun String.toLocalDateTime(): LocalDateTime {
val format = DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm:ss")
return LocalDateTime.parse(this, format)
}
Original file line number Diff line number Diff line change
@@ -1,77 +1,72 @@
package com.emmsale.presentation.eventdetail
package com.emmsale.presentation.ui.eventdetail

import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.emmsale.R
import com.emmsale.databinding.ActivityEventDetailBinding
import com.emmsale.presentation.common.extension.showToast
import com.emmsale.presentation.ui.eventdetail.EventDetailFragmentStateAdpater
import com.emmsale.presentation.ui.eventdetail.EventDetailViewModel
import com.emmsale.presentation.ui.eventdetail.EventTag
import com.emmsale.presentation.ui.eventdetail.uistate.EventDetailUiState
import com.google.android.material.tabs.TabLayoutMediator

class EventDetailActivity : AppCompatActivity() {
private lateinit var binding: ActivityEventDetailBinding
private val viewModel: EventDetailViewModel by viewModels { EventDetailViewModel.factory }
private val eventId: Long by lazy {
intent.getLongExtra(EVENT_ID_KEY, DEFAULT_EVENT_ID)
}

private val viewModel: EventDetailViewModel by viewModels { EventDetailViewModel.factory(eventId) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setUpBinding()
setUpEventDetail()
setBackPress()
viewModel.fetchEventDetail(eventId)
initBackPressButtonClickListener()
}

private fun setUpBinding() {
binding = ActivityEventDetailBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.lifecycleOwner = this
binding.vm = viewModel
Comment on lines +29 to +30
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드는 setContentView 이후에 호출해주는 것도 좋을 것 같아요.
viewModel이 초기화되면서 실행되는 로직이 있다면 버그가 발생할 수 있습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인이요

}

private fun setUpEventDetail() {
viewModel.eventDetail.observe(this) { eventDetailUiState ->
when (eventDetailUiState) {
is EventDetailUiState.Success -> {
binding.eventDetail = eventDetailUiState
addTag(eventDetailUiState.tags)
initFragmentStateAdapter(
eventDetailUiState.informationUrl,
eventDetailUiState.imageUrl,
)
}

else -> showToast("행사 받아오기 실패")
if (eventDetailUiState.isError) {
showToast(getString(R.string.eventdetail_fetch_eventdetail_error_message))
} else {
addEventTag(eventDetailUiState.tags)
initFragmentStateAdapter(
eventDetailUiState.informationUrl,
eventDetailUiState.imageUrl,
)
}
}
}

private fun initFragmentStateAdapter(informationUrl: String, imageUrl: String?) {
binding.vpEventdetail.adapter =
EventDetailFragmentStateAdpater(this, eventId, informationUrl, imageUrl)
TabLayoutMediator(binding.tablayoutEventdetail, binding.vpEventdetail) { tab, position ->
when (position) {
INFORMATION_TAB_POSITION -> tab.text = "상세 정보"
COMMENT_TAB_POSITION -> tab.text = "댓글"
RECRUITMENT_TAB_POSITION -> tab.text = "같이가요"
}
val tabNames = listOf(
getString(R.string.eventdetail_tab_infromation),
getString(R.string.eventdetail_tab_comment),
getString(R.string.eventdetail_tab_recruitment),
)
TabLayoutMediator(binding.tablayoutEventdetail, binding.vpEventdetail) { _, _ ->
tabNames
}.attach()
binding.vpEventdetail.isUserInputEnabled = false
}

private fun addTag(tags: List<String>) {
tags.forEach { binding.chipgroupEvendetailTags.addView(createTag(it)) }
private fun addEventTag(tags: List<String>) {
tags.forEach { binding.chipgroupEvendetailTags.addView(createEventTag(it)) }
}

private fun createTag(tag: String) = EventTag(this).apply {
private fun createEventTag(tag: String) = EventTag(this).apply {
text = tag
}

private fun setBackPress() {
private fun initBackPressButtonClickListener() {
binding.ivEventdetailBackpress.setOnClickListener {
finish()
}
Expand All @@ -80,10 +75,6 @@ class EventDetailActivity : AppCompatActivity() {
companion object {
private const val EVENT_ID_KEY = "EVENT_ID_KEY"
private const val DEFAULT_EVENT_ID = 1L
private const val INFORMATION_TAB_POSITION = 0
private const val COMMENT_TAB_POSITION = 1
private const val RECRUITMENT_TAB_POSITION = 2

fun startActivity(context: Context, eventId: Long) {
val intent = Intent(context, EventDetailActivity::class.java)
intent.putExtra(EVENT_ID_KEY, eventId)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,47 +1,63 @@
package com.emmsale.presentation.ui.eventdetail

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.emmsale.data.common.ApiError
import com.emmsale.data.common.ApiException
import com.emmsale.data.common.ApiSuccess
import com.emmsale.data.eventdetail.EventDetail
import com.emmsale.data.eventdetail.EventDetailRepository
import com.emmsale.data.member.MemberRepository
import com.emmsale.presentation.KerdyApplication
import com.emmsale.presentation.common.ViewModelFactory
import com.emmsale.presentation.common.livedata.NotNullLiveData
import com.emmsale.presentation.common.livedata.NotNullMutableLiveData
import com.emmsale.presentation.ui.eventdetail.uistate.EventDetailUiState
import kotlinx.coroutines.launch

class EventDetailViewModel(
private val eventId: Long,
private val eventDetailRepository: EventDetailRepository,
private val memberRepository: MemberRepository,
) : ViewModel() {

private val _eventDetail: MutableLiveData<EventDetailUiState> =
MutableLiveData<EventDetailUiState>()
val eventDetail: LiveData<EventDetailUiState>
get() = _eventDetail
private val _eventDetail: NotNullMutableLiveData<EventDetailUiState> =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_eventDetail의 타입을 명시해주지 않으면 한 줄에 깔끔하게 작성할 수 있어요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 무조건 타입을 명시해야 한다고 생각합니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어떤 타입인지 명확하게 적는 것이 실수를 방지한다고 생각해요

NotNullMutableLiveData(EventDetailUiState())
val eventDetail: NotNullLiveData<EventDetailUiState> = _eventDetail

fun fetchEventDetail(id: Long) {
viewModelScope.launch {
when (val result = eventDetailRepository.fetchEventDetail(id)) {
is ApiSuccess -> _eventDetail.postValue(
EventDetailUiState.from(result.data),
)
init {
fetchEventDetail(eventId)
}

is ApiError -> _eventDetail.postValue(EventDetailUiState.Error)
is ApiException -> _eventDetail.postValue(EventDetailUiState.Error)
private fun fetchEventDetail(id: Long) {
setLoadingState(true)
viewModelScope.launch {
when (val result = eventDetailRepository.getEventDetail(id)) {
is ApiSuccess -> fetchSuccessEventDetail(result.data)
is ApiError -> changeToErrorState()
is ApiException -> changeToErrorState()
}
}
}

private fun setLoadingState(state: Boolean) {
_eventDetail.value = _eventDetail.value.copy(isLoading = state)
}

private fun fetchSuccessEventDetail(eventDetail: EventDetail) {
_eventDetail.value = EventDetailUiState.from(eventDetail)
}

private fun changeToErrorState() {
_eventDetail.value = _eventDetail.value.copy(
isError = true,
isLoading = false,
)
}

companion object {
val factory = ViewModelFactory {
fun factory(eventId: Long) = ViewModelFactory {
EventDetailViewModel(
eventId,
eventDetailRepository = KerdyApplication.repositoryContainer.eventDetailRepository,
memberRepository = KerdyApplication.repositoryContainer.memberRepository,
)
}
}
Expand Down
Loading