지난 1편에서 위젯에 대한 개념을 알아봤다면 이제 2편에서는 위젯을 만드는 법에 대해서 알아볼 것이다.
들어가기 전 본 포스팅은 Xml 개발이 아닌 Compose로 개발을 진행할 것임으로 Xml으로 개발을 원한다면 공식문서를 보길 바란다.
우선 위젯을 Kotlin과 Compose를 사용하여 빌드하려면 Jetpack Glance라는 것을 이용해야 한다.
차근차근 알아보자!
# Glance란?
공식문서에서는 아래와 같이 말하고 있다.
Jetpack Glance는 Jetpack Compose 런타임을 기반으로 빌드된 프레임워크로, Kotlin API를 사용하여 앱 위젯을 개발하고 설계할 수 있습니다.
Glance는 적은 코드로 홈 화면의 반응형 위젯을 빠르게 빌드하는 데 도움이 되는 컴포저블 세트를 제공합니다.
정리하자면 Glance는 적은 코드로 반응형 위젯을 빠르게 빌드하는 데 도움이 되는 컴포저블 세트라고 생각할 수 있다.
## 유의 사항 (2024. 04. 29 기준)

Glance는 안정화 버전이긴 하지만 개인적으로는 아직 부족한 기능이나 속성들이 많은 거 같다고 느꼈다(컴포저블 함수를 커스텀 못하는 문제..)
그래서 이 점을 참고하길 바란다.
물론 간단한 위젯을 만들기에는 현재 나온 버전으로도 충분히 가능!

이 부분을 못 읽어서 가장 많이 삽질을 했다.
저 말을 정리하하면 Glance로 개발 시 android.glance 패키지에 들어있는 컴포저블 함수만 사용해야 한다는 것이다!
Box 컴포저블로 예를 들면 기존 사용하는 패키지를 아래 사진과 같이 android.glance 패키지로 사용해야 한다.
<기존>

<변경>

이 과정에서 android.glance 패키지에 없는 컴포저블 함수도 많고 원래 알고 있던 컴포저블의 속성들도 약간씩 다르기 때문에 잘 살펴보면서 하길 추천한다!
⭐️ 대표적으로는 그냥 Modifier가 아닌 GlanceModifier를 사용해야 한다.
# Glance를 사용하여 간단 위젯 구현하기
## 의존성 추가
- 앱 수준의 build.gradle 추가 (최신 버전은 여기 확인)
dependencies {
// For AppWidgets support
implementation( "androidx.glance:glance-appwidget:1.0.0" )
// For interop APIs with Material 2
implementation( "androidx.glance:glance-material:1.0.0" )
// For interop APIs with Material 3
implementation( "androidx.glance:glance-material3:1.0.0" )
}
- Compose 컴파일러를 Glance에 사용할 수 있도록 활성화
android {
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.11"
}
kotlinOptions {
jvmTarget = "1.8"
}
}
1. AppWidgetProviderInfo XML 선언
Compose만 사용할 줄 알았는데 갑자기 XML?이라는 생각이 들것이다.

