From a38d451b4890e7646312379eee9ab6560883863f Mon Sep 17 00:00:00 2001 From: bngsh Date: Thu, 8 Dec 2022 12:11:28 +0900 Subject: [PATCH 1/3] =?UTF-8?q?:sparkles:=20=EB=84=A4=ED=8A=B8=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=20=EC=97=B0=EA=B2=B0=20=EC=83=81=ED=83=9C=20=EA=B0=80?= =?UTF-8?q?=EC=A0=B8=EC=98=A4=EB=8A=94=20=EB=84=A4=ED=8A=B8=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=20=EC=BD=9C=EB=B0=B1=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runningstart/RunningStartFragment.kt | 8 +++ .../runningstart/RunningStartViewModel.kt | 24 ++++++- .../NetworkConnectionStateHolder.kt | 67 +++++++++++++++---- 3 files changed, 84 insertions(+), 15 deletions(-) diff --git a/presentation/src/main/java/com/whyranoid/presentation/runningstart/RunningStartFragment.kt b/presentation/src/main/java/com/whyranoid/presentation/runningstart/RunningStartFragment.kt index 0a72a22c..557b3791 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/runningstart/RunningStartFragment.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/runningstart/RunningStartFragment.kt @@ -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( @@ -78,6 +85,7 @@ internal class RunningStartFragment : } private fun observeState() { + viewModel.startObservingNetworkState() viewLifecycleOwner.repeatWhenUiStarted { viewModel.runnerCount.collect { runnerCount -> binding.tvRunnerCountNumber.text = runnerCount.toString() diff --git a/presentation/src/main/java/com/whyranoid/presentation/runningstart/RunningStartViewModel.kt b/presentation/src/main/java/com/whyranoid/presentation/runningstart/RunningStartViewModel.kt index 83fd1b16..40f9eb29 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/runningstart/RunningStartViewModel.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/runningstart/RunningStartViewModel.kt @@ -4,22 +4,34 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope 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.stateIn import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class RunningStartViewModel @Inject constructor( getRunnerCountUseCase: GetRunnerCountUseCase, - networkConnectionStateHolder: NetworkConnectionStateHolder + private val networkConnectionStateHolder: NetworkConnectionStateHolder ) : ViewModel() { val runningDataManager = RunningDataManager.getInstance() - val networkState = networkConnectionStateHolder.networkState + @OptIn(FlowPreview::class) + val networkState = networkConnectionStateHolder.networkConnectionState + .debounce(500) + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = NetworkState.UnInitialized + ) val runnerCount = getRunnerCountUseCase() @@ -37,4 +49,12 @@ class RunningStartViewModel @Inject constructor( _eventFlow.emit(event) } } + + fun startObservingNetworkState() { + networkConnectionStateHolder.startObservingState() + } + + fun finishObservingNetworkState() { + networkConnectionStateHolder.finishObservingState() + } } diff --git a/presentation/src/main/java/com/whyranoid/presentation/util/networkconnection/NetworkConnectionStateHolder.kt b/presentation/src/main/java/com/whyranoid/presentation/util/networkconnection/NetworkConnectionStateHolder.kt index c0e5c9d3..7a370fd5 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/util/networkconnection/NetworkConnectionStateHolder.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/util/networkconnection/NetworkConnectionStateHolder.kt @@ -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.UnInitialized) - val networkState: StateFlow get() = _networkState.asStateFlow() + private val _networkConnectionState = MutableStateFlow(NetworkState.UnInitialized) + val networkConnectionState: StateFlow = _networkConnectionState - 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 } } From 3c5e0c1077f54e572c007a31a1661382485d361d Mon Sep 17 00:00:00 2001 From: bngsh Date: Thu, 8 Dec 2022 12:23:06 +0900 Subject: [PATCH 2/3] =?UTF-8?q?:sparkles:=20=EB=84=A4=ED=8A=B8=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=20=EC=97=B0=EA=B2=B0=20=EC=83=81=ED=83=9C=EB=A5=BC=20?= =?UTF-8?q?repository=EB=A1=9C=20=EB=85=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/src/main/AndroidManifest.xml | 1 + .../com/whyranoid/data/di/ConnectionModule.kt | 22 ++++++ .../data/running/NetworkRepositoryImpl.kt | 78 +++++++++++++++++++ .../domain/repository/NetworkRepository.kt | 12 +++ .../runningstart/RunningStartViewModel.kt | 14 ++-- 5 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 data/src/main/java/com/whyranoid/data/di/ConnectionModule.kt create mode 100644 data/src/main/java/com/whyranoid/data/running/NetworkRepositoryImpl.kt create mode 100644 domain/src/main/java/com/whyranoid/domain/repository/NetworkRepository.kt diff --git a/data/src/main/AndroidManifest.xml b/data/src/main/AndroidManifest.xml index a5918e68..8c4c9826 100644 --- a/data/src/main/AndroidManifest.xml +++ b/data/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ + \ No newline at end of file diff --git a/data/src/main/java/com/whyranoid/data/di/ConnectionModule.kt b/data/src/main/java/com/whyranoid/data/di/ConnectionModule.kt new file mode 100644 index 00000000..491dfd96 --- /dev/null +++ b/data/src/main/java/com/whyranoid/data/di/ConnectionModule.kt @@ -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) + } +} diff --git a/data/src/main/java/com/whyranoid/data/running/NetworkRepositoryImpl.kt b/data/src/main/java/com/whyranoid/data/running/NetworkRepositoryImpl.kt new file mode 100644 index 00000000..bf3d115d --- /dev/null +++ b/data/src/main/java/com/whyranoid/data/running/NetworkRepositoryImpl.kt @@ -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 + get() = (getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager)?.activeNetworkInfo?.isConnected == true diff --git a/domain/src/main/java/com/whyranoid/domain/repository/NetworkRepository.kt b/domain/src/main/java/com/whyranoid/domain/repository/NetworkRepository.kt new file mode 100644 index 00000000..d5ffa3da --- /dev/null +++ b/domain/src/main/java/com/whyranoid/domain/repository/NetworkRepository.kt @@ -0,0 +1,12 @@ +package com.whyranoid.domain.repository + +import kotlinx.coroutines.flow.StateFlow + +interface NetworkRepository { + + fun getNetworkConnectionState(): StateFlow + + fun addNetworkConnectionCallback() + + fun removeNetworkConnectionCallback() +} diff --git a/presentation/src/main/java/com/whyranoid/presentation/runningstart/RunningStartViewModel.kt b/presentation/src/main/java/com/whyranoid/presentation/runningstart/RunningStartViewModel.kt index 40f9eb29..7c030546 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/runningstart/RunningStartViewModel.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/runningstart/RunningStartViewModel.kt @@ -2,8 +2,8 @@ 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 @@ -12,6 +12,7 @@ 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 @@ -19,13 +20,16 @@ import javax.inject.Inject @HiltViewModel class RunningStartViewModel @Inject constructor( getRunnerCountUseCase: GetRunnerCountUseCase, - private val networkConnectionStateHolder: NetworkConnectionStateHolder + private val networkRepository: NetworkRepository ) : ViewModel() { val runningDataManager = RunningDataManager.getInstance() @OptIn(FlowPreview::class) - val networkState = networkConnectionStateHolder.networkConnectionState + val networkState = networkRepository.getNetworkConnectionState() + .map { isConnected -> + if (isConnected) NetworkState.Connection else NetworkState.DisConnection + } .debounce(500) .stateIn( scope = viewModelScope, @@ -51,10 +55,10 @@ class RunningStartViewModel @Inject constructor( } fun startObservingNetworkState() { - networkConnectionStateHolder.startObservingState() + networkRepository.addNetworkConnectionCallback() } fun finishObservingNetworkState() { - networkConnectionStateHolder.finishObservingState() + networkRepository.removeNetworkConnectionCallback() } } From cdfc9a261b76d3839869610ea5d0fd7145add3b9 Mon Sep 17 00:00:00 2001 From: bngsh Date: Thu, 8 Dec 2022 04:16:50 +0900 Subject: [PATCH 3/3] =?UTF-8?q?:sparkles:=20=EB=84=A4=ED=8A=B8=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=20=EC=97=B0=EA=B2=B0=20=EC=83=81=ED=83=9C=20CreateRun?= =?UTF-8?q?ningPostFragment=EC=97=90=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runningpost/CreateRunningPostFragment.kt | 7 +++++ .../runningpost/CreateRunningPostViewModel.kt | 30 +++++++++++++++++-- .../layout/fragment_create_running_post.xml | 15 +++++++++- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/presentation/src/main/java/com/whyranoid/presentation/community/runningpost/CreateRunningPostFragment.kt b/presentation/src/main/java/com/whyranoid/presentation/community/runningpost/CreateRunningPostFragment.kt index 28a6ca93..2507358f 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/community/runningpost/CreateRunningPostFragment.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/community/runningpost/CreateRunningPostFragment.kt @@ -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) { diff --git a/presentation/src/main/java/com/whyranoid/presentation/community/runningpost/CreateRunningPostViewModel.kt b/presentation/src/main/java/com/whyranoid/presentation/community/runningpost/CreateRunningPostViewModel.kt index 1b731e49..38b78c4f 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/community/runningpost/CreateRunningPostViewModel.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/community/runningpost/CreateRunningPostViewModel.kt @@ -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(null) val createPostButtonEnableState: StateFlow - get() = runningPostContent.map { content -> - content.isNullOrBlank().not() + get() = runningPostContent.combine(networkState) { content, networkState -> + 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> get() = _createPostState.asStateFlow() + @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 + ) + 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" } diff --git a/presentation/src/main/res/layout/fragment_create_running_post.xml b/presentation/src/main/res/layout/fragment_create_running_post.xml index 26fbae98..6e786f2a 100644 --- a/presentation/src/main/res/layout/fragment_create_running_post.xml +++ b/presentation/src/main/res/layout/fragment_create_running_post.xml @@ -18,6 +18,19 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + +