⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content

Conversation

@oesnuj
Copy link
Member

@oesnuj oesnuj commented Jan 24, 2026

1. 배경 (Background)

기존에는 동아리 상세 페이지의 상단 탑바(뒤로가기, 타이틀)는 네이티브에서, 내용은 웹뷰에서 렌더링했습니다.
하지만 스크롤 시 웹뷰 내부 콘텐츠와 네이티브 탑바 간의 디자인 일관성을 맞추기 어려웠고, 더 몰입감 있는 UI를 제공하기 위해 상세 페이지의 탑바 영역까지 웹뷰에서 모두 처리하도록 변경했습니다.

이에 따라 기존 네이티브 헤더에 있던 '뒤로가기''알림 구독/취소' 기능을 웹뷰 내부 버튼으로 제어해야 하는 요구사항이 발생하여, WebView와 Native 간의 통신 구조를 구축했습니다.


2. 주요 변경 사항 (Changes)

2026-01-24.143236.mp4

A. WebView 메시지 통신 구조 (useWebViewMessageHandler)

웹에서 네이티브 기능을 호출할 수 있도록 단방향 메시지 통신을 구현했습니다.

통신 구조 (Architecture)

sequenceDiagram
    participant Web as WebView Content
    participant RN as React Native WebView
    participant Hook as useWebViewMessageHandler
    participant Native as Native Logic

    Note over Web, Native: 예: 알림 구독 요청
    Web->>RN: window.ReactNativeWebView.postMessage(JSON)
    RN->>Hook: onMessage 이벤트 수신
    Hook->>Hook: JSON 파싱 및 타입 확인 (WebViewMessage)
    
    rect rgb(200, 240, 255)
    Note right of Hook: Switch Case로 분기 처리
    Hook->>Native: onSubscribe() 콜백 실행
    end
    
    Native-->>Native: 구독 로직 실행 / 믹스패널 트래킹
Loading

구현된 이벤트 (Events)

  • NAVIGATE_BACK: 앱 내비게이션 뒤로가기 실행
  • NOTIFICATION_SUBSCRIBE: 알림 구독 요청 (권한 확인 및 로직 수행)
  • NOTIFICATION_UNSUBSCRIBE: 알림 구독 취소

새로운 이벤트 추가 방법 (How to Extend)

  1. webview-message.types.tsWebViewMessage Union 타입에 새로운 typepayload 정의
  2. useWebViewMessageHandler.tshandleMessage 내부 switch 문에 해당 case 추가
  3. useWebViewMessageHandler 호출처(Page)에서 핸들러 함수 연결

B. 동적 Safety Area 적용 (Dynamic Safety Area)

웹뷰가 상단 영역(Status Bar)까지 확장되도록 네이티브 레이아웃을 변경했습니다.

  • Native: SafeAreaView의 Top inset을 제거하고, StatusBar를 투명(translucent)하게 설정.
  • Web: 웹 콘텐츠 상단에 padding-top: env(safe-area-inset-top)을 적용하여 콘텐츠가 가려지지 않게 처리.

C. 초기 알림 상태 동기화 (Initial State Sync)

웹뷰 로드 시점의 알림 구독 상태를 전달하기 위해 URL Query Parameter를 활용했습니다.

is_subscribed=true: 이미 구독 중인 상태로 아이콘 렌더링 필요


3. 트러블슈팅 (Troubleshooting)

Q1. 상단 영역 콘텐츠 가림 문제 (Safety Area Overlap)

문제: 웹뷰가 상단(Status Bar)까지 확장됨에 따라, 웹 콘텐츠의 상단 부분이 노치나 상태바에 가려지는 문제 발생.
해결: 웹 콘텐츠(Layout 또는 Header)의 상단에 padding-top: env(safe-area-inset-top)을 적용하여, 안전 영역만큼 공간을 확보하고 그 아래부터 콘텐츠가 시작되도록 처리.

Q2. iOS 오버스크롤(Bounce)로 인한 UI 깨짐

