Support dynamic API base URL via Firebase Remote Config#1110
Support dynamic API base URL via Firebase Remote Config#1110DongJun-H wants to merge 20 commits into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
Walkthrough빌드 설정(폴백/키) → RemoteConfigBaseUrlProvider(정규화·서명검증·캐시) → 네트워크 인터셉터(요청 재작성·auth 분기) → Compose CompositionLocal로 baseUrl 주입 → UI 컴포넌트에서 remoteUrl/remoteImageUrl로 이미지·문서 URL 생성 통합. 변경 사항개요Firebase Remote Config를 통해 API 기본 URL을 동적으로 제공하는 기능을 구현합니다. 폴백 URL과 캐시된 마지막 성공값을 지원하며, RSA 서명 검증으로 신뢰도를 보장합니다. OkHttp 인터셉터가 요청 URL을 동적으로 재작성하고, Compose CompositionLocal을 통해 UI 계층에서 동적 URL을 사용할 수 있습니다. Domain 계약 및 빌드 기반 설정
핵심 구현: RemoteConfigBaseUrlProvider
DI 및 네트워크 계층 통합
Presentation 계층 통합
가능한 관련 이슈
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4f6511d680
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
app/build.gradle (1)
18-31: ⚖️ Poor tradeoffGradle 헬퍼 함수 중복 제거 고려
buildConfigString과fallbackBaseUrlProperty클로저가app/build.gradle,data/build.gradle,presentation/build.gradle3개 파일에 동일하게 중복되어 있습니다. 향후 수정 시 3곳을 모두 변경해야 하므로 유지보수 리스크가 있습니다.Gradle buildSrc 또는 convention plugin으로 공통화하면 DRY 원칙을 준수할 수 있지만, 이 PR의 범위를 넘으므로 선택적으로 고려하시기 바랍니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/build.gradle` around lines 18 - 31, The two helper closures buildConfigString and fallbackBaseUrlProperty are duplicated across app/build.gradle, data/build.gradle, and presentation/build.gradle; extract them into a single shared location (for example a buildSrc utility class or a convention plugin or a common Gradle script applied by all modules) and replace the module-local closures with calls to that shared helper to avoid repetition and future drift; ensure the shared helper exposes the same method names (buildConfigString and fallbackBaseUrlProperty) and behavior so each module can call them without changing existing call sites.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/build.gradle`:
- Line 16: 현재 app/build.gradle에서 def REMOTE_CONFIG_BASE_URL_PUBLIC_KEY =
properties.getProperty('REMOTE_CONFIG_BASE_URL_PUBLIC_KEY', '')로 기본값을 빈 문자열로 두어
프로퍼티 누락 시 빌드가 통과됩니다; release 빌드에서 빈 공개키로 인해
RemoteConfigBaseUrlProvider.verifyBaseUrlSignature()가 false를 반환해
refreshBaseUrl()가 실패하므로, release 빌드에서 REMOTE_CONFIG_BASE_URL_PUBLIC_KEY가 빈 값일 경우
GradleException을 발생시켜 빌드 실패 처리하도록 검증을 추가하세요 (검증 위치: app/build.gradle, 변수명
REMOTE_CONFIG_BASE_URL_PUBLIC_KEY); 로컬 개발을 해치는 파일 존재 검사 대신 값의 공백 여부만 판단하고, 예외
메시지에 어떤 키가 누락되었는지 명시해 주세요.
In `@presentation/src/main/java/daily/dayo/presentation/common/url/BaseUrl.kt`:
- Around line 19-20: remoteImageUrl currently builds "images/null" when
imageFileName is null or blank; update remoteImageUrl to first check if
imageFileName.isNullOrBlank() and, if so, return an empty string (or otherwise
skip URL creation), otherwise call remoteUrl(baseUrl = baseUrl, path =
"images/$imageFileName"); reference the remoteImageUrl function and the
remoteUrl helper when making this change.
In
`@presentation/src/main/java/daily/dayo/presentation/screen/settings/SettingsScreen.kt`:
- Around line 260-270: The call to remoteImageUrl(baseUrl, profile?.profileImg)
passes a nullable profileImg and results in requests like "/images/null"; update
SettingsScreen so you guard the nullable before building the URL (e.g., only
call remoteImageUrl when profile?.profileImg is non-null or provide a fallback
image URL/resource) and pass that to RoundImageView; locate the remoteImageUrl
invocation in SettingsScreen.kt and change it to use profile?.profileImg?.let {
remoteImageUrl(baseUrl, it) } or supply a default/placeholder value so false
"/images/null" requests are not made.
---
Nitpick comments:
In `@app/build.gradle`:
- Around line 18-31: The two helper closures buildConfigString and
fallbackBaseUrlProperty are duplicated across app/build.gradle,
data/build.gradle, and presentation/build.gradle; extract them into a single
shared location (for example a buildSrc utility class or a convention plugin or
a common Gradle script applied by all modules) and replace the module-local
closures with calls to that shared helper to avoid repetition and future drift;
ensure the shared helper exposes the same method names (buildConfigString and
fallbackBaseUrlProperty) and behavior so each module can call them without
changing existing call sites.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: b106687d-d1ce-480f-8cf9-3b87ce8beb80
📒 Files selected for processing (32)
app/build.gradleapp/src/main/AndroidManifest.xmlapp/src/main/java/com/daily/dayo/config/RemoteConfigBaseUrlProvider.ktapp/src/main/java/com/daily/dayo/di/BaseUrlProviderModule.ktdata/build.gradledata/src/main/java/daily/dayo/data/datasource/remote/retrofit/interceptor/BaseUrlRewriteInterceptor.ktdata/src/main/java/daily/dayo/data/di/NetworkModule.ktdomain/src/main/java/daily/dayo/domain/provider/BaseUrlProvider.ktpresentation/build.gradlepresentation/src/main/java/daily/dayo/presentation/activity/LoginActivity.ktpresentation/src/main/java/daily/dayo/presentation/activity/MainActivity.ktpresentation/src/main/java/daily/dayo/presentation/common/url/BaseUrl.ktpresentation/src/main/java/daily/dayo/presentation/screen/bookmark/BookmarkScreen.ktpresentation/src/main/java/daily/dayo/presentation/screen/folder/FolderScreen.ktpresentation/src/main/java/daily/dayo/presentation/screen/mypage/FollowScreen.ktpresentation/src/main/java/daily/dayo/presentation/screen/mypage/MyPageEditScreen.ktpresentation/src/main/java/daily/dayo/presentation/screen/mypage/MyPageScreen.ktpresentation/src/main/java/daily/dayo/presentation/screen/notification/NotificationScreen.ktpresentation/src/main/java/daily/dayo/presentation/screen/post/PostLikeUsersScreen.ktpresentation/src/main/java/daily/dayo/presentation/screen/profile/ProfileScreen.ktpresentation/src/main/java/daily/dayo/presentation/screen/rules/RuleScreen.ktpresentation/src/main/java/daily/dayo/presentation/screen/search/SearchPostHashtagScreen.ktpresentation/src/main/java/daily/dayo/presentation/screen/search/SearchResultScreen.ktpresentation/src/main/java/daily/dayo/presentation/screen/settings/BlockedUsersScreen.ktpresentation/src/main/java/daily/dayo/presentation/screen/settings/SettingsScreen.ktpresentation/src/main/java/daily/dayo/presentation/screen/write/WriteFolderScreen.ktpresentation/src/main/java/daily/dayo/presentation/screen/write/WriteScreen.ktpresentation/src/main/java/daily/dayo/presentation/view/Comment.ktpresentation/src/main/java/daily/dayo/presentation/view/DetailPostView.ktpresentation/src/main/java/daily/dayo/presentation/view/FeedPostView.ktpresentation/src/main/java/daily/dayo/presentation/view/FolderView.ktpresentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt
|
@codex @coderabbitai reveiw |
|
✅ Action performedReview finished.
|
|
Codex Review: Didn't find any major issues. Keep it up! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
배경
local.properties기반 BuildConfig 값으로 빌드 시점에 주입해 사용중작업 목표
작업 내용
1. Runtime base URL provider 추가
BaseUrlProvider와BaseUrlSource를 추가해 현재 base URL이 어디에서 선택됐는지 구분할 수 있도록 처리REMOTE_CONFIG: Remote Config에서 받아온 URLCACHED_LAST_SUCCESS: 이전에 Remote Config 검증에 성공해 저장된 URLFALLBACK: 빌드 시점에 주입된BuildConfig.FALLBACK_BASE_URL앱 진입점인
LoginActivity,MainActivity에서는setContent전에refreshBaseUrl()을 호출하고, 선택된 base URL을LocalBaseUrl로 Compose tree에 제공하는 방식2. URL 선택 순서
CACHED_LAST_SUCCESS사용FALLBACK사용refreshBaseUrl()에서 Remote Config fetch/activate 성공 시REMOTE_CONFIG로 전환3. URL 검증 및 보안 처리
Remote Config 또는 cache URL은 origin 단위(scheme + host + port 조합. ex.
https://api.dayo.com:8080)로만 허용http,httpsscheme만 허용/만 허용.local, private/link-local/loopback/multicast 계열 host 차단SHA256withRSA서명 검증 필요4. API 요청 base URL rewrite
Retrofit은 생성 시 항상
BuildConfig.FALLBACK_BASE_URL을 사용하되 실제 요청 직전에는BaseUrlRewriteInterceptor가BaseUrlProvider의 현재 URL을 읽고, 신뢰 가능한 URL이면 요청의scheme,host,port만 교체하는 방식path, query, method, body는 Retrofit이 만든 원본 요청을 유지하고, provider URL이 invalid/untrusted이거나 rewrite 중 예외가 발생하면 원본 fallback URL로 요청. Authorization 헤더는 base URL rewrite 이후 토큰이 엉뚱한 서버로 가지 않도록 요청 origin이 신뢰 가능한 base URL일 때만 첨부하도록 보완.
5. Presentation URL 전환
BuildConfig.BASE_URL기반 이미지/WebView URL 생성 코드를LocalBaseUrl,remoteUrl,remoteImageUrl기반으로 변경하였으며 이에 따라 게시글 이미지, 프로필 이미지, 폴더 썸네일, 알림 이미지, 검색 결과 이미지, 약관 WebView URL이 런타임 base URL을 사용URL 선택 / 적용 흐름
적용 확인 순서
참고
풀 리퀘스트 요약
모듈별 기능적 영향 및 사용자 영향
빌드·설정 (app, data, presentation의 build.gradle)
런타임 Base URL 공급 (RemoteConfigBaseUrlProvider, BaseUrlProviderModule, domain 인터페이스)
사용자 영향: 운영자가 Remote Config로 새로운 API 베이스 URL을 배포하면 재배포 없이 앱이 새 엔드포인트를 사용하도록 전환됨. 잘못된/비신뢰 URL 배포 시 서명 검증 실패로 폴백되어 사용자 서비스 중단 위험을 완화.
네트워킹 (NetworkModule, BaseUrlRewriteInterceptor, Retrofit 설정)
UI / 프레젠테이션 (Activity 변경 및 Compose 전역값)
주요 위험 포인트 (네트워크·오류·동시성·보안 관점)
콜드 스타트·UI 차단 위험
페치/검증 실패 처리 및 재시도 부재
race condition 및 상태 원자성
@Volatile로가시성만 확보. refreshBaseUrl()의 읽기-검증-쓰기 흐름은 원자적이지 않아 동시성 간섭 발생 가능(동시 요청 중 갱신으로 인한 불일치, Authorization 부착 판단 시점의 미스매치).Authorization 결정 시점 문제
SharedPreferences 기반 캐시의 일관성
인터셉터의 예외 처리
보안 리스크 (디버그 빌드와 공개키 관리)
권장되는 후속 테스트 및 검증 항목
기능적 검증
네트워크·요청 검증
동시성·상태 일관성 테스트
타임아웃·오프라인 상황
보안 테스트
로깅·모니터링
UI 연동 테스트
요약: 런타임에서 Remote Config로 API 베이스 URL을 안전하게 교체하는 기능을 도입해 운영 유연성을 크게 개선했으나, 콜드 스타트·동시성·서명/공개키 관리·예외 로깅과 관련된 검증 및 보완 작업이 필요합니다. 운영 전 위의 테스트 항목(특히 릴리스 빌드 보안·타임아웃/동시성 시나리오 및 로그/모니터링)을 우선적으로 수행하시길 권장합니다.