Skip to content

Commit

Permalink
[feat/#10] Implement WebViewScreen with MVI pattern to manage state
Browse files Browse the repository at this point in the history
  • Loading branch information
sxunea committed Feb 3, 2025
1 parent f29c785 commit 1d072d1
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
package com.nexters.misik.webview

import android.os.Bundle
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import com.nexters.misik.webview.MainActivity.Companion.WEB_URL
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject

Expand All @@ -29,31 +23,8 @@ class MainActivity : ComponentActivity() {

setContent {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
WebViewScreen(webAppInterface, modifier = Modifier.padding(innerPadding))
WebViewScreen(webAppInterface = webAppInterface, modifier = Modifier.padding(innerPadding))
}
}
}

companion object {
const val WEB_URL = "https://misik-web.vercel.app/"
}
}

@Composable
fun WebViewScreen(
webAppInterface: WebInterface,
modifier: Modifier = Modifier,
) {
AndroidView(
modifier = modifier,
factory = { context ->
WebView(context).apply {
settings.javaScriptEnabled = true
webViewClient = WebViewClient()
webChromeClient = WebChromeClient()
addJavascriptInterface(webAppInterface, "AndroidBridge")
loadUrl(WEB_URL)
}
},
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.nexters.misik.webview

import android.webkit.WebChromeClient
import android.webkit.WebResourceError
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.viewinterop.AndroidView
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import timber.log.Timber

@Composable
fun WebViewScreen(
webAppInterface: WebInterface,
modifier: Modifier = Modifier,
viewModel: WebViewViewModel = hiltViewModel(),
) {
val state by viewModel.state.collectAsStateWithLifecycle()

// 페이지 로딩 이벤트 발생 (WebView 로드 시작 시)
LaunchedEffect(Unit) {
viewModel.onEvent(WebViewEvent.LoadPage)
}

Box(modifier = modifier.fillMaxSize()) {
// 로딩 상태 UI
if (state.isLoading) {
Timber.d("WebViewScreen_UiState", "Loading")
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center),
)
}

// 에러 상태 UI
state.error?.let {
Timber.d("WebViewScreen_UiState", "Error: $it")
Text(
text = "Error: $it",
color = Color.Red,
modifier = Modifier.align(Alignment.Center),
)
}

// 콘텐츠가 있을 경우, WebView를 보여줌
if (!state.isLoading) {
Timber.d("WebViewScreen_UiState", "Loaded")
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
WebView(context).apply {
settings.javaScriptEnabled = true
webViewClient = WebViewClient()
webChromeClient = WebChromeClient()

addJavascriptInterface(webAppInterface, "AndroidBridge")

loadUrl("https://misik-web.vercel.app/")

// 페이지 로딩 완료 후 이벤트 처리
webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
viewModel.onEvent(WebViewEvent.PageLoaded)
}

override fun onReceivedError(
view: WebView?,
request: WebResourceRequest?,
error: WebResourceError?,
) {
super.onReceivedError(view, request, error)
// 오류 발생 시 이벤트 호출
viewModel.onEvent(WebViewEvent.JsError("Error loading page: ${error?.description}"))
}
}
}
},
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.nexters.misik.webview

import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject

@HiltViewModel
class WebViewViewModel @Inject constructor() : ViewModel() {
private val _state = MutableStateFlow(WebViewState())
val state: StateFlow<WebViewState> get() = _state

fun onEvent(event: WebViewEvent) {
when (event) {
WebViewEvent.LoadPage -> {
_state.value = _state.value.copy(isLoading = true, error = null)
}
WebViewEvent.PageLoaded -> {
_state.value = _state.value.copy(isLoading = false, error = null)
}
is WebViewEvent.JsResponse -> {
_state.value = _state.value.copy(isLoading = false, content = event.response, error = null)
}
is WebViewEvent.JsError -> {
_state.value = _state.value.copy(isLoading = false, error = event.error)
}
}
}
}

0 comments on commit 1d072d1

Please sign in to comment.