8만+ 가 사용한 대학생 캘린더, NETY 를 운영중입니다.
동아리 관리로 시작한 NETY 가 캘린더로 메인 기능이 전환되며,
홈 화면 캘린더 위젯도 만들어달라는 요구사항이 빗발치게 됩니다.
많은 유저가 요구하는 기능을 정리해보면 다음과 같았다.
- 월간 캘린더 뷰
- 이벤트가 일자별로 보여질 것
- 여러 일자에 걸친 이벤트가 캘린더 위에 이어져서 나타날 것
- 위젯에서 바로 월을 넘길 수 있을 것
- 데이터가 바로바로 반영 될 것
레퍼런스로는 구글 캘린더의 홈 화면 위젯을 잡을 수 있었다.
---
Flutter 로 어떻게 홈 화면 위젯을 구현할까?
https://pub.dev/packages/home_widget
위 라이브러리를 사용하면, Flutter side 에서 데이터를 저장하고, Native side 에서 해당 데이터를 불러올 수 있다.
정리하자면,
- Flutter Side 에서 일정, 휴일 정보 저장
- Natvie Side 에서 일정, 휴일 정보 불러오기
- Native Side 에서 2번의 데이터를 가지고, 요구사항에 맞는 캘린더 구현하기
1, 2번 까지는 위의 home_widget 라이브러리의 역할이다.
1, 2번의 과정은 생락하겠다. 공식 문서를 참고
일정 추가/수정/삭제 등의 데이터 동기화 로직도 생략하겠다.
3번만큼은 iOS, Android 각각의 Native code 로 구현해야 했다.
이를 위해,
iOS -> SwiftUI WidgetKit
Android -> Jetpack Glance
를 사용하기로 결정했다.
문제는, 다른 라이브러리 사용이 제한되며, 간단한 기존 Component 만 사용할 수 있다는 점이다.
Widget 내에서 사용할 수 있는 Box, Row, Column 같은 최소 단위의 요소들로 캘린더 홈 화면 위젯을 구현해야 했다.
이러한 Widget 관련된 정보가 많지 않으며, 요구사항에 맞는 기능을 구현해보았다는 사람을 찾을 수도 없었다.
스스로의 시간 절약을 위해, 외주를 알아보기도 했으나, 캘린더 위젯을 구현해보았다는 사람들을 만나보더라도
다중 일자 이벤트 처리는 안돼요..
반복 일정 처리는 안해봤어요..
위와 같은 부분들이 충족될 수가 없었다.
레이아웃 설정
머릿속에, 레이아웃부터 그려보자.
모든 요구사항을 만족시킬 수 있을까?
- 월간 캘린더 뷰
- 이벤트가 일자별로 보여질 것
여러 일자에 걸친 이벤트가 캘린더 위에 이어져서 나타날 것- 위젯에서 바로 월을 넘길 수 있을 것
- 데이터가 바로바로 반영 될 것
아니다.
전형적인 Grid 로 나누고, 각 일자 아래에 이벤트를 나열하는 단순한 방식으로는, 1-2를 만족하기가 어렵다.
- 여러 일자에 걸친 이벤트가 여러개 겹쳐져 있다면, 순서를 어떻게 배치할 것인가?
- 여러 일자에 걸친 이벤트의 제목이 길다면, 1번째 칸에서, ..., n번째 칸에서는 어디까지 제목을 출력할 것인가?
- 여러 일자에 걸친 이벤트가 여러 주에 걸쳐있다면, 주마다 내용을 출력하고 싶은데..
이벤트를 배치하는 알고리즘을 만들어보기도 하였으나,
근본적으로, 내용을 출력하는 부분에 있어 자연스러움을 연출하기가 어려웠다. 애초에, Widget 내부에서는 Text Component Layout 의 크기를 계산할 수 있는 방법을 제공하지 않아, 엄밀한 계산이 어려웠다.
더 나아가, 구글 캘린더, 네이버 캘린더 등의 캘린더 홈 화면 위젯을 보았을 때, 이런 방식으로 이벤트를 출력하는 것 같지 않았다. 제목이 Clip 없이 긴 일자에 걸쳐 보여지며, Border 위에 이벤트가 출력되는 것을 보았을 때, 분명 다른 방식이 있는 것으로 보인다.
캘린더를 일자별로 나누지 않고, 주별로 나누자!
"여러 일자에 걸친 이벤트 제목이 캘린더 위에 매끄럽게 나타날 것" 에 집중해서, 레이아웃을 다시 설계해보았다.
기존의 가장 큰 문제점은 Loop 를 다음과 같이 돌기 때문이었다.
- 일자 (1일 ~ 30일)
- 해당 일자에 포함되는 이벤트를 Column 내에 출력
- 해당 일자에 포함되는 이벤트를 Column 내에 출력
...
이를 위해 [DayIndex][EventIndex] 의 2중 배열을 사용했었다.
이러한 방식은, 이벤트가 각 일자별로 어디에 배치되어야 하는지를 모두 고려해야함과 동시에, 이벤트의 내용이 각 일자에서 어느정도까지 출력되어야 할지를 고민해야 했다. (이는, Widget 에서 제공하는 범위를 넘어섰다.)
각 주별로 나누어보자.
위와 같은 레이아웃을 상상해보자. 미묘하게 다르지만, 많은 것들이 해결된다.
어떻게 Event 가 배치될지, Loop 를 상상해보자.
- 현재 주차 Week Index
- 현재 주차 내에서의 Event Index
- Row 내에 해당하는 Event 를 배치하자.
- Event 가 어디에서 시작하는지, 길이를 일자의 길이에 따라 할당한다. ((전체 Width - 여타 padding) / 7 * 일자의 길이)
그렇다면, 내가 사전에 계산해두어야 할 정보는 다음과 같다.
- Week Index, Event Index 에 시작하는 모든 Event 를 계산한다.
- 각 Event 가 해당 Week Index 에서 어느정도 길이를 가져야할지 계산한다.
이를 위하여, 아래와 같은 3중 Array 를 활용했다.
[현재 주차 Index: length 5][주차별 Event Index:length 5][WeekDay Index:length 7]
각 이벤트의 위치를 계산해두고, 간단히 배치하자.
- Week Index 별로 순회한다.
- WeekDay Index 별로 순회한다.
- 해당 일자에 해당하는 이벤트를 전부 가져온다.
- Event Index 별로 순회한다
- 해당 일자에 해당하는 이벤트를 모두 순회한다.
- 이미 배치된 적이 있다면 return
- 해당 이벤트를 배치할 수 있다면 (= [Week Index][Event Index][WeekDay Index] ... [Week Index][Event Index][WeekDay Index + Event 의 길이] 까지 모두 비어있다면) 배열에 해당 이벤트의 아이디를 채워넣는다.
- 해당 일자에 해당하는 이벤트를 모두 순회한다.
- WeekDay Index 별로 순회한다.
이후, 이벤트를 실제 Layout 에 맞게 배치하였다.
*다음과 같은 가정을 통해 기존 요구사항을 모두 만족시켰다.
- 월간 캘린더는 5주를 보여준다.
- 각 일자별로 최대 5개의 이벤트를 보여준다.
- 5개를 넘어가면 ellipsis 로 표현한다.
한계
- Widget 자체의 Memory 제한
- *iOS 는 30MB 의 Widget Memory 를 제한한다.
- 현재 한개의 Schedule 이 아래와 같은 구조를 Json 으로 저장하므로,
- id (String): 평균 20바이트로 가정
- name (String): 평균 30바이트로 가정
- background_color (int): 4바이트
- color (int): 4바이트
- start_date (DateTime): 8바이트
- end_date (DateTime): 8바이트
- JSON으로 저장할 때 필드 이름과 구조를 위한 추가 오버헤드를 고려하면, 한 개의 Schedule 객체가 대략 100바이트를 차지한다고 가정하자.
- 30만건의 일정이 넘는다면, memory 가 초과되어 문제가 발생할 수 있다.
- 현재 유저가 평균적으로 200개 정도의 일정을 가지므로, 아직은 괜찮다.
- 현재의 Action handling 에 대한 한계
- 현재 보여지는 월을 앞으로, 뒤로, 오늘로 갈 수 있는 3가지의 action button 을 제공한다.
- 하지만, 이는 앱이 Background 에 켜져 있을 때 수행될 수 있다.
- 완전히 꺼져있다면,
- 앱을 Background 에 실행
- Action 수행
- 위 2가지의 과정을 거치므로, action 수행이 매우 느려보일 수 있다.
2024.09.01 기준 홈 화면 위젯
*2x2 Today 위젯도 추가하였다.
아래 링크에서 확인해보세요!
'Nety' 카테고리의 다른 글
왜 Async 를 사용해야 할까 (0) | 2024.08.28 |
---|---|
8만 다운로드+ 캘린더 어플의 알림 설정은 어떻게 구현됐을까? (0) | 2024.08.28 |
(SqlAlchemy + SQLModel + Alembic) Table 설계부터 Migration까지 (0) | 2024.08.28 |
Redis 를 활용한 API Caching 과 Rate Limit (0) | 2024.08.28 |
AWS 3-Tier Architecture (0) | 2024.08.28 |