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

[feat] 네트워크 연결 상태 확인 (로딩,에러 뷰) #103

Merged
merged 17 commits into from
Mar 2, 2024
Merged
Show file tree
Hide file tree
Changes from 13 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 app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package sopt.motivoo.data.datasource.remote
package sopt.motivoo.data.datasource.remote.intercepter

import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package sopt.motivoo.data.datasource.remote.intercepter

import okhttp3.Interceptor
import okhttp3.Response
import sopt.motivoo.data.datasource.remote.listener.NetworkErrorListener
import javax.inject.Inject

class ErrorInterceptor @Inject constructor(
private val networkErrorListener: NetworkErrorListener,
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
var response = chain.proceed(chain.request())

when (response.code) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

현재는 if문을 써도 될 것 같은데 확장성 고려하신 건가요 ??

Copy link
Contributor Author

Choose a reason for hiding this comment

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

넵! 에러코드가 아직 정리가 안된것 같아서 우선 500번대만 처리해 두었습니다!


in SERVER_ERROR_START..SERVER_ERROR_END -> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

이 상수화가 필요한 상수화인지 생각해봐도 좋을 것 같아요! 물론 나쁘다는 의미는 아니지만, HTTP 코드 정도는 어느정도 상식으로 알고 계시는 분도 많다보니 오히려 어색한 느낌이 들기도 하네요 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

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

오...그런 부분은 생각 못해봤네요! 좋은 피드백 감사합니다!

networkErrorListener.onApiCallFailed()

response.close()
response = chain.proceed(request)
}
}
return response
}

