반응형



Flutter가 또 한 번의 의미 있는 업데이트, 3.32 버전을 선보였습니다! 이번 릴리스는 개발 속도를 높이고 앱의 완성도를 한층 끌어올릴 수 있는 강력한 기능들로 가득합니다. 특히, 많은 개발자들이 기다려온 웹 핫 리로드(실험적) 지원, iOS 네이티브의 느낌을 그대로 살린 Cupertino 스쿼클(Squircle), 그리고 Firebase와의 더욱 강력해진 AI 통합 기능이 주목할 만합니다.

총 156명의 기여자가 참여하여 1024개의 커밋을 통해 완성된 Flutter 3.32의 주요 업데이트 내용을 자세히 살펴보겠습니다.

✨ Flutter 3.32 주요 하이라이트

  1. 웹 (Web):
    • 웹 핫 리로드 (실험적): 드디어 웹에서도 핫 리로드를 사용할 수 있게 되었습니다! --web-experimental-hot-reload 플래그를 통해 활성화할 수 있으며, VS Code 및 DartPad에서도 지원됩니다. Flutter 개발자들의 오랜 요청사항 중 하나였던 만큼, 많은 기대를 모으고 있습니다. (버그 발견 시 적극적인 제보 부탁드립니다!)
  2. 프레임워크 (Framework):
    • Material 라이브러리의 로직을 위젯 라이브러리로 이전하는 작업이 지속됩니다.
    • 새로운 Expansible 위젯 (Material의 ExpansionTile 기반)과 커뮤니티 기여자의 RawMenuAnchor 위젯 (Material의 MenuAnchor 기반)이 추가되어, 다양한 디자인 언어를 일관성 있게 지원하고 코드 공유를 개선합니다.
  3. Cupertino (iOS 스타일):
    • 스쿼클 (Squircles) 지원: iOS 디자인의 핵심인 '스쿼클(Squircle)' 형태를 지원하여 더욱 부드럽고 자연스러운 곡선 UI 표현이 가능해졌습니다. RoundedSuperellipseBorder, ClipRSuperellipse 등의 API를 통해 사용할 수 있으며, CupertinoAlertDialog 와 CupertinoActionSheet에 이미 적용되었습니다. (현재 iOS/Android에서만 지원, 지속적인 최적화 진행 중)
    • 시트 (Sheet) 개선: Android에서 시트가 열릴 때 시스템 UI 테마 설정 오류 수정, 네비게이션 바 높이 조정으로 인한 콘텐츠 잘림 문제 해결 등 다양한 이슈가 개선되었습니다. CupertinoSheetRoute에 enableDrag 인수가 추가되어 시트의 드래그 앤 드롭 해제 동작을 비활성화할 수 있습니다.
    • 네비게이션 바 (Navigation bars) 개선: CupertinoSliverNavigationBar.search의 검색 뷰 열기/닫기 애니메이션 및 아이콘 정렬이 개선되었고, CupertinoNavigationBar 또는 CupertinoSliverNavigationBar를 사용하는 경로 간 전환이 최신 iOS 전환과 일치하도록 업데이트되었습니다.
  4. Material (Android 스타일):
    • CarouselController에 animateToIndex 메서드가 추가되어 인덱스 기반의 부드러운 캐러셀 탐색이 가능해졌습니다.
    • TabBar에 onHover 및 onFocusChange 콜백이 추가되어 다양한 상태에 따른 위젯 모양 제어가 용이해졌습니다.
    • SearchAnchor 및 SearchAnchor.bar에 각각 viewOnOpen 및 onOpen 콜백이 포함되어 열기/닫기 이벤트를 더 잘 관찰하고 처리할 수 있습니다.
    • CalendarDatePicker가 calendarDelegate를 수락하여 그레고리력 이외의 사용자 정의 달력 로직 통합을 지원합니다.
    • 그 외 showDialog 등에 animationStyle 추가, Divider에 borderRadius 추가 등의 소소한 개선 사항이 있습니다.
    • DropdownMenu 너비 관련 문제, RangeSlider 호버 시 오버레이 문제, Slider 썸네일이 트랙 끝에 도달하지 못하는 문제 등 여러 버그가 수정되었습니다.
  5. 접근성 (Accessibility):
    • 시맨틱 트리 컴파일 시간이 약 80% 단축되어 웹에서는 프레임 시간이 30% 개선되었습니다.
    • 새로운 SemanticsRole API를 통해 UI 요소의 접근성 역할을 더욱 세밀하게 제어할 수 있습니다 (현재 웹에서 사용 가능).
    • 스크린 리더 경험 향상, 웹 포커스 탐색 부드러움 개선, Android TalkBack의 링크 인식 개선, Windows 고대비 모드 지원, iOS Voice Control 경험 개선 등 다양한 접근성 향상이 이루어졌습니다.
  6. 텍스트 입력 (Text Input):
    • iOS 시스템 텍스트 선택 컨텍스트 메뉴가 도입되었습니다.
    • Autocomplete 위젯의 옵션 레이아웃이 OverlayPortal로 이전되어 성능이 향상되고 레이아웃 버그가 수정되었습니다.
    • 텍스트 필드에서 onTapUpOutside 동작을 사용자 정의할 수 있게 되었습니다.
    • FormField의 오류 메시지로 텍스트뿐만 아니라 모든 위젯을 생성할 수 있습니다.
    • 웹에서 선택 가능한 텍스트의 버그가 줄고 성능이 향상되었습니다.
  7. 데스크톱 (Desktop):
    • 다중 창 지원 진행 (Canonical 기여): 접근성, 앱 생명주기 알림, 포커스, 키보드 이벤트, 텍스트 입력, 마우스 이벤트 등 다중 창 환경에서의 여러 기능 오류가 수정되었습니다. Dart FFI가 Flutter 엔진과 직접 통신할 수 있는 기능이 추가되어 향후 Flutter의 윈도잉 API 기반을 마련했습니다. Linux에서는 래스터 스레드가 도입되어 여러 창이 있더라도 부드러운 프레임 처리량을 보장합니다.
    • 데스크톱 스레드 병합 (Canonical 기여): Windows 및 macOS에서 UI 스레드와 플랫폼 스레드를 병합할 수 있게 되어, Dart FFI를 사용하여 플랫폼 스레드에서 호출해야 하는 네이티브 API와 상호 운용할 수 있습니다 (예: Windows에서 win32 API로 앱 창 크기 조절). 향후 Windows 및 macOS에서 기본으로 활성화될 예정입니다.
  8. iOS:
    • 사용자 정의 액션이 없는 기본 텍스트 필드의 경우, 다른 앱에서 콘텐츠를 붙여넣을 때 더 이상 확인 대화상자가 표시되지 않아 사용자 경험이 향상되었습니다 (기본 활성화).
  9. Android:
    • Gradle 툴링 Kotlin으로 재작성: Flutter의 Gradle 플러그인이 Groovy에서 Kotlin으로 전환되어 코드베이스 기여 용이성, 단위 테스트 도입, 빌드 프로세스 안정성이 향상될 것으로 기대됩니다.
    • Scribe / 스타일러스 지원: Android 14 이상에서 스타일러스 펜을 사용하여 텍스트 필드에 직접 필기 입력이 가능해졌습니다 (TextField.stylusHandwritingEnabled 또는 CupertinoTextField.stylusHandwritingEnabled 매개변수로 제어).
  10. 엔진 (Engine - Impeller):
    • Android Impeller 업데이트: Android API 레벨 28 (Android 9) 이하 기기에서는 안정성을 위해 레거시 Skia 렌더러를 사용하며, API 레벨 29 (Android 10) 이상 기기에서는 Impeller가 계속 기본 렌더러입니다. 에뮬레이터, 일부 MediaTek 기기 등 특정 환경에서는 Vulkan 대신 OpenGLES 백엔드를 사용하도록 조정되었습니다. Impeller 비활성화 옵션은 향후 안정 버전에서 제거될 예정입니다.
    • 기타 Impeller 업데이트: 텍스트 애니메이션이 더 부드러워지고 지터가 감소했으며 (고해상도 글리프, 부동 소수점 계산 오류 수정), 원뿔 곡선 직접 테셀레이션, 부분 재그리기 최적화, 블러 속도 개선, 180도 회전된 텍스트 방향 수정 등 다양한 충실도 및 성능 개선이 이루어졌습니다.
  11. 개발 도구 및 IDE (DevTools and IDEs):
    • [신규] Flutter Property Editor: VS Code 사이드바 패널 또는 Android Studio/IntelliJ 도구 창에서 위젯 속성을 쉽게 편집하고 문서를 읽을 수 있는 새로운 도구입니다.
    • DevTools 개선: 네트워크 화면 오프라인 지원, 검토 기록 관련 버그 수정, CPU 프로파일러 및 메모리 화면 데이터 개선 등 다양한 기능 향상이 있었습니다. DevTools 자체의 성능 및 메모리 사용량도 개선되었습니다.
    • Analyzer 개선: "doc imports" (실제 import 없이 문서 주석에서 외부 요소 참조) 기능 추가, 빠른 수정, 어시스트, 이름 변경 기능 등이 향상되었습니다.
    • Android Studio의 Gemini, Dart/Flutter 완벽 지원: 이제 Android Studio 내에서 Gemini의 강력한 AI 기능을 활용하여 Dart 및 Flutter 앱 개발을 더 빠르고 쉽게 할 수 있습니다.
    • MCP (Model Context Protocol) 지원 예정: Dart 및 Flutter의 MCP 지원이 진행 중이며, 이를 통해 더욱 정확하고 관련성 높은 코드 생성 및 복잡한 작업 처리가 가능해질 것으로 기대됩니다.
  12. AI와 함께 빌드하기 (Build with AI):
    • Firebase AI Logic 소개: 기존 Vertex AI in Firebase가 발전하여 firebase_ai 패키지 하나로 Gemini API 제공자(Vertex AI 및 Gemini Developer API 무료 티어 포함) 모두에 접근할 수 있게 되었습니다. 서버 측 SDK 없이 Flutter 앱에서 직접 Gemini 및 Imagen 모델 사용이 가능합니다.
    • AI Monitoring 대시보드: Firebase 콘솔에 새로 추가된 AI Monitoring 대시보드를 통해 Gemini API 사용량에 대한 상세하고 실행 가능한 인사이트(소비 패턴, 성능 지표, 잠재적 문제 등)를 얻고 디버깅에 활용할 수 있습니다.

