Skip to content

Commit fd2a23a

Browse files
authored
🔀 #85 from boostcampwm-2022/feat/network_connectivity
네트워트 연결 상태 Repository로 구현
2 parents 4ec59f0 + cdfc9a2 commit fd2a23a

File tree

10 files changed

+251
-19
lines changed

10 files changed

+251
-19
lines changed

data/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
33

4+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
45
</manifest>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.whyranoid.data.di
2+
3+
import android.content.Context
4+
import com.whyranoid.data.running.NetworkRepositoryImpl
5+
import com.whyranoid.domain.repository.NetworkRepository
6+
import dagger.Module
7+
import dagger.Provides
8+
import dagger.hilt.InstallIn
9+
import dagger.hilt.android.qualifiers.ApplicationContext
10+
import dagger.hilt.components.SingletonComponent
11+
import javax.inject.Singleton
12+
13+
@Module
14+
@InstallIn(SingletonComponent::class)
15+
class ConnectionModule {
16+
17+
@Provides
18+
@Singleton
19+
fun provideNetworkRepository(@ApplicationContext context: Context): NetworkRepository {
20+
return NetworkRepositoryImpl(context)
21+
}
22+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.whyranoid.data.running
2+
3+
import android.content.BroadcastReceiver
4+
import android.content.Context
5+
import android.content.Intent
6+
import android.content.IntentFilter
7+
import android.net.ConnectivityManager
8+
import android.net.Network
9+
import android.net.NetworkCapabilities
10+
import android.os.Build
11+
import com.whyranoid.domain.repository.NetworkRepository
12+
import kotlinx.coroutines.flow.MutableStateFlow
13+
import kotlinx.coroutines.flow.asStateFlow
14+
15+
class NetworkRepositoryImpl(private val context: Context) :
16+
NetworkRepository {
17+
18+
private val _networkConnectionState = MutableStateFlow(getCurrentNetwork())
19+
override fun getNetworkConnectionState() = _networkConnectionState.asStateFlow()
20+
21+
private val connectivityManager =
22+
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
23+
24+
private val connectivityCallback = object : ConnectivityManager.NetworkCallback() {
25+
override fun onAvailable(network: Network) {
26+
emitNetworkConnectionState(true)
27+
}
28+
29+
override fun onLost(network: Network) {
30+
emitNetworkConnectionState(false)
31+
}
32+
}
33+
private val connectivityReceiver = object : BroadcastReceiver() {
34+
override fun onReceive(context: Context?, intent: Intent?) {
35+
if (context != null && context.isConnected) {
36+
emitNetworkConnectionState(true)
37+
} else {
38+
emitNetworkConnectionState(false)
39+
}
40+
}
41+
}
42+
43+
override fun addNetworkConnectionCallback() {
44+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
45+
connectivityManager.registerDefaultNetworkCallback(connectivityCallback)
46+
} else {
47+
context.registerReceiver(
48+
connectivityReceiver,
49+
IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
50+
)
51+
}
52+
}
53+
54+
override fun removeNetworkConnectionCallback() {
55+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
56+
connectivityManager.unregisterNetworkCallback(connectivityCallback)
57+
} else {
58+
context.unregisterReceiver(connectivityReceiver)
59+
}
60+
}
61+
62+
private fun getCurrentNetwork(): Boolean {
63+
return try {
64+
val networkCapabilities =
65+
connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
66+
networkCapabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) == true
67+
} catch (e: Exception) {
68+
false
69+
}
70+
}
71+
72+
private fun emitNetworkConnectionState(currentState: Boolean) {
73+
_networkConnectionState.value = currentState
74+
}
75+
}
76+
77+
val Context.isConnected: Boolean
78+
get() = (getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager)?.activeNetworkInfo?.isConnected == true
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.whyranoid.domain.repository
2+
3+
import kotlinx.coroutines.flow.StateFlow
4+
5+
interface NetworkRepository {
6+
7+
fun getNetworkConnectionState(): StateFlow<Boolean>
8+
9+
fun addNetworkConnectionCallback()
10+
11+
fun removeNetworkConnectionCallback()
12+
}

presentation/src/main/java/com/whyranoid/presentation/community/runningpost/CreateRunningPostFragment.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,20 @@ internal class CreateRunningPostFragment :
2525
observeState()
2626
}
2727

28+
override fun onDestroyView() {
29+
viewModel.finishObservingNetworkState()
30+
super.onDestroyView()
31+
}
32+
2833
private fun initViews() {
2934
binding.vm = viewModel
3035
binding.selectedRunningHistory = viewModel.selectedRunningHistory
36+
binding.executePendingBindings()
3137
setUpMenu()
3238
}
3339

3440
private fun observeState() {
41+
viewModel.startObservingNetworkState()
3542
viewLifecycleOwner.repeatWhenUiStarted {
3643
viewModel.createPostState.collect { createPostState ->
3744
when (createPostState) {

presentation/src/main/java/com/whyranoid/presentation/community/runningpost/CreateRunningPostViewModel.kt

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,19 @@ package com.whyranoid.presentation.community.runningpost
33
import androidx.lifecycle.SavedStateHandle
44
import androidx.lifecycle.ViewModel
55
import androidx.lifecycle.viewModelScope
6+
import com.whyranoid.domain.repository.NetworkRepository
67
import com.whyranoid.domain.usecase.CreateRunningPostUseCase
78
import com.whyranoid.presentation.model.RunningHistoryUiModel
89
import com.whyranoid.presentation.model.UiState
10+
import com.whyranoid.presentation.util.networkconnection.NetworkState
911
import dagger.hilt.android.lifecycle.HiltViewModel
12+
import kotlinx.coroutines.FlowPreview
1013
import kotlinx.coroutines.flow.MutableStateFlow
1114
import kotlinx.coroutines.flow.SharingStarted
1215
import kotlinx.coroutines.flow.StateFlow
1316
import kotlinx.coroutines.flow.asStateFlow
17+
import kotlinx.coroutines.flow.combine
18+
import kotlinx.coroutines.flow.debounce
1419
import kotlinx.coroutines.flow.map
1520
import kotlinx.coroutines.flow.stateIn
1621
import kotlinx.coroutines.launch
@@ -19,6 +24,7 @@ import javax.inject.Inject
1924
@HiltViewModel
2025
class CreateRunningPostViewModel @Inject constructor(
2126
private val createRunningPostUseCase: CreateRunningPostUseCase,
27+
private val networkRepository: NetworkRepository,
2228
savedState: SavedStateHandle
2329
) : ViewModel() {
2430

@@ -27,8 +33,8 @@ class CreateRunningPostViewModel @Inject constructor(
2733
val runningPostContent = MutableStateFlow<String?>(null)
2834

2935
val createPostButtonEnableState: StateFlow<Boolean>
30-
get() = runningPostContent.map { content ->
31-
content.isNullOrBlank().not()
36+
get() = runningPostContent.combine(networkState) { content, networkState ->
37+
content.isNullOrBlank().not() && networkState is NetworkState.Connection
3238
}.stateIn(
3339
initialValue = false,
3440
started = SharingStarted.WhileSubscribed(5000),
@@ -39,6 +45,18 @@ class CreateRunningPostViewModel @Inject constructor(
3945
val createPostState: StateFlow<UiState<Boolean>>
4046
get() = _createPostState.asStateFlow()
4147

48+
@OptIn(FlowPreview::class)
49+
val networkState = networkRepository.getNetworkConnectionState()
50+
.map { isConnected ->
51+
if (isConnected) NetworkState.Connection else NetworkState.DisConnection
52+
}
53+
.debounce(500)
54+
.stateIn(
55+
scope = viewModelScope,
56+
started = SharingStarted.WhileSubscribed(5000),
57+
initialValue = NetworkState.UnInitialized
58+
)
59+
4260
fun createRunningPost() {
4361
viewModelScope.launch {
4462
_createPostState.value = UiState.Loading
@@ -56,6 +74,14 @@ class CreateRunningPostViewModel @Inject constructor(
5674
}
5775
}
5876

77+
fun startObservingNetworkState() {
78+
networkRepository.addNetworkConnectionCallback()
79+
}
80+
81+
fun finishObservingNetworkState() {
82+
networkRepository.removeNetworkConnectionCallback()
83+
}
84+
5985
companion object {
6086
private const val RUNNING_HISTORY_SAFE_ARGS_KEY = "selectedRunningHistory"
6187
}

presentation/src/main/java/com/whyranoid/presentation/runningstart/RunningStartFragment.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,15 @@ internal class RunningStartFragment :
6565
observeState()
6666
}
6767

68+
override fun onDestroyView() {
69+
viewModel.finishObservingNetworkState()
70+
super.onDestroyView()
71+
}
72+
6873
private fun initViews() {
6974
binding.vm = viewModel
75+
binding.executePendingBindings()
76+
7077
if ((viewModel.runningDataManager.runningState.value is RunningState.NotRunning).not()) {
7178
runningActivityLauncher.launch(
7279
Intent(
@@ -78,6 +85,7 @@ internal class RunningStartFragment :
7885
}
7986

8087
private fun observeState() {
88+
viewModel.startObservingNetworkState()
8189
viewLifecycleOwner.repeatWhenUiStarted {
8290
viewModel.runnerCount.collect { runnerCount ->
8391
binding.tvRunnerCountNumber.text = runnerCount.toString()

presentation/src/main/java/com/whyranoid/presentation/runningstart/RunningStartViewModel.kt

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,40 @@ package com.whyranoid.presentation.runningstart
22

33
import androidx.lifecycle.ViewModel
44
import androidx.lifecycle.viewModelScope
5+
import com.whyranoid.domain.repository.NetworkRepository
56
import com.whyranoid.domain.usecase.GetRunnerCountUseCase
6-
import com.whyranoid.presentation.util.networkconnection.NetworkConnectionStateHolder
7+
import com.whyranoid.presentation.util.networkconnection.NetworkState
78
import com.whyranoid.runningdata.RunningDataManager
89
import dagger.hilt.android.lifecycle.HiltViewModel
10+
import kotlinx.coroutines.FlowPreview
911
import kotlinx.coroutines.flow.MutableSharedFlow
12+
import kotlinx.coroutines.flow.SharingStarted
1013
import kotlinx.coroutines.flow.asSharedFlow
14+
import kotlinx.coroutines.flow.debounce
15+
import kotlinx.coroutines.flow.map
16+
import kotlinx.coroutines.flow.stateIn
1117
import kotlinx.coroutines.launch
1218
import javax.inject.Inject
1319

1420
@HiltViewModel
1521
class RunningStartViewModel @Inject constructor(
1622
getRunnerCountUseCase: GetRunnerCountUseCase,
17-
networkConnectionStateHolder: NetworkConnectionStateHolder
23+
private val networkRepository: NetworkRepository
1824
) : ViewModel() {
1925

2026
val runningDataManager = RunningDataManager.getInstance()
2127

22-
val networkState = networkConnectionStateHolder.networkState
28+
@OptIn(FlowPreview::class)
29+
val networkState = networkRepository.getNetworkConnectionState()
30+
.map { isConnected ->
31+
if (isConnected) NetworkState.Connection else NetworkState.DisConnection
32+
}
33+
.debounce(500)
34+
.stateIn(
35+
scope = viewModelScope,
36+
started = SharingStarted.WhileSubscribed(5000),
37+
initialValue = NetworkState.UnInitialized
38+
)
2339

2440
val runnerCount = getRunnerCountUseCase()
2541

@@ -37,4 +53,12 @@ class RunningStartViewModel @Inject constructor(
3753
_eventFlow.emit(event)
3854
}
3955
}
56+
57+
fun startObservingNetworkState() {
58+
networkRepository.addNetworkConnectionCallback()
59+
}
60+
61+
fun finishObservingNetworkState() {
62+
networkRepository.removeNetworkConnectionCallback()
63+
}
4064
}

presentation/src/main/java/com/whyranoid/presentation/util/networkconnection/NetworkConnectionStateHolder.kt

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,68 @@ import android.content.Context
55
import android.content.Intent
66
import android.content.IntentFilter
77
import android.net.ConnectivityManager
8+
import android.net.ConnectivityManager.NetworkCallback
9+
import android.net.Network
10+
import android.net.NetworkCapabilities
11+
import android.os.Build
812
import kotlinx.coroutines.flow.MutableStateFlow
913
import kotlinx.coroutines.flow.StateFlow
10-
import kotlinx.coroutines.flow.asStateFlow
1114

12-
class NetworkConnectionStateHolder(context: Context) {
15+
class NetworkConnectionStateHolder(private val context: Context) {
1316

14-
private val _networkState = MutableStateFlow<NetworkState>(NetworkState.UnInitialized)
15-
val networkState: StateFlow<NetworkState> get() = _networkState.asStateFlow()
17+
private val _networkConnectionState = MutableStateFlow<NetworkState>(NetworkState.UnInitialized)
18+
val networkConnectionState: StateFlow<NetworkState> = _networkConnectionState
1619

17-
private val networkReceiver = object : BroadcastReceiver() {
18-
override fun onReceive(context: Context, intent: Intent) {
19-
_networkState.value =
20-
if (context.isConnected) NetworkState.Connection else NetworkState.DisConnection
20+
private val connectivityManager =
21+
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
22+
private val connectivityCallback = object : NetworkCallback() {
23+
override fun onAvailable(network: Network) {
24+
emitNetworkConnectionState(NetworkState.Connection)
25+
}
26+
27+
override fun onLost(network: Network) {
28+
emitNetworkConnectionState(NetworkState.DisConnection)
29+
}
30+
}
31+
private val connectivityReceiver = object : BroadcastReceiver() {
32+
override fun onReceive(context: Context?, intent: Intent?) {
33+
if (context != null && context.isConnected) {
34+
emitNetworkConnectionState(NetworkState.Connection)
35+
} else {
36+
emitNetworkConnectionState(NetworkState.DisConnection)
37+
}
38+
}
39+
}
40+
41+
fun startObservingState() {
42+
val networkCapabilities =
43+
connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
44+
if (networkCapabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) == true) {
45+
emitNetworkConnectionState(NetworkState.Connection)
46+
} else {
47+
emitNetworkConnectionState(NetworkState.DisConnection)
48+
}
49+
50+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
51+
connectivityManager.registerDefaultNetworkCallback(connectivityCallback)
52+
} else {
53+
context.registerReceiver(
54+
connectivityReceiver,
55+
IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
56+
)
57+
}
58+
}
59+
60+
fun finishObservingState() {
61+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
62+
connectivityManager.unregisterNetworkCallback(connectivityCallback)
63+
} else {
64+
context.unregisterReceiver(connectivityReceiver)
2165
}
2266
}
2367

24-
init {
25-
context.registerReceiver(
26-
networkReceiver,
27-
IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
28-
)
68+
private fun emitNetworkConnectionState(currentState: NetworkState) {
69+
_networkConnectionState.value = currentState
2970
}
3071
}
3172

0 commit comments

Comments
 (0)