들어가며…
E&I 프로젝트를 진행하며 푸시 알림, 사용자 채팅이 만들어진 MVP에 GA를 적용한 이후 사용자들의 참여도가 떨어지는 것을 확인했다. 이 부분에 있어 팀원과의 회의와 베타 테스터 설문 조사를 통해 다음과 같은 문제점이 있는 것을 확인했다.
- 앱을 열지 않고도 사용자들이 참여할 수 있는 방법이 필요하다.
- 방해금지 기능, 알림 설정 등으로 인해 푸시 알림이 제대로 전달되지 않는 경우가 있다.
즉, 앱 내에서만 사용자와 상호작용이 이루어져, 재방문율과 참여도를 높이는 데 한계가 있다. 즉, 사용자와의 소통을 중시한다는 역할에서 제 역할을 하지 못하고 있었다.
Home Widget이란
Home Widget은 사용자가 앱을 실행하지 않고도 주요 정보를 확인하거나 즉시 행동할 수 있게 하는 인터페이스 요소로 스마트폰의 홈 화면에 상주한다는 점에서, 앱의 진입 장벽을 낮추고 일일 사용자 점유율(DAU)을 높이는 핵심 수단으로 활용된다.
특히 최근에는 단순히 ‘정보 표시’ 수준을 넘어, 다음을 통해 사용자 경험(UX)을 확장하고 있다.
- 앱 재방문 유도(리텐션 향상)
- 즉각적인 상호작용(인터랙션 강화)
- 개인화된 콘텐츠 노출(맞춤형 피드백 제공)
즉, 홈 위젯은 ‘보조 수단’이 아니라 앱의 일부 기능을 홈 화면으로 확장한 마이크로 인터페이스(Micro-Interface)로서, 앱과 사용자의 접점을 더 촘촘히 만들어 점유 시간을 늘리는 중요한 구성 요소이다.
Earth & I 에서는?
게이미피케이션의 요소 중 “많이 보일수록 참여한다”라는 점에서 알림은 부족하다는 의견이 나왔고, 새롭게 할 수 있는 것이 무엇이 있을까 생각 중 홈의 캘린더 위젯이 눈에 보였다. 계속 보인다는 점에서 매력이 있었고, 앱을 보지 않더라도 캐릭터의 상태를 나타낼 수 있기에 Home Widget을 만들기로 결정했다.
Flutter에서의 Widget 개발
이를 위해 Flutter의 Home Widget Library를 활용하기로 했다. 하지만, 기본적으로 Library를 사용하더라도 Home Widget은 Native Code로 작성해야 하기에 Flutter에서는 Method Channel을 통해 Native Code를 호출하였다.
abstract class WidgetUtil {
  static const String appGroupIdentifier = 'group.earthAndI.userInfoWidget';
  static const String iOSWidgetName = 'userInfoWidget';
  static const String androidWidgetName = 'UserInfoWidget';
  static Future<void> onInit() async {
    await HomeWidget.setAppGroupId(appGroupIdentifier);
  }
  static void setInformation({
    required double positiveDeltaCO2,
    required double negativeDeltaCO2,
    required bool isHealthCondition,
    required bool isMentalCondition,
    required bool isCashCondition,
  }) {
    // Saving Character Asset
    HomeWidget.saveWidgetData<String>(
      WidgetUtilExtension.characterAsset,
      _toCharacterAssetPath(
        positiveDeltaCO2: positiveDeltaCO2,
        negativeDeltaCO2: negativeDeltaCO2,
        isHealthCondition: isHealthCondition,
        isMentalCondition: isMentalCondition,
        isCashCondition: isCashCondition,
      ),
    );
    // Saving Positive Delta CO2
    // 안드로이드 아이폰 경우의 수 나누기, 안드로이드는 int 값으로, 아이폰은 double 값으로 저장
    if (foundation.defaultTargetPlatform == foundation.TargetPlatform.android) {
      HomeWidget.saveWidgetData<int>(WidgetUtilExtension.positiveDeltaCO2,
          (positiveDeltaCO2.abs() * 10000).round());
    } else {
      HomeWidget.saveWidgetData<double>(
          WidgetUtilExtension.positiveDeltaCO2, positiveDeltaCO2.abs());
    }
    // Saving Negative Delta CO2
    if (foundation.defaultTargetPlatform == foundation.TargetPlatform.android) {
      HomeWidget.saveWidgetData<int>(WidgetUtilExtension.negativeDeltaCO2,
          (negativeDeltaCO2 * 10000).round());
    } else {
      HomeWidget.saveWidgetData<double>(
          WidgetUtilExtension.negativeDeltaCO2, negativeDeltaCO2);
    }
    // Update Widget
    HomeWidget.updateWidget(
      iOSName: iOSWidgetName,
      androidName: androidWidgetName,
    );
  }
  static String _toCharacterAssetPath({
    required double positiveDeltaCO2,
    required double negativeDeltaCO2,
    required bool isHealthCondition,
    required bool isMentalCondition,
    required bool isCashCondition,
  }) {
    String eco = positiveDeltaCO2.abs() >= negativeDeltaCO2 ? '1' : '2';
    String health = isHealthCondition ? '1' : '2';
    String mental = isMentalCondition ? '1' : '2';
    String cash = isCashCondition ? '1' : '2';
    return '${eco}_${health}_${mental}_$cash';
  }
}
결과
아래는 만들어진 Widget의 모습이다.
| Android | iOS | 
|---|---|
|  |  | 
이후 개발된 홈 위젯을 테스트 사용자 그룹에 배포한 결과, 위젯이 들어간 버전을 Update한 이후 사용자들의 12% 높아진 것을 확인할 수 있었다.
나오며…
이번 홈 위젯 개발은 단순한 기능 추가를 넘어, **‘앱 밖에서도 사용자와 소통할 수 있는 방법’**을 고민한 경험이었다. 특히 푸시 알림에만 의존하던 기존 방식에서 벗어나, 사용자가 직접 앱을 실행하지 않아도 참여할 수 있는 지속적인 접점을 만들었다는 점에서 의미가 있었다.
기술적으로는 Flutter 환경에서 Native 코드를 호출해야 하는 제약 속에서도, 플랫폼별 데이터 처리 차이(Android: int / iOS: double), AppGroup 설정 및 MethodChannel 통신 구조, 위젯 갱신 시점 관리(HomeWidget.updateWidget) 등을 직접 다루며 Flutter와 Native의 경계에 대한 이해를 넓힐 수 있었다.
무엇보다 이 경험을 통해 느낀 점은, “사용자의 행동을 유도하는 것은 기술이 아니라 노출 빈도와 맥락이다.” 즉, 사용자가 앱을 떠나 있는 순간에도 지속적으로 관계를 유지할 수 있는 UX 설계가 중요하다는 것이다.
