본문 바로가기
앱인벤터/실전 앱

앱인벤터 가계부앱 만들기 #1 (월별 화면 및 지출항목 추가하기)

by 이지이지(EGEasy) 2021. 12. 8.

가계부앱 전체화면

 

안녕하세요. 이지이지입니다.

2회에 걸쳐 앱인벤터로 가계부앱을 만들어보도록 하겠습니다.

 

사족으로 가계부앱을 만들기 전에는 굉장히 쉬울거라 예상했는데...

생각보다는 어렵습니다. 특히, 일별, 월별 통계가 나와야하고 목록에서 삭제도 가능해야 하는 등, 데이터 저장방식을 굉장히 많이 고민하게 되었고, 썩 만족스럽지는 않네요..

 

본 포스팅을 참고하시어 여러분들만의 방식을 생각해 멋진 가계부앱을 만들어보시기 바랍니다.

 

글을 읽으시면서 부족한 부분은 다음 소스파일을 참고하시기 바랍니다.

 

EG_Expenditure.aia
0.03MB

 

 

    Screen1 화면 구성 (월별 화면 및 지출항목 추가 스크린)

 

Screen1 화면 구성

 

주요 컴포넌트 이름

 

 

Screen1의 화면 구성은 위의 이미지와 같으며, 각 Layout 컴포넌트 아래 또 많은 컴포넌트들이 배치되어 있습니다.

 

먼저 컴포넌트들이 어떤 역할을 하는지에 대한 설명입니다.

 

1. hor_header: 앱의 상단에 월을 표시하고, 월을 이동할 수 있게 하는 버튼과 월별 총 지출금액을 나타내기 위한 수평정렬 컴포넌트입니다.

 

2. ver_calendar: 날짜와 일별 총 사용금액을 나타내기 위한 수직정렬 컴포넌트입니다.

안에는 각 주별로 수평정렬 컴포넌트가 있고, 그 안에 요일을 나타내는 레이블을 제외하고 버튼 컴포넌트와 레이블 컴포넌트가 각 42개씩 배치되어 있습니다.

 

버튼 컴포넌트는 날짜를 표시하며, 그 날짜를 클릭하면 일별 지출목록으로 이동하기 위한 것입니다.

레이블 컴포넌트는 해당 날짜의 총 지출액을 나타냅니다.

 

버튼 컴포넌트와 레이블 컴포넌트가 42개씩 있는 이유는, 앱인벤터는 변동하는 숫자에 따라 컴포넌트를 생성하거나 삭제할 수 없기 때문에 가능한 모든 경우의 수를 고려하여 42개를 배치한 것입니다.

불필요한 컴포넌트는 모든 데이터를 표시한 다음 블록으로 보이지 않게 처리해줍니다. (블록코딩 참고)

 

만약 어떤 달의 1일이 일요일부터 시작하고 31일까지 있는 경우와, 어떤 달의 1일이 토요일부터 시작하고 31일까지 있는 경우를 생각해보면 이해가 될 것입니다.

 

3. ver_add: 지출항목을 추가하기 위한 수직정렬 컴포넌트입니다. 컴포넌트 아래에는 항목을 추가하기 위한 텍스트박스와 버튼 등의 컴포넌트가 배치되어 있습니다.

 

4. hor_icon: 추가하기 아이콘을 나타내기 위한 수평정렬 컴포넌트입니다.

 

 

다음은 예제앱을 구현하기 위해 컴포넌트 중에 기본 속성을 변경해야 할 필요가 있는 부분입니다.

기능적인 측면의 속성만 설명드리니 디자인적인 측면의 속성은 소스파일을 참고하시기 바랍니다.

 

1. ver_add: [Visible: 체크 해제]

 

2. hor_icon - img_icon: [Clickable: 체크]

 

 

 

    Screen1 블록 코딩

 

다음은 Screen1의 전체 블록 코딩입니다. 하~~ 잠시만 봐도 복잡해 보입니다. 

 

Screen1 전체 블록 코딩

 

 

단계별로 변수와 블록에 대해 설명드리겠습니다.

 


 

# Step 1. 레이블과 버튼을 저장해 둘 리스트 변수 설정

 

레이블 및 버튼 리스트 변수

 

리스트 변수 lbl_prices에는 일별로 가격을 표시하기 위한 레이블을 저장해둡니다.

 

