프로그래밍/flutter

[flutter] Flutter에서 홈 화면 앱 위젯을 만드는 방법 1 (안드로이드)

소행성왕자 2023. 10. 24. 18:18

본 포스트에서는 Flutter 앱에서 홈 위젯을 만들어 앱을 더욱 멋지고 화려하게 만드는 방법을 알고자 합니다.

위젯의 버튼을 클릭하면 환율이 변경됩니다.

현재 환율은 랜덤으로 변경된다. -> url 페이지에 받아와서 출력해야함

 

1. pubspec.yaml  home_widget 패키지를 추가

dependencies:
  home_widget: ^0.3.0

pubspec.yaml

name: webview_flutter_onlyweb_kt
description: A new Flutter project.
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1

environment:
  sdk: '>=3.1.1 <4.0.0'
  #sdk: '>=2.19.1 <3.0.0'

# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  webview_flutter: ^4.2.2
  flutter_inappwebview: ^5.3.2
  hex: ^0.2.0
  flutter_launcher_icons: ^0.13.1
  http: ^0.13.6
  dynatrace_flutter_plugin: ^3.275.1
  home_widget: ^0.3.0


dev_dependencies:
  flutter_test:
    sdk: flutter

  # The "flutter_lints" package below contains a set of recommended lints to
  # encourage good coding practices. The lint set provided by the package is
  # activated in the `analysis_options.yaml` file located at the root of your
  # package. See that file for information about deactivating specific lint
  # rules and activating additional ones.
  flutter_lints: ^2.0.0



# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

# The following section is specific to Flutter packages.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg

  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.dev/assets-and-images/#resolution-aware

  # For details regarding adding assets from package dependencies, see
  # https://flutter.dev/assets-and-images/#from-packages

  # To add custom fonts to your application, add a fonts section here,
  # in this "flutter" section. Each entry in this list should have a
  # "family" key with the font family name, and a "fonts" key with a
  # list giving the asset and other descriptors for the font. For
  # example:
  # fonts:
  #   - family: Schyler
  #     fonts:
  #       - asset: fonts/Schyler-Regular.ttf
  #       - asset: fonts/Schyler-Italic.ttf
  #         style: italic
  #   - family: Trajan Pro
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #
  # For details regarding fonts from package dependencies,
  # see https://flutter.dev/custom-fonts/#from-packages

 

2. layout / xml 디렉토리 및 파일 생성

  • android/app/src/main/res/layout/widget_layout.xml
  • android/app/src/main/res/xml/widget_info.xml

widget_layout.xml

<LinearLayout
    android:id="@+id/widget_root"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="280dp"
    android:layout_height="210dp"
    android:background="#1f303d"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_counter"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:padding="12dp"
        android:text="--"
        android:textColor="@android:color/white"
        android:textSize="16sp" />
    <TextView
        android:id="@+id/tv_prdcd_sell"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:padding="12dp"
        android:text="--"
        android:textColor="@android:color/white"
        android:textSize="16sp" />
    <TextView
        android:id="@+id/tv_prdcd_buy"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:padding="12dp"
        android:text="--"
        android:textColor="@android:color/white"
        android:textSize="16sp" />

    <Button
        android:id="@+id/bt_update"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="환율 업데이트"
        android:textColor="@android:color/holo_blue_dark"
        android:textSize="12sp" />
</LinearLayout>

widget_info.xml

<appwidget-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/widget_layout"
    android:minWidth="280dp"
    android:minHeight="210dp"
    android:minResizeWidth="280dp"
    android:minResizeHeight="210dp"
    android:widgetCategory="home_screen" />

 

3. MainActivity 클래스가 포함된 디렉터리에 WidgetProvider.kt  추가

WidgetProvider.kt

package com.coforward.webview_flutter_onlyweb_kt

import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
import android.widget.RemoteViews
import es.antonborri.home_widget.HomeWidgetBackgroundIntent
import es.antonborri.home_widget.HomeWidgetLaunchIntent
import es.antonborri.home_widget.HomeWidgetProvider

class AppWidgetProvider : HomeWidgetProvider() {
    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, widgetData: SharedPreferences) {
        appWidgetIds.forEach { widgetId ->
            val views = RemoteViews(context.packageName, R.layout.widget_layout).apply {

                // Open App on Widget Click
                val pendingIntent = HomeWidgetLaunchIntent.getActivity(context,
                    MainActivity::class.java)
                setOnClickPendingIntent(R.id.widget_root, pendingIntent)

                val counter = widgetData.getInt("_counter", 0)
                val prdcdSellString = widgetData.getString("_prdcdSell", "0")
                val prdcdBuyString = widgetData.getString("_prdcdBuy", "0")

                var counterText = "현재 카운터 : $counter"
                val counterTextSell = "USD/KRW 팔때 $prdcdSellString"
                val counterTextBuy = "USD/KRW 살때 $prdcdBuyString"

                setTextViewText(R.id.tv_counter, counterText)
                setTextViewText(R.id.tv_prdcd_sell, counterTextSell)
                setTextViewText(R.id.tv_prdcd_buy, counterTextBuy)

                // Pending intent to update counter on button click
                val backgroundIntent = HomeWidgetBackgroundIntent.getBroadcast(context,
                    Uri.parse("myAppWidget://updatecounter"))
                setOnClickPendingIntent(R.id.bt_update, backgroundIntent)
            }
            appWidgetManager.updateAppWidget(widgetId, views)
        }
    }
}

 