companion object {
private const val SERVER_ERROR_START = 500
private const val SERVER_ERROR_END = 599
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ package sopt.motivoo.data.datasource.remote.listener

interface AuthTokenRefreshListener {
fun onTokenRefreshFailed()
fun setOnTokenRefreshFailedCallback(callback: () -> Unit)
fun clearOnTokenRefreshFailedCallback()
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@ package sopt.motivoo.data.datasource.remote.listener
import javax.inject.Inject

class AuthTokenRefreshListenerImpl @Inject constructor() : AuthTokenRefreshListener {
lateinit var onTokenRefreshFailedCallback: (() -> Unit)
private var onTokenRefreshFailedCallback: (() -> Unit)? = null

override fun onTokenRefreshFailed() {
if (this::onTokenRefreshFailedCallback.isInitialized) {
onTokenRefreshFailedCallback()
}
onTokenRefreshFailedCallback?.invoke()
}

override fun setOnTokenRefreshFailedCallback(callback: () -> Unit) {
onTokenRefreshFailedCallback = callback
}

override fun clearOnTokenRefreshFailedCallback() {
onTokenRefreshFailedCallback = null
Comment on lines +16 to +17
Copy link
Collaborator

Choose a reason for hiding this comment

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

😎 나이스 코드네요!

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package sopt.motivoo.data.datasource.remote.listener

interface NetworkErrorListener {
fun onApiCallFailed()
fun setOnApiCallFailedCallback(callback: () -> Unit)
fun clearOnApiCallFailedCallback()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package sopt.motivoo.data.datasource.remote.listener

import javax.inject.Inject

class NetworkErrorListenerImpl @Inject constructor() : NetworkErrorListener {
private var onApiCallFailedCallback: (() -> Unit)? = null

override fun onApiCallFailed() {
onApiCallFailedCallback?.invoke()
}

override fun setOnApiCallFailedCallback(callback: () -> Unit) {
onApiCallFailedCallback = callback
}

override fun clearOnApiCallFailedCallback() {
onApiCallFailedCallback = null
}
}
Comment on lines +5 to +19
Copy link
Collaborator

Choose a reason for hiding this comment

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

오 너무 좋은 설계인 것 같은데요 ?? 👍

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package sopt.motivoo.data.repository

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import sopt.motivoo.domain.repository.NetworkRepository
import sopt.motivoo.util.NetworkStateLiveData
import javax.inject.Inject

class NetworkRepositoryImpl @Inject constructor(
networkState: NetworkStateLiveData
) : NetworkRepository {

override val networkStateLiveData = networkState

private val _isLoading = MutableLiveData<Boolean>()
override val isLoading: LiveData<Boolean> = _isLoading

override fun setLoading(isLoading: Boolean) {
_isLoading.value = isLoading
}
}
6 changes: 5 additions & 1 deletion app/src/main/java/sopt/motivoo/di/Logger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ import javax.inject.Qualifier

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class Logger
annotation class AuthInterceptorQualifier

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class ErrorInterceptorQualifier
21 changes: 21 additions & 0 deletions app/src/main/java/sopt/motivoo/di/NetworkModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package sopt.motivoo.di

import android.content.Context
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import sopt.motivoo.util.NetworkStateLiveData
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

@Provides
@Singleton
fun provideNetworkStateLiveData(@ApplicationContext context: Context): NetworkStateLiveData {
return NetworkStateLiveData(context)
}
}
11 changes: 9 additions & 2 deletions app/src/main/java/sopt/motivoo/di/RepositoryModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import sopt.motivoo.data.repository.AuthRepositoryImpl
import sopt.motivoo.data.repository.DummyRepositoryImpl
import sopt.motivoo.data.repository.OnboardingRepositoryImpl
import sopt.motivoo.data.repository.HomeRepositoryImpl
import sopt.motivoo.data.repository.NetworkRepositoryImpl
import sopt.motivoo.data.repository.OnboardingRepositoryImpl
import sopt.motivoo.domain.repository.AuthRepository
import sopt.motivoo.domain.repository.DummyRepository
import sopt.motivoo.domain.repository.OnboardingRepository
import sopt.motivoo.domain.repository.HomeRepository
import sopt.motivoo.domain.repository.NetworkRepository
import sopt.motivoo.domain.repository.OnboardingRepository
import javax.inject.Singleton

@Module
Expand All @@ -37,4 +39,9 @@ object RepositoryModule {
@Singleton
fun providesOnboardingRepository(onboardingRepositoryImpl: OnboardingRepositoryImpl): OnboardingRepository =
onboardingRepositoryImpl

@Provides
@Singleton
fun providesNetworkRepository(networkRepositoryImpl: NetworkRepositoryImpl): NetworkRepository =
networkRepositoryImpl
}
46 changes: 33 additions & 13 deletions app/src/main/java/sopt/motivoo/di/RetrofitModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ import retrofit2.Retrofit
import sopt.motivoo.BuildConfig.BASE_URL
import sopt.motivoo.BuildConfig.DEBUG
import sopt.motivoo.data.datasource.local.MotivooStorageImpl
import sopt.motivoo.data.datasource.remote.AuthInterceptor
import sopt.motivoo.data.datasource.remote.intercepter.AuthInterceptor
import sopt.motivoo.data.datasource.remote.intercepter.ErrorInterceptor
import sopt.motivoo.data.datasource.remote.listener.AuthTokenRefreshListener
import sopt.motivoo.data.datasource.remote.listener.AuthTokenRefreshListenerImpl
import sopt.motivoo.data.datasource.remote.listener.NetworkErrorListener
import sopt.motivoo.data.datasource.remote.listener.NetworkErrorListenerImpl
import sopt.motivoo.domain.entity.MotivooStorage
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
Expand All @@ -32,14 +35,36 @@ object RetrofitModule {

@Provides
@Singleton
@Logger
fun provideAuthInterceptor(interceptor: AuthInterceptor): Interceptor = interceptor
@AuthInterceptorQualifier
fun provideAuthInterceptor(authInterceptor: AuthInterceptor): Interceptor = authInterceptor

@Provides
@Singleton
fun provideRefreshListener(authTokenRefreshListenerImpl: AuthTokenRefreshListenerImpl): AuthTokenRefreshListener =
authTokenRefreshListenerImpl

@Provides
@Singleton
@ErrorInterceptorQualifier
fun provideErrorInterceptor(errorInterceptor: ErrorInterceptor): Interceptor = errorInterceptor

@Provides
@Singleton
fun provideNetworkErrorListener(networkErrorListenerImpl: NetworkErrorListenerImpl): NetworkErrorListener =
networkErrorListenerImpl

@Provides
@Singleton
fun providesLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor().apply {
level = if (DEBUG) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.NONE
}
}
}

@Provides
@Singleton
fun provideJson(): Json = Json {
Expand All @@ -52,22 +77,17 @@ object RetrofitModule {
@Provides
@Singleton
fun providesOkHttpClient(
@Logger loggingInterceptor: Interceptor,
loggingInterceptor: HttpLoggingInterceptor,
@AuthInterceptorQualifier authInterceptor: Interceptor,
@ErrorInterceptorQualifier errorInterceptor: Interceptor,
): OkHttpClient =
OkHttpClient.Builder().apply {
connectTimeout(10, TimeUnit.SECONDS)
writeTimeout(10, TimeUnit.SECONDS)
readTimeout(10, TimeUnit.SECONDS)
addInterceptor(authInterceptor)
addInterceptor(errorInterceptor)
addInterceptor(loggingInterceptor)
if (DEBUG) {
addInterceptor(
HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
},
)
} else {
HttpLoggingInterceptor.Level.NONE
}
}.build()

@Provides
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package sopt.motivoo.domain.repository

import androidx.lifecycle.LiveData

interface NetworkRepository {

val networkStateLiveData: LiveData<Boolean>
val isLoading: LiveData<Boolean>
Copy link
Collaborator

Choose a reason for hiding this comment

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

LiveData 를 사용하신 이유가 궁금합니다!!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

엇 기존에 베이스 뷰모델로 하려던게 인스턴스 문제 때문에 repository로 옴겼는데 그 과정에서 놓친 부분이네요ㅠㅠ
도메인 레이어와 데이터 레이어에서는 순수 코틀린 코드로만 이루어져야 안드로이드 프레임워크와 결합이 생기지 않는데 지금 코드는 아키텍쳐에 위배되는 코드인 것 같네요..!
짚어주셔서 감삼당~

fun setLoading(isLoading: Boolean)
}

This file was deleted.

Loading
Loading