⚠️ 주요 변경 사항 및 지원 중단 (Breaking Changes and Deprecations)

  • Android 접근성 알림: API 36부터 시맨틱 알림 이벤트가 폐기될 예정입니다. SemanticProperties.liveRegion을 구성하여 "polite" 암시적 알림을 사용하는 것이 권장됩니다. (자세한 내용은 공식 문서 참조)
  • 6개 패키지 지원 중단: flutter_markdown, ios_platform_images, css_colors, palette_generator, flutter_image, flutter_adaptive_scaffold 패키지 지원이 중단됩니다. 각 이슈에서 대안을 확인하세요.
  • iOS 및 macOS 최소 버전 변경 예정: 다음 안정 릴리스부터 iOS 12 및 macOS 10.14 (Mojave) 지원이 중단되고, 최소 iOS 13 및 macOS 10.15 (Catalina)를 대상으로 합니다.
  • 기타 변경 사항: ExpansionTileController가 Widgets 레이어의 새로운 ExpansibleController로 대체, SelectionChangedCause.scribble이 SelectionChangedCause.stylusHandwriting으로 이름 변경, ThemeData.indicatorColor가 TabBarThemeData.indicatorColor로 변경, 일부 테마 타입 마이그레이션 필요, SpringDescription 공식 수정 등. 자세한 내용은 공식 브레이킹 체인지 페이지를 참고하고, dart fix 명령으로 일부 변경 사항을 자동 마이그레이션할 수 있습니다.