리스트 변수 btn_dates에는 날짜를 표시하고 클릭하면 일별 지출목록 화면으로 이동하기 위한 버튼을 저장해둡니다.

 

=> 레이블과 버튼을 리스트 변수로 저장해 두는 이유는 42개나 되는 레이블과 버튼을 일일이 코딩하지 않고, 블록 코딩 부분에서 Any component 블록을 사용하여 한번의 명령으로 처리하기 위한 것입니다.

 

 


 

# Step 2. Screen1이 처음 실행되었을 때 실행되는 블록과 변수

 

Screeen1 초기 설정 블록

 

① Screen1이 처음 실행될 때 변수 chosenmonth에 공백을 저장합니다.

→ 사용자가 선택한 월을 저장해 둘 변수입니다.

 

② Screen1이 처음 실행되었을 때

 

③ 변수 chosenmonth에 yyyy.MM 형식으로 월을 저장합니다.

→ instant에는 pattern의 형식으로 지정할 시간을 가져오는데 Clock1 컴포넌트에서 지금 시간을 가져옵니다.

→ pattern에는 표시형식을 나타내는데 yyyy.MM으로 설정하면 2021.03과 같은 식으로 표현됩니다.

 

④ lbl_month에서 보여질 텍스트를 변수 chosenmonth에 저장된 값으로 설정합니다.

 

⑤ SetCalendar 프로시저와 ShowData프로시저를 호출합니다.

 

 


 

# Step 3. SetCalendar 프로시저와 필요한 변수

 

SetCalendar 프로시저

 

① 변수 startingday는 각 월에서 1일의 요일을 저장해두기 위한 변수입니다.

→ 1일이 시작되는 요일이 각 월마다 달라 달력에 날짜 표시가 달라지므로 이를 처리하기 위한 것입니다.

 

② 변수 enddate는 각 월의 마지막 일자를 저장하기 위한 변수입니다.

 

③ 리스트 변수 dates는 각 월에 있는 모든 날짜를 저장해 둘 변수입니다.

 

④ 리스트 변수 dates31은 31일까지 있는 달을 저장해둡니다.

 

⑤ 리스트 변수 dates30은 30일까지 있는 달을 저장해둡니다.

 


 

⑥ hor_6th week를 보이지 않게 설정합니다.

→ 한 주 별로 수평정렬 컴포넌트가 있는데 모두 6개를 사용했습니다.

한 수평정렬 컴포넌트당 7일을 표현하게 되는데 만약 1일이 토요일에 시작한다면 달력에 6개의 주를 표시할 필요가 있습니다. 하지만 이런 경우가 흔치는 않기 때문에 일단 마지막 수평정렬 컴포넌트를 보이지 않게 설정한 것입니다.

 

⑦ SetCalendar 프로시저를 호출할 때 마다 변수를 초기화 시킨 것입니다.

→ 전체 코딩으로 보았을 때, startingday와 enddate는 초기화가 필요없지만 만일의 경우를 대비해 초기화하였습니다.

 

⑧ 변수 startingday에 사용자가 지정한 월, 1일의 요일을 저장합니다.

→ Clock1.Weekday는 사용자가 지정한 날짜의 요일을 반환하는 블록입니다.

일요일이면 1, 월요일이면 2, .... 토요일이면 7을 반환합니다.

→ 다음 블록을 예를 들어 설명하면 lbl_month.Text에는 Step2 단계에서 2021.12 형식으로 저장되어 있을 수 있습니다.

split을 통해 마침표(.)에서 글자를 구분하여 index 1(마침표 앞)에 해당하는 2021을 가져오게 됩니다.

연도 가져오기

 

⑨ 각 월의 마지막 날짜를 찾아 변수 enddate에 저장합니다.

→ 먼저 다음 블록을 통해 월을 가져옵니다.

월 가져오기

→ 가져온 월이 31일까지 있는 달을 모아 둔 dates31에 포함되는지 알아봅니다.

31일까지 있는 달에 포함되어 있는지 알아보기

 

→ 만약 31일까지 있는 달에 포함된다면 enddate를 30으로 설정합니다. 

(변수 startingday에 저장된 숫자가 나중에 하루를 차지할 것이라 1을 빼고 저장해둡니다.)

 

modulo of나머지를 구하는 블록으로 윤년(2월이 29일까지 있는 연도)을 계산하기 위해 사용한 것입니다.

 