위젯을 구성하려면 AppWidgetProviderInfo XML은 필수적으로 만들어야 한다.
바로 만들기 전에 AppWidgetProviderInfo XML에 대해 알아보자!
1-1 AppWidgetProviderInfo XML 란?
- 위젯 생성을 위해서는 필수적으로 만들어야 한다.
- 위젯의 필수 특성(크기, 미리 보기 이미지, 설명, 업데이트 간격)등 정의를 한 xml이다.
- 단일 <appwidget-provider> 요소를 사용하여 XML 리소스 파일에 AppWidgetProviderInfo 객체를 정의하고 프로젝트의 res/xml/ 폴더에 저장한다.
예를 들면 아래 코드와 같이 작성한다.
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="40dp"
android:minHeight="40dp"
android:targetCellWidth="1"
android:targetCellHeight="1"
android:maxResizeWidth="250dp"
android:maxResizeHeight="120dp"
android:minResizeWidth="40dp"
android:minResizeHeight="40dp"
android:updatePeriodMillis="86400000"
android:description="@string/example_appwidget_description"
android:previewImage="@drawable/example_appwidget_preview"
android:previewLayout="@drawable/example_appwidget_preview"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen">
</appwidget-provider>
아니면 Glance에서 제공하는 기본 정의 로드 레이아웃을 아래와 같이 사용해도 된다!
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/glance_default_loading_layout">
</appwidget-provider>
각 속성에 대해 알고 싶으면 더 보기를 클릭하면 된다.
이제 각 주요 속성들을 알아보자
🔵 targetCellWidth 및 targetCellHeight (Android 12), minWidth 및 minHeight
android:minWidth="40dp"
android:minHeight="40dp"
android:targetCellWidth="1"
android:targetCellHeight="1"
targetCellWidth, targetCellHeight은 Android 12부터 사용 가능하고, 그리드 셀 측면에서 위젯의 기본크기를 지정할 수 있다.
Android 11 이하에서는 무시된다. 또한 홈화면이 그리드 기반 레이아웃을 지원하지 않으면 무시된다.
ex) targetCellWidth: 3, targetCellHeight: 3 이면 3*3 크기의 위젯 설정
minWidth, minHeight은 위젯의 기본 크기를 dp 단위로 지정한다.
따라서 Android 11 이하에서도 위젯의 크기 설정하는 것이 호환하도록 안드로이드 공식문서에서는 둘 다 지정하는 것을 권장한다.
또한 Android 12 이상 둘 다 지정했을 경우에는 targetCell의 우선순위가 더 높다!
🔵 minResizeWidth 및 minResizeHeight
android:minResizeWidth="40dp"
android:minResizeHeight="40dp"
위젯의 절대 최소 크기를 지정한다.
만약 minResizeWidth가 minWidth보다 크면 무시된다.
마찬가지로 minResizeHeight도 동일하다.
🔵 maxResizeWidth 및 maxResizeHeight
android:maxResizeWidth="250dp"
android:maxResizeHeight="120dp"
위젯의 권장 최대 크기를 지정한다.
만약 maxResizeWidth가 maxWidth보다 작으면 무시된다.
마찬가지로 maxResizeHeight도 동일하다.
🔵 resizeMode
android:resizeMode="horizontal|vertical"
위젯 크기를 조절할 수 있는 규칙 지정
속성 값에는 horizontal, vertical , none 이 세 가지를 사용할 수 있다.
horizontal → 가로만 조절 가능
vertical → 세로만 조절 가능
none → 조절 불가
horizontal | vertical → 가로 세로 조절 가능
🔵 updatePeriodMillis - Xml 개발 전용 Glance는 작동 안 함
android:updatePeriodMillis="86400000"
위젯 프레임워크가 onUpdate 콜백 메서드를 호출하여 AppWidgetProvider에 업데이트를 요청하는 빈도를 정의한다.
30분 미만의 값을 지원하지 않는다.
Glance에서는 작동을 안 하기 때문에 WorkManager 사용을 권장한다.
🔵 description - Android 12 이상 사용 가능
android:description="@string/example_appwidget_description"
아래 사진처럼 위젯에 표시할 위젯 선택 도구의 설정을 지정 Android 12에서 도입

🔵 previewLayout (Android 12) 및 previewImage (Android 11 이하)
android:previewImage="@drawable/example_appwidget_preview"
android:previewLayout="@drawable/example_appwidget_preview"
둘 다 앱 위젯 선택 도구의 미리 보기를 설정해 주는 속성이다.
previewLayout는 Android 12부터 사용 가능하고, previewImage는 Android 11 이하에서 사용 가능하다.
이 속성도 모든 버전의 호환성을 위해 두 개다 지정해 주는 걸 권장한다!
<Android 11 이하> <Android 12 이상>


