Skip to content

Commit

Permalink
[System] Error Handling 고도화
Browse files Browse the repository at this point in the history
  • Loading branch information
ajou4095 committed Jan 23, 2024
1 parent e890049 commit 905c913
Show file tree
Hide file tree
Showing 15 changed files with 132 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import ac.dnd.bookkeeping.android.data.remote.network.util.convert
import io.ktor.client.HttpClient
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import javax.inject.Inject

class AuthenticationApi(
class AuthenticationApi @Inject constructor(
@NoAuthHttpClient private val client: HttpClient,
private val baseUrlProvider: BaseUrlProvider,
private val errorMessageMapper: ErrorMessageMapper
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import ac.dnd.bookkeeping.android.data.remote.network.util.convert
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import io.ktor.client.request.parameter
import javax.inject.Inject

class BookkeepingApi(
class BookkeepingApi @Inject constructor(
@AuthHttpClient private val client: HttpClient,
private val baseUrlProvider: BaseUrlProvider,
private val errorMessageMapper: ErrorMessageMapper
Expand Down
Original file line number Diff line number Diff line change
@@ -1,66 +1,17 @@
package ac.dnd.bookkeeping.android.presentation.common.base

import ac.dnd.bookkeeping.android.presentation.common.util.coroutine.event.eventObserve
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.lifecycle.viewModelScope
import com.ray.rds.window.alert.AlertDialogFragmentProvider
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber

abstract class BaseActivity : AppCompatActivity() {

protected abstract val viewModel: BaseViewModel

protected val handler = CoroutineExceptionHandler { _, throwable ->
Timber.e(throwable)
AlertDialogFragmentProvider.makeAlertDialog(
title = throwable.javaClass.simpleName,
message = throwable.message
).show()
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initView()
initObserver()
observeViewModelError()
}

protected open fun initView() = Unit

protected open fun initObserver() = Unit

private fun observeViewModelError() {
repeatOnStarted {
viewModel.errorEvent.eventObserve { event ->
handler.handleException(viewModel.viewModelScope.coroutineContext, event.throwable)
}
}
}

fun DialogFragment.show() {
if (
!this@BaseActivity.isFinishing
&& !this@BaseActivity.isDestroyed
&& !this@BaseActivity.supportFragmentManager.isDestroyed
&& !this@BaseActivity.supportFragmentManager.isStateSaved
) {
show(this@BaseActivity.supportFragmentManager, javaClass.simpleName)
}
}

protected fun repeatOnStarted(
block: suspend CoroutineScope.() -> Unit
) {
this.lifecycleScope.launch(handler) {
repeatOnLifecycle(Lifecycle.State.STARTED, block)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

abstract class BaseViewModel : ViewModel() {
protected val handler = CoroutineExceptionHandler { _, throwable ->
val handler = CoroutineExceptionHandler { _, throwable ->
viewModelScope.launch {
_errorEvent.emit(ErrorEvent(throwable))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package ac.dnd.bookkeeping.android.presentation.common.util

import ac.dnd.bookkeeping.android.presentation.common.base.BaseViewModel
import ac.dnd.bookkeeping.android.presentation.common.util.coroutine.event.eventObserve
import ac.dnd.bookkeeping.android.presentation.common.view.DialogScreen
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import io.sentry.Sentry
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext

@Composable
fun ErrorObserver(
viewModel: BaseViewModel
) {
val dialogIsShowingState = remember { mutableStateOf(false) }
DialogScreen(dialogIsShowingState)

LaunchedEffectWithLifecycle(viewModel.errorEvent) {
viewModel.errorEvent.eventObserve { event ->
Timber.d(event.throwable)
Sentry.captureException(event.throwable)

dialogIsShowingState.value = true
}
}
}

@Composable
fun LaunchedEffectWithLifecycle(
key1: Any?,
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> Unit
) {
val lifecycleOwner = LocalLifecycleOwner.current

LaunchedEffect(key1) {
lifecycleOwner.lifecycleScope.launch(
coroutineContext + context
) {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED, block)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
package ac.dnd.bookkeeping.android.presentation.common.util.coroutine

import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext

// TODO : 고도화하기
suspend fun <T1, T2> zip(
Expand All @@ -23,12 +16,3 @@ suspend fun <T1, T2> zip(
deferred1.await().getOrThrow() to deferred2.await().getOrThrow()
}
}

fun LifecycleOwner.repeatOnStarted(
context: CoroutineContext,
block: suspend CoroutineScope.() -> Unit
) {
this.lifecycleScope.launch(context) {
repeatOnLifecycle(Lifecycle.State.STARTED, block)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,23 @@ import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import com.google.accompanist.systemuicontroller.SystemUiController
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import kotlin.coroutines.CoroutineContext

@Stable
class ApplicationState(
val navController: NavHostController,
val systemUiController: SystemUiController,
val coroutineExceptionHandler: (CoroutineContext, Throwable) -> Unit
val systemUiController: SystemUiController
)

@Composable
fun rememberApplicationState(
navController: NavHostController = rememberNavController(),
systemUiController: SystemUiController = rememberSystemUiController(),
coroutineExceptionHandler: (CoroutineContext, Throwable) -> Unit = { _, _ -> }
systemUiController: SystemUiController = rememberSystemUiController()
) = remember(
navController,
systemUiController,
coroutineExceptionHandler
systemUiController
) {
ApplicationState(
navController = navController,
systemUiController = systemUiController,
coroutineExceptionHandler = coroutineExceptionHandler
systemUiController = systemUiController
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,10 @@ package ac.dnd.bookkeeping.android.presentation.ui.main

import ac.dnd.bookkeeping.android.presentation.common.base.BaseActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : BaseActivity() {

// TODO : MainScreen 으로 이동
override val viewModel: MainViewModel by viewModels()

override fun initView() {
setContent {
MainScreen()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,19 @@ import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.compose.NavHost
import io.sentry.Sentry

@Composable
fun MainScreen() {
fun MainScreen(
viewModel: MainViewModel = hiltViewModel()
) {
BookkeepingTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
val appState = rememberApplicationState(
coroutineExceptionHandler = { _, throwable ->
Sentry.captureException(throwable)
// TODO : Dialog Screen
}
)
val appState = rememberApplicationState()
ManageSystemUiState(appState = appState)

NavHost(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ac.dnd.bookkeeping.android.presentation.ui.main.home

import ac.dnd.bookkeeping.android.presentation.common.util.ErrorObserver
import ac.dnd.bookkeeping.android.presentation.common.view.CustomSnackBarHost
import ac.dnd.bookkeeping.android.presentation.ui.main.ApplicationState
import ac.dnd.bookkeeping.android.presentation.ui.main.home.bookkeeping.BookkeepingScreen
Expand Down Expand Up @@ -29,6 +30,11 @@ fun HomeScreen(
appState: ApplicationState,
viewModel: HomeViewModel = hiltViewModel()
) {
Observer(
appState = appState,
viewModel = viewModel
)

val scaffoldState = rememberScaffoldState()
var selectedItem by rememberSaveable { mutableIntStateOf(0) }
var snackBarIsShowingState by remember { mutableStateOf(false) }
Expand Down Expand Up @@ -89,3 +95,11 @@ fun HomeScreen(
}
}
}

@Composable
private fun Observer(
appState: ApplicationState,
viewModel: HomeViewModel
) {
ErrorObserver(viewModel)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ac.dnd.bookkeeping.android.presentation.ui.main.home.bookkeeping

import ac.dnd.bookkeeping.android.presentation.common.util.ErrorObserver
import ac.dnd.bookkeeping.android.presentation.ui.main.ApplicationState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
Expand All @@ -17,6 +18,11 @@ fun BookkeepingScreen(
appState: ApplicationState,
viewModel: BookkeepingViewModel = hiltViewModel()
) {
Observer(
appState = appState,
viewModel = viewModel
)

Box(
modifier = Modifier
.fillMaxSize()
Expand All @@ -30,3 +36,11 @@ fun BookkeepingScreen(
)
}
}

@Composable
private fun Observer(
appState: ApplicationState,
viewModel: BookkeepingViewModel
) {
ErrorObserver(viewModel)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ac.dnd.bookkeeping.android.presentation.ui.main.home.setting

import ac.dnd.bookkeeping.android.presentation.common.util.ErrorObserver
import ac.dnd.bookkeeping.android.presentation.common.view.BottomSheetScreen
import ac.dnd.bookkeeping.android.presentation.common.view.DialogScreen
import ac.dnd.bookkeeping.android.presentation.ui.main.ApplicationState
Expand All @@ -26,6 +27,10 @@ fun SettingScreen(
onShowSnackBar: () -> Unit,
viewModel: SettingViewModel = hiltViewModel()
) {
Observer(
appState = appState,
viewModel = viewModel
)

val dialogIsShowingState = remember { mutableStateOf(false) }
val bottomSheetIsShowingState = remember { mutableStateOf(false) }
Expand Down Expand Up @@ -77,3 +82,11 @@ fun SettingScreen(
BottomSheetScreen(bottomSheetIsShowingState = bottomSheetIsShowingState)
DialogScreen(dialogIsShowingState = dialogIsShowingState)
}

@Composable
private fun Observer(
appState: ApplicationState,
viewModel: SettingViewModel
) {
ErrorObserver(viewModel)
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fun NavGraphBuilder.loginNavGraph(
route = LoginConstant.ROUTE_STEP_1
) {
val backStackEntry = remember(it) {
appState.navController.getBackStackEntry(LoginConstant.ROUTE)
appState.navController.getBackStackEntry(LoginConstant.ROUTE_STEP_1)
}
val loginViewModel: LoginViewModel = hiltViewModel(backStackEntry)
LoginScreen(appState = appState, viewModel = loginViewModel)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ac.dnd.bookkeeping.android.presentation.ui.main.login

import ac.dnd.bookkeeping.android.presentation.common.util.ErrorObserver
import ac.dnd.bookkeeping.android.presentation.ui.main.ApplicationState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
Expand All @@ -26,6 +27,12 @@ fun LoginScreen(
appState: ApplicationState,
viewModel: LoginViewModel = hiltViewModel()
) {

Observer(
appState = appState,
viewModel = viewModel
)

val nextStageState = remember { mutableStateOf(false) }
if (nextStageState.value) {
appState.navController.navigate(LoginConstant.ROUTE_STEP_2)
Expand Down Expand Up @@ -65,3 +72,11 @@ fun LoginScreen(
}
}
}

@Composable
private fun Observer(
appState: ApplicationState,
viewModel: LoginViewModel
) {
ErrorObserver(viewModel)
}
Loading

0 comments on commit 905c913

Please sign in to comment.