⑩ 1부터 변수 startingday에 저장된 숫자에서 1을 뺀 값 까지 1씩 증가하며 리스트변수 dates안에 공백으로 추가합니다.

→ 만약 1일의 날짜가 목요일이라면 startingday는 5가 되어, 1부터 4까지 do안의 블록이 실행됩니다.

→ 결과적으로 dates에는 [공백,공백,공백,공백]으로 저장됩니다.

 

⑪ startingday에 저장된 숫자부터 (startingday + enddate)까지 1씩 증가하며 리스트변수에 날짜를 추가합니다.

→ 변수 dates에 다음과 같이 number-startingday+1을 저장하는 이유를 예로 들어 설명하면..

만약 1일이 목요일이라면 startingday에는 5가 저장되고 number가 5일경우 리스트변수 dates에는 1이 추가됩니다.

위의 10번과 연결해보면 dates에는 결과적으로 [공백(일),공백(월),공백(화),공백(수),1(목)]이 저장됩니다.

 

날짜를 변수 dates에 추가

 

⑫ 현재까지 저장된 dates 변수 안의 개수에 1을 더한 숫자에서 42까지 1씩 증가하며 do안의 블록을 실행합니다.

→ Step1에서 설명했듯 모든 경우의 수를 대비하기 위해 날짜를 표시하기 위한 버튼과 가격을 표시하기 위한 버튼을 42개 만들어 두었습니다. 따라서 나머지 공백을 저장하기 위한 블록입니다.

→ 만약 사용자가 선택한 달이 3월이라 가정하고 10번부터 12번까지 연결해서 보았을 때,

변수 dates에 저장된 값은 [공백,공백,공백,공백,1,2,3.....29,30,31,공백,공백,공백,공백,공백,공백] (총 42개 항목) 이 됩니다. 

 

Any componet를 활용하여 날짜를 나타내는 각 버튼과, 지출액을 나타낼 레이블에 리스트 변수 dates에 저장되어 있던 값을 하나씩 대입합니다.

 

⑭ (hor_6thweek에 포함되어 있는) 36번째 button에서부터 42번째 button까지 텍스트가 공백인지 아닌지를 파악하여 만약 하나라도 공백이 아니라면 6번째 주가 필요하므로 hor_6thweek를 보이게 하고, break 명령을 통해 for문을 빠져나옵니다.

 

 


 

# Step 4. ShowData 프로시저

 

ShowData 프로시저

 

① lbl.total(화면 우측 상단에서 월별 총 지출액을 나타내는 레이블)의 텍스트를 설정합니다.

→ 월별 총 지출액은 TinyDB1에서 tag chosenmonth+total(ex. 2021.12total)에 저장되어 있습니다.

→ btn_add를 클릭하여 항목을 추가할 때 값이 변동됩니다. btn_add 블록을 참고하세요.

→ 만약 선택된 달에 저장된 값이 없어 태그가 아직 생성이 되지 않았다면 0을 가져옵니다.

 

② 1부터 42까지 1씩 증가하며 do안의 블록을 실행합니다.

 

③ 만약 버튼의 텍스트가 공백이 아니라면,

→ 버튼의 텍스트로 날짜가 표시되어 있다는 것입니다.

 

④ 레이블의 텍스트에 그 날 사용한 총 금액을 표시하고, 만약 아직 그 날짜에 지출한 항목이 없다면 공백을 값으로 가져옵니다.

→ 각 날짜에 사용된 총 금액에 대한 태그는 2021.12.08total 형식으로 저장됩니다.

→ btn_add를 클릭하여 항목을 추가할 때 값이 변동됩니다. btn_add 블록을 참고하세요.

 

 


 

# Step 5. 이동버튼 이벤트

 

이동버튼 이벤트

 

①~②: 이전 또는 다음 화살표 버튼을 클릭했을 때 MoveCalendar 프로시저를 호출한 다음 ShowData 프로시저를 호출합니다.

gap 매개변수에는 이전 달이면 -1로, 다음 달이면 1을 전달합니다.

 

 


# Step 6. MoveCalenDar 프로시저

 

MoveCalendar 프로시저

 

MoveCalendar의 ②에서 축소되어 있는 부분

 

① MoveCalendar 프로시저를 호출할 때 입력할 매개변수입니다.