반응형
반응형

 

가계부앱을 만들 때 매출과 지출을 RederList를 사용하여서 매출과 지출의 순서를 변경 할 수 있게 코드를 짰습니다. 

그리고 그 매출 카테고리에 따른 값들이 보이고, 지출에 따른 카테고리 값들이 보이게 했습니다. 

 

각 카테고리 값들이 처음 선택 했을 때는 문제 없이 돌아갔지만, 실수로 누른 값을 다시 수정 할 때 카테고리의 값이 초기 값으로 변환되는 문제가 발생하였습니다. 

 

그 과정을 해결하는 글 입니다. 

 


 

문제 상황

매출(수입)에서 현금을 선택한 후, 지출로 전환하여 광고비를 선택했을 때 여전히 현금이 표시되는 문제가 발생했습니다. 이는 카테고리 전환 시 값이 제대로 업데이트되지 않기 때문이었습니다

원인 분석

 

  • 컨트롤러의 상태 관리 문제: MoneyTrackerInputController changeIncomeType 메서드에서 타입 변경 시 반대 타입의 카테고리 값을 초기화하지 않았습니다.
  • 젯에서의 상태 관리 문: IncomeExpenseSelectWidget에서 타입 전환 시 카테고리 값이 제대로 초기화되지 않았습니다.
  • 장된 값 관리 문제: 타입 전환 시 이전에 저장된 카테고리 값이 계속 유지되어 문제가 발생했습니다.

 

 

 

ReorderList에서 현재 인덱스에 있는 타입을 명확히 하게 할 것 

onReorder: (oldIndex, newIndex) {
  if (newIndex > oldIndex) {
    newIndex -= 1;
  }
  final item = items.removeAt(oldIndex);
  items.insert(newIndex, item);
  _saveOrder();

  // 순서 변경 후 첫 번째 항목을 활성화
  Future.microtask(() {
    final newType = items[0]['type'] as IncomeType;
    
    // 타입 변경 전에 현재 타입 확인
    if (controller.incomeType.value != newType) {
      // 타입 변경
      controller.changeIncomeType(newType);
      
      // 타입에 따라 카테고리 값 명확하게 설정
      // (동일한 로직 적용)
    }
  });
}

 

 

해당 타입을 변수로 받기 

// 수정 전
void changeIncomeType(IncomeType type) {
  incomeType.value = type;

  // 타입 변경 시 해당 타입의 카테고리가 비어있으면 기본값 설정
  if (type == IncomeType.income && selectedCategory1.value.isEmpty) {
    if (incomeCategories.isNotEmpty) {
      selectedCategory1.value = incomeCategories.first;
    }
  } else if (type == IncomeType.expense && selectedCategoryE1.value.isEmpty) {
    if (expenseCategories.isNotEmpty) {
      selectedCategoryE1.value = expenseCategories.first;
    }
  }

  update();
}

// 수정 후
void changeIncomeType(IncomeType type) {
  // 타입이 변경될 때만 처리
  if (incomeType.value != type) {
    incomeType.value = type;

    // 타입에 따라 카테고리 값 명확하게 설정
    if (type == IncomeType.income) {
      // 수입 타입으로 변경 시
      // 지출 카테고리 값 초기화
      selectedCategoryE1.value = '';
      
      // 수입 카테고리가 비어있으면 기본값 설정
      if (selectedCategory1.value.isEmpty && incomeCategories.isNotEmpty) {
        selectedCategory1.value = incomeCategories.first;
      }
    } else {
      // 지출 타입으로 변경 시
      // 수입 카테고리 값 초기화
      selectedCategory1.value = '';
      
      // 지출 카테고리가 비어있으면 기본값 설정
      if (selectedCategoryE1.value.isEmpty && expenseCategories.isNotEmpty) {
        selectedCategoryE1.value = expenseCategories.first;
      }
    }

    // 디버깅용 로그
    print('컨트롤러에서 타입 변경: $type');
    print('수입 카테고리: $selectedCategory1');
    print('지출 카테고리: $selectedCategoryE1');

    update();
  }
}



