CodeLab: Adding a Home Screen Widget in Flutter App

DoHyunKim·2023년 9월 6일
1

flutter

목록 보기
1/1

CodeLab 링크
코드 github 링크

CodeLab 소개

  • 여기서 소개하고자 하는 widget 은 flutter widget 에 대한 내용이 아니라 실제 device 홈화면에서의 widget 에 대한 내용이다.
  • 위젯은 홈화면에서 구현되는 부분이다 보니 복잡한 기능을 구현 할 수 없다.
    • IOS : 기본 텍스트, 간단한 그래픽
    • ANDROID : 기본 텍스트, 간단한 그래픽, 기본 컨트롤

  • flutter 프레임워크에서는 홈 화면 위젯 UI 를 자체적으로 그리는 것이 불가능! ⇒ Jetpack Compose 나 SwiftUI 등의 플랫폼 프레임워크로 생성된 위젯을 flutter 앱에 추가하자.

CodeLab 목표

  1. 홈위젯에 Flutter 앱의 데이터 표시.
  2. 홈위젯에 Flutter 앱에서 공유하는 글꼴을 사용하여 텍스트 표시.
  3. 홈위젯에 Flutter 위젯의 이미지를 표시.

파일 종류 및 패키지

  • home_screen.dart : 헤드라인 및 간단한 설명이 있는 뉴스 리스트 화면
  • article_screen.dart : 차트 및 전체 기사를 볼 수 있는 화면
  • news_data.dart : NewsArticle class 및 mock data를 정의해 놓은 파일.
  • shared_preferences : local device 에 저장하기 위한 패키지
  • home_widget : 홈화면의 위젯과 flutter 간에 데이터를 공유시키는 패키지

In IOS

1. Make Basic IOS Home Widget

  1. Xcode 실행
  2. 설정 창에서 file→new→target 클릭 후
  3. template 에서 widget extension 선택
  4. flutter 앱 구성 업데이트 필요 ( flutter 앱에 새 패키지 추가 + Xcode에서 프로젝트 target 을 run 시킬 때) ⇒ 프로젝트 터미널에 flutter build ios --config-only 입력
  5. Xcode 에서 Runner 대상 목록 새로 생성한 위젯을 선택하여 Run

(해당 과정까지 오류가 발생하지 않는다면?! 홈 화면 위젯 에서 새로 생성한 위젯을 찾을 수 있음.)

2. Connect HomeWidget and Flutter App Data

  • codelab 에서는 flutter 와 homewidget 간의 data를 공유하는 방법은 local device storage 를 이용해서 key/value 값을 통해 접근.
  • 해당 기능들은 SharedPreferences, home_widget 패키지를 통해서 구현이 가능.


  1. Xcode의 singing & capabilities 에서 Runner, NewsWidgetExtension 모두 Bundle Identifier 설정.
  2. Xcode의 singing & capabilities 에서 Runner, NewsWidgetExtension 모두 Capability 에서 App Groups 를 추가. (같은 그룹으로 설정해야 한다.)
  3. dart 코드를 이용해서 홈 화면 위젯에 업데이트 하고자 하는 내용 추가. (코드 참고)
  4. Xcode 에서 ios/NewsWidgets/NewsWidgets.swift 수정
struct NewsArticleEntry: TimelineEntry {
    let date: Date
    let title: String
    let description:String
}

기존의 SimpleEntry(원래 기본 홈위젯은 시간만 띄워줌) 를 우리가 원하는 정보(기사 제목, 기사 내용 등)를 띄워줄 NewsArticleEntry 로 바꾼다.

struct NewsWidgetsEntryView : View {
    var entry: Provider.Entry

    var body: some View {
      VStack {
        Text(entry.title)
        Text(entry.description)
      }
    }
}

여기서는 뉴스 기사 제목, 내용 들을 띄우기 위해 VStack 수정

struct Provider: TimelineProvider {

// 위젯을 처음 생성할 때 예시로 보이는 부분.
    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")
    }