4. home_widget 패키지를 사용하여 flutter에서 백그라운드로 위젯 데이터를 검색하고 업데이트 위해 main.dart 수정

main.dart

Future<void> main() async {
  // 앱 초기화 로직 및 설정
  await initialization(null);

  // Dynatrace 초기화
  Dynatrace().startWithoutWidget();

  WidgetsFlutterBinding.ensureInitialized();
  await HomeWidget.registerBackgroundCallback(backgroundCallback);

  runApp(
    const MaterialApp(
      home: WebViewApp(),
    ),
  );
}

...
...

Future<void> backgroundCallback(Uri? uri) async {
  if (uri?.host == 'updatecounter') {

    double randomNumberSell = generateRandomNumber(1200.0, 1299.99);
    String prdcdSell = randomNumberSell.toStringAsFixed(2);

    double randomNumberBuy = generateRandomNumber(1300.0, 1399.99);
    String prdcdBuy = randomNumberBuy.toStringAsFixed(2);

    print("랜덤 숫자 Sell: $prdcdSell");
    print("랜덤 숫자 Buy : $prdcdBuy");

    int _counter = 0;
    await HomeWidget.getWidgetData<int>('_counter', defaultValue: 0)
        .then((int? value) {
      _counter = value ?? 0;
      _counter++;
    });

    await HomeWidget.saveWidgetData<String>('_prdcdSell', prdcdSell);
    await HomeWidget.saveWidgetData<String>('_prdcdBuy', prdcdBuy);


    await HomeWidget.saveWidgetData<int>('_counter', _counter);
    await HomeWidget.updateWidget(name: 'AppWidgetProvider', iOSName: 'AppWidgetProvider');
  }
}

double generateRandomNumber(double min, double max) {
  Random random = Random();
  return min + random.nextDouble() * (max - min);
}

 

5. AndroidManifest.xml에 앱 위젯에 대한 수신기와 백그라운드 서비스를 추가

 

AndroidManifest.xml

...
        </activity>
        
        <!-- Your Background receiver and service goes here -->
       <receiver android:name="AppWidgetProvider" >
           <intent-filter>
               <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
           </intent-filter>
           <meta-data android:name="android.appwidget.provider"
               android:resource="@xml/widget_info" />
       </receiver>

       <receiver android:name="es.antonborri.home_widget.HomeWidgetBackgroundReceiver">
           <intent-filter>
               <action android:name="es.antonborri.home_widget.action.BACKGROUND" />
           </intent-filter>
       </receiver>
       <service android:name="es.antonborri.home_widget.HomeWidgetBackgroundService"
           android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true"/>
           
           ...

혹시

안드로이드 스튜디오 프로젝트를 코틀린으로 설정했는데도

WidgetProvider.kt  파일의 import 시 빨간색으로 나타나면

flutter 버전을 확인해보고 flutter upgrade 하면 해결된다.

 

참고

https://pub.dev/packages/home_widget

 

home_widget | Flutter Package

A plugin to provide a common interface for creating HomeScreen Widgets for Android and iOS.

pub.dev

https://medium.com/@ashishgarg1998/how-to-create-home-screen-app-widgets-in-flutter-ce3458f3638e

 

How to create Home Screen App Widgets in Flutter!

Hey! Welcome to my first article on Medium.

medium.com

https://developer.android.com/guide/topics/appwidgets?hl=ko#CreatingLayout

 

앱 위젯 빌드  |  Android 개발자  |  Android Developers

앱 위젯은 다른 애플리케이션(예: 홈 화면)에 삽입되어 주기적인 업데이트를 받을 수 있는 소형 애플리케이션 뷰입니다. 이러한 뷰는 사용자 인터페이스에서 위젯이라고 하며 앱 위젯 공급자를

developer.android.com

https://medium.com/@ABausG/interactive-homescreen-widgets-with-flutter-using-home-widget-83cb0706a417

 

Interactive HomeScreen Widgets with Flutter using home_widget

With the introduction of iOS 17 Apple now also supports adding interactivity to HomeScreen Widgets meaning we can build interactive…

medium.com