[Flutter]

Flutter 안드로이드 위젯 구현 / 특정 화면으로 routing하기

Hevton 2022. 12. 27. 14:30
반응형

 

며칠을 삽질하며 완성했다.

그것도 이것보다 중요한게 있는 시기에 ㅜㅜ 이거 삽질에만 며칠을 쏟았다.

 

저처럼 고생하지 마시고, 도움이 되시길 바랍니다!!

 


 

플러터만으로도 대부분의 기능들을 편리하게 사용할 수 있지만, 안드로이드 시스템 API를 사용해야 한다거나, 성능상의 문제로 native 코드를 작성해야 하는 경우가 있다. pub.dev의 대부분의 패키지 뿐 아니라 native code 와 병행해서 만들어진 flutter package 들도 아주 많다.

 

플러터와 안드로이드 네이티브 간 통신(MethodChannel)에 대한 구현도 있을 것이고

플러터의 동작 원리로부터, 안드로이드 코드를 수정하여 플러터의 동작을 제어하는 구현도 있다.

 

 

그 중 이번에는, 네이티브와 연동한 홈 위젯 구현에 대해 정리할 것이다.

 

 

home_widget을 사용할 것이다.

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

 

이번 글의 설명 기준은 안드로이드에서의 구현이며, iOS에서도 구현하려면 추가적인 작업이 필요하다.

 

 

참고로 Flutter에서 안드로이드 작업을 해줄 때엔

무조건 android 파일 -> 오른쪽버튼 -> Flutter -> Open Android module in Android Studio 로 열어서 작업해줘야 한다.

 

그래야 문제없이 작업을 진행할 수 있다.

 

 

 

1. 위젯의 설정값

android/app/src/main/res/xml/example_appwidget_info.xml

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

minWidth가 72이면 최대 1픽셀 잡아먹을 수 있는 크기이다.

나는 1x1로 위젯을 만들 것이라서 min을 72로 잡았다. 더 여러가지 크기는 여기서 확인할 수 있다.

 

 

 

2. 위젯의 모양

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

<FrameLayout
    android:id="@+id/forever"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="72dp"
    android:layout_height="72dp" >


    <ImageView
        android:layout_width="72dp"
        android:layout_height="72dp"
        android:src="@drawable/cam"
        />


</FrameLayout>

나는 카메라 위젯을 만들 것이고, 이걸 누르면 내 앱의 기능 중 '카메라' 기능으로 이동하게끔 할 것이라서 이렇게 작성했다.

 

 

 

3. 위젯 기능 구현

android/app/src/main/kotlin/AppWidgetProvider.kt

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.widget.RemoteViews
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 {
                val intent = Intent(context, CameraActivity::class.java);
                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);

                setOnClickPendingIntent(R.id.forever, pendingIntent)
            }
            appWidgetManager.updateAppWidget(widgetId, views)
        }
    }
}

위젯을 클릭하면, CameraActivity로 이동하게끔 구현했다.

Android 12부터, PendingIntent를 실행할 때, FLAG_IMMUTABLE 또는 FLAG_MUTABLE 둘 중 하나를 설정해야만한다.

 

 

android/app/src/main/kotlin/CameraActivity.java

import java.util.ArrayList;
import java.util.List;


import io.flutter.embedding.android.FlutterActivity;

public class CameraActivity extends FlutterActivity {

    @NonNull
    @Override
    public String getDartEntrypointFunctionName() {
        return "main"; // main 대신 다른값 넣으면 특정 함수로 entry point 지정 가능
    }

    @Nullable
    @Override
    public List<String> getDartEntrypointArgs() {

        List<String> li = new ArrayList<String>();
        li.add("Cam");
        return li;
    }
}

 

위젯을 클릭했을 때, 내 앱의 특정 화면으로 이동시키고 싶을 때

크게 두 가지 방법이 있다.

 

 

 

1. getDartEntrypointFunctionName() 오버라이딩

=> 여기에 메인 엔트리 포인트 진입점으로 할 함수명을 넣어주고, 그 함수를 main.dart에 정의해주면 된다.

=> 기본값은 main이며, 이는 우리가 Flutter에서 void main() { runApp(....)} 하는 부분이다.

 

2. getDartEntrypointArgs() 오버라이딩

=> 기본적으로 우리가 Flutter의 메인함수를 보면 void main() {} 이러하다. 여기서 인자가 없음을 알 수 있다.

=> getDartEntrypointArgs() 를 오버라이딩하여 List<String> 을 넘겨주고, Flutter의 메인함수를 void main(List<String>? list) {} 로 수정하여, 어디서 어느 목적으로 main함수를 실행시켰는지를 구분해 줄 수 있다.

 

 

 

4. 컴포넌트 기능 상세

AndroidManifest.xml

 

<Application> </Application> 이 태그 사이에 아래 부분들을 넣어준다.

       <activity android:name=".CameraActivity"
           android:exported="true"
           android:launchMode="singleTop"
           android:theme="@style/LaunchTheme"
           android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
           android:hardwareAccelerated="true"
           android:windowSoftInputMode="adjustResize"
           >
           <intent-filter>
               <action android:name="es.antonborri.home_widget.action.LAUNCH" />
           </intent-filter>
       </activity>

        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />

       <receiver android:name="AppWidgetProvider" 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/example_appwidget_info" />
       </receiver>
       <receiver android:name="es.antonborri.home_widget.HomeWidgetBackgroundReceiver" android:exported="true">
           <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"/>

Android 12부터 exported를 명시적으로 넣어줘야한다.

 

 

위젯 자체적으로 특정 기능을 수행하는게 목적이 아니었으므로, 이렇게 하면 구현이 완료된다.

Flutter의 main.dart의 메인함수의 인자를 void main(List<String>? list) {} 이렇게 만들어 주고, 인자에 따라 진입점을 구분해주어

이용해주면 된다!

 

위젯 자체적인 기능( 카운팅이나 기타 및 )을 추가하고 싶으면 home_widget API를 참고하면 된다!!

 

 

여기까지 구현하고 트러블슈팅하는데에 굉장히 오랜시간이 걸렸다..

부디 나처럼 고생하는 사람이 없기를 바라면서..

도움이 되셨으면 좋겠습니다!!

 

 

 

참고

home_widget API

home_widget 이용하기

FlutterActivity 활용

반응형