일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 안드로이드 레트로핏 crud
- 안드로이드 유닛 테스트
- rxjava hot observable
- 서비스 쓰레드 차이
- jvm 작동 원리
- 클래스
- 자바 다형성
- jvm이란
- 객체
- 2022 플러터 안드로이드 스튜디오
- rxjava cold observable
- android retrofit login
- Rxjava Observable
- 안드로이드 os 구조
- 스택 큐 차이
- 스택 자바 코드
- 멤버변수
- 2022 플러터 설치
- 큐 자바 코드
- 안드로이드 레트로핏 사용법
- 안드로이드 라이선스 종류
- 안드로이드 라이선스
- 플러터 설치 2022
- rxjava disposable
- android ar 개발
- 안드로이드 유닛테스트란
- 서비스 vs 쓰레드
- ar vr 차이
- 안드로이드 유닛 테스트 예시
- ANR이란
- Today
- Total
나만을 위한 블로그
[Android] Flow, LiveData를 써서 네트워크 연결 상태를 확인하는 방법 본문
※ 이 방법이 정답이 아니고 다른 방법들이 많으니 찾아보자
2년 전 자바, 코틀린 별 네트워크 연결 상태를 처리하는 방법에 대해 포스팅한 적이 있다.
https://onlyfor-me-blog.tistory.com/322
그러나 여기서 사용하는 코드가 구식인 것도 있고, Flow, LiveData를 사용해 네트워크 연결 상태를 확인하는 포스팅을 미디엄에서 봐서 새로 포스팅하려고 한다.
내가 본 미디엄 포스팅 링크는 아래에 있다.
먼저 로그 찍기 위해 Timber 라이브러리를 사용했기 때문에 앱 gradle에 아래 문구를 넣어준다. 만약 쓰기 싫다면 건너뛰어도 된다.
implementation 'com.jakewharton.timber:timber:5.0.1'
그리고 Application을 상속한 클래스에 함수를 만들고, 해당 클래스의 onCreate() 안에 이 함수를 넣어서 Timber 사용 준비를 끝낸다.
import android.app.Application
import com.example.kotlinprac.BuildConfig
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber
@HiltAndroidApp
class BaseApplication: Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(AppDebugTree())
}
}
private class AppDebugTree: Timber.DebugTree() {
override fun createStackElementTag(element: StackTraceElement): String =
"${element.fileName}:${element.lineNumber}:${element.methodName}"
}
}
BuildConfig 밑에 빨간 줄이 뜰 텐데 내 패키지명과 본인의 패키지명이 일치하지 않아서 생기는 현상이다.
com.example.kotlinprac 임포트문을 지우고 본인의 BuildConfig 경로를 넣으면 에러는 발생하지 않는다. 그 다음 매니페스트에 등록한다.
<application
android:name=".BaseApplication"
android:allowBackup="true"
.
.
.
이제 코틀린 파일에 유틸 함수를 만든다.
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
@FlowPreview
inline fun <Result> Flow<NetworkStatus>.map(
crossinline onUnavailable: suspend () -> Result,
crossinline onAvailable: suspend () -> Result,
): Flow<Result> = map { status ->
when (status) {
NetworkStatus.Unavailable -> onUnavailable()
NetworkStatus.Available -> onAvailable()
}
}
원래 flatMapConcat {}을 사용한 flatMap()도 있었지만 해당 예제에서 사용하지 않기 때문에 뺐다.
그리고 네트워크 상태 구별 시 사용할 sealed class를 만든다. 이것은 한 파일에 만들었다.
sealed class NetworkStatus {
object Available: NetworkStatus()
object Unavailable: NetworkStatus()
}
sealed class MyState {
object Fetched: MyState()
object Error: MyState()
}
다음으로 네트워크 상태를 추적하는 Tracker 클래스를 만든다.
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import timber.log.Timber
class NetworkStatusTracker(context: Context) {
private val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkStatus = callbackFlow {
val networkStatusCallback = object : ConnectivityManager.NetworkCallback() {
override fun onUnavailable() {
super.onUnavailable()
Timber.e("onUnavailable()")
trySend(NetworkStatus.Unavailable).isSuccess
}
override fun onAvailable(network: Network) {
super.onAvailable(network)
Timber.e("onAvailable()")
trySend(NetworkStatus.Available).isSuccess
}
override fun onLost(network: Network) {
Timber.e("onLost()")
trySend(NetworkStatus.Unavailable).isSuccess
}
}
val request = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
connectivityManager.registerNetworkCallback(request, networkStatusCallback)
awaitClose {
connectivityManager.unregisterNetworkCallback(networkStatusCallback)
}
}.distinctUntilChanged()
}
이 클래스가 있어야 네트워크 연결 상태를 추적해서 각 상태마다 로그캣에 로그를 띄울 수 있다.
그리고 뷰모델을 만들어 네트워크 상태를 받아 LiveData로 변환하게 한다.
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import kotlinx.coroutines.Dispatchers
class NetworkStatusViewModel(
networkStatusTracker: NetworkStatusTracker
): ViewModel() {
val state = networkStatusTracker.networkStatus.map(
onAvailable = { MyState.Fetched },
onUnavailable = { MyState.Error }
).asLiveData(Dispatchers.IO)
}
이제 준비가 끝났으니 액티비티를 수정한다. XML은 저번에 사용한 XML을 그대로 가져왔다.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
tools:context=".networkstate.NetworkStateTestActivity">
<LinearLayout
android:id="@+id/layoutDisconnected"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<ImageView
android:layout_width="200dp"
android:layout_height="200dp"
android:src="@drawable/ic_baseline_wifi_off_24"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="DISCONNECTED"
android:textAllCaps="true"
android:textColor="@android:color/holo_red_dark"
android:textSize="24sp"
android:textStyle="bold"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="40dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="40dp"
android:gravity="center"
android:text="인터넷에 연결되어 있지 않습니다"
android:textSize="18sp"/>
</LinearLayout>
<LinearLayout
android:id="@+id/layoutConnected"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone"
android:gravity="center">
<ImageView
android:layout_width="200dp"
android:layout_height="200dp"
android:src="@drawable/ic_baseline_wifi_24"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="DISCONNECTED"
android:textAllCaps="true"
android:textColor="@android:color/holo_green_dark"
android:textSize="24sp"
android:textStyle="bold"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="40dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="40dp"
android:gravity="center"
android:text="인터넷에 연결되어 있습니다"
android:textSize="18sp"/>
</LinearLayout>
</FrameLayout >
</layout>
import android.os.Bundle
import android.view.View.GONE
import android.view.View.VISIBLE
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.example.kotlinprac.databinding.ActivityNetworkStateTestBinding
class NetworkStateTestActivity : AppCompatActivity() {
private val binding: ActivityNetworkStateTestBinding by lazy {
ActivityNetworkStateTestBinding.inflate(layoutInflater)
}
private val viewModel: NetworkStatusViewModel by lazy {
ViewModelProvider(this, object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val networkStatusTracker = NetworkStatusTracker(this@NetworkStateTestActivity)
return NetworkStatusViewModel(networkStatusTracker) as T
}
})[NetworkStatusViewModel::class.java]
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
viewModel.state.observe(this) { state ->
when (state) {
MyState.Fetched -> {
binding.layoutConnected.visibility = VISIBLE
binding.layoutDisconnected.visibility = GONE
}
MyState.Error -> {
binding.layoutConnected.visibility = GONE
binding.layoutDisconnected.visibility = VISIBLE
}
}
}
}
}
뷰모델을 초기화하고, 뷰모델의 LiveData를 액티비티에서 관찰해서 받는 값(Fetched, Error)에 따라 레이아웃을 보여주거나 숨김 처리한다.
앱을 실행하면 지난번과 똑같이 작동한다. 네트워크 연결 상태에 따라 로그캣에 다른 메시지가 표시되는 것도 볼 수 있다.
'Android' 카테고리의 다른 글
[Android] 리사이클러뷰가 있는 레이아웃의 성능 향상 - 1 - (0) | 2023.01.26 |
---|---|
[Android] hilt 사용 시 Cannot create an instance of class ViewModel 에러 해결 (0) | 2023.01.25 |
[Android] 앱 시작 시간의 종류 (Cold / Warm / Hot Start) (0) | 2023.01.19 |
[Android] Jetpack Navigation, Room DB, Flow 같이 사용하기 - 4 - (0) | 2022.12.13 |
[Android] Jetpack Navigation, Room DB, Flow 같이 사용하기 - 3 - (0) | 2022.12.13 |