현재 타입에 맞게 데이터가 저장 되게 함 

onTap: () {
  if (controller.incomeType.value != item['type']) {
    // 먼저 타입 변경
    controller.changeIncomeType(item['type']);
    
    // 타입에 따라 카테고리 값 명확하게 설정
    if (item['type'] == IncomeType.income) {
      // 수입 카테고리 설정
      final String? savedCategory = storage.read<String>(incomeCategoryKey);
      if (savedCategory != null && 
          savedCategory.isNotEmpty && 
          controller.incomeCategories.contains(savedCategory)) {
        controller.selectedCategory1.value = savedCategory;
        // 지출 카테고리 초기화
        controller.selectedCategoryE1.value = '';
      } else if (controller.incomeCategories.isNotEmpty) {
        controller.selectedCategory1.value = controller.incomeCategories.first;
        // 지출 카테고리 초기화
        controller.selectedCategoryE1.value = '';
      }
    } else {
      // 지출 카테고리 설정
      final String? savedCategory = storage.read<String>(expenseCategoryKey);
      if (savedCategory != null && 
          savedCategory.isNotEmpty && 
          controller.expenseCategories.contains(savedCategory)) {
        controller.selectedCategoryE1.value = savedCategory;
        // 수입 카테고리 초기화
        controller.selectedCategory1.value = '';
      } else if (controller.expenseCategories.isNotEmpty) {
        controller.selectedCategoryE1.value = controller.expenseCategories.first;
        // 수입 카테고리 초기화
        controller.selectedCategory1.value = '';
      }
    }
    
    // 디버깅용 로그 추가
    print('타입 변경: ${item['type']}');
    print('수입 카테고리: ${controller.selectedCategory1.value}');
    print('지출 카테고리: ${controller.selectedCategoryE1.value}');
  }
}

 

 

핵심 수정 사항

 

  • 타입 변경 시 반대 타입의 카테고리 값 초기: 수입 타입으로 변경 시 지출 카테고리 값을 초기화하고, 지출 타입으로 변경 시 수입 카테고리 값을 초기화합니다.

2. 저장된 카테고리 값 확인: 저장된 카테고리 값이 현재 사용 가능한 카테고리 목록에 있는지 확인합니다.

  • 디버깅 로그 추가: 타입 변경 시 카테고리 값이 어떻게 변하는지 확인할 수 있도록 디버깅 로그를 추가했습니다.

 

결과

이러한 수정을 통해 매출에서 현금을 선택한 후 지출로 전환하여 광고비를 선택했을 때, 광고비가 올바르게 표시되는 문제를 해결했습니다. 또한 반대로 지출에서 광고비를 선택한 후 매출로 전환하여 현금을 선택했을 때도 현금이 올바르게 표시됩니다.

 

  • 상태 관리의 중요성: 여러 상태가 연결된 애플리케이션에서는 상태 변경 시 관련된 모든 상태를 명확하게 초기화하거나 업데이트해야 합니다.

2. 디버깅 로그의 유용성: 복잡한 상태 관리 문제를 해결할 때 디버깅 로그를 추가하면 문제를 더 쉽게 파악할 수 있습니다.3. 일관된 로직 적용: 동일한 기능이 여러 곳에서 사용될 때는 일관된 로직을 적용하여 예측 가능한 동작을 보장해야 합니다.

 

 

반응형
반응형

 

https://github.com/MaikuB/flutter_local_notifications/issues/2336