// 현재 시간과 상태(ex.사용자의 기본값 데이터)에 대한 부분.
    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 함수를 통해서 현재 상태를 가져옴
      getSnapshot(in: context) { (entry) in
// atEnd method 는 현재 시간이 지날 시 데이터를 새로고침 하도록 홈화면 위젯에 명령
        let timeline = Timeline(entries: [entry], policy: .atEnd)
                  completion(timeline)
              }
    }
}

에러가 나지 않는다면? 정상적으로 작동한다.

  1. 추가적으로 flutter 앱에서 뉴스 기사 세부 화면에서 floating button 클릭 시 홈화면 위젯이 정상적으로 바뀌는지도 체크하자!

3. Change Home Screen Widget Font by Flutter app custom fonts

  • Xcode 에서 ios/NewsWidgets/NewsWidgets.swift 수정
struct NewsWidgetsEntryView : View {
   ...

   // font url 을 쉽게 가져올 수 있도록 도움을 주는 함수
   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
       }
// path를 통해서 폰트를 등록
       init(entry: Provider.Entry){
         self.entry = entry
         CTFontManagerRegisterFontsForURL(bundle.appending(path: "/fonts/Chewy-Regular.ttf") as CFURL, CTFontManagerScope.process, nil)
       }
   ...
}
var body: some View {
    VStack {
      // Update the following line.
      Text(entry.title).font(Font.custom("Chewy", size: 13))
			Text(entry.description).font(.system(size: 12)).padding(10)
    }
   }
  • 기존의 VStack은 뉴스 기사 제목, 내용만 간단하게 보여줬다면 해당 font 추가를 통해서 사이즈 및 폰트가 적용됨.

3. Rendering Flutter widgets as an image

  • 간단하게 플러터 위젯을 홈화면 위젯에 보여지게 하고 싶다면? 이미지로 표시하면 된다!
 //global key 생성 (해당 위젯의 크기를 가져오는데 필요한 context를 가져올때)
final _globalKey = GlobalKey();
String? imagePath;
...
floatingActionButton: FloatingActionButton.extended( 
        onPressed: () async {
          if (_globalKey.currentContext != null) {
            var path = await HomeWidget.renderFlutterWidget(
//home_widget package 에 있는 renderFlutterWidget 메소드를 통해서 LineChart()를 image화 한다.
              const LineChart(),
             /// fileName: 'screenshot', 이 부분은 실제로 사용을 안함. 변수 존재 x
              key: 'filename',
              logicalSize: _globalKey.currentContext!.size,//global key 사용
              pixelRatio:
                  MediaQuery.of(_globalKey.currentContext!).devicePixelRatio,
            );
            setState(() {
              imagePath = path as String?; //flutter위젯이 렌더링되는 image 위치 저장
            });
          }
          updateHeadline(widget.article);
        },

...
			Center(
            // New: Add this key 해당 currentContext 사용하기 위해서
            key: _globalKey,
            child: const LineChart(),
          ),
  • renderFlutterWidget 메소드가 호출 시

    • Flutter Widget 을 PNG 파일로 변환
    • Local DB 에 저장. (이때 key는 filename으로 저장되어 있다.)
  • Xcode 에서 ios/NewsWidgets/NewsWidgets.swift 수정

struct NewsArticleEntry: TimelineEntry {
   ...
		let date: Date
    let title: String
    let description:String

   // New: add the filename and displaySize.
   let filename: String
   let displaySize: CGSize
}

새롭게 들어올 NewsArticleEntry 구조를 적어준다.

func placeholder(in context: Context) -> NewsArticleEntry {
      NewsArticleEntry(date: Date(), title: "Placholder Title", description: "Placholder description", filename: "No screenshot available",  displaySize: context.displaySize)
    }

처음 홈화면위젯을 선택시에 보여지는 부분인 placeholder 를 수정한다.

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())
    }
   ...
}

chartImage 가 보일 수 있도록 다음과 같이 코드를 추가해준다. 이때 크기는 frame의 50% 로 설정했다.

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
}

Vstack 에 CharImage 를 추가해서 실제로 잘 동작 하는지 체크해보자.

최종적으로 IOS device 에서

flutter 내부의 data 를 홈화면 위젯에서 공유 할 수 있다.

profile
Do My Best!

0개의 댓글