문제: 웹뷰를 최상단에서 아래로 당길 때(Pull-down), iOS 특유의 바운스 효과로 인해 웹뷰 전체가 내려가면서 네이티브 흰색 배경이 드러나고 탑바 위치가 어긋나는 이질감 발생.
해결: WebView 속성에 bounces={false} (iOS) 및 overScrollMode="never" (Android)를 추가하여 오버스크롤을 막고, 탑바가 상단에 고정된 듯한 UX 제공.

image

Summary by CodeRabbit

  • 새로운 기능

    • WebView에서 공유(SHARE) 메시지 지원 및 공유 콜백 추가
    • 클럽 상세 화면에서 공유 플로우, 세션 ID 및 구독 상태 기반 쿼리 구성 지원
    • WebView 메시지 핸들러 통합 및 로딩/헤더/상태바 표시 개선
  • 버그 수정

    • 메시지 파싱 실패와 누락 데이터에 대한 예외 처리 및 안전성 강화

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 24, 2026

Walkthrough

React Native WebView 메시지 파싱 훅 useWebViewMessageHandler를 추가 및 공개(내보내기)하고, 메시지 타입에 SHARE를 도입해 공유 페이로드를 처리하도록 타입과 훅을 확장한 뒤 클럽 상세 화면에 통합해 뒤로가기·알림 구독/해제·공유 이벤트를 처리합니다.

Changes

Cohort / File(s) 요약
타입 정의
types/webview-message.types.ts
WebViewMessageTypesSHARE 추가; WebViewMessage 판별 유니온에 { type: 'SHARE'; payload: { title: string; text: string; url: string } } 추가; WebViewMessageEvent 포함
훅 구현
hooks/use-webview-message-handler.ts
useWebViewMessageHandler 옵션에 `onShare?: (payload: { title: string; text: string; url }) => Promise
훅 내보내기
hooks/index.ts
useWebViewMessageHandler를 public export에 추가
UI 통합 및 WebView 변경
ui/club-detail/club-detail-screen.tsx
훅 사용으로 onMessage 교체 및 콜백 전달(네비게이트 백, subscribe/unsubscribe, share), StatusBar·hasError·onError 추가, 쿼리스트링 구성 변경 및 분석 이벤트 연계

Sequence Diagram(s)

sequenceDiagram
    participant WebView as React Native WebView
    participant Hook as useWebViewMessageHandler
    participant Screen as ClubDetailScreen
    participant Native as Native / App Logic

    WebView->>Hook: onMessage(event)\nevent.nativeEvent.data (JSON string)
    Hook->>Hook: JSON.parse -> WebViewMessage
    alt NAVIGATE_BACK
        Hook->>Screen: onNavigateBack()
        Screen->>Native: navigateBack()
    else NOTIFICATION_SUBSCRIBE
        Hook->>Screen: onSubscribe(clubId, clubName?)
        Screen->>Native: requestPermissionIfNeeded -> subscribe(clubId)
    else NOTIFICATION_UNSUBSCRIBE
        Hook->>Screen: onUnsubscribe(clubId)
        Screen->>Native: unsubscribe(clubId)
    else SHARE
        Hook->>Screen: onShare({title, text, url})
        Screen->>Native: openShareSheet(payload)
    else Unknown / parse error
        Hook->>Hook: console.warn / ignore
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • SeongHoonC
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 WebView 통신 핸들러 구현 및 동적 Safety Area 적용이라는 주요 변경사항을 명확하게 반영하고 있으며, 실제 코드 변경사항(새로운 훅 구현, 타입 정의 추가, UI 개선)과 일치합니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@hooks/use-webview-message-handler.ts`:
- Around line 19-45: The handleMessage function must validate the parsed message
shape and payload types before acting: after JSON.parse in handleMessage, check
that message is an object and message.type is a string; for
WebViewMessageTypes.NOTIFICATION_SUBSCRIBE ensure message.payload exists and
typeof message.payload.clubId === 'string' and typeof message.payload.clubName
=== 'string' before calling onSubscribe, and for
WebViewMessageTypes.NOTIFICATION_UNSUBSCRIBE ensure typeof
message.payload.clubId === 'string' before calling onUnsubscribe; if validations
fail, log a warning (use console.warn) and skip calling
onSubscribe/onUnsubscribe.