→ 이전달 버튼을 클릭했을 때는 -1을, 다음달 버튼을 클릭했을 때는 1을 전달하게 됩니다. (# Step 5 참조)

 

② 사용자가 선택한 달을 담아두는 변수 chosenmonth를 설정합니다.

→ Clock.Add Months를 통해 달을 변경시킵니다. quantity 부분에 몇 달이나 이동할지 입력하는데, 매개변수 gap 만큼 이동합니다.

 

③ lbl_month와 lbl_expenditure의 텍스트를 설정합니다.

 

④ 달이 변경되었으므로, 변경된 달에 맞게 화면을 표시하기 위해 SetCalendar 프로시저를 호출합니다.

 

 


 

# Step 7. 더하기 표시 아이콘 클릭 이벤트 및 Visibility 프로시저

 

아이콘 클릭 이벤트 및 Visibility 프로시저

 

 

더하기 아이콘을 클릭했을 때의 화면

 

① 더하기 아이콘을 클릭했을 때 위와 같이 항목을 입력할 수 있는 화면을 보여주기 위한 Visibility 프로시저를 호출합니다.

→ Visibility 프로시저는 더하기 아이콘 또는 [추가] 버튼을 클릭한 후 원래 화면으로 돌아갈 때 호출됩니다.

→ 따라서 [기본 화면=>추가 화면], [추가 화면 => 기본 화면]할 때 반대의 값으로 설정하게 됩니다.

 

② 각 컴포넌트별로 상황에 따라 보이게 또는 보이지 않게 설정합니다.

 

③ Screen1의 AlignVertical(세로정렬) 값이 1이라면 (기본 화면의 경우입니다.) 

세로 정렬을 아래 정렬로 하여 사용자의 입력을 편하게 하고, 

그렇지 않다면 세로 정렬이 아래로 되어 있다는 것이고, 추가 화면을 보고 있다는 의미이므로 다시 기본 화면으로 돌아오기 위해 세로정렬을 위로 설정합니다.

1은 위, 2는 가운데, 3은 아래를 의미합니다.

 

④ Screen1의 배경색을 설정합니다. ③과 대동소이하여 설명을 생략합니다.

 

 


 

# Step 8. 지출 항목 추가 화면에서 날짜 선택 및 취소 버튼 클릭 이벤트

 

날짜 선택 및 취소 버튼 이벤트

 

① 변수 setdate에 공백을 저장해둡니다.

 

② 변수 setmonth에 공백을 저장해둡니다.

 

③ DatePicker1(날짜선택)에서 날짜를 선택했을 때

 

④ 변수 setdate에 DatePicker1에서 선택한 날짜를 yyyy.MM.d(ex. 2021.12.8) 형식으로 저장합니다.

 

⑤ 변수 setmonth에 DatePicker1에서 yyyy.MM(ex. 2021.12) 형식으로 저장합니다.

 

⑥ DatePicker1에 보여질 텍스트를 변수 setdate에 저장된 값으로 합니다.

 

⑦ [취소] 버튼을 클릭했을 때, 

→ 원래 화면을 보여주기 위해 Visibility 프로시저를 호출합니다.

→ Hidekeyboard를 통해 항목을 추가하기 위해 나타나있던 키보드를 숨깁니다.

 

 


 

# Step 9. [추가] 버튼 클릭 이벤트

 

[추가] 버튼 클릭 이벤트

 

① 지출항목과 사용한 금액을 저장하기 위한 리스트 변수 items를 생성하고 빈 리스트를 저장해둡니다.

 

② 만약 지출항목을 입력할 텍스트상자나 금액을 입력할 텍스트상자가 공백으로 되어 있다면, 알림창을 띄우고 사용자의 편의를 위해 비어 있는 텍스트상자에 커서가 깜빡이도록 합니다.

 

③ ②에서 공백이 아니라면, 이제 필요한 값은 모두 입력되어 있는 것이므로, 입력을 위해 나타난 키보드를 사라지게 합니다.

 

④ DatePicker1의 텍스트가 "날짜 선택"이라면

→ 사용자가 날짜를 선택하지 않았을 경우 기본적으로 "날짜 선택"으로 보이게 디자이너창에서 설정해 두었습니다.

보통 날짜 선택을 따로 하지 않았다면, 무의식적으로 오늘 날짜에 지출항목을 추가한다고 가정하였습니다.

 

⑤ 변수 setdate에는 오늘 날짜를, setmonth에는 오늘이 속한 월을 저장합니다.

 

⑥ 만약 TinyDB1의 tag setdate에 저장된 값이 공백이라면,

→ 사용자가 지정한 날짜에 아직 지출항목이 추가된 것이 없다는 의미입니다.

→ 공백이 아니라면 ⑬으로 이동합니다.

 

⑦ 리스트 변수 items에 지출항목과 사용금액을 추가합니다.

→ 만약 치킨, 20000원을 입력했다면 [["item": "치킨"], ["price": 20000]] 형식으로 저장됩니다.

 

⑧ TinyDB1에 tag를 변수 setdate에 저장되어 있는 값으로 하고, 리스트변수 items를 값으로 저장합니다.

 

⑨ TinyDB1에 tag를 변수 setdate+total (ex. 2021.12.8total)로 하고, 값에는 사용자가 입력한 지출항목의 가격을 저장합니다.

 

⑩ 만약 TinyDB1의 tag setmonth+total (ex. 2021.12total)에 저장된 값이 공백이라면

→ 아직 지정된 달에 지출한 항목이 없다는 의미입니다.

 

⑪ TinyDB1의 tag setmonth+total에 사용자가 입력한 지출항목의 가격을 저장합니다.

 

⑫ (⑩에 연결) TinyDB1의 tag setmonth+total에 저장된 값이 공백이 아니라면

→ 기존에 지정된 달에 지출한 항목이 있다는 의미입니다.

→ TinyDB1의 tag setmonth+total에 저장된 값을 가져오고 거기에 사용자가 입력한 지출항목의 가격을 더하여 다시 같은 tag setmonth+total에 값으로 저장합니다.

 

⑬~ⓑ (⑥에 연결) TinyDB1의 tag setdate에 저장된 값이 공백이 아니라면

 

⑬ 리스트 변수 items에 기존의 setdate에 저장된 값을 불러와 저장합니다.

 

⑭ 리스트 변수 items에 사용자가 입력한 지출항목과 지출금액을 추가합니다.

 

⑮ TinyDB1의 tag setdate에 리스트 변수 items를 값으로 저장합니다.

 

ⓐ TinyDB1의 tag setdate+total에 기존에 저장되어 있던 값을 가져오고 여기에 사용자가 입력한 가격을 더해 저장합니다.

 

ⓑ TinyDB1의 tag setmonth+total에 기존에 저장되어 있던 값을 가져오고 여기에 사용자가 입력한 가격을 더해 저장합니다.

 

ⓒ 이제 추가하기가 끝이 났으므로 다시 기본화면으로 돌아가기 위한 작업을 실행합니다.

 

 


 

# Step 10. 날짜 버튼 클릭 이벤트

 

날짜 버튼 클릭 이벤트

 

Screen1에서 달력의 날짜 버튼을 클릭하면 ScreenDay 스크린으로 이동합니다. 

ScreenDay에서는 선택한 날짜의 지출항목을 보여주게 됩니다.

따라서 Screen1에서 선택한 날짜를 ScreenDay로 보내주어야 하는 과정을 담은 블록입니다.

 

① Screen1에서 ScreenDay로 전달할 정보를 담아 둘 변수 pass_to_ScreenDay를 공백으로 저장해둡니다.

 

① 만약 클릭한 버튼이 날짜 버튼들을 저장해 둔 리스트 변수 btn_dates에 포함되어 있다면

[추가] 또는 [삭제] 버튼을 눌렀을 때와 구분하기 위한 것입니다.

 

② 만약 클릭한 버튼과 동일한 위치의 lbl_prices의 텍스트가 공백이 아니라면

→ 공백이라면 사용자가 선택한 날짜에 지출한 항목이 없다는 의미입니다.

 

③ 변수 pass_to_ScreenDay에 lbl_month.Text+.(마침표)+Button.Text(선택된 날짜)를 저장합니다.

ex.) 2021.12.8와 같이 저장됩니다.

 

④ ScreenDay로 이동하는데 startValue에 변수 pass_to_ScreenDay를 담아 이동합니다.

 

 


 

이상 앱인벤터로 가계부앱 만들기 포스팅 1편을 마치겠습니다.

지금까지 보셨듯 블록이 굉장히 복잡합니다.

여러분께서 직접 알고리즘을 생각해보시고 더 간단한 코드로 작성했으면 좋겠네요.

 

다음 2편에서 가계부앱 만들기를 완성하도록 하겠습니다.~~~

댓글