Glance Widget

말랑돌·2025년 7월 24일

안드로이드 학습

목록 보기
10/17
post-thumbnail

Glance Widget은 Jetpack Compose 환경에서 바탕화면에 위젯을 만들기 위한 라이브러리이다.

build.gradle

// For AppWidgets support
implementation("androidx.glance:glance-appwidget:1.1.1")
// For Glance support
implementation("androidx.glance:glance:1.1.1")

주요 컴포넌트 및 기능

GlanceAppWidget()

Compose에서의 @Composable 함수처럼 Content()를 오버라이드하여 UI를 정의

class MyWidget : GlanceAppWidget() {

    //Glance 위젯 그리기를 시작
    override suspend fun provideGlance(context: Context, id: GlanceId) {
        
        provideContent {
            Content()
        }
    }

    @Composable
    private fun Content() {
        GlanceTheme {
            MyContent()
        }
    }
}

GlanceAppWidgetReceiver()

위젯을 시스템에 등록하기 위한 리시버 클래스

class MyWidgetReceiver : GlanceAppWidgetReceiver() {
    override val glanceAppWidget: GlanceAppWidget = MyWidget()
}

GlanceModifier

Compose를 전체 지원하지 않는다. 따라서 Modifier 대신 GlanceModifier라는 별도 함수를 사용해야한다.


Action, State

Action은 버튼 클릭 등의 사용자 이벤트에 반응하는 방식

함수설명
actionRunCallback<T>()클릭 시 지정한 콜백 클래스(T)를 실행
actionStartActivity(intent)특정 Activity 실행
actionSendBroadcast(intent)브로드캐스트 발송
//버튼 사용
Button(
    text = "증가",
	onClick = actionRunCallback<IncreaseCounterAction>()
)

//GlanceModifier.clickable 사용
modifier = GlanceModifier.clickable(actionRunCallback<UpdateWidgetAction>())


//ActionCallback 클래스 정의
class UpdateWidgetAction : ActionCallback {
    override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) {
        LmsWidget().updateAll(context) 
    }
}

  • State는 위젯의 데이터 값을 저장하고, 위젯 UI에 반영되도록 한다.
    Compose의 State와 달리 재시작해도 항상 상태를 유지해야하는 영속적이고 공유 가능한 형태(Preferences 기반)로 관리한다.
  • Room DB의 데이터 저장하거나 가져올수도 있다.
//각 위젯에 새롭게 가져온 데이터를 보냄
private suspend fun setWidgetState(glanceIds: List<GlanceId>) {
    glanceIds.forEach { glanceId -> //각 위젯마다
        updateAppWidgetState(context, glanceId){pref -> //위젯 상태 업데이트 실행
            pref[stringPreferencesKey("monthRange")] = "0"
        }
    }
}

fun getMonthRange(prefs : Preferences) : String{
    return prefs[stringPreferencesKey("monthRange")] ?: "0"
}

반응형

위젯의 크기에 따라서 서로 다른 UI을 보이도록 할 수 있다.

SizeMode설명용도
Single고정 크기에서만 작동. 크기 변화에 따라 UI를 다르게 하지 않음단일 크기 위젯 (예: 2x2 고정)
Exact현재 위젯의 실제 픽셀 크기를 기반으로 UI 조정픽셀 단위 정밀 제어 필요할 때
Responsive시스템이 지정한 여러 개의 크기 조합에 따라 UI 분기 가능다양한 크기 대응 필요할 때 (가장 권장)

SizeMode.Single

  • 크기에 따라 UI를 변경할 수 없습니다.
  • 하나의 @Composable UI만 정의합니다.
  • 크기를 변경해도 같은 레이아웃이 유지됩니다.
override val sizeMode: SizeMode = SizeMode.Single

SizeMode.Exact

  • Size를 직접 받아서 정확한 크기(픽셀 단위)에 따라 동적으로 UI 구성
  • 정밀한 제어가 가능하지만, 일반적으로는 잘 사용되지 않습니다.
override val sizeMode: SizeMode = SizeMode.Exact

@Composable
override fun Content(size: Size) {
    if (size.height > 200.dp) {
        Text("큰 위젯")
    } else {
        Text("작은 위젯")
    }
}

SizeMode.Responsive

  • 시스템이 지원하는 여러 크기 조합(예: 2x2, 4x2 등) 에 맞춰 각각 UI를 다르게 정의할 수 있음
  • @Composable UI를 크기 조합별로 분기하여 구성 가능
  • 위젯 확장성, 유연성 면에서 가장 추천되는 방식
override val sizeMode: SizeMode = SizeMode.Responsive

@Composable
override fun Content(responsiveSize: ResponsiveSize) {
    when (responsiveSize) {
        ResponsiveSize.Small -> Text("작은 위젯")
        ResponsiveSize.Medium -> Text("중간 위젯")
        ResponsiveSize.Large -> Text("큰 위젯")
    }
}

WorkManager

Glance Widget은 UI만 제공하며, 업데이트 요청을 대부분 수정이다.

  • 사용자 액션(버튼 클릭)
  • 시스템 이벤트(부팅, 시간 변경)
  • 앱 내 호출(update(context, glanceId) 직접 호출)
  • 백그라운드 처리(WorkManager)

기본흐름

주기적 작업 정의 (Worker)
→ 데이터 수집 및 상태 저장 (updateAppWidgetState)
→ GlanceAppWidget.update()로 UI 갱신

Worker 정의

class WeatherUpdateWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        val weather = fetchWeatherFromApi() // API 요청

        val glanceIds = GlanceAppWidgetManager(context)
            .getGlanceIds(WeatherWidget::class.java)

        glanceIds.forEach { glanceId ->
            updateAppWidgetState(context, glanceId) { prefs ->
                prefs.toMutablePreferences()[WeatherKey] = weather
            }
            WeatherWidget().update(context, glanceId)
        }

        return Result.success()
    }
}

주기적 작업 예약

val request = PeriodicWorkRequestBuilder<WeatherUpdateWorker>(15, TimeUnit.MINUTES)
    .setConstraints(
        Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED) //네트워크 필요
            .build()
    )
    .build()

WorkManager.getInstance(context).enqueueUniquePeriodicWork(
    "weather_update",
    ExistingPeriodicWorkPolicy.KEEP,
    request
)

하지만?

  • 반드시 일정 시간마다가 아니라, 일정 시간 이후 조건이 충족되면 실행

  • Android 12+ 이상에서는 정책으로 인해서 실제로 작동이 알 될 수 있다.

  • appwidget-provider XML에 updatePeriodMillis를 설정해야한다.

    • Glance 위젯에서는 updatePeriodMillis를 0으로 설정하고 WorkManager를 사용하는 것이 권장
    • XML로 시스템이 호출하는 구조가 아닌, update()로 명시적으로 호출해서 데이터의 최신화를 항상 반영하기 위함
    • updatePeriodMillis가 설정되어 있으면, 해당 시간에 맞추어 UI 업데이트가 되기 때문에 최신상태가 유지되지 않을 수 있음

0개의 댓글