Glance Widget은 Jetpack Compose 환경에서 바탕화면에 위젯을 만들기 위한 라이브러리이다.
// For AppWidgets support
implementation("androidx.glance:glance-appwidget:1.1.1")
// For Glance support
implementation("androidx.glance:glance:1.1.1")
Compose에서의 @Composable 함수처럼 Content()를 오버라이드하여 UI를 정의
class MyWidget : GlanceAppWidget() {
//Glance 위젯 그리기를 시작
override suspend fun provideGlance(context: Context, id: GlanceId) {
provideContent {
Content()
}
}
@Composable
private fun Content() {
GlanceTheme {
MyContent()
}
}
}
위젯을 시스템에 등록하기 위한 리시버 클래스
class MyWidgetReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget = MyWidget()
}
Compose를 전체 지원하지 않는다. 따라서 Modifier 대신 GlanceModifier라는 별도 함수를 사용해야한다.
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 분기 가능 | 다양한 크기 대응 필요할 때 (가장 권장) |
override val sizeMode: SizeMode = SizeMode.Single
override val sizeMode: SizeMode = SizeMode.Exact
@Composable
override fun Content(size: Size) {
if (size.height > 200.dp) {
Text("큰 위젯")
} else {
Text("작은 위젯")
}
}
override val sizeMode: SizeMode = SizeMode.Responsive
@Composable
override fun Content(responsiveSize: ResponsiveSize) {
when (responsiveSize) {
ResponsiveSize.Small -> Text("작은 위젯")
ResponsiveSize.Medium -> Text("중간 위젯")
ResponsiveSize.Large -> Text("큰 위젯")
}
}
Glance Widget은 UI만 제공하며, 업데이트 요청을 대부분 수정이다.
- 사용자 액션(버튼 클릭)
- 시스템 이벤트(부팅, 시간 변경)
- 앱 내 호출(update(context, glanceId) 직접 호출)
- 백그라운드 처리(WorkManager)
주기적 작업 정의 (Worker)
→ 데이터 수집 및 상태 저장 (updateAppWidgetState)
→ GlanceAppWidget.update()로 UI 갱신
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를 설정해야한다.