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

네트워트 연결 상태 Repository로 구현 #85

Merged
merged 3 commits into from
Dec 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions data/src/main/AndroidManifest.xml
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" />
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

data 모듈의 매니페스트에 추가하는 게 맞나 싶어서 팀원분들이랑 얘기 나눠봤는데, data도 안드로이드 모듈이기 때문에 전혀 이상하지 않다는 의견을 받았습니다

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

의견 이전에 data 모듈이 domain밖에 알고있지 않고, domain은 자바/코틀린 라이브러리 모듈이기 때문에 매니페스트가 없어서 data 모듈의 매니페스트에 밖에 정의할 수 없다!

</manifest>
22 changes: 22 additions & 0 deletions data/src/main/java/com/whyranoid/data/di/ConnectionModule.kt
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
Copy link
Member

Choose a reason for hiding this comment

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

isNetworkConnected는 어떨까요?
Context의 범위가 너무너무 커서 조금 더 구체적으로 명시해주면 좋을 것 같아요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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
Expand Up @@ -25,13 +25,20 @@ internal class CreateRunningPostFragment :
observeState()
}

override fun onDestroyView() {
viewModel.finishObservingNetworkState()
super.onDestroyView()
}

private fun initViews() {
binding.vm = viewModel
binding.selectedRunningHistory = viewModel.selectedRunningHistory
binding.executePendingBindings()
setUpMenu()
}

private fun observeState() {
viewModel.startObservingNetworkState()
viewLifecycleOwner.repeatWhenUiStarted {
viewModel.createPostState.collect { createPostState ->
when (createPostState) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -19,6 +24,7 @@ import javax.inject.Inject
@HiltViewModel
class CreateRunningPostViewModel @Inject constructor(
private val createRunningPostUseCase: CreateRunningPostUseCase,
private val networkRepository: NetworkRepository,
savedState: SavedStateHandle
) : ViewModel() {

Expand All @@ -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 ->
Copy link
Member

Choose a reason for hiding this comment

The 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),
Expand All @@ -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)
Copy link
Member

Choose a reason for hiding this comment

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

네트워크 연결관련된 flow가 감지되었을 때 0.5초이상 동일한 연결상태일 경우에만 처리를 해주기 위해서 debounce(500)을 사용하신건가요??

Copy link
Member

Choose a reason for hiding this comment

The 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
Expand All @@ -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"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,15 @@ internal class RunningStartFragment :
observeState()
}

override fun onDestroyView() {
viewModel.finishObservingNetworkState()
super.onDestroyView()
}

private fun initViews() {
binding.vm = viewModel
binding.executePendingBindings()

if ((viewModel.runningDataManager.runningState.value is RunningState.NotRunning).not()) {
runningActivityLauncher.launch(
Intent(
Expand All @@ -78,6 +85,7 @@ internal class RunningStartFragment :
}

private fun observeState() {
viewModel.startObservingNetworkState()
viewLifecycleOwner.repeatWhenUiStarted {
viewModel.runnerCount.collect { runnerCount ->
binding.tvRunnerCountNumber.text = runnerCount.toString()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,40 @@ package com.whyranoid.presentation.runningstart

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.whyranoid.domain.repository.NetworkRepository
import com.whyranoid.domain.usecase.GetRunnerCountUseCase
import com.whyranoid.presentation.util.networkconnection.NetworkConnectionStateHolder
import com.whyranoid.presentation.util.networkconnection.NetworkState
import com.whyranoid.runningdata.RunningDataManager
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class RunningStartViewModel @Inject constructor(
getRunnerCountUseCase: GetRunnerCountUseCase,
networkConnectionStateHolder: NetworkConnectionStateHolder
private val networkRepository: NetworkRepository
) : ViewModel() {

val runningDataManager = RunningDataManager.getInstance()

val networkState = networkConnectionStateHolder.networkState
@OptIn(FlowPreview::class)
val networkState = networkRepository.getNetworkConnectionState()
.map { isConnected ->
if (isConnected) NetworkState.Connection else NetworkState.DisConnection
}
.debounce(500)
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = NetworkState.UnInitialized
)

val runnerCount = getRunnerCountUseCase()

Expand All @@ -37,4 +53,12 @@ class RunningStartViewModel @Inject constructor(
_eventFlow.emit(event)
}
}

fun startObservingNetworkState() {
networkRepository.addNetworkConnectionCallback()
}

fun finishObservingNetworkState() {
networkRepository.removeNetworkConnectionCallback()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

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

요기선 asStateFlow를 사용 안 하신 이유가 있으실까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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
}
}

Expand Down
Loading