1. 소개
위젯이란 무엇인가요?
Flutter 개발자의 경우 widget의 일반적인 정의는 Flutter 프레임워크를 사용하여 만든 UI 구성요소를 의미합니다. 이 Codelab의 컨텍스트에서 위젯은 앱을 열지 않고도 앱 정보를 볼 수 있는 미니 버전의 앱을 의미합니다. Android에서는 위젯이 홈 화면에 표시됩니다. iOS에서는 홈 화면, 잠금 화면 또는 오늘 뷰에 추가할 수 있습니다.
위젯은 얼마나 복잡할 수 있나요?
홈 화면 위젯은 대부분 단순합니다. 기본 텍스트, 간단한 그래픽 또는 Android의 경우 기본 컨트롤을 포함할 수 있습니다. Android와 iOS 모두 개발자가 사용할 수 있는 UI 구성요소 및 기능을 제한합니다.
위젯 UI 만들기
이러한 UI 제한으로 인해 Flutter 프레임워크를 사용하여 홈 화면 위젯의 UI를 직접 그릴 수 없습니다. 대신 Jetpack Compose 또는 SwiftUI와 같은 플랫폼 프레임워크로 만든 위젯을 Flutter 앱에 추가할 수 있습니다. 이 Codelab에서는 복잡한 UI를 다시 작성하지 않도록 앱과 위젯 간에 리소스를 공유하는 예를 설명합니다.
빌드할 항목
이 Codelab에서는 사용자가 기사를 읽을 수 있는 home_widget 패키지를 사용하여 간단한 Flutter 앱을 위해 Android와 iOS 모두에서 홈 화면 위젯을 빌드합니다. 위젯이 다음과 같이 작동합니다.
- Flutter 앱의 데이터를 표시합니다.
- Flutter 앱에서 공유한 글꼴 애셋을 사용하여 텍스트를 표시합니다.
- 렌더링된 Flutter 위젯의 이미지를 표시합니다.
이 Flutter 앱에는 두 화면 (또는 경로)이 포함되어 있습니다.
- 첫 번째에는 헤드라인과 설명이 포함된 뉴스 기사 목록이 표시됩니다.
- 두 번째 표에는
CustomPaint
를 사용하여 만든 차트와 함께 전체 문서가 표시됩니다.
.
학습할 내용
- iOS 및 Android에서 홈 화면 위젯을 만드는 방법
- home_widget 패키지를 사용하여 홈 화면 위젯과 Flutter 앱 간에 데이터를 공유하는 방법
- 재작성해야 하는 코드의 양을 줄이는 방법
- Flutter 앱에서 홈 화면 위젯을 업데이트하는 방법
2. 개발 환경 설정
두 플랫폼 모두 Flutter SDK와 IDE가 필요합니다. Flutter 작업에 원하는 IDE를 사용할 수 있습니다. Dart 코드 및 Flutter 확장 프로그램이 포함된 Visual Studio Code, Flutter 및 Dart 플러그인이 설치된 Android 스튜디오 또는 IntelliJ일 수 있습니다.
iOS 홈 화면 위젯을 만들려면 다음 단계를 따르세요.
- 이 Codelab은 실제 iOS 기기 또는 iOS 시뮬레이터에서 실행할 수 있습니다.
- Xcode IDE로 macOS 시스템을 구성해야 합니다. 이렇게 하면 앱의 iOS 버전을 빌드하는 데 필요한 컴파일러가 설치됩니다.
Android 홈 화면 위젯을 만들려면 다음 단계를 따르세요.
- 이 Codelab은 실제 Android 기기 또는 Android Emulator에서 실행할 수 있습니다.
- Android 스튜디오를 사용하여 개발 시스템을 구성해야 합니다. 이렇게 하면 앱의 Android 버전을 빌드하는 데 필요한 컴파일러가 설치됩니다.
시작 코드 가져오기
GitHub에서 프로젝트의 초기 버전 다운로드
명령줄에서 GitHub 저장소를 flutter-codelabs 디렉터리로 클론합니다.
$ git clone https://github.com/flutter/codelabs.git flutter-codelabs
저장소를 클론한 후 flutter-codelabs/homescreen_codelab 디렉터리에서 이 Codelab의 코드를 찾을 수 있습니다. 이 디렉터리에는 Codelab의 각 단계에서 완성된 프로젝트 코드가 포함되어 있습니다.
시작 앱 열기
flutter-codelabs/homescreen_codelab/step_03
디렉터리를 원하는 IDE로 엽니다.
패키지 설치
모든 필수 패키지가 프로젝트의 pubspec.yaml 파일에 추가되었습니다. 프로젝트 종속 항목을 검색하려면 다음 명령어를 실행합니다.
$ flutter pub get
3. 기본 홈 화면 위젯 추가
먼저 네이티브 플랫폼 도구를 사용하여 홈 화면 위젯을 추가합니다.
기본 iOS 홈 화면 위젯 만들기
Flutter iOS 앱에 앱 확장 프로그램을 추가하는 것은 SwiftUI 또는 UIKit 앱에 앱 확장 프로그램을 추가하는 것과 유사합니다.
- Flutter 프로젝트 디렉터리의 터미널 창에서
open ios/Runner.xcworkspace
를 실행합니다. 또는 VSCode에서 ios 폴더를 마우스 오른쪽 버튼으로 클릭하고 Open in Xcode를 선택합니다. 그러면 Flutter 프로젝트에서 기본 Xcode 작업공간이 열립니다. - 메뉴에서 파일 → 새로 만들기 → 대상을 선택합니다. 그러면 프로젝트에 새 대상이 추가됩니다.
- 템플릿 목록이 표시됩니다. 위젯 확장 프로그램을 선택합니다.
- 'NewsWidgets'를 입력합니다. 이 위젯의 제품 이름 상자에 입력합니다. Include Live Activity 및 Include Configuration Intent 체크박스를 모두 선택 해제합니다.
샘플 코드 검사
새 대상을 추가하면 Xcode가 선택한 템플릿을 기반으로 샘플 코드를 생성합니다. 생성된 코드 및 WidgetKit에 대한 자세한 내용은 Apple의 앱 확장 프로그램 문서 를 통해 개인정보처리방침을 정의할 수 있습니다.
샘플 위젯 디버그 및 테스트
- 먼저 Flutter 앱의 구성을 업데이트합니다. Flutter 앱에 새 패키지를 추가하고 Xcode에서 프로젝트의 대상을 실행하려는 경우 이 작업을 실행해야 합니다. 앱 구성을 업데이트하려면 Flutter 앱 디렉터리에서 다음 명령어를 실행합니다.
$ flutter build ios --config-only
- Runner(실행기)를 클릭하여 대상 목록을 표시합니다. 방금 만든 위젯 대상인 NewsWidgets를 선택하고 실행을 클릭합니다. iOS 위젯 코드 변경 시 Xcode에서 위젯 대상을 실행합니다.
- 시뮬레이터나 기기 화면에 기본 홈 화면 위젯이 표시됩니다. 앱이 표시되지 않는 경우 화면에 추가할 수 있습니다. 홈 화면을 길게 클릭한 다음 왼쪽 상단의 +를 클릭합니다.
- 앱 이름을 검색합니다. 이 Codelab에서는 '홈 화면 위젯'을 검색합니다.
- 홈 화면 위젯을 추가하면 시간을 제공하는 간단한 텍스트가 표시되어야 합니다.
기본 Android 위젯 만들기
- Android에 홈 화면 위젯을 추가하려면 Android 스튜디오에서 프로젝트의 빌드 파일을 엽니다. 이 파일은 android/build.gradle에서 찾을 수 있습니다. 또는 VSCode에서 android 폴더를 마우스 오른쪽 버튼으로 클릭하고 Open in Android Studio를 선택합니다.
- 프로젝트가 빌드되면 왼쪽 상단에서 앱 디렉터리를 찾습니다. 이 디렉터리에 새 홈 화면 위젯을 추가합니다. 디렉토리를 마우스 오른쪽 버튼으로 클릭하고 새로 만들기 -> 위젯 -> App Widget에서 사용할 수 있습니다.
- Android 스튜디오에 새 양식이 표시됩니다. 클래스 이름, 배치, 크기, 출발어 등 홈 화면 위젯에 관한 기본 정보를 추가합니다.
이 Codelab에서는 다음 값을 설정합니다.
- Class Name 상자를 NewsWidget에 추가할 수 있습니다.
- 최소 너비 (셀) 드롭다운을 3으로 설정
- 최소 높이 (셀) 드롭다운을 3으로 설정
샘플 코드 검사
양식을 제출하면 Android 스튜디오는 여러 파일을 만들고 업데이트합니다. 이 Codelab과 관련된 변경사항은 아래 표에 나와 있습니다.
작업 | 대상 파일 | 변경 |
업데이트 |
| NewsWidget을 등록하는 새 수신기를 추가합니다. |
만들기 |
| 홈 화면 위젯 UI를 정의합니다. |
만들기 |
| 홈 화면 위젯 구성을 정의합니다. 이 파일에서 위젯의 크기나 이름을 조정할 수 있습니다. |
만들기 |
| 홈 화면 위젯에 기능을 추가하는 Kotlin 코드를 포함합니다. |
이 Codelab 전반에서 이러한 파일에 관한 자세한 내용을 확인할 수 있습니다.
샘플 위젯 디버그 및 테스트
이제 애플리케이션을 실행하고 홈 화면 위젯을 확인합니다. 앱을 빌드한 후 Android 기기의 애플리케이션 선택 화면으로 이동하여 이 Flutter 프로젝트의 아이콘을 길게 누릅니다. 팝업 메뉴에서 위젯을 선택합니다.
Android 기기나 에뮬레이터에 Android용 기본 홈 화면 위젯이 표시됩니다.
4. Flutter 앱에서 홈 화면 위젯으로 데이터 전송
내가 만든 기본 홈 화면 위젯을 맞춤설정할 수 있습니다. 홈 화면 위젯을 업데이트하여 뉴스 기사의 헤드라인과 요약을 표시합니다. 다음 스크린샷은 헤드라인과 요약을 표시하는 홈 화면 위젯의 예를 보여줍니다.
앱과 홈 화면 위젯 간에 데이터를 전달하려면 Dart 및 네이티브 코드를 작성해야 합니다. 이 섹션에서는 이 프로세스를 세 부분으로 나뉩니다.
- Android와 iOS에서 모두 사용할 수 있는 Flutter 앱에서 Dart 코드 작성
- 네이티브 iOS 기능 추가
- 네이티브 Android 기능 추가
iOS 앱 그룹 사용
iOS 상위 앱과 위젯 확장 프로그램 간에 데이터를 공유하려면 두 타겟 모두 동일한 앱 그룹에 속해야 합니다. 앱 그룹에 대한 자세한 내용은 Apple의 앱 그룹 문서를 참고하세요.
번들 식별자를 업데이트합니다.
Xcode에서 대상 설정으로 이동합니다. Signing & 기능 탭에서 팀 및 번들 식별자가 설정되어 있는지 확인합니다.
Xcode에서 앱 그룹을 실행기 타겟과 NewsWidgetExtension 타겟 모두에 추가합니다.
+ 기능 ->을 선택합니다. 앱 그룹을 클릭하고 새 앱 그룹을 추가합니다. 실행기 (상위 앱) 대상과 위젯 대상 모두에 대해 위 작업을 반복합니다.
Dart 코드 추가
iOS 및 Android 앱은 몇 가지 방법으로 Flutter 앱과 데이터를 공유할 수 있습니다.이러한 앱과 통신하려면 기기의 로컬 key/value
스토어를 활용합니다. iOS는 이 스토어를 UserDefaults
이라고 하고 Android에서는 이 스토어를 SharedPreferences
이라고 합니다. home_widget 패키지는 이러한 API를 래핑하여 두 플랫폼에 데이터를 저장하는 작업을 간소화하고 홈 화면 위젯이 업데이트된 데이터를 가져올 수 있도록 합니다.
광고 제목 및 설명 데이터는 news_data.dart
파일에서 가져옵니다. 이 파일에는 모의 데이터와 NewsArticle
데이터 클래스가 포함되어 있습니다.
lib/news_data.dart
class NewsArticle {
final String title;
final String description;
final String? articleText;
NewsArticle({
required this.title,
required this.description,
this.articleText = loremIpsum,
});
}
광고 제목 및 설명 값 업데이트
Flutter 앱에서 홈 화면 위젯을 업데이트하는 기능을 추가하려면 lib/home_screen.dart
파일로 이동합니다. 파일의 내용을 다음 코드로 바꿉니다. 그런 다음 <YOUR APP GROUP>
를 앱 그룹의 식별자로 바꿉니다.
lib/home_screen.dart
import 'package:flutter/material.dart';
import 'package:home_widget/home_widget.dart'; // Add this import
import 'article_screen.dart';
import 'news_data.dart';
// TODO: Replace with your App Group ID
const String appGroupId = '<YOUR APP GROUP>'; // Add from here
const String iOSWidgetName = 'NewsWidgets';
const String androidWidgetName = 'NewsWidget'; // To here.
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
void updateHeadline(NewsArticle newHeadline) { // Add from here
// Save the headline data to the widget
HomeWidget.saveWidgetData<String>('headline_title', newHeadline.title);
HomeWidget.saveWidgetData<String>(
'headline_description', newHeadline.description);
HomeWidget.updateWidget(
iOSName: iOSWidgetName,
androidName: androidWidgetName,
);
} // To here.
class _MyHomePageState extends State<MyHomePage> {
@override // Add from here
void initState() {
super.initState();
HomeWidget.setAppGroupId(appGroupId);
// Mock read in some data and update the headline
final newHeadline = getNewsStories()[0];
updateHeadline(newHeadline);
} // To here.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Top Stories'),
centerTitle: false,
titleTextStyle: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Colors.black)),
body: ListView.separated(
separatorBuilder: (context, idx) {
return const Divider();
},
itemCount: getNewsStories().length,
itemBuilder: (context, idx) {
final article = getNewsStories()[idx];
return ListTile(
key: Key('$idx ${article.hashCode}'),
title: Text(article.title!),
subtitle: Text(article.description!),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) {
return ArticleScreen(article: article);
},
),
);
},
);
},
));
}
}
updateHeadline
함수는 키-값 쌍을 기기의 로컬 저장소에 저장합니다. headline_title
키에는 newHeadline.title
의 값이 포함됩니다. headline_description
키에는 newHeadline.description
의 값이 포함됩니다. 또한 이 함수는 홈 화면 위젯의 새 데이터를 검색하고 렌더링할 수 있음을 네이티브 플랫폼에 알립니다.
floatingActionButton 수정
다음과 같이 floatingActionButton
를 누르면 updateHeadline
함수를 호출합니다.
lib/article_screen.dart
// New: import the updateHeadline function
import 'home_screen.dart';
...
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('Updating home screen widget...'),
));
// New: call updateHeadline
updateHeadline(widget.article);
},
label: const Text('Update Homescreen'),
),
...
이번 변경으로 사용자가 기사 페이지에서 Update Headline 버튼을 누르면 홈 화면 위젯 세부정보가 업데이트됩니다.
기사 데이터를 표시하도록 iOS 코드 업데이트
iOS용 홈 화면 위젯을 업데이트하려면 Xcode를 사용합니다.
Xcode에서 NewsWidgets.swift
파일을 엽니다.
TimelineEntry
를 구성합니다.
SimpleEntry
구조체를 다음 코드로 바꿉니다.
ios/NewsWidgets/NewsWidgets.swift
// The date and any data you want to pass into your app must conform to TimelineEntry
struct NewsArticleEntry: TimelineEntry {
let date: Date
let title: String
let description:String
}
이 NewsArticleEntry
구조체는 업데이트 시 홈 화면 위젯에 전달할 수신 데이터를 정의합니다. TimelineEntry
유형에는 날짜 매개변수가 필요합니다.TimelineEntry
프로토콜에 대한 자세한 내용은 Apple의 TimelineEntry 문서를 확인하세요.
콘텐츠를 표시하는 View
수정
날짜 대신 뉴스 기사 헤드라인과 설명이 표시되도록 홈 화면 위젯을 수정하세요. SwiftUI에 텍스트를 표시하려면 Text
뷰를 사용합니다. SwiftUI에서 뷰를 서로 쌓으려면 VStack
뷰를 사용합니다.
생성된 NewsWidgetEntryView
뷰를 다음 코드로 바꿉니다.
ios/NewsWidgets/NewsWidgets.swift
//View that holds the contents of the widget
struct NewsWidgetsEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack {
Text(entry.title)
Text(entry.description)
}
}
}
홈 화면 위젯에 업데이트 시기와 방법을 알리도록 제공자 수정
기존 Provider
를 다음 코드로 바꿉니다. 그런 다음 <YOUR APP GROUP>을 앱 그룹 식별자로 대체합니다.
ios/NewsWidgets/NewsWidgets.swift
struct Provider: TimelineProvider {
// Placeholder is used as a placeholder when the widget is first displayed
func placeholder(in context: Context) -> NewsArticleEntry {
// Add some placeholder title and description, and get the current date
NewsArticleEntry(date: Date(), title: "Placeholder Title", description: "Placeholder description")
}
// Snapshot entry represents the current time and state
func getSnapshot(in context: Context, completion: @escaping (NewsArticleEntry) -> ()) {
let entry: NewsArticleEntry
if context.isPreview{
entry = placeholder(in: context)
}
else{
// Get the data from the user defaults to display
let userDefaults = UserDefaults(suiteName: <YOUR APP GROUP>)
let title = userDefaults?.string(forKey: "headline_title") ?? "No Title Set"
let description = userDefaults?.string(forKey: "headline_description") ?? "No Description Set"
entry = NewsArticleEntry(date: Date(), title: title, description: description)
}
completion(entry)
}
// getTimeline is called for the current and optionally future times to update the widget
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
// This just uses the snapshot function you defined earlier
getSnapshot(in: context) { (entry) in
// atEnd policy tells widgetkit to request a new entry after the date has passed
let timeline = Timeline(entries: [entry], policy: .atEnd)
completion(timeline)
}
}
}
이전 코드의 Provider
는 TimelineProvider
를 준수합니다. Provider
에는 다음과 같은 세 가지 메서드가 있습니다.
placeholder
메서드는 사용자가 처음 홈 화면 위젯을 미리 볼 때 자리표시자 항목을 생성합니다.
getSnapshot
메서드는 사용자 기본값에서 데이터를 읽고 현재 시간의 항목을 생성합니다.getTimeline
메서드는 타임라인 항목을 반환합니다. 이는 콘텐츠를 업데이트할 시기가 예측되었을 때 도움이 됩니다. 이 Codelab에서는 getSnapshot 함수를 사용하여 현재 상태를 가져옵니다..atEnd
메서드는 현재 시간이 지나면 데이터를 새로고침하도록 홈 화면 위젯에 지시합니다.
NewsWidgets_Previews
주석 처리
미리보기 사용은 이 Codelab에서 다루지 않습니다. SwiftUI 홈 화면 위젯 미리보기에 관한 자세한 내용은 Apple의 위젯 디버깅 문서를 참고하세요.
모든 파일을 저장하고 앱 및 위젯 타겟을 다시 실행합니다.
타겟을 다시 실행하여 앱과 홈 화면 위젯이 작동하는지 확인합니다.
- Xcode에서 앱 스키마를 선택하여 앱 타겟을 실행합니다.
- Xcode에서 확장 프로그램 스키마를 선택하여 확장 프로그램 타겟을 실행합니다.
- 앱에서 기사 페이지로 이동합니다.
- 버튼을 클릭하여 광고 제목을 업데이트합니다. 홈 화면 위젯도 헤드라인을 업데이트합니다.
Android 코드 업데이트
홈 화면 위젯 XML 추가
Android 스튜디오에서 이전 단계에서 생성된 파일을 업데이트합니다.res/layout/news_widget.xml
파일을 엽니다. 홈 화면 위젯의 구조와 레이아웃을 정의합니다. 오른쪽 상단의 Code(코드)를 선택하고 이 파일의 콘텐츠를 다음 코드로 교체합니다.
android/app/res/layout/news_widget.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/widget_container"
style="@style/Widget.Android.AppWidget.Container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@android:color/white"
android:theme="@style/Theme.Android.AppWidgetContainer">
<TextView
android:id="@+id/headline_title"
style="@style/Widget.Android.AppWidget.InnerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:background="@android:color/white"
android:text="Title"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/headline_description"
style="@style/Widget.Android.AppWidget.InnerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/headline_title"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="4dp"
android:background="@android:color/white"
android:text="Title"
android:textSize="16sp" />
</RelativeLayout>
이 XML은 두 개의 텍스트 뷰를 정의합니다. 하나는 기사 제목용이고 다른 하나는 기사 설명용입니다. 이러한 텍스트 뷰는 스타일도 정의합니다. 이 Codelab 전반에 걸쳐 이 파일로 돌아옵니다.
NewsWidget 기능 업데이트
NewsWidget.kt
Kotlin 소스 코드 파일을 엽니다. 이 파일에는 AppWidgetProvider
클래스를 확장하는 NewsWidget
라는 생성된 클래스가 포함되어 있습니다.
NewsWidget
클래스에는 슈퍼클래스의 메서드 세 개가 포함되어 있습니다. onUpdate
메서드를 수정합니다. Android는 고정된 간격으로 위젯에 이 메서드를 호출합니다.
NewsWidget.kt
파일의 콘텐츠를 다음 코드로 바꿉니다.
android/app/java/com.mydomain.homescreen_widgets/NewsWidget.kt
// Import will depend on App ID.
package com.mydomain.homescreen_widgets
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.widget.RemoteViews
// New import.
import es.antonborri.home_widget.HomeWidgetPlugin
/**
* Implementation of App Widget functionality.
*/
class NewsWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray,
) {
for (appWidgetId in appWidgetIds) {
// Get reference to SharedPreferences
val widgetData = HomeWidgetPlugin.getData(context)
val views = RemoteViews(context.packageName, R.layout.news_widget).apply {
val title = widgetData.getString("headline_title", null)
setTextViewText(R.id.headline_title, title ?: "No title set")
val description = widgetData.getString("headline_description", null)
setTextViewText(R.id.headline_description, description ?: "No description set")
}
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
}
이제 onUpdate
가 호출되면 Android는 the widgetData.getString()
메서드를 사용하여 로컬 저장소에서 최신 값을 가져온 다음 setTextViewText
를 호출하여 홈 화면 위젯에 표시되는 텍스트를 변경합니다.
업데이트 테스트
앱을 테스트하여 홈 화면 위젯이 새 데이터로 업데이트되는지 확인합니다. 데이터를 업데이트하려면 기사 페이지의 Update Home Screen FloatingActionButton
을 사용합니다. 홈 화면 위젯이 기사 제목으로 업데이트됩니다.
5. iOS 홈 화면 위젯에서 Flutter 앱 맞춤 글꼴 사용
지금까지 Flutter 앱이 제공하는 데이터를 읽도록 홈 화면 위젯을 구성했습니다. Flutter 앱에는 홈 화면 위젯에 사용할 수 있는 맞춤 글꼴이 포함되어 있습니다. iOS 홈 화면 위젯에서 맞춤 글꼴을 사용할 수 있습니다. Android에서는 홈 화면 위젯에서 맞춤 글꼴을 사용할 수 없습니다.
iOS 코드 업데이트
Flutter는 iOS 애플리케이션의 mainBundle에 애셋을 저장합니다. 홈 화면 위젯 코드에서 이 번들의 애셋에 액세스할 수 있습니다.
NewsWidgets.swift 파일의 NewsWidgetsEntryView 구조체에서 다음과 같이 변경합니다.
Flutter 애셋 디렉터리 경로를 가져오는 도우미 함수를 만듭니다.
ios/NewsWidgets/NewsWidgets.swift
struct NewsWidgetsEntryView : View {
...
// New: Add the helper function.
var bundle: URL {
let bundle = Bundle.main
if bundle.bundleURL.pathExtension == "appex" {
// Peel off two directory levels - MY_APP.app/PlugIns/MY_APP_EXTENSION.appex
var url = bundle.bundleURL.deletingLastPathComponent().deletingLastPathComponent()
url.append(component: "Frameworks/App.framework/flutter_assets")
return url
}
return bundle.bundleURL
}
...
}
URL을 사용하여 맞춤 글꼴 파일에 글꼴을 등록합니다.
ios/NewsWidgets/NewsWidgets.swift
struct NewsWidgetsEntryView : View {
...
// New: Register the font.
init(entry: Provider.Entry){
self.entry = entry
CTFontManagerRegisterFontsForURL(bundle.appending(path: "/fonts/Chewy-Regular.ttf") as CFURL, CTFontManagerScope.process, nil)
}
...
}
맞춤 글꼴을 사용하도록 제목 텍스트 뷰를 업데이트합니다.
ios/NewsWidgets/NewsWidgets.swift
struct NewsWidgetsEntryView : View {
...
var body: some View {
VStack {
// Update the following line.
Text(entry.title).font(Font.custom("Chewy", size: 13))
Text(entry.description)
}
}
...
}
이제 홈 화면 위젯을 실행하면 다음 이미지와 같이 헤드라인의 맞춤 글꼴이 사용됩니다.
6. Flutter 위젯을 이미지로 렌더링
이 섹션에서는 Flutter 앱의 그래프를 홈 화면 위젯으로 표시합니다.
이 위젯은 홈 화면에 표시한 텍스트보다 더 큰 도전과제를 제공합니다. Flutter 차트는 네이티브 UI 구성요소를 사용하여 다시 만드는 것보다 이미지로 표시하는 것이 훨씬 쉽습니다.
Flutter 차트를 PNG 파일로 렌더링하도록 홈 화면 위젯을 코딩합니다. 홈 화면 위젯에 해당 이미지가 표시될 수 있습니다.
Dart 코드 작성
Dart 측에서 home_widget 패키지의 renderFlutterWidget
메서드를 추가합니다. 이 메서드는 위젯, 파일 이름 및 키를 사용합니다. Flutter 위젯의 이미지를 반환하여 공유 컨테이너에 저장합니다. 코드에 이미지 이름을 제공하고 홈 화면 위젯이 컨테이너에 액세스할 수 있는지 확인합니다. key
는 전체 파일 경로를 기기의 로컬 저장소에 문자열로 저장합니다. 이렇게 하면 Dart 코드에서 이름이 변경되면 홈 화면 위젯이 파일을 찾을 수 있습니다.
이 Codelab에서는 lib/article_screen.dart
파일의 LineChart
클래스가 차트를 나타냅니다. 빌드 메서드는 이 차트를 화면에 그리는 CustomPainter를 반환합니다.
이 기능을 구현하려면 lib/article_screen.dart
파일을 엽니다. home_widget 패키지를 가져옵니다. 다음으로 _ArticleScreenState
클래스의 코드를 다음 코드로 바꿉니다.
lib/article_screen.dart
import 'package:flutter/material.dart';
// New: import the home_widget package.
import 'package:home_widget/home_widget.dart';
import 'home_screen.dart';
import 'news_data.dart';
...
class _ArticleScreenState extends State<ArticleScreen> {
// New: add this GlobalKey
final _globalKey = GlobalKey();
String? imagePath;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.article.title!),
),
// New: add this FloatingActionButton
floatingActionButton: FloatingActionButton.extended(
onPressed: () async {
if (_globalKey.currentContext != null) {
var path = await HomeWidget.renderFlutterWidget(
const LineChart(),
fileName: 'screenshot',
key: 'filename',
logicalSize: _globalKey.currentContext!.size,
pixelRatio:
MediaQuery.of(_globalKey.currentContext!).devicePixelRatio,
);
setState(() {
imagePath = path as String?;
});
}
updateHeadline(widget.article);
},
label: const Text('Update Homescreen'),
),
body: ListView(
padding: const EdgeInsets.all(16.0),
children: [
Text(
widget.article.description!,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 20.0),
Text(widget.article.articleText!),
const SizedBox(height: 20.0),
Center(
// New: Add this key
key: _globalKey,
child: const LineChart(),
),
const SizedBox(height: 20.0),
Text(widget.article.articleText!),
],
),
);
}
}
이 예에서는 _ArticleScreenState
클래스에 세 가지 변경사항을 적용합니다.
GlobalKey 만들기
GlobalKey
는 특정 위젯의 컨텍스트를 가져오며 이는 해당 위젯의 크기를 가져오는 데 필요합니다 .
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
// New: add this GlobalKey
final _globalKey = GlobalKey();
...
}
imagePath 추가
imagePath
속성은 Flutter 위젯이 렌더링되는 이미지의 위치를 저장합니다.
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
...
// New: add this imagePath
String? imagePath;
...
}
렌더링할 키를 위젯에 추가
_globalKey
에는 이미지에 렌더링되는 Flutter 위젯이 포함되어 있습니다. 이 경우 Flutter 위젯은 LineChart
가 포함된 센터입니다.
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
...
Center(
// New: Add this key
key: _globalKey,
child: const LineChart(),
),
...
}
- 위젯을 이미지로 저장
renderFlutterWidget
메서드는 사용자가 floatingActionButton
를 클릭할 때 호출됩니다. 이 메서드는 결과로 생성된 PNG 파일을 '스크린샷'으로 저장합니다. 공유 컨테이너 디렉터리로 복사됩니다 또한 이 메서드는 이미지의 전체 경로를 기기 저장소에 파일 이름 키로 저장합니다.
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
...
floatingActionButton: FloatingActionButton.extended(
onPressed: () async {
if (_globalKey.currentContext != null) {
var path = await HomeWidget.renderFlutterWidget(
LineChart(),
fileName: 'screenshot',
key: 'filename',
logicalSize: _globalKey.currentContext!.size,
pixelRatio:
MediaQuery.of(_globalKey.currentContext!).devicePixelRatio,
);
setState(() {
imagePath = path as String?;
});
}
updateHeadline(widget.article);
},
...
}
iOS 코드 업데이트
iOS의 경우 코드를 업데이트하여 저장소에서 파일 경로를 가져오고 SwiftUI를 사용하여 파일을 이미지로 표시합니다.
NewsWidgets.swift
파일을 열어 다음과 같이 변경합니다.
NewsArticleEntry
구조체에 filename
및 displaySize
추가
filename
속성에는 이미지 파일의 경로를 나타내는 문자열이 포함됩니다. displaySize
속성은 사용자 기기의 홈 화면 위젯 크기를 보유합니다. 홈 화면 위젯의 크기는 context
에서 가져옵니다.
ios/NewsWidgets/NewsWidgets.swift
struct NewsArticleEntry: TimelineEntry {
...
// New: add the filename and displaySize.
let filename: String
let displaySize: CGSize
}
placeholder
함수 업데이트
자리표시자 filename
및 displaySize
를 포함합니다.
ios/NewsWidgets/NewsWidgets.swift
func placeholder(in context: Context) -> NewsArticleEntry {
NewsArticleEntry(date: Date(), title: "Placeholder Title", description: "Placeholder description", filename: "No screenshot available", displaySize: context.displaySize)
}
getSnapshot의 userDefaults
에서 파일 이름을 가져옵니다.
이렇게 하면 홈 화면 위젯이 업데이트될 때 filename
변수가 userDefaults
저장소의 filename
값으로 설정됩니다.
ios/NewsWidgets/NewsWidgets.swift
func getSnapshot(
...
let title = userDefaults?.string(forKey: "headline_title") ?? "No Title Set"
let description = userDefaults?.string(forKey: "headline_description") ?? "No Description Set"
// New: get fileName from key/value store
let filename = userDefaults?.string(forKey: "filename") ?? "No screenshot available"
...
)
경로의 이미지를 표시하는 ChartImage를 만듭니다.
ChartImage
뷰는 Dart 측에서 생성된 파일의 콘텐츠로 이미지를 생성합니다. 여기에서는 크기를 프레임의 50% 로 설정합니다.
ios/NewsWidgets/NewsWidgets.swift
struct NewsWidgetsEntryView : View {
...
// New: create the ChartImage view
var ChartImage: some View {
if let uiImage = UIImage(contentsOfFile: entry.filename) {
let image = Image(uiImage: uiImage)
.resizable()
.frame(width: entry.displaySize.height*0.5, height: entry.displaySize.height*0.5, alignment: .center)
return AnyView(image)
}
print("The image file could not be loaded")
return AnyView(EmptyView())
}
...
}
NewsWidgetsEntryView의 본문에 ChartImage 사용
NewsWidgetsEntryView의 본문에 ChartImage 뷰를 추가하여 홈 화면 위젯에 ChartImage를 표시합니다.
ios/NewsWidgets/NewsWidgets.swift
VStack {
Text(entry.title).font(Font.custom("Chewy", size: 13))
Text(entry.description).font(.system(size: 12)).padding(10)
// New: add the ChartImage to the NewsWidgetEntryView
ChartImage
}
변경사항 테스트
변경사항을 테스트하려면 Xcode에서 Flutter 앱 (Runner) 타겟과 확장 프로그램 타겟을 모두 다시 실행합니다. 이미지를 보려면 앱에서 기사 페이지 중 하나로 이동한 다음 버튼을 눌러 홈 화면 위젯을 업데이트하세요.
Android 코드 업데이트
Android 코드는 iOS 코드와 동일하게 작동합니다.
android/app/res/layout/news_widget.xml
파일을 엽니다. 여기에는 홈 화면 위젯의 UI 요소가 포함되어 있습니다. 파일 내용을 다음 코드로 바꿉니다.
android/app/res/layout/news_widget.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/widget_container"
style="@style/Widget.Android.AppWidget.Container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@android:color/white"
android:theme="@style/Theme.Android.AppWidgetContainer">
<TextView
android:id="@+id/headline_title"
style="@style/Widget.Android.AppWidget.InnerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:background="@android:color/white"
android:text="Title"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/headline_description"
style="@style/Widget.Android.AppWidget.InnerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/headline_title"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="4dp"
android:background="@android:color/white"
android:text="Title"
android:textSize="16sp" />
<!--New: add this image view -->
<ImageView
android:id="@+id/widget_image"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_below="@+id/headline_description"
android:layout_alignBottom="@+id/headline_title"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="-134dp"
android:layout_weight="1"
android:adjustViewBounds="true"
android:background="@android:color/white"
android:scaleType="fitCenter"
android:src="@android:drawable/star_big_on"
android:visibility="visible"
tools:visibility="visible" />
</RelativeLayout>
이 새 코드는 현재 홈 화면 위젯에 이미지를 추가합니다. 위젯에는 현재 일반 별표 아이콘이 표시됩니다. 이 별표 아이콘을 Dart 코드에 저장한 이미지로 바꿉니다.
NewsWidget.kt
파일을 엽니다. 콘텐츠를 다음 코드로 바꿉니다.
android/app/java/com.mydomain.homescreen_widgets/NewsWidget.kt
// Import will depend on App ID.
package com.mydomain.homescreen_widgets
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.widget.RemoteViews
import java.io.File
import es.antonborri.home_widget.HomeWidgetPlugin
/**
* Implementation of App Widget functionality.
*/
class NewsWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray,
) {
for (appWidgetId in appWidgetIds) {
val widgetData = HomeWidgetPlugin.getData(context)
val views = RemoteViews(context.packageName, R.layout.news_widget).apply {
val title = widgetData.getString("headline_title", null)
setTextViewText(R.id.headline_title, title ?: "No title set")
val description = widgetData.getString("headline_description", null)
setTextViewText(R.id.headline_description, description ?: "No description set")
// New: Add the section below
// Get chart image and put it in the widget, if it exists
val imageName = widgetData.getString("filename", null)
val imageFile = File(imageName)
val imageExists = imageFile.exists()
if (imageExists) {
val myBitmap: Bitmap = BitmapFactory.decodeFile(imageFile.absolutePath)
setImageViewBitmap(R.id.widget_image, myBitmap)
} else {
println("image not found!, looked @: ${imageName}")
}
// End new code
}
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
}
이 Dart 코드는 filename
키를 사용하여 로컬 저장소에 스크린샷을 저장합니다. 또한 이미지의 전체 경로를 가져오고 여기서 File
객체를 만듭니다. 이미지가 있으면 Dart 코드가 홈 화면 위젯의 이미지를 새 이미지로 바꿉니다.
- 앱을 새로고침하고 기사 화면으로 이동합니다. 홈 화면 업데이트를 누릅니다. 홈 화면 위젯에 차트가 표시됩니다.
7. 다음 단계
축하합니다.
축하합니다. Flutter iOS 및 Android 앱용 홈 화면 위젯을 성공적으로 만들었습니다.
Flutter 앱의 콘텐츠에 연결
사용자가 클릭하는 위치에 따라 사용자를 앱의 특정 페이지로 유도할 수 있습니다. 예를 들어 이 Codelab의 뉴스 앱에서는 표시된 헤드라인의 뉴스 기사를 사용자에게 표시할 수 있습니다.
이 기능은 이 Codelab에서 다루지 않습니다. home_widget 패키지가 제공하는 스트림을 사용하여 홈 화면 위젯에서 앱 실행을 식별하고 URL을 통해 홈 화면 위젯에서 메시지를 보내는 예를 확인할 수 있습니다. 자세한 내용은 docs.flutter.dev의 딥 링크 문서 를 참고하세요.
백그라운드에서 위젯 업데이트
이 Codelab에서는 버튼을 사용하여 홈 화면 위젯의 업데이트를 트리거했습니다. 이는 테스트에는 합리적이지만 프로덕션 코드에서는 앱이 백그라운드에서 홈 화면 위젯을 업데이트하도록 할 수 있습니다. Workmanager 플러그인을 사용하여 백그라운드 작업을 만들어 홈 화면 위젯에 필요한 리소스를 업데이트할 수 있습니다. 자세한 내용은 home_widget 패키지의 백그라운드 업데이트 섹션을 참고하세요.
iOS의 경우 홈 화면 위젯이 UI 업데이트를 위해 네트워크 요청을 하도록 할 수도 있습니다. 요청의 조건이나 빈도를 제어하려면 타임라인을 사용하세요. 타임라인 사용에 관한 자세한 내용은 Apple의 '위젯을 최신 상태로 유지하기'를 참고하세요. 문서를 참조하세요. 를 통해 개인정보처리방침을 정의할 수 있습니다.