In `@ui/club-detail/club-detail-screen.tsx`:
- Around line 134-154: When rendering the error header (when hasError is true)
ensure the top safe-area inset is applied so the Header doesn't overlap the
translucent StatusBar: modify the conditional branch that renders Header to use
a container that includes the top inset (either by changing Container edges to
include 'top' when hasError or wrapping Header in a SafeAreaView) so the top
padding is applied only for the error state; update the hasError branch (where
Container, StatusBar, Header, BackButton, HeaderTitle, SubscribeButton, and
MoaImage are used) to add that top-safe-area adjustment.

@oesnuj oesnuj self-assigned this Jan 24, 2026
@oesnuj oesnuj requested a review from SeongHoonC January 24, 2026 05:33
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@ui/club-detail/club-detail-screen.tsx`:
- Around line 108-136: Add a minimal validity guard for the incoming targetId in
the onSubscribe and onUnsubscribe handlers: check that targetId is a non-empty
string (e.g., typeof targetId === 'string' && targetId.trim().length > 0) and
return early (optionally log or track an error) if it fails; apply this guard
before calling isSubscribed(targetId), toggleSubscribe(targetId), or using
targetId elsewhere, ensuring you still call trackEvent where appropriate (e.g.,
keep the subscribe trackEvent but only proceed with
toggleSubscribe/setShowPermissionDialog when targetId is valid).
♻️ Duplicate comments (1)
ui/club-detail/club-detail-screen.tsx (1)

139-159: 에러 헤더에 상단 Safe Area 보정이 필요합니다.
StatusBar translucent + edges={['bottom']} 조합에서 hasError 헤더가 상태바와 겹칠 수 있습니다.

🛠️ 제안 수정안
-import { SafeAreaView } from 'react-native-safe-area-context';
+import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
...
   const trackEvent = useMixpanelTrack();
+  const insets = useSafeAreaInsets();
...
-      {hasError && (
-        <Header>
+      {hasError && (
+        <Header style={{ paddingTop: insets.top + 12 }}>

웹뷰에서 전달되는 SHARE 메시지를 처리하기 위해 타입과 훅을 확장하고, ClubWebViewScreen에 네이티브 공유 로직을 연결했습니다. 기존 구조를 유지하며 기능을 확장했습니다.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@ui/club-detail/club-detail-screen.tsx`:
- Around line 137-142: The onShare handler currently passes url only in the url
field (iOS-only) and lacks error handling; update the onShare function to build
a single message that concatenates text and url (e.g., `${text}\n${url}`) so
Android receives the link, call Share.share with that combined message and
maintain title, and wrap the await Share.share call in a try/catch to
handle/rethrow or log errors as appropriate.
♻️ Duplicate comments (1)
ui/club-detail/club-detail-screen.tsx (1)

146-167: 에러 헤더의 Safe Area 보정 이슈는 기존 코멘트와 동일합니다.
이 구간은 상단 inset 미적용 가능성이 있어 이전 리뷰 의견을 참고해주세요.

🧹 Nitpick comments (1)
types/webview-message.types.ts (1)

2-16: 타입 리터럴을 상수 기반으로 묶어 드리프트를 줄여보세요.
현재 문자열 리터럴과 상수 객체가 분리되어 있어 불일치 위험이 있습니다. 상수 값을 참조하도록 타입을 연결하면 유지보수가 쉬워집니다.

♻️ 제안 변경
 export type WebViewMessage =
