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: ViewModel의 LiveData를 Flow로 변경 #695

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
1 change: 1 addition & 0 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ dependencies {
implementation(libs.play.services.location)

// coroutines
implementation(libs.kotlinx.coroutines.core)
testImplementation(libs.kotlinx.coroutines.test)

// glide
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import android.view.KeyEvent
import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.mulberry.ody.R
import com.mulberry.ody.databinding.FragmentAddressSearchBinding
import com.mulberry.ody.presentation.address.adapter.AddressesAdapter
import com.mulberry.ody.presentation.address.listener.AddressSearchListener
import com.mulberry.ody.presentation.common.binding.BindingFragment
import com.mulberry.ody.presentation.common.listener.BackListener
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch

@AndroidEntryPoint
class AddressSearchFragment :
Expand Down Expand Up @@ -69,16 +71,24 @@ class AddressSearchFragment :
showRetrySnackBar { viewModel.retryLastAction() }
}

viewModel.addressUiModels.observe(viewLifecycleOwner) {
adapter.submitList(it)
viewLifecycleOwner.lifecycleScope.launch {
viewModel.addressUiModels.collect {
adapter.submitList(it)
}
}
viewModel.addressSelectEvent.observe(viewLifecycleOwner) {
(parentFragment as? AddressSearchListener)?.onReceive(it)
(activity as? AddressSearchListener)?.onReceive(it)
onBack()

viewLifecycleOwner.lifecycleScope.launch {
viewModel.addressSelectEvent.collect {
(parentFragment as? AddressSearchListener)?.onReceive(it)
(activity as? AddressSearchListener)?.onReceive(it)
onBack()
}
}
viewModel.addressSearchKeyword.observe(viewLifecycleOwner) {
if (it.isEmpty()) viewModel.clearAddresses()

viewLifecycleOwner.lifecycleScope.launch {
viewModel.addressSearchKeyword.collect {
if (it.isEmpty()) viewModel.clearAddresses()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package com.mulberry.ody.presentation.address

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.map
import androidx.lifecycle.viewModelScope
import com.mulberry.ody.domain.apiresult.onFailure
import com.mulberry.ody.domain.apiresult.onNetworkError
Expand All @@ -13,11 +10,17 @@ import com.mulberry.ody.presentation.address.listener.AddressListener
import com.mulberry.ody.presentation.address.model.AddressUiModel
import com.mulberry.ody.presentation.address.model.toAddressUiModels
import com.mulberry.ody.presentation.common.BaseViewModel
import com.mulberry.ody.presentation.common.MutableSingleLiveData
import com.mulberry.ody.presentation.common.SingleLiveData
Copy link
Contributor

Choose a reason for hiding this comment

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

SingleLiveData야 고생 많았다

import com.mulberry.ody.presentation.common.analytics.AnalyticsHelper
import com.mulberry.ody.presentation.common.analytics.logNetworkErrorEvent
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
Expand All @@ -29,22 +32,37 @@ class AddressSearchViewModel
private val analyticsHelper: AnalyticsHelper,
private val addressRepository: AddressRepository,
) : BaseViewModel(), AddressListener {
val addressSearchKeyword: MutableLiveData<String> = MutableLiveData()
val hasAddressSearchKeyword: LiveData<Boolean> = addressSearchKeyword.map { it.isNotEmpty() }
val addressSearchKeyword: MutableStateFlow<String> = MutableStateFlow("")
val hasAddressSearchKeyword: StateFlow<Boolean> =
addressSearchKeyword.map { it.isNotEmpty() }.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(STATE_FLOW_SUBSCRIPTION_TIMEOUT_MILLIS),
initialValue = false,
)

private val addresses: MutableLiveData<List<Address>> = MutableLiveData(emptyList())
val isEmptyAddresses: LiveData<Boolean> = addresses.map { it.isEmpty() }
val addressUiModels: LiveData<List<AddressUiModel>> = addresses.map { it.toAddressUiModels() }
private val addresses: MutableStateFlow<List<Address>> = MutableStateFlow(emptyList())
val isEmptyAddresses: StateFlow<Boolean> =
addresses.map { it.isEmpty() }.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(STATE_FLOW_SUBSCRIPTION_TIMEOUT_MILLIS),
initialValue = true,
)
val addressUiModels: StateFlow<List<AddressUiModel>> =
addresses.map { it.toAddressUiModels() }.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = emptyList(),
)

private val _addressSelectEvent: MutableSingleLiveData<Address> = MutableSingleLiveData()
val addressSelectEvent: SingleLiveData<Address> get() = _addressSelectEvent
private val _addressSelectEvent: MutableSharedFlow<Address> = MutableSharedFlow()
val addressSelectEvent: SharedFlow<Address> get() = _addressSelectEvent.asSharedFlow()

fun clearAddressSearchKeyword() {
addressSearchKeyword.value = ""
}

fun searchAddress() {
val addressSearchKeyword = addressSearchKeyword.value ?: return
val addressSearchKeyword = addressSearchKeyword.value
if (addressSearchKeyword.isEmpty()) return

viewModelScope.launch {
Expand All @@ -65,7 +83,9 @@ class AddressSearchViewModel

override fun onClickAddressItem(id: Long) {
val address = addresses.value?.find { it.id == id } ?: return
_addressSelectEvent.setValue(address)
viewModelScope.launch {
_addressSelectEvent.emit(address)
}
}

fun clearAddresses() {
Expand All @@ -74,6 +94,8 @@ class AddressSearchViewModel

companion object {
private const val TAG = "AddressSearchViewModel"

private const val PAGE_SIZE = 10
private const val STATE_FLOW_SUBSCRIPTION_TIMEOUT_MILLIS = 5000L
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import androidx.activity.OnBackPressedCallback
import androidx.activity.viewModels
import androidx.fragment.app.Fragment
import androidx.fragment.app.commit
import androidx.lifecycle.lifecycleScope
import com.mulberry.ody.R
import com.mulberry.ody.databinding.ActivityMeetingCreationBinding
import com.mulberry.ody.domain.model.Address
Expand All @@ -21,6 +22,7 @@ import com.mulberry.ody.presentation.creation.name.MeetingNameFragment
import com.mulberry.ody.presentation.creation.time.MeetingTimeFragment
import com.mulberry.ody.presentation.join.MeetingJoinActivity
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch

@AndroidEntryPoint
class MeetingCreationActivity :
Expand Down Expand Up @@ -65,21 +67,27 @@ class MeetingCreationActivity :

private fun initializeObserve() {
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
viewModel.inviteCode.observe(this) {
viewModel.onClickCreationMeeting()
lifecycleScope.launch {
viewModel.inviteCode.collect {
viewModel.onClickCreationMeeting()
}
}
viewModel.nextPageEvent.observe(this) {
handleMeetingInfoNextClick()
lifecycleScope.launch {
viewModel.nextPageEvent.collect {
handleMeetingInfoNextClick()
}
}
viewModel.navigateAction.observe(this) {
when (it) {
MeetingCreationNavigateAction.NavigateToMeetings -> {
finish()
}
lifecycleScope.launch {
viewModel.navigateAction.collect {
when (it) {
MeetingCreationNavigateAction.NavigateToMeetings -> {
finish()
}

is MeetingCreationNavigateAction.NavigateToMeetingJoin -> {
startActivity(MeetingJoinActivity.getIntent(it.inviteCode, this))
finish()
is MeetingCreationNavigateAction.NavigateToMeetingJoin -> {
startActivity(MeetingJoinActivity.getIntent(it.inviteCode, this@MeetingCreationActivity))
finish()
}
}
}
}
Expand Down
Loading