🔍 AGP 8에서 zonedSchedule 알림이 작동하지 않는 문제 요약 (#2336)

 

 

 

zonedSchedule notifications not working in AGP8 · Issue #2336 · MaikuB/flutter_local_notifications

Describe the bug After upgrading AGP to version 8.4, zonedSchedule notifications causes app to crash To Reproduce Use this minimally altered sample project: NotificationsSample Run Pixel 7 API 33 e...

github.com

 

🛠️ 문제 요약

 

Flutter 프로젝트에서 Android Gradle Plugin (AGP) 8.5로 업그레이드한 후 zonedSchedule 알림을 사용하면 앱이 충돌하는 버그가 발생함.

📌 오류 메시지:

java.lang.RuntimeException: Missing type parameter.

📌 발생 조건:

AGP 8.5사용

zonedSchedule 실행 시 앱 크래시

 

🕵️ 문제 원인 분석

1. R8(Code Shrinker) 관련 문제

R8이 특정 클래스를 최적화하면서 필요한 코드가 제거됨

android.enableR8.fullMode = false 설정 시 문제 해결됨 → 그러나 근본적인 해결책은 아님

2. GSON 관련 문제 (com.google.gson.reflect.TypeToken 누락)

AGP 8.5에서 특정 버전의 GSON이 제대로 작동하지 않음

app/build.gradle에 아래 추가 시 문제 해결됨

implementation 'com.google.code.gson:gson:2.11.0'

 

3. ProGuard 설정 부족

proguard-rules.pro에 GSON 관련 규칙이 없으면 충돌 발생

아래 규칙 추가 시 해결 가능

-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken

 

 

 

1️⃣ proguard-rules.pro 수정

-keep class org.xmlpull.** { *; }
-keep class com.google.gson.** { *; }
-keep class com.dexterous.flutterlocalnotifications.** { *; }
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken

2️⃣ GSON 버전 업데이트 (build.gradle 수정)

implementation 'com.google.code.gson:gson:2.11.0'

 

4️⃣ Gradle 설정 확인 (gradle.properties 수정)

android.enableR8.fullMode = false

📌 최종 결론

AGP 8.5에서 zonedSchedule 사용 시 R8과 GSON 관련 문제가 있음

proguard-rules.pro, build.gradle 설정 수정 후 해결됨

Flutter 최신 버전에서는 기본적으로 GSON 2.11.0을 사용하도록 변경됨

 

🔍 추가 참고 사항

최신 Flutter 3.27프로젝트에서는 기본적으로 Java 17호환 설정 유지됨

**Android Studio Ladybug(2024년 출시)**에서는 JDK 17 → 21로 변경됨

향후 Flutter 및 AGP 업데이트에서 추가적인 수정이 필요할 수 있음

 

🔹 요약: 위 해결 방법을 적용하면 flutter_local_notificationszonedSchedule 충돌 문제를 해결할 수 있음 🚀

 

 

 

반응형
반응형

https://docs.flutter.dev/release/breaking-changes/default-systemuimode-edge-to-edge

 

Set default of `SystemUiMode` to edge-to-edge

By default, apps targeting Android SDK 15+ will opt in to edge-to-edge mode.

docs.flutter.dev

 

앱을 업데이트 하려고 하니 ~ 안드로이드 15 화면 어쩌구 경고창이 뜨길래 무시했더니..

바로 안드로이드 사용하시는 유저분이 접속이 안된다고 하시네요 ㅠㅠ

 

위 내용 참고하셔서 불이익 안당하시길... 

1. flutter 3.27 버전으로 하면 기본적엔 셋팅이 완료가 됩니다. 
2. flutter 3.27 버전 미만으로 사용하고 싶으시면 위 링크에 들어가서 설정을 따로 해야합니다. 

 

 

 

 

반응형
반응형

 

제가 일기월장 회고록이 아닌 이 글을 작성하는 이유는 그간 2년동안 코딩을 배우고, 앱을 만들고 운영을 하면서 배우고 느꼈던 점을 기록하고 싶기 때문입니다. 그 중에 "앱 기획" 에 대한 이야기를 중점적으로 적어보려고 합니다. 그나마 제가 가장 잘 하는 일이거든요.

 

오랫동안 외식업에 몸담고 있다가, It로 넘어와도 되겠다 라는 확신을 가진 계기가 있었습니다. 초보 스터디를 참여했었습니다. 그 모임에서 기술적으로 정말 뛰어난 개발자분들이셨지만 저를 부러워 하셨습니다. 만들고 싶은 서비스가 있다라는 이유였습니다.

 

제겐 꽤 신기한 경험이었습니다. 개발자분들은 그런 걱정 없이 사는 줄 알았거든요., IT 종사자는 다 개발일만 하는 줄 알았습니다. 코딩을 공부하고 개발세계를 알아가다보니 IT에는 여러 직군들이 있습니다. 이 곳에 들어와도 제가 할 일은 있겠다 싶었습니다.

 

이 업계에서 그나마 제가 잘하는 직군으로 뽑자면 PM, 기획자 역할이겠네요. 그래서 이 글은 초보 기획자 입장에서 작성한 회고 입니다. 앱을 기획하고, 운영하시는데 조금이나마 도움이 되셨으면 좋겠습니다.

 


앱을 만들기전에 고민해야 할 것
기능 중심인가? 기록 중심인가?

 

 

요즘 AI 툴이 너무 좋아져서 코딩을 조금만 공부해도 간단한 앱은 금방 만드는 시대가 왔습니다.

앱 서비스는 이제 기업이 아니라, 개인이 운영 가능한 시대가 온 것이죠.

 

 

 

앱은 스마트폰 기기 내에서 작동을 합니다. 항상 사람들 손에 들려 있다는 뜻이지요. 접근성이 좋기 때문에 사람들은 "자주" 들여다 쓸 수 있는 앱을 좋아합니다. 구글플레이, 앱스토어 랭킹이 그 지표입니다. 그 랭킹을 보자면 앱 카테고리가 딱 두가지로 나누어 집니다.

<기능> 중심의 앱, <기록> 중심의 앱 입니다.

 

<기능> 중심 카테고리에는

금융앱, 날씨앱, 카메라 필터 등이 있습니다. 일생상활에서 꼭 필요한 서비스가 많습니다. . 유저의

체류시간이 짧은 특성을 지니고 있습니다. 이유는 내가 필요한 기능을 완료한 후에는 쓰지 않기 때문입니다.

 

<기록> 중심 카테고리에는

게임, SNS, 일기, 가계부 등이 있습니다. 살아가는데 꼭 필요한 서비스는 아닙니다만, 개인의 목적,선호에 따라 사용하는 카테고리 입니다. 체류시간은 기능 중심의 앱보다는 긴 특성을 가지고 있습니다. 자신의 목적을 이루기까지 시간이 필요하기 때문입니다.

 

앱을 만들기 전에 내가 만드려고 하는 앱이 <기능> 인지 <기록> 인지 구분하시는 것이 좋습니다. 앱 특성에 따라 개발

방향이 달라지기 때문입니다.

 

먼저 앱 광고가 있습니다. <기능> 중심의 앱에서는 광고를 많이 달아도 거부감이 덜 한 편입니다. 어차피 광고 한 편만 보고 나면 내가 원하는 기능을 바로 사용 할 수 있을테니까 말이죠, 하지만 <기록> 중심의 앱은 광고 거부감이 상당한 편입니다. 게임에서도 광고를 보여주고 보상을 주는 이유입니다. 이전에는 무작위 광고만 보여주어 이탈률이 높았었죠.

 

다음으로는 매몰비용입니다. 이 용어는 게임에서 많이 쓰이는 용어 입니다. 유저가 해당 컨텐츠에 얼마나 물질적, 시간적 비용을 넣었느냐에 대한 수치입니다. 리니지가 대표적이죠. 한 달 동안 수천, 수억을 투자하는 사람이 하루 아침에 리니지를 접을리 없습니다.

 

즉 매몰비용이 낮으면 유저는 언제든 앱을 이탈 할 수 있습니다. 매몰비용이 높으면 유저는 앱을 이탈하기가 쉽지 않죠.

 

<기능> 중심의 앱은 매몰비용이 낮습니다. 그래서 언제든 비슷한 기능의 앱으로 이탈 할 수 있습니다.

<기록> 중심의 앱은 상대적으로 매몰비용이 높습니다. 새로운 기능의 앱이 나와도 기존에 작성한 기록 때문에 옮기기가 쉽지 않습니다.

 

제가 운영하는 일기월장은 <기록> 중심의 앱 입니다. 메인컨텐츠를 가계부로 한 이유도 바로 위와 같습니다.

유저의 체류시간이 높고, 자신만의 매장 기록을 작성함에 따라 매몰비용이 높아지기 때문에 쉽게 다른 앱으로 이동 하기가 힘들 것이기 때문입니다.

 

 

기능 중심
기록 중심
대표적인 앱 카테고리
금융앱, 날씨앱, 카메라 필터, 등등
게임, SNS, 일기,
유저의 체류시간
짧음
길다
광고에 대한 반응
거부감 없음
거부감이 있음
유저의 매몰비용
낮다
높다

 

 

1인 개발자가 앱을 만드는 이유는 "돈" 을 벌기 위해서입니다. <기능> 중심의 앱, <기록> 중심의 앱 둘 중 어느 앱이 돈이 될지는 그 누구도 알 수 없습니다. 각 특성에 따라 돈을 버는 방식이 조금 차이가 있기 때문입니다.

 

<기능>중심의 앱은 최대한 빠르게 만들고 런칭을 합니다. 요즘 개발자 커뮤니티에서 유행처럼 퍼지고 있는 "앱 100개 만들기" 챌린지의 대다수가 <기능> 중심의 앱 입니다.

사람들이 필요로 하는 것을 빠르게 체크하고, 이미 만들어진 앱을 사용해보면서 보완을 하고 출시합니다. 그 앱안에 광고를 달고, 광고 그만보기 위주의 수익을 많이 진행합니다.

 

<기록> 중심의 앱은 "컨텐츠"가 제일 중요합니다. 이 "컨텐츠"를 만들고 유저들에게 설득하는 과정이 필수입니다. 구글플레이나, 앱스토어에 정말 많은 Todo 앱이 있습니다. 하지만 어떤 Todo 앱은 많은 유저가 사용을 하고, 어떤 Todo앱은 사용자가 1명도 없을 수 있습니다. 이러한 차이는 그 Todo앱들간의 "컨텐츠"의 격차입니다.

단순히 앱을 만드는 것이 아닌 유저가 왜 이 서비스를 이용해야하는지 충분한 설득이 필요합니다. "컨텐츠"의 설득 당한 유저는 더 많은 시간을 앱에 투자 할 것입니다. 그 충성 유저를 모아서 다양한 실물 광고나, 상품을 판매하는 것이 주 수익입니다.

 

 

물론 구글플레이, 앱스토어에 있는 모든 앱들을 단순하게 <기능>, <기록> 두 카테고리로 나눌 수는 없습니다.

왜냐면 요즘 앱들의 트렌드가 <기능> 과 <기록> 두 가지 특성 모두를 가지는 것이기 때문입니다.

 

토스앱은 송금을 위한 <기능>중심의 금융앱으로 출발을 했고, 지금은 만보기, 일정 광고 상품을 보면 리워드 제공하기 등등 <기록> 컨텐츠를 추가하고 있습니다. 이러한 방향성은 <기능>과 <기록> 이 각각 가지고 있는 좋은 점들을 앱에 녹이면 유저의 만족도를 높일 수 있습니다.

 


 

결론

 

1인 개발자가 <기능>, <기록> 특성을 모두 가진 앱을 동시에 개발하는 것은 물리적으로 정신적으로 힘든 일입니다.

앱 개발 초기에는 잘 할 수 있는 카테고리의 앱을 빠르게 개발하여 추후에 추가하는 방법이 좋아보입니다.

 

제가 이 글을 작성하는 이유는 다음과 같습니다.

위와 같은 고민을 하지 않은채 앱을 만든다면 <기록> 특성을 가진 앱 운영 초기부터 광고나 유료상품을 넣는 일들이 발생합니다.

유저에게 왜 이 앱을 사용해야 하는지 충분한 설득을 하지 않은채 말이죠. 아쉽게도 그러한 앱을 사용해줄 유저는 많지 않습니다.

 

또 <기능> 특성을 가진 앱을 개발하고 나서 꾸준히 트렌드를 파악하지 않는다면 유저는 비슷한 경쟁사의 앱으로 쉽게 떠날 것입니다.

그러한 상황을 맞이하기전에 새로운 카테고리의 앱을 개발하는 편이 좋습니다.

 

 

 


이 글은 사장님을 위한 가계부앱 일기월장이 작성한 블로그 입니다. 
https://litt.ly/jalam

 

매일기록, 매월성장

일 기 월 장

litt.ly

 

 

반응형
반응형

 

 

일기월장 가계부를 코드 리팩토링 하고 있습니다. 

Get x 상태관리 
- local db :  sqlite3

- server :  firebase, supabae  

 

 

기존에는 dbhelper 클래스를 만들어서 바로바로 직접 연결을 사용했습니다. 

하지만 이러한 방법은 새로운 추가기능을 구현 할 때 위험하다는 생각이 들었습니다. 

 

기존 dbhelper 클래스는 appdata 클래스로 이름을 변경하고 

dbhelper에 있는 CRUD 코드는 

 

expense_dao 클래스로로 
expense_dao 클래스에서 다시 책임분담을해서 

expense_repo  클래스를 만들었습니다. 

 

ui page에서는 expense_repo 클래스에서 받아와서 사용중이네요. 

 

 

lib/
├── data/
│   ├── local/
│   │   ├── db/
│   │   │   ├── database_helper.dart        // SQLite 초기화 및 마이그레이션 관리
│   │   │   ├── expense1_dao.dart           // expense_1 테이블 DAO
│   │   │   ├── repeat_expense1_dao.dart    // repeatExpense_1 테이블 DAO
│   │   │   ├── expense_dao.dart            // expenses 테이블 DAO
│   │   │   ├── repeat_expense_dao.dart     // repeatExpense 테이블 DAO
│   │   │   └── merchant_dao.dart           // merchants 테이블 DAO
│   │   └── repositories/
│   │       ├── expense1_repository.dart    // expense_1 Repository
│   │       ├── repeat_expense1_repository.dart // repeatExpense_1 Repository
│   │       ├── expense_repository.dart     // expenses Repository
│   │       ├── repeat_expense_repository.dart // repeatExpense Repository
│   │       └── merchant_repository.dart    // merchants Repository
└── main.dart

가장 큰 문제는 

 

Moneytrackder controller  부분입니다. 

 

현재 이 controller 에서 일별, 통계, 달력, 결산 페이지, 반복 페이지를 다 담당하고 있네요. 
잘 풀어나가야 할 것 같습니다. 

반응형
반응형

위 코드는 비동기적으로 API 요청을 병렬로 처리하는 방식입니다. 병렬 처리와 직렬 처리의 차이를 이해하는 것은 비동기 프로그래밍에서 매우 중요합니다. 아래에서 두 방식을 비교해 설명하겠습니다.

병렬 처리 (Parallel Processing)

병렬 처리는 여러 작업을 동시에 처리하는 방식입니다. 위 코드에서 Future.wait(futures)를 사용하여 futures 리스트에 포함된 모든 비동기 작업을 동시에 실행하고, 모든 작업이 완료될 때까지 기다립니다. 각 정류장에 대해 도착 정보를 요청하는 작업이 병렬로 실행됩니다.

장점:

  • 작업이 동시에 진행되므로 전체 실행 시간이 더 짧을 수 있습니다.
  • 독립적인 작업을 동시에 처리하는 데 효율적입니다.

단점:

  • 동시에 많은 작업이 실행되면, 네트워크나 시스템 자원에 부담을 줄 수 있습니다.
  • 작업들 간의 순서 보장이 필요하지 않은 경우에만 적합합니다.

직렬 처리 (Serial Processing)

직렬 처리는 작업을 하나씩 순차적으로 처리하는 방식입니다. 작업이 순서대로 실행되며, 하나의 작업이 완료된 후에 다음 작업이 시작됩니다.

예시 코드:

dart
코드 복사
for (var stop in busStops) { final info = await apiService.fetchBusArrivalInfo(stop.nodeId, routeId); // 다음 작업은 이전 작업이 끝난 후에 실행됨 }

장점:

  • 작업 간의 순서가 중요한 경우 적합합니다.
  • 각 작업이 다른 작업에 의존적인 경우 안전합니다.

단점:

  • 모든 작업이 순차적으로 실행되므로 전체 실행 시간이 길어질 수 있습니다.
  • 독립적인 작업을 처리할 때 비효율적일 수 있습니다.

차이점 요약

  1. 실행 순서:
    • 병렬: 모든 작업이 동시에 실행됨. 순서가 보장되지 않음.
    • 직렬: 작업이 순차적으로 실행됨. 순서가 보장됨.
  2. 성능:
    • 병렬: 더 빠른 성능을 제공할 수 있지만, 리소스 사용이 많을 수 있음.
    • 직렬: 안정적이지만 전체 실행 시간이 길어질 수 있음.
  3. 사용 사례:
    • 병렬: 독립적인 작업을 처리할 때 사용.
    • 직렬: 작업 간의 순서가 중요하거나 작업이 서로 의존적일 때 사용.

결론

위 코드의 병렬 처리 방식은 각 정류장에 대한 API 요청이 서로 독립적이므로, 여러 요청을 동시에 처리하여 시간을 절약할 수 있습니다. 직렬 처리 방식에서는 하나의 요청이 완료된 후에 다음 요청이 시작되므로 시간이 더 많이 걸릴 수 있습니다. 병렬 처리를 사용하면 네트워크 및 시스템 자원을 더 효율적으로 활용할 수 있지만, 리소스가 제한된 환경에서는 조심해서 사용해야 합니다.

반응형
반응형

 

 

일기월장에 로컬 알람 기능을 넣는 중 겪은 문제를 해결하는 과정입니다. 

 

문제 인식

 

아래 패키지 사용 

IOS 는 잘 작동.. 
안드로이드는..먹통..

https://pub.dev/packages/flutter_local_notifications

 

flutter_local_notifications | Flutter package

A cross platform plugin for displaying and scheduling local notifications for Flutter applications with the ability to customise for each platform.

pub.dev

 

 

이유 

 

use_full_screen_intent 권한 문제 

 

위 패키지에서는 안드로이드 알람 설정시 위 권한을 사용하라고 나와있다. 

하지만 안드로이드에서는 해당 권한을 사용하려면 "전화앱" 또는 "알람앱" 만 가능하다고 나와 있다. 

 

 

https://source.android.com/docs/core/permissions/fsi-limits?hl=ko

 

전체 화면 인텐트 제한  |  Android 오픈소스 프로젝트  |  Android Open Source Project

전체 화면 인텐트 제한 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 광고 스팸과 사용자 인증 정보 피싱을 방지하기 위해 Android 14부터 전체 화면 알림을

source.android.com

 

 

 

문제 해결 과정 

 

1.  use_full_screen_intent 권한 삭제 
권한을 안드로이드 AndroidManifest.xml 파일에서 권한을 삭제 했다. 

 

2. 패키지 버전을 최신 버전 말고 16.0.0 버전을 사용했다 .
- 최신 버전인 17.0.0 버전에서도 비슷한 문제가 나타났다는 깃헙을 보았다. 

 

 

 

위 두가지 방법으로 해결!

 

 

 

 

 

 

 

반응형
반응형
문제 해결 목적 

 

 

세광2차아í

현재 강화도 공공 버스앱을 만들고 있는데 해당 버스의 정류장 이름이 한글로 안나옴 

 

 

https://okky.kr/questions/1169440

 

OKKY - api 호출시 xml 한글깨짐현상관련하여 질문드립니다..

https://openapi.gg.go.kr/AbdmAnimalProtect여기 공공데이터에서 api호출로 데이터값을 가져오고싶은데호출하면영어와 숫자는 가져와지는데 한글은 ???물음표로 깨져서 나옵니다.인코딩과정에서 한글이 깨

okky.kr

여기서는 UTF-8 변환 문제 

 

 

문제 해결 방법 

 

utf8. 디코드 하는 코드를 추가 

Future<List<BusStop>> fetchBusStops(String routeId) async {
  final url = Uri.parse(
      'https://apis.data.go.kr/1613000/BusRouteInfoInqireService/getRouteAcctoThrghSttnList?serviceKey=서비스키&_type=json&cityCode=23&routeId=$routeId');

  final response = await http.get(url, headers: {
    'Content-Type': 'application/json; charset=UTF-8',
  });

  if (response.statusCode == 200) {
    final responseData = utf8.decode(response.bodyBytes); // UTF-8로 디코딩
    print('API response data for route $routeId: $responseData'); // API 응답 출력

    try {
      final jsonData = json.decode(responseData);
      if (jsonData['response']['header']['resultCode'] == '00') {
        final items = jsonData['response']['body']['items']['item'];
        print('Parsed JSON items for route $routeId: $items'); // JSON 파싱된 데이터 출력
        return items is List
            ? items.map((item) => BusStop.fromJson(item)).toList()
            : [BusStop.fromJson(items)];
      } else {
        throw Exception('Error: ${jsonData['response']['header']['resultMsg']}');
      }
    } catch (e) {
      print('Failed to parse JSON for route $routeId: $e');
      throw Exception('Failed to parse JSON');
    }
  } else {
    throw Exception('Failed to load data');
  }
}

 

 

 

결과

 

 

 

잘 나온당~~~

반응형

+ Recent posts