-  | { type: 'NAVIGATE_BACK' }
-  | { type: 'NOTIFICATION_SUBSCRIBE'; payload: { clubId: string; clubName?: string } }
-  | { type: 'NOTIFICATION_UNSUBSCRIBE'; payload: { clubId: string } }
-  | { type: 'SHARE'; payload: { title: string; text: string; url: string } };
+  | { type: typeof WebViewMessageTypes.NAVIGATE_BACK }
+  | { type: typeof WebViewMessageTypes.NOTIFICATION_SUBSCRIBE; payload: { clubId: string; clubName?: string } }
+  | { type: typeof WebViewMessageTypes.NOTIFICATION_UNSUBSCRIBE; payload: { clubId: string } }
+  | { type: typeof WebViewMessageTypes.SHARE; payload: { title: string; text: string; url: string } };

Comment on lines +137 to +142
onShare: async ({ title, text, url }: { title: string; text: string; url: string }) => {
await Share.share({
title,
message: text,
url,
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

React Native Share.share API Android url field behavior documentation

💡 Result:

In React Native’s built-in Share.share(content, options) API, the content.url field is documented as iOS-only. On Android, the supported content fields are message (text to share) and title (used as the chooser/title), so url is not part of the Android behavior and may be ignored by the native share sheet. To share a link on Android, put it in message (e.g., message: 'Check this out: https://…'). [1][2]

Docs: React Native Share API reference (shows url - a URL to share iOS, title - … Android). [1][2]

Sources

  1. React Native docs (v0.82) – Share API: url is iOS-only, Android uses message/title. [1]
  2. React Native docs (“Next”) – same platform notes for url/title. [2]

Android에서 공유 URL이 누락될 수 있습니다.

React Native Share.share에서 url 필드는 iOS 전용이며 Android에서는 무시됩니다. Android에서 링크 공유를 보장하려면 message에 URL을 포함해야 합니다. 또한 공유 실패 시 예외가 발생할 수 있으므로 최소한의 try/catch 처리도 권장됩니다.

🐛 제안 수정안
-    onShare: async ({ title, text, url }: { title: string; text: string; url: string }) => {
-      await Share.share({
-        title,
-        message: text,
-        url,
-      });
-    },
+    onShare: async ({ title, text, url }: { title: string; text: string; url: string }) => {
+      try {
+        const message =
+          Platform.OS === 'android'
+            ? [text, url].filter(Boolean).join('\n')
+            : text;
+        await Share.share({
+          title,
+          message,
+          url,
+        });
+      } catch (e) {
+        console.warn('[ClubWebViewScreen] Share failed', e);
+      }
+    },
🤖 Prompt for AI Agents
In `@ui/club-detail/club-detail-screen.tsx` around lines 137 - 142, The onShare
handler currently passes url only in the url field (iOS-only) and lacks error
handling; update the onShare function to build a single message that
concatenates text and url (e.g., `${text}\n${url}`) so Android receives the
link, call Share.share with that combined message and maintain title, and wrap
the await Share.share call in a try/catch to handle/rethrow or log errors as
appropriate.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@ui/club-detail/club-detail-screen.tsx`:
- Line 22: The error flag state (hasError via setHasError) is not reset on
subsequent loads, so add an onLoadStart handler that calls setHasError(false) to
clear the error when a new load begins; ensure this pairs with the existing
onError handler which sets setHasError(true), and attach the onLoadStart prop to
the same component responsible for loading (e.g., in ClubDetailScreen where
onError is used) so the header will hide after a successful retry.
♻️ Duplicate comments (3)
ui/club-detail/club-detail-screen.tsx (3)

108-136: (중복) targetId 유효성 가드 필요.
이전 리뷰에서 지적된 사항과 동일합니다.


137-142: (중복) Android 공유 시 URL 누락 가능성.
이전 리뷰와 동일하게, Android에서는 url 필드가 무시될 수 있어 message에 포함 및 예외 처리 권장이 필요합니다.

React Native Share.share url field Android message

147-167: (중복) 에러 헤더 상단 Safe Area 보정 필요.
이전 리뷰에서 제안된 상단 inset 적용 이슈가 여전히 존재합니다.

react-native-safe-area-context SafeAreaView edges top translucent StatusBar

@SeongHoonC SeongHoonC merged commit 380f362 into main Jan 25, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants