Skip to content

Conversation

@HI-JIN2
Copy link
Member

@HI-JIN2 HI-JIN2 commented Feb 23, 2025

Summary

홈 화면 위젯을 구현했습니다. 사용자는 위젯을 통해 학생 식당, 도담 식당, 기숙사 식당의 식단 정보를 홈 화면에서 바로 확인할 수 있습니다.

  • Glance AppWidget: Jetpack Compose 기반 위젯 개발
  • DataStore: 위젯별 설정 및 상태 저장, 메뉴 저장
  • WorkManager: 백그라운드 메뉴 업데이트
  • Hilt: 의존성 주입
  • Retrofit: API 호출
  • Gson: JSON 직렬화/역직렬화

Describe your changes

주요 기능

1. 위젯 설정 시스템

  • 식당 선택: 학생 식당, 도담 식당, 기숙사 식당, 교직원 식당 중 선택 가능
  • 위젯별 독립 설정: 각 위젯마다 서로 다른 식당 설정 가능
  • 즉시 반영: 설정 변경 시 위젯에 즉시 반영

2. 시간별 메뉴 표시

  • 3개 식사 시간 지원: 아침, 점심, 저녁 메뉴 모두 저장
  • 자동 시간 감지: 현재 시간에 맞는 식사 시간 메뉴 자동 표시
  • 실시간 업데이트: 30분마다 자동으로 메뉴 정보 업데이트

3. 사용자 경험

  • 직관적 UI: 깔끔하고 모던한 위젯 디자인
  • 상태 표시: 로딩, 메뉴 없음, 네트워크 오류 등 다양한 상태 표시
  • 앱 연동: 위젯 클릭 시 앱 실행

시스템 아키텍처

graph TD
    A[위젯 설정 화면] --> B[식당 선택]
    B --> C[DataStore 저장]
    C --> D[위젯별 고유 키]
    
    E[MealInfoWidget] --> F[provideGlance]
    F --> G[식당 정보 로드]
    G --> H[MealWorker 실행]
    H --> I[API 호출]
    I --> J[메뉴 데이터 처리]
    J --> K[위젯 상태 업데이트]
    K --> L[UI 렌더링]
    
    M[시간별 메뉴] --> N[아침/점심/저녁]
    N --> O[현재 시간 감지]
    O --> P[해당 메뉴 표시]
Loading

핵심 컴포넌트

1. 위젯 UI (MealInfoWidget.kt)

class MealInfoWidget : GlanceAppWidget() {
    override val stateDefinition = MealInfoStateDefinition
    
    @RequiresApi(Build.VERSION_CODES.O)
    override suspend fun provideGlance(context: Context, id: GlanceId) {
        // 위젯 UI 렌더링 로직
    }
}

2. 상태 관리 (MealInfoStateDefinition.kt)

object MealInfoStateDefinition : GlanceStateDefinition<WidgetMealInfo> {
    // DataStore 기반 상태 관리
    // 3개 식사 시간 메뉴 저장
}

3. 데이터 처리 (WidgetDataDisplayManager.kt)

object WidgetDataDisplayManager {
    // API 호출 및 메뉴 데이터 처리
    // 시간별 메뉴 분류 및 캐싱
}

4. 백그라운드 작업 (MealWorker.kt)

@HiltWorker
class MealWorker : CoroutineWorker() {
    // 30분마다 메뉴 정보 업데이트
    // 위젯 상태 동기화
}

5. 설정 관리 (WidgetSettingActivity.kt)

@AndroidEntryPoint
class WidgetSettingActivity : ComponentActivity() {
    // 위젯별 식당 설정 저장/로드
    // DataStore 기반 설정 관리
}

데이터 모델

WidgetMealInfo

sealed interface WidgetMealInfo {
    object Loading : WidgetMealInfo
    data class Available(
        val breakfast: List<List<String>>,  // 아침 메뉴
        val lunch: List<List<String>>,      // 점심 메뉴
        val dinner: List<List<String>>,     // 저녁 메뉴
        val restaurant: Restaurant,         // 식당 정보
    ) : WidgetMealInfo
    object Unavailable : WidgetMealInfo
}

데이터 플로우

1. 위젯 초기화

위젯 추가 → 설정 화면 → 식당 선택 → DataStore 저장 → 위젯 렌더링

2. 메뉴 업데이트

MealWorker 실행 → API 호출 → 메뉴 데이터 처리 → 위젯 상태 업데이트