🔵 widgetCategory
android:widgetCategory="home_screen"
위젯을 홈 화면, 잠금 화면 또는 둘 다에 표시할 수 있는지 여부를 선언한다.
홈 화면 → home_screen
잠금 화면 → keyguard
하지만 어떤 이유인지는 모르겠지만 Android 5.0 이상에서는 홈 화면(home_screen)만 유효하다!
이외에도 더 알고 싶으면 공식문서를 참고 바란다.
2. GlanceAppWidgetReceiver를 구현하는 클래스 생성
class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget = TODO("Create GlanceAppWidget")
}
GlanceAppWidgetReceiver의 함수인 glanceAppWidget를 오버라이드를 받고 리턴은 우선 비워놓는다.
3. 매니페스트에서 AppWidget 선언
<receiver android:name=".MyAppWidgetReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/my_app_widget_info" />
</receiver>
여기서 방금 전 2단계에서 생성했던 xml과 class를 등록해 준다.
4. GlanceAppWidget를 구현하는 클래스 생성
class MyAppWidget : GlanceAppWidget() {
// provideGlance override를 해준다.
override suspend fun provideGlance (context: Context, id: GlanceId) {
// provideGlance는 기본 스레드에서 실행된다.
// 비용이 많이 드는 작업 실행 시 withContext 사용
provideContent {
// 앱 위젯 UI를 작성한다.
Text("Hello World")
}
}
}
추상 클래스의 GlanceAppWidget안에 보면 추상 함수인 provideGlance이 있다.
그래서 오버라이드를 필수적으로 해줘야 하며 provideContent 스코프 내에 앱 위젯 UI를 작성해 주면 된다.
5. GlanceAppWidgetReceiver의 glanceAppWidget에서 인스턴스 화
class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
// MyAppWidgetReceiver에게 사용할 GlanceAppWidget을 알려준다.
override val glanceAppWidget: GlanceAppWidget = MyAppWidget()
}
2번째 GlanceAppWidgetReceiver 구현하는 클래스로 가서 4번째에서 만들어준 GlanceAppWidget 구현한 클래스를 넣어준다.
6. 버튼이 2개 있는 예제 UI 코드 입력
import androidx.glance.Button
import androidx.glance.layout.Column
import androidx.glance.layout.Row
import androidx.glance.text.Text
class MyAppWidget : GlanceAppWidget() {
override suspend fun provideGlance(context: Context, id: GlanceId) {
provideContent {
MyContent()
}
}
@Composable
private fun MyContent() {
Column(
modifier = GlanceModifier.fillMaxSize()
.background(GlanceTheme.colors.background),
verticalAlignment = Alignment.Top,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "어디로 이동할까요?",
modifier = GlanceModifier.padding(12.dp)
)
Button(
text = "Home",
onClick = actionStartActivity<MainActivity>()
)
}
}
}
❗️ 여기서 중요한 점이 1편에서도 설명을 했는데 꼭 androidx.glance 패키지에 있는 컴포저블 함수를 사용해야 한다!
절대로 androidx.compose.foundation 패키지와 머터리얼 패키지를 혼용해서 사용하면 안 된다!!
완성 화면

이렇게 버튼을 누르면 메인액티비로 이동하는 간단한 위젯을 만들어봤다.
다음 3편에서는 조금 더 다양한 상호작용 방법에 대해 알아보자!
[Android] 안드로이드 위젯 알아보기 3편 - Glance, Compose, Kotlin (Android Compose Glance Appwidget) 상호작용
지난 2편에서는 간단한 앱 위젯을 만들어봤다면 이번 3편에서는 액티비티 실행, 서비스 실행 등 앱 위젯 상호작용에 대해서 알아보자!# 상호작용 종류- 활동 실행 (Activity)- 서비스 실행- 브로드
jige.tistory.com