-
Notifications
You must be signed in to change notification settings - Fork 5
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
네트워트 연결 상태 Repository로 구현 #85
Changes from all commits
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 |
---|---|---|
@@ -1,4 +1,5 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> | ||
|
||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | ||
</manifest> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.whyranoid.data.di | ||
|
||
import android.content.Context | ||
import com.whyranoid.data.running.NetworkRepositoryImpl | ||
import com.whyranoid.domain.repository.NetworkRepository | ||
import dagger.Module | ||
import dagger.Provides | ||
import dagger.hilt.InstallIn | ||
import dagger.hilt.android.qualifiers.ApplicationContext | ||
import dagger.hilt.components.SingletonComponent | ||
import javax.inject.Singleton | ||
|
||
@Module | ||
@InstallIn(SingletonComponent::class) | ||
class ConnectionModule { | ||
|
||
@Provides | ||
@Singleton | ||
fun provideNetworkRepository(@ApplicationContext context: Context): NetworkRepository { | ||
return NetworkRepositoryImpl(context) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package com.whyranoid.data.running | ||
|
||
import android.content.BroadcastReceiver | ||
import android.content.Context | ||
import android.content.Intent | ||
import android.content.IntentFilter | ||
import android.net.ConnectivityManager | ||
import android.net.Network | ||
import android.net.NetworkCapabilities | ||
import android.os.Build | ||
import com.whyranoid.domain.repository.NetworkRepository | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.asStateFlow | ||
|
||
class NetworkRepositoryImpl(private val context: Context) : | ||
NetworkRepository { | ||
|
||
private val _networkConnectionState = MutableStateFlow(getCurrentNetwork()) | ||
override fun getNetworkConnectionState() = _networkConnectionState.asStateFlow() | ||
|
||
private val connectivityManager = | ||
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager | ||
|
||
private val connectivityCallback = object : ConnectivityManager.NetworkCallback() { | ||
override fun onAvailable(network: Network) { | ||
emitNetworkConnectionState(true) | ||
} | ||
|
||
override fun onLost(network: Network) { | ||
emitNetworkConnectionState(false) | ||
} | ||
} | ||
private val connectivityReceiver = object : BroadcastReceiver() { | ||
override fun onReceive(context: Context?, intent: Intent?) { | ||
if (context != null && context.isConnected) { | ||
emitNetworkConnectionState(true) | ||
} else { | ||
emitNetworkConnectionState(false) | ||
} | ||
} | ||
} | ||
|
||
override fun addNetworkConnectionCallback() { | ||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { | ||
connectivityManager.registerDefaultNetworkCallback(connectivityCallback) | ||
} else { | ||
context.registerReceiver( | ||
connectivityReceiver, | ||
IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION) | ||
) | ||
} | ||
} | ||
|
||
override fun removeNetworkConnectionCallback() { | ||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { | ||
connectivityManager.unregisterNetworkCallback(connectivityCallback) | ||
} else { | ||
context.unregisterReceiver(connectivityReceiver) | ||
} | ||
} | ||
|
||
private fun getCurrentNetwork(): Boolean { | ||
return try { | ||
val networkCapabilities = | ||
connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) | ||
networkCapabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) == true | ||
} catch (e: Exception) { | ||
false | ||
} | ||
} | ||
|
||
private fun emitNetworkConnectionState(currentState: Boolean) { | ||
_networkConnectionState.value = currentState | ||
} | ||
} | ||
|
||
val Context.isConnected: Boolean | ||
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. isNetworkConnected는 어떨까요? 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. 네 반영하겠습니다! |
||
get() = (getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager)?.activeNetworkInfo?.isConnected == true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.whyranoid.domain.repository | ||
|
||
import kotlinx.coroutines.flow.StateFlow | ||
|
||
interface NetworkRepository { | ||
|
||
fun getNetworkConnectionState(): StateFlow<Boolean> | ||
|
||
fun addNetworkConnectionCallback() | ||
|
||
fun removeNetworkConnectionCallback() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,14 +3,19 @@ package com.whyranoid.presentation.community.runningpost | |
import androidx.lifecycle.SavedStateHandle | ||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.viewModelScope | ||
import com.whyranoid.domain.repository.NetworkRepository | ||
import com.whyranoid.domain.usecase.CreateRunningPostUseCase | ||
import com.whyranoid.presentation.model.RunningHistoryUiModel | ||
import com.whyranoid.presentation.model.UiState | ||
import com.whyranoid.presentation.util.networkconnection.NetworkState | ||
import dagger.hilt.android.lifecycle.HiltViewModel | ||
import kotlinx.coroutines.FlowPreview | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.SharingStarted | ||
import kotlinx.coroutines.flow.StateFlow | ||
import kotlinx.coroutines.flow.asStateFlow | ||
import kotlinx.coroutines.flow.combine | ||
import kotlinx.coroutines.flow.debounce | ||
import kotlinx.coroutines.flow.map | ||
import kotlinx.coroutines.flow.stateIn | ||
import kotlinx.coroutines.launch | ||
|
@@ -19,6 +24,7 @@ import javax.inject.Inject | |
@HiltViewModel | ||
class CreateRunningPostViewModel @Inject constructor( | ||
private val createRunningPostUseCase: CreateRunningPostUseCase, | ||
private val networkRepository: NetworkRepository, | ||
savedState: SavedStateHandle | ||
) : ViewModel() { | ||
|
||
|
@@ -27,8 +33,8 @@ class CreateRunningPostViewModel @Inject constructor( | |
val runningPostContent = MutableStateFlow<String?>(null) | ||
|
||
val createPostButtonEnableState: StateFlow<Boolean> | ||
get() = runningPostContent.map { content -> | ||
content.isNullOrBlank().not() | ||
get() = runningPostContent.combine(networkState) { content, networkState -> | ||
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. 오.. 네트워크 연결 상태에 따라서 아예 버튼을 비활성화하는 것도 좋네요! |
||
content.isNullOrBlank().not() && networkState is NetworkState.Connection | ||
}.stateIn( | ||
initialValue = false, | ||
started = SharingStarted.WhileSubscribed(5000), | ||
|
@@ -39,6 +45,18 @@ class CreateRunningPostViewModel @Inject constructor( | |
val createPostState: StateFlow<UiState<Boolean>> | ||
get() = _createPostState.asStateFlow() | ||
|
||
@OptIn(FlowPreview::class) | ||
val networkState = networkRepository.getNetworkConnectionState() | ||
.map { isConnected -> | ||
if (isConnected) NetworkState.Connection else NetworkState.DisConnection | ||
} | ||
.debounce(500) | ||
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. 네트워크 연결관련된 flow가 감지되었을 때 0.5초이상 동일한 연결상태일 경우에만 처리를 해주기 위해서 debounce(500)을 사용하신건가요?? 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. 굉장한 디테일이네요 굳굳 |
||
.stateIn( | ||
scope = viewModelScope, | ||
started = SharingStarted.WhileSubscribed(5000), | ||
initialValue = NetworkState.UnInitialized | ||
) | ||
|
||
fun createRunningPost() { | ||
viewModelScope.launch { | ||
_createPostState.value = UiState.Loading | ||
|
@@ -56,6 +74,14 @@ class CreateRunningPostViewModel @Inject constructor( | |
} | ||
} | ||
|
||
fun startObservingNetworkState() { | ||
networkRepository.addNetworkConnectionCallback() | ||
} | ||
|
||
fun finishObservingNetworkState() { | ||
networkRepository.removeNetworkConnectionCallback() | ||
} | ||
|
||
companion object { | ||
private const val RUNNING_HISTORY_SAFE_ARGS_KEY = "selectedRunningHistory" | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,27 +5,68 @@ import android.content.Context | |
import android.content.Intent | ||
import android.content.IntentFilter | ||
import android.net.ConnectivityManager | ||
import android.net.ConnectivityManager.NetworkCallback | ||
import android.net.Network | ||
import android.net.NetworkCapabilities | ||
import android.os.Build | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.StateFlow | ||
import kotlinx.coroutines.flow.asStateFlow | ||
|
||
class NetworkConnectionStateHolder(context: Context) { | ||
class NetworkConnectionStateHolder(private val context: Context) { | ||
|
||
private val _networkState = MutableStateFlow<NetworkState>(NetworkState.UnInitialized) | ||
val networkState: StateFlow<NetworkState> get() = _networkState.asStateFlow() | ||
private val _networkConnectionState = MutableStateFlow<NetworkState>(NetworkState.UnInitialized) | ||
val networkConnectionState: StateFlow<NetworkState> = _networkConnectionState | ||
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. 요기선 asStateFlow를 사용 안 하신 이유가 있으실까요? 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. 혹시 이렇게 정의하면 _networkConnectionState의 subscriberCount가 잡힐까 해서 바꿨었는데 어떻게 해도 잡히지 않았어서 asStateFlow로 바꿔보도록 하겠습니다 |
||
|
||
private val networkReceiver = object : BroadcastReceiver() { | ||
override fun onReceive(context: Context, intent: Intent) { | ||
_networkState.value = | ||
if (context.isConnected) NetworkState.Connection else NetworkState.DisConnection | ||
private val connectivityManager = | ||
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager | ||
private val connectivityCallback = object : NetworkCallback() { | ||
override fun onAvailable(network: Network) { | ||
emitNetworkConnectionState(NetworkState.Connection) | ||
} | ||
|
||
override fun onLost(network: Network) { | ||
emitNetworkConnectionState(NetworkState.DisConnection) | ||
} | ||
} | ||
private val connectivityReceiver = object : BroadcastReceiver() { | ||
override fun onReceive(context: Context?, intent: Intent?) { | ||
if (context != null && context.isConnected) { | ||
emitNetworkConnectionState(NetworkState.Connection) | ||
} else { | ||
emitNetworkConnectionState(NetworkState.DisConnection) | ||
} | ||
} | ||
} | ||
|
||
fun startObservingState() { | ||
val networkCapabilities = | ||
connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) | ||
if (networkCapabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) == true) { | ||
emitNetworkConnectionState(NetworkState.Connection) | ||
} else { | ||
emitNetworkConnectionState(NetworkState.DisConnection) | ||
} | ||
|
||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { | ||
connectivityManager.registerDefaultNetworkCallback(connectivityCallback) | ||
} else { | ||
context.registerReceiver( | ||
connectivityReceiver, | ||
IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION) | ||
) | ||
} | ||
} | ||
|
||
fun finishObservingState() { | ||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { | ||
connectivityManager.unregisterNetworkCallback(connectivityCallback) | ||
} else { | ||
context.unregisterReceiver(connectivityReceiver) | ||
} | ||
} | ||
|
||
init { | ||
context.registerReceiver( | ||
networkReceiver, | ||
IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION) | ||
) | ||
private fun emitNetworkConnectionState(currentState: NetworkState) { | ||
_networkConnectionState.value = currentState | ||
} | ||
} | ||
|
||
|
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.
data 모듈의 매니페스트에 추가하는 게 맞나 싶어서 팀원분들이랑 얘기 나눠봤는데, data도 안드로이드 모듈이기 때문에 전혀 이상하지 않다는 의견을 받았습니다
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.
의견 이전에 data 모듈이 domain밖에 알고있지 않고, domain은 자바/코틀린 라이브러리 모듈이기 때문에 매니페스트가 없어서 data 모듈의 매니페스트에 밖에 정의할 수 없다!