3. 시간별 표시

현재 시간 감지 → 해당 식사 시간 메뉴 선택 → UI 업데이트
Screen_Recording_20250831_141253_One.UI.Home.mp4

Issue

To reviewers

@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2025

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@HI-JIN2 HI-JIN2 self-assigned this Feb 23, 2025
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements a home screen widget feature that allows users to view meal information from different restaurants directly on their home screen. The widget is built using Jetpack Compose Glance and includes comprehensive backend support for data management and updates.

  • Widget implementation with restaurant selection and automatic meal time detection
  • Background data synchronization using WorkManager with 30-minute intervals
  • DataStore-based configuration management for widget-specific settings

Reviewed Changes

Copilot reviewed 48 out of 62 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
settings.gradle Added core:design-system module dependency
gradle/libs.versions.toml Updated dependency versions and added widget-related libraries (Glance, WorkManager, DataStore)
core/design-system/* New design system module with reusable UI components for widget configuration
app/src/main/res/xml/* Widget configuration files defining widget properties and behavior
app/src/main/java/.../widget/* Complete widget implementation including UI, data management, and background workers
app/src/main/java/.../domain/* New domain models and use cases for widget meal data handling
app/src/main/java/.../data/* Enhanced repository and service layers to support widget data requirements
app/src/main/AndroidManifest.xml Widget receiver registration and deep link configuration
app/build.gradle.kts Updated dependencies and build configuration for widget support
Comments suppressed due to low confidence (2)

app/src/main/java/com/eatssu/android/presentation/widget/ui/MealWidget.kt:1

  • Commented-out code should be removed. If the padding is not needed, delete the comment entirely.
package com.eatssu.android.presentation.widget.ui

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +19 to +20

fun fromRestaurantEnumName(enumName: String): String {
Copy link

Copilot AI Aug 31, 2025

Choose a reason for hiding this comment

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

Inconsistent indentation on line 19-20. The function declaration should align with the other companion object functions.

Suggested change
fun fromRestaurantEnumName(enumName: String): String {
fun fromRestaurantEnumName(enumName: String): String {

Copilot uses AI. Check for mistakes.
Comment on lines 16 to 20
override suspend fun getTodayMeal( //todo 분기처리 어떻게 할지?
date: String,
restaurant: String,
time: String
): Flow<ArrayList<GetMealResponse>> {
Copy link

Copilot AI Aug 31, 2025

Choose a reason for hiding this comment

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

TODO comment indicates incomplete implementation. The branching logic should be implemented or the TODO should be moved to a proper issue tracker.

Copilot uses AI. Check for mistakes.
Comment on lines 29 to 34
// 실패한 경우에는 Result.failure()로 실패 정보 반환
// emit(response.message))
}
} catch (e: Exception) {
// 네트워크 오류 또는 예외가 발생한 경우에는 Result.failure()로 반환
// emit(ApiResult.Failure(e))
Copy link

Copilot AI Aug 31, 2025

Choose a reason for hiding this comment

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

Commented-out error handling code leaves the failure cases unhandled. Either implement proper error handling or throw appropriate exceptions.

Suggested change
// 실패한 경우에는 Result.failure()로 실패 정보 반환
// emit(response.message))
}
} catch (e: Exception) {
// 네트워크 오류 또는 예외가 발생한 경우에는 Result.failure()로 반환
// emit(ApiResult.Failure(e))
// 실패한 경우에는 예외를 던져서 Flow에서 에러로 처리
throw Exception(response.message ?: "Unknown error")
}
} catch (e: Exception) {
// 네트워크 오류 또는 예외가 발생한 경우에는 예외를 던져서 Flow에서 에러로 처리
throw e

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +55
var shrinkResources = false
var minifyEnabled = false
Copy link

Copilot AI Aug 31, 2025

Choose a reason for hiding this comment

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

These variables are declared but never used. They should either be assigned to the build configuration properties or removed.

Suggested change
var shrinkResources = false
var minifyEnabled = false

Copilot uses AI. Check for mistakes.
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.material3)
// androidTestImplementation(libs.androidx.compose.ui.test.junit4)
Copy link

Copilot AI Aug 31, 2025

Choose a reason for hiding this comment

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

Commented-out dependency should either be removed or uncommented if needed for testing.

Suggested change
// androidTestImplementation(libs.androidx.compose.ui.test.junit4)
androidTestImplementation(libs.androidx.compose.ui.test.junit4)

Copilot uses AI. Check for mistakes.
@PeraSite
Copy link
Member

전반적으로 식당 이름 표기나 로직 상 구분할 때 String(Restaurant.displayName 값)이 많이 사용되는 것 같아요.
명확한 타입을 사용하면 코드 가독성이나 혹시 모를 런타임 오류를 방지할 수 있으니 String 대신 Restaurant enum 자체를 사용하면 더 좋을 것 같습니다.
그러려면 WidgetSettingActivity의 selectedRestaurant, WidgetSettingScreen의 파라미터, EatSsuRadioButtonGroup에 제네릭스를 추가해stringify만 가능한 값이면 onOptionSelected로 그 타입에 맞게 반환 등 꽤 큰 구조적 변화를 예상합니다.
이 부분은 진님 시간이 바쁘시면 제가 처리할 수 있을 것 같아요! 어떻게 진행하면 될까요?

}
}
// fallback to legacy key
val legacyRaw = prefs[legacyRestaurantKey(appWidgetId)] ?: ""
Copy link
Member

Choose a reason for hiding this comment

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

prefs에 값이 존재하지 않을 경우에 빈 문자열로 Restaurant.fromDisplayName를 호출하는데, 이 경우 무조건 Unknown display name 오류가 뜨게 되어있습니다!
실제로 로그를 봐도 Failed to cleanup DataStore for widget 4: Unknown display name: 이렇게 오류가 뜨더라구요.
loadRestaurantPref가 Restaurant?를 반환하게 해서 cleanupWidgetDataStore에서 호출할 때 null이면 파일 삭제 시도 안하게하면 될 것 같습니다!


override fun getLocation(context: Context, fileKey: String): File {
// fileKey를 그대로 사용하여 파일명 생성 (appWidget-25 -> MealInfo_appWidget-25)
val filename = "${DATA_STORE_FILENAME_PREFIX}${fileKey}"
Copy link
Member

Choose a reason for hiding this comment

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

이 부분도 const로 되어있지만 파일명을 한 곳에서 모두 처리할 수 있게 로직을 분리하면 좋을 것 같아요.

Copy link
Member Author

Choose a reason for hiding this comment

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

오... 이거 무슨 말인지 잘 모르겠어요.. 추가 코멘트 가능할까요?

Comment on lines 109 to 172
companion object {
private val gson = Gson()

data class WidgetSettings(
val restaurant: String = "",
val appWidgetLayout: String? = null,
)

private fun settingsKey(appWidgetId: Int) =
stringPreferencesKey("widget_settings_$appWidgetId")

private fun legacyRestaurantKey(appWidgetId: Int) =
stringPreferencesKey("widget_restaurant_$appWidgetId")

private fun fileKeyRestaurantKey(fileKey: String) =
stringPreferencesKey("widget_restaurant_by_fileKey_$fileKey")

suspend fun saveRestaurantByFileKey(
context: Context,
fileKey: String,
restaurant: String,
) {
context.dataStore.edit { prefs ->
prefs[fileKeyRestaurantKey(fileKey)] = restaurant
}
Timber.d("saveRestaurantByFileKey 호출됨: fileKey='$fileKey', restaurant='$restaurant'")
}

suspend fun loadRestaurantByFileKey(context: Context, fileKey: String): Restaurant? {
val prefs: Preferences = context.dataStore.data.first()
val value = prefs[fileKeyRestaurantKey(fileKey)]
Timber.d("loadRestaurantByFileKey 호출됨: fileKey='$fileKey', value='$value'")
if (value.isNullOrBlank()) {
return null
}
return runCatching { Restaurant.valueOf(value) }.getOrNull()
}


suspend fun loadRestaurantPref(context: Context, appWidgetId: Int): Restaurant {
val prefs: Preferences = context.dataStore.data.first()
val json = prefs[settingsKey(appWidgetId)]
if (!json.isNullOrBlank()) {
val settings =
runCatching { gson.fromJson(json, WidgetSettings::class.java) }.getOrNull()
if (settings != null && settings.restaurant.isNotBlank()) {
val enumName = Restaurant.fromDisplayName(settings.restaurant)
Timber.d("load restaurant from settings $enumName")
return Restaurant.valueOf(enumName)
}
}
// fallback to legacy key
val legacyRaw = prefs[legacyRestaurantKey(appWidgetId)] ?: ""
val legacyEnumName = Restaurant.fromDisplayName(legacyRaw)
return if (legacyEnumName.isNotBlank()) {
Timber.d("load restaurant (legacy) $legacyEnumName from '$legacyRaw' for appWidgetId $appWidgetId")
Restaurant.valueOf(legacyEnumName)
} else {
Timber.w("No saved restaurant for appWidgetId=$appWidgetId, defaulting to HAKSIK")
Restaurant.HAKSIK
}
}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

WidgetSettingActivity.kt 파일에서 companion object로 DataStore에 접근하는 코드를 public으로 제공하는 것은 좋지 않은 것 같아요.
PreferencesRepository처럼 WidgetPreferencesRepository를 만들어서 데이터 저장, 불러오기 코드를 모두 그쪽에 넣고 구현하는 것이 좋다고 생각합니다.

Copy link
Member Author

Choose a reason for hiding this comment

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

data 레이어로의 분리는 생각하지 못했어요..!!! 분리해보도록 하겠습니다

Copy link
Member Author

Choose a reason for hiding this comment

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

85066d6 반영하였습니다!

Copy link
Member

@kangyuri1114 kangyuri1114 left a comment

Choose a reason for hiding this comment

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

수고하셨어요!! P1 이 두 부분입니다

이건 배포 전에 꼭 확인해주셨으면 좋겠어요
(아시다시피 P3 -> P1로 갈 수록 중요한 것!)
P2 이하는 위젯 기능에 영향이 가지 않는다고 판단하시면 배포 후 수정하셔도 될 것 같습니다!

SNACK_CORNER("스낵 코너", MenuType.FIXED),
THE_KITCHEN("더 키친", MenuType.FIXED);

companion object {
Copy link
Member

Choose a reason for hiding this comment

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

[ Etc ]

data모듈에서 UI <> 서버 <> 비즈니스 로직 간 매핑 함수들이 생겨난거군요 좋네욤

private val mealService: MealService,
) : MealRepository {

override suspend fun getTodayMeal( //todo 분기처리 어떻게 할지?
Copy link
Member

Choose a reason for hiding this comment

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

[Suggestion P2 ]

지금 실패 처리가 애매한거군요
UI단에는 성공 데이터만 방출하니..
커스텀 APIresult를 만들어 감싸서 모든 api 실패 시의 처리를 통일하면 좋겠지만 지금은 불가능하니

차라리 실패시에는 emptyList를 emit하는 건 어떨까요?
UI에서는 emptyList로 받는 경우의 예외처리(분기처리)를 진행하구요

Copy link
Member Author

Choose a reason for hiding this comment

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

지금 레포지토리에서 dto 자체를 반환하고 있어서 emptyList 반환이 어렵습니다. 일단 List가 아니라 ArrayList이고, 레거시 자체를 고쳐야해서 다음 PR로 넘기는게 조을 것 같슴다..

리뷰 v2 작업하면서 리뷰 부분은 dto 자체를 넘기는 문제나, 불필요한 flow 등 레거시 제거가 완료되어 설계 자체에 큰 시간을 쓰지는 않을 것 같습니다!

Copy link
Member Author

@HI-JIN2 HI-JIN2 Sep 4, 2025

Choose a reason for hiding this comment

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

e445acc 반영하였습니다!

공통 APIresult는 차차해보겠슴다

): Call<BaseResponse<ArrayList<GetMealResponse>>>

// todo 위에 함수를 call 없애서 하나로 합치길 바람 ㅜㅜ 위젯 때문에 급하게 복사본을 만듦
@GET("meals")
Copy link
Member

Choose a reason for hiding this comment

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

[ Suggestion - P1 ]
알고 계시듯 기존 함수에서 Call 빼고 하나의 함수 사용으로 수정해야 겠네요!
이건 개인적으로 따로 기록해두시고 refact 브랜치에서 진행하죠!! 시간이 없으니

android:minHeight="110dp"
android:minResizeWidth="180dp"
android:minResizeHeight="110dp"
android:previewImage="@drawable/img_widget_preview"
Copy link
Member

Choose a reason for hiding this comment

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

[ Question ]
이 이미지는 왜 필요한가요,,??

Copy link
Member Author

Choose a reason for hiding this comment

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

기기에서 홈화면에 위젯 추가하는 화면에서 프리뷰 이미지가 필요합니당

Copy link
Member Author

Choose a reason for hiding this comment

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

작동 영상 보시면 위젯 프리뷰 이미지 있자나용 그거에여

Comment on lines +28 to +31
private const val DATA_STORE_FILENAME_PREFIX = "MealInfo_"

private val dataStoreScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private val storeByPath = ConcurrentHashMap<String, DataStore<WidgetMealInfo>>()
Copy link
Member

Choose a reason for hiding this comment

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

[ Question ]
신기하네요.. 같은 파일에 대한 캐싱처리군용
근데 왜 ConcurrentHashMap를 활용한 캐싱이 필요한가요??

Copy link
Member

Choose a reason for hiding this comment

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

구글링 해봤을 때는 "DataStore가 제공하는 내부 캐싱 메커니즘"이 있다고 해서요

try {
runBlocking {
val restaurant = WidgetSettingActivity.loadRestaurantPref(context, appWidgetId)
val filename = "MealInfo_${restaurant.name}"
Copy link
Member

Choose a reason for hiding this comment

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

[ Suggestion - P1 ]

MealInfoStateDefinition 파일 > getLocation 함수
에서 fileKey : Glance 프레임워크가 넘겨주는 위젯 인스턴스 구분자

-> 위젯 ID 단위로 DataStore 파일이 하나씩 생성됨

근데 MealWidgetReceiver 파일에서는 restaurant.name을 파일명 키로 사용 중

삭제할 때도 restaurant.name으로 찾아서 삭제하는 게 아닌 appWidgetId을 사용해 찾아서 삭제하는 게 맞을 거 같아요

Copy link
Member Author

Choose a reason for hiding this comment

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

맞습니다!!!!
이전 방법으로 식당별로 datastore를 할당해서 저장하게 했다가 지금 방식으로 바꿔서 이렇게 남아있는 것 같아요!!!
코드 자세하게 봐주셔서 감사합니다🙇‍♀️ 바로 반영했어욤

Timber.d("LaunchedEffect: 저장된 식당 정보 없음")
}

MealWorker.enqueue(context)
Copy link
Member

Choose a reason for hiding this comment

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

[ Question ]
위젯 추가할 때마다 Worker가 중복 등록될 거 같은데 의도 하신걸까요??
모든 밀 위젯에 대해서는 1개의 워커만 있어도 되는 거 아닌지 그냥 궁금해요

Copy link
Member Author

Choose a reason for hiding this comment

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

각각의 워커를 가지고 있는게 맞습니다!
하나의 워커는 하나의 식당의 식단만 불러오고 저장하고 UI한테 가져다주는 역할을 합니다.
사용자가 추가하지 않은 식당에 대해서 식단을 로컬에 저장할 필요가 없고, 또 일반적인 선에서는 한 식당의 위젯을 하나만 만들기에 각각 만드는 것이 리소스가 문제되지 않을 것이라 생각하여 그리 했습니다.

혹시 납득이 되셨나옹?

Comment on lines 52 to 58
val glanceId: GlanceId? =
if (appWidgetId != null && appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
runBlocking {
GlanceAppWidgetManager(this@WidgetSettingActivity).getGlanceIdBy(
appWidgetId
)
}
Copy link
Member

Choose a reason for hiding this comment

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

[ Suggestion - P1 ]
setContent는 UI 스레드로 알고 있는데 runblocking 을 사용하면 ANR이 난다고 합니다
suspend 호출은 LaunchedEffect나 lifecycleScope.launch로 비동기 처리하는 게 안전하다고 하네요

Suggested change
val glanceId: GlanceId? =
if (appWidgetId != null && appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
runBlocking {
GlanceAppWidgetManager(this@WidgetSettingActivity).getGlanceIdBy(
appWidgetId
)
}
var glanceId by remember { mutableStateOf<GlanceId?>(null) }
LaunchedEffect(appWidgetId) {
if (appWidgetId != null && appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
glanceId = GlanceAppWidgetManager(context).getGlanceIdBy(appWidgetId)
}
}

Copy link
Member

@kangyuri1114 kangyuri1114 left a comment

Choose a reason for hiding this comment

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

💯

@HI-JIN2 HI-JIN2 merged commit 8c92506 into develop Sep 6, 2025
1 check passed
@HI-JIN2 HI-JIN2 deleted the feat/advanced-widget branch September 6, 2025 00:32
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.

안드로이드 위젯 배포

4 participants