๊ฐœ๋ฐœ ์ผ์ง€ ๐Ÿ’ป/Swemo

Swemo - Iteration 1: ๋ฉ”๋ชจ ์•ฑ์˜ ์ฒญ์‚ฌ์ง„(๊ธฐ์ดˆ)

interfacer_han 2025. 12. 28. 19:28

#1 ๊ฐœ๋ฐœ ๋ชฉํ‘œ (Iteration Goal)

#1-1 ์ด์ƒ(Ideal)๊ณผ ์ง•๊ฒ€๋‹ค๋ฆฌ

์ด์ƒ(Ideal)

'์•ž์„  ๊ฒŒ์‹œ๊ธ€'์—์„œ ๋งํ–ˆ๋˜ ๋‚ด์šฉ์˜ ์ถฉ์‹คํ•˜๊ณ  ์ข‹์€ ๊ตฌํ˜„์ด ๋‚ด๊ฐ€ ๋ฐ”๋ผ๋Š” ์ด์ƒ์ด๋‹ค. ํ•˜์ง€๋งŒ ์•ฑ์„ ๋งŒ๋“ค๋‹ค ๋ณด๋ฉด ๋˜ ๋‹ค๋ฅธ ํ˜•ํƒœ๋กœ, ์กฐ๊ธˆ์ด๋ผ๋„ ๋ณ€ํ•  ๊ฑฐ๋ผ๋Š” ์ง๊ฐ์ด ๋“ ๋‹ค. ๊ทธ๋ž˜์„œ ํ˜„์‹œ์ ์— '์ด์ƒ'์„ ๊ตฌ์ฒด์ ์œผ๋กœ ์ƒ์ƒํ•˜๋Š” ๊ฒƒ ์ž์ฒด๊ฐ€ ์‹œ๊ฐ„ ๋‚ญ๋น„๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค. ๊ทธ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ์˜คํžˆ๋ ค ์ฒญ์‚ฌ์ง„์„ ๊ทธ๋ ค์•ผ ํ•˜๋Š” ์ง€๊ธˆ ์‹œ์ ์—์„œ๋Š” ๋ฐฉํ•ด๊ฐ€ ๋  ์ˆ˜๋„ ์žˆ๋‹ค๋Š” ์ƒ๊ฐ๋„ ๋“ ๋‹ค. ์ง€๊ธˆ์€ '์™„์„ฑ๋œ ์•ฑ์˜ ๋ชจ์Šต'์„ ๋ฏธ์ง€์ˆ˜ x์ฒ˜๋Ÿผ ์ƒ๊ฐํ•˜๊ฒ ๋‹ค. x๊ฐ’์€ ๋ถ„๋ช… ์ข‹์€ ๊ฐš์„ ๊ฒƒ์ด๋‹ค.

 

์•ž์œผ๋กœ๋„ ์ œ๋ชฉ์ด "Iteration n"๊ณผ ๊ฐ™์€ ํ˜•์‹์˜ ๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๊ธ€์—์„œ๋Š” '์ด์ƒ'์„ ๋ช…์‹œ์ ์œผ๋กœ ์งš๊ณ  ๊ฐˆ ๊ฒƒ์ด๋‹ค. 'Top-down' ์ ์œผ๋กœ๋„ ์ƒ๊ฐํ•ด์•ผ 'Bottom-up'์„ ์ •ํ™•ํ•˜๊ฒŒ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

Iteration 1: ๋ฉ”๋ชจ ์•ฑ์˜ ์ฒญ์‚ฌ์ง„(๊ธฐ์ดˆ)

์ฒ˜์Œ์—๋Š” Iteration 1์˜ ์ฃผ์ œ๋ฅผ ๋ง‰์—ฐํžˆ "๋ฉ”๋ชจ ์•ฑ์˜ ๊ธฐ์ดˆ"๋กœ ์žก์•˜๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ '๊ธฐ์ดˆ'๋ผ๋Š” ๋‹จ์–ด๋Š” ๋„ˆ๋ฌด๋‚˜ ์ถ”์ƒ์ ์ด๊ณ  ๋ฒ”์œ„๊ฐ€ ๋„“๊ธฐ์—, ์–ด์ฐŒ ๋ณด๋ฉด ๊ต‰์žฅํžˆ ๋ฌด์ฑ…์ž„ํ•œ ํ‘œํ˜„์ด ๋  ์ˆ˜๋„ ์žˆ๋‹ค. ๊ทธ๋ž˜์„œ ์•ฝ๊ฐ„ ๋” ์ฃผ์ œ๋ฅผ ๊ตฌ์ฒดํ™”ํ•ด์„œ, "๋ฉ”๋ชจ ์•ฑ์˜ ์ฒญ์‚ฌ์ง„(๊ธฐ์ดˆ)"์œผ๋กœ ์žก์•˜๋‹ค. ๋‹จ์ˆœํžˆ ์–ด๋–ค, ๋‚ฎ์€(Low) ๋‹จ๊ณ„์˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ํ•œ๋‹ค๋“ ๊ฐ€ ์ „์ฒด์ ์ธ ๋ชจ๋“ˆ ๊ตฌ์„ฑ์„ ์žก๋Š”๋‹ค๋Š”๊ฐ€ ํ•˜๋Š” ์‹์ด ์•„๋‹˜์„ ๋ณด์ด๊ธฐ ์œ„ํ•ด์„œ๋‹ค. '๋ผˆ๋Œ€' ๊ทธ์ค‘์—์„œ๋„ 'ํ•ต์‹ฌ ๋ผˆ๋Œ€', ๋” ์ •ํ™•ํžˆ๋Š” ๋‹ค๋ฅธ '๋ผˆ๋Œ€'๋“ค์˜ ํ† ๋Œ€๊ฐ€ ๋˜๋Š” ๋ผˆ๋Œ€๋ฅผ ๋งŒ๋“ค ๊ฒƒ์ด๋‹ค. ์–ด๋–ค ๊ณ ๋ฏผ์ด๋“  ๊ฐ€์ง€๊ณ  ๋†€ ์‹ค์ฒด๊ฐ€ ์žˆ์–ด์•ผ ๋ฌธ์ œ ํ•ด๊ฒฐ์ด ๋น ๋ฆฟ๋น ๋ฆฟํ•˜๊ฒŒ ๋œ๋‹ค. ์•ž์œผ๋กœ ์–ด๋–ค ์„ค๊ณ„๋ฅผ ์ถ”๊ฐ€ํ•ด ๋‚˜๊ฐˆ์ง€ ๊ณ ์‹ฌํ•˜๊ธฐ ์œ„ํ•œ ํ‹€(Plate)์„ ๋งŒ๋“ค์–ด๋ณด๊ฒ ๋‹ค. ํ™•์‹คํ•˜์ง€๋Š” ์•Š์ง€๋งŒ, ์•„๋งˆ Iteration 2์˜ ์ฃผ์ œ๋Š” "๋ฉ”๋ชจ ์•ฑ์˜ ๊ธฐ๋ณธ"์ด์ง€ ์•Š์„๊นŒ?

 

#1-2 ๊ฐœ๋ฐœ ๋ฒ”์œ„

์ด๋ฒˆ ๊ฐœ๋ฐœ ๋ฒ”์œ„ (In Scope)

  1. ๋ฉ”๋ชจ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ (์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜๋Š” ๋ฌธ์ž์—ด์„ ๋„ฃ์„ ์ˆ˜๋Š” ์—†๊ณ , ๊ทธ๋ƒฅ '๋ฉ”๋ชจ ๊ฐ์ฒด'๋งŒ ์ถ”๊ฐ€)
  2. ๋ฉ”๋ชจ ์ฝ๊ธฐ ๊ธฐ๋Šฅ
  3. ๋ฉ”๋ชจ ์นดํ…Œ๊ณ ๋ฆฌ ์ฝ๊ธฐ ๊ธฐ๋Šฅ (์˜ˆ๋ฅผ ๋“ค๋ฉด ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก ๋ฆฌ์ŠคํŠธ ๋“ฑ)
  4. ๋ฉ”๋ชจ ์นดํ…Œ๊ณ ๋ฆฌ '์„ ํƒ' ๊ธฐ๋Šฅ (= ํŠน์ • ๋ฉ”๋ชจ ์นดํ…Œ๊ณ ๋ฆฌ ์†์˜ ๋ฉ”๋ชจ๋งŒ ๋ชจ์•„ ์ฝ๋Š” ๊ธฐ๋Šฅ)
  5. ์นดํ…Œ๊ณ ๋ฆฌ ์ฝ๊ธฐ ๋ฐ ์„ ํƒ์„ ์œ„ํ•œ UI
  6. Compose ๊ธฐ๋ฐ˜ UI
  7. Nutri Capture ์‹œ์ ˆ์˜ ์ฝ”๋“œ ์ค‘ ํ˜„ Iteration์—์„œ ๋‹น์žฅ ์“ธ ๊ฒƒ ์•„๋‹ˆ๋ฉด (๋‚˜์ค‘์— ๋‹ค์‹œ ์ถ”๊ฐ€ํ•˜๋Š” ํ•œ์ด ์žˆ๋”๋ผ๋„) ์ตœ๋Œ€ํ•œ ์ œ๊ฑฐ

 

๋ฒ”์œ„ ์™ธ (Out of Scope)

  1. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค (์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋Œ€์‹  ๋ชฉ์—…์œผ๋กœ๋งŒ ์ง„ํ–‰)
  2. ๋ฉ”๋ชจ์— ๋‚ด์šฉ(์–ด๋–ค ๋ฌธ์ž์—ด) ๋„ฃ๊ธฐ
  3. ๋ฉ”๋ชจ ์ˆ˜์ •
  4. ๋ฉ”๋ชจ ์‚ญ์ œ
  5. ๋ฉ”๋ชจ ์นดํ…Œ๊ณ ๋ฆฌ ์ถ”๊ฐ€ (๋นŒํŠธ์ธ ๊ฐ€๊ตฌ์ฒ˜๋Ÿผ, ์ปดํŒŒ์ผ ๋‹จ๊ณ„์—์„œ ์นดํ…Œ๊ณ ๋ฆฌ ํ•˜๋“œ ์ฝ”๋”ฉํ•ด์„œ ๋„ฃ์–ด์ค„ ๊ฒƒ์ž„)
  6. ๋‹ค๋ฅธ ์นดํ…Œ๊ณ ๋ฆฌ์— ๋ฉ”๋ชจ ๋„˜๊ฒจ์ฃผ๊ธฐ (์˜ˆ๋ฅผ ๋“ค๋ฉด A ์นดํ…Œ๊ณ ๋ฆฌ์—์„œ ์ž‘์„ฑํ•œ ๋ฉ”๋ชจ๋ฅผ B ์นดํ…Œ๊ณ ๋ฆฌ๋กœ ์˜ฎ๊ธฐ๊ธฐ)
  7. ๋ฉ”๋ชจ ์ถ”๊ฐ€ ์‹œ์˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ (๊ธฐ๋ณธ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์žˆ์œผ๋ฉด ๊ทธ๋Œ€๋กœ๋งŒ ์‚ฌ์šฉ)
  8. ์นดํ…Œ๊ณ ๋ฆฌ UI ์—ฌ๋‹ซ์„ ๋•Œ์˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ (๊ธฐ๋ณธ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์žˆ์œผ๋ฉด ๊ทธ๋Œ€๋กœ๋งŒ ์‚ฌ์šฉ)
  9. ๊ธฐ๋Šฅ ํ™•์ธ๋งŒ์„ ์œ„ํ•œ ์ตœ์†Œ UI ์™ธ ๋ชจ๋“  UI ์ž‘์—…
  10. 'Bubble' ๊ด€๋ จ ์ž‘์—…

 

#2 ๋ณ€๊ฒฝ ๋‚ด์—ญ (์ปค๋ฐ‹ ๋ชฉ๋ก)

#2-1 Iteration 1-1: ๊ธฐ์กด ์ฝ”๋“œ ์ œ๊ฑฐ

์˜์กด์„ฑ ๊ทธ๋ž˜ํ”„๋‹ค. NutrientScreen()์„ ํ˜ธ์ถœํ•˜๋Š” ์ž๋ฆฌ์— TestScreen()์„ ๋Œ€์‹  ๋’€๋‹ค. TestScreen()์€ ์ž๋ฆฌ๋งŒ ์ฐจ์ง€ํ•˜๊ฒŒ ๋‘๋Š”, ์ž„์‹œ ์ปดํฌ๋„ŒํŠธ์˜ ์—ญํ• ์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ์ƒํƒœ์—์„œ ์•ฑ ์‹คํ–‰ ๊ตฌ๋™ ์‹œ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š”, '๊ฑฐ์˜' ๋ชจ๋“  ์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐํ–ˆ๋‹ค. "ํ˜น์‹œ๋ผ๋„ ๋‚˜์ค‘์— ์“ธ ์ˆ˜๋„ ์žˆ์ง€ ์•Š์„๊นŒ?"๋ผ๋Š” ์ƒ๊ฐ์ด ๋“œ๋Š” ์ฝ”๋“œ๋Š” ์‚ญ์ œํ•˜๋˜, "์ด๊ฑด ๋จธ์ง€์•Š์•„ ๋ฐ˜๋“œ์‹œ ์“ด๋‹ค"๋ผ๋Š” ์ƒ๊ฐ์ด ๋“œ๋Š” ์ฝ”๋“œ๋Š” ๋‚จ๊ฒผ๋‹ค. ์‚ญ์ œํ•œ ์ฝ”๋“œ ์ค‘ ์ถ”ํ›„ ์ •๋ง๋กœ ํ•„์š”ํ•œ ์ฝ”๋“œ๊ฐ€ ์žˆ๋‹ค๋ฉด, ๊ทธ๋•Œ ๊ฐ€์„œ ๋‹ค์‹œ ์ถ”๊ฐ€ํ•œ๋‹ค.

 

๊ด€๋ จ ๊ฐœ๋ฐœ ๋ฒ”์œ„

  • Nutri Capture ์‹œ์ ˆ์˜ ์ฝ”๋“œ ์ค‘ ํ˜„ Iteration์—์„œ ๋‹น์žฅ ์“ธ ๊ฒƒ ์•„๋‹ˆ๋ฉด (๋‚˜์ค‘์— ๋‹ค์‹œ ์ถ”๊ฐ€ํ•˜๋Š” ํ•œ์ด ์žˆ๋”๋ผ๋„) ์ตœ๋Œ€ํ•œ ์ œ๊ฑฐ

 

์ฃผ์š” ๋ณ€๊ฒฝ์ 

  • ๊ฑฐ์˜ ๋ชจ๋“  ์ฝ”๋“œ ์ œ๊ฑฐ
  • ์—ฌ๋Ÿฌ ์‹คํ—˜์ ์ธ ์‹œ๋„๋“ค์„ ์ˆ˜ํ–‰ํ•  TestScreen() ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ

 

์ฐธ๊ณ 

 

#2-2 Iteration 1-2: Data entity์™€ Data access abstraction ์ถ”๊ฐ€

์ฒ˜์Œ์—๋Š” "์ผ๋‹จ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•˜๋‚˜?"๋ผ๊ณ  ๋ง‰์—ฐํžˆ ์ƒ๊ฐํ–ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ํ˜„ ๋‹จ๊ณ„์—์„œ๋Š” ๋ถˆํ•„์š”ํ•œ ๊ตฌํ˜„์ด๋ผ๊ณ  ํŒ๋‹จํ–ˆ๋‹ค. ๊ทธ ๋Œ€์‹ , DIP ์›์น™์„ ๊ณ ๋ คํ•ด ๋จผ์ € '์ •์ฑ…'์— ํ•ด๋‹นํ•˜๋Š” 'MemoRepository'๋ฅผ ๋งŒ๋“ค๊ธฐ๋กœ ํ–ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ ค๋ฉด Repository์—์„œ ๋‹ค๋ฃฐ ๋ฐ์ดํ„ฐ ํด๋ž˜์Šค๊ฐ€ ํ•„์š”ํ•˜๋ฏ€๋กœ Memo ํด๋ž˜์Šค๋„ ๋งŒ๋“ค์—ˆ๋‹ค.

 

app ๋ชจ๋“ˆ์ด data ๋ชจ๋“ˆ์„ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•˜๋ฏ€๋กœ, build.gradle์—

implementation(project(":data"))

๋ฅผ ์ถ”๊ฐ€ํ•ด ์ฃผ์–ด์•ผ ํ•˜๋Š”๋ฐ, Now in Android์—์„œ๋Š”

implementation(projects.data)

์™€ ๊ฐ™์€ ํ˜•ํƒœ๋กœ ๋ชจ๋“ˆ ์˜์กด์„ฑ ์ถ”๊ฐ€๋ฅผ ํ•˜๊ณ  ์žˆ์—ˆ๋‹ค. ํ›„์ž์˜ ๋ฐฉ์‹์€ Gradle์˜ Type-safe Project Accessors ๊ธฐ๋Šฅ์ธ๋ฐ, ๋‚˜๋„ ์ด๋ฒˆ ์ปค๋ฐ‹๋ถ€ํ„ฐ ์ ์šฉํ–ˆ๋‹ค.

 

๊ด€๋ จ ๊ฐœ๋ฐœ ๋ฒ”์œ„

  • ๋ฉ”๋ชจ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ (์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜๋Š” ๋ฌธ์ž์—ด์„ ๋„ฃ์„ ์ˆ˜๋Š” ์—†๊ณ , ๊ทธ๋ƒฅ '๋ฉ”๋ชจ ๊ฐ์ฒด'๋งŒ ์ถ”๊ฐ€)
  • ๋ฉ”๋ชจ ์ฝ๊ธฐ ๊ธฐ๋Šฅ
  • Compose ๊ธฐ๋ฐ˜ UI

 

์ฃผ์š” ๋ณ€๊ฒฝ์ 

  • Data entity (Memo)์™€ Repository (MemoRepository) ์ถ”๊ฐ€
  • FakeMemoRepository ์ถ”๊ฐ€ ๋ฐ ์ด๋ฅผ ํ™œ์šฉํ•ด TestScreen()์— ์•„์ฃผ ๊ฐ„๋‹จํ•œ ํ…Œ์ŠคํŠธ UI ์ž‘์„ฑ
  • build.gradle์— Type-safe Project Accessors ๊ธฐ๋Šฅ ์ ์šฉ

 

์Šคํฌ๋ฆฐ์ƒท

 

Flow ๊ฐœ๋… ๋ณต์Šต

๋”๋ณด๊ธฐ
@Composable
fun TestScreen() {
    val repository: MemoRepository = remember { FakeMemoRepository() }
    val memos by repository.getMemos().collectAsState(initial = emptyList())
    val memosText = memos.joinToString("\n") { it.content }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .clickable(
                onClick = {
                    repository.insertMemo("Inserted memo")
                }
            ),
        contentAlignment = Alignment.Center
    ) {
        Text(memosText)
    }
}

Flow๋Š” ๋‹จ์–ด ๊ทธ๋Œ€๋กœ '๋ฐ์ดํ„ฐ(๊ฐ’)์˜ ํ๋ฆ„'์ด๋ผ, ํ˜„์žฌ ๊ฐ’์ด๋ผ๋Š” ๊ฐœ๋…์ด ์—†๋‹ค. ๋ฐ˜๋ฉด, UI(Jetpack Compose)๋Š” '์ง€๊ธˆ' ๊ทธ๋ ค์•ผ ํ•  ๊ฐ’์ด ๋ฌด์—‡์ธ์ง€๋ฅผ ์•Œ์•„์•ผ ํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ์œ„์˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ Flow -> State๋กœ ๋ณ€ํ™˜ํ•˜๋Š” Flow.collectAsState()๋ฅผ ์จ์•ผ ํ•œ๋‹ค.

์ข€ ๋” ์ง€์—ฝ์ ์ธ ์ถ”๊ฐ€ ์„ค์ •์„ ํ•˜๋ ค๋ฉด, Flow -> StateFlow -> State๋กœ ๋ณ€ํ™˜ํ•ด์•ผํ•œ๋‹ค. StateFlow -> State๋Š” Flow.collectAsState()๋ฅผ ๋˜‘๊ฐ™์ด ์“ฐ๋ฉด ๋˜๊ณ , Flow -> StateFlow ๋ณ€ํ™˜์—” Flow.stateIn()์„ ์“ฐ๋ฉด ๋œ๋‹ค. stateIn()์€ Flow์˜ ์ˆ˜๋ช…์„ ์ฝ”๋ฃจํ‹ด๊ณผ ๋™๊ธฐํ™”์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” scope ๋งค๊ฐœ๋ณ€์ˆ˜์™€ Flow์˜ collect๋ฅผ ์–ธ์ œ ์‹œ์ž‘ํ•˜๊ณ  ์–ธ์ œ ๋ฉˆ์ถœ ๊ฒƒ์ธ์ง€๋ฅผ ์ •ํ•˜๋Š” started ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ณด์œ ํ•œ๋‹ค.

 

2026-01-15 ์ˆ˜์ •

Repository์—์„œ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•˜๋Š” ๊ฑด Data entity๊ฐ€ ์•„๋‹ˆ๋ผ, Domain model์ด๋‹ค. ๋”ฐ๋ผ์„œ ์—ฌ๊ธฐ์„œ ๋‹ค๋ฃฌ ๊ฑด ์‚ฌ์‹ค Domain model์ด๋‹ค. ์ด๋Ÿฐ ๊นจ์•Œ ๊ฐ™์€ ์šฉ์–ด ๊ฐ€์ง€๊ณ  ์ด๋Ÿฌ๋Š” ๊ฑธ๋กœ, ์Šค์Šค๋กœ ์ชผ์ž”ํ•˜๋‹ค๋Š” ์ƒ๊ฐ๋„ ์กฐ๊ธˆ ๋“ ๋‹ค. ๋‹ค๋งŒ ์‚ฌ์†Œํ•˜๋‹ค๋Š” ์ด์œ ๋กœ, ๋„˜๊ฒจ์งš๋“ฏ์ด ์Šค๋ฅด๋ฅต ๋น ์ ธ๋‚˜๊ฐ€๋‹ค ๋ณด๋ฉด ๋‚˜์ค‘์—” ํšŒํ”ผํ˜•(?) ๊ฐœ๋ฐœ์ž๊ฐ€ ๋˜์–ด ๋ฒ„๋ฆด์ง€๋„ ๋ชจ๋ฅธ๋‹ค.

 

์ฐธ๊ณ 

 

#2-3 Iteration 1-3: ์ฒญ์‚ฌ์ง„์„ ์œ„ํ•œ ์ตœ์†Œ UI

'๋ฉ”๋‰ด ๋ฐ”'๋ฅผ ๊ตฌํ˜„ํ–ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ๊ฐœ๋ฐœ ๋ฒ”์œ„์— ์žˆ๋Š” '๋ฉ”๋ชจ ์นดํ…Œ๊ณ ๋ฆฌ ์ฝ๊ธฐ ๊ธฐ๋Šฅ' ๊ตฌํ˜„์„ ์œ„ํ•ด ์นดํ…Œ๊ณ ๋ฆฌ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋‘˜ ์žฅ์†Œ๊ฐ€ ํ•„์š”ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ฒ˜์Œ์—๋Š” ๋ฉ”๋‰ด ๋ฐ”๊ฐ€ ์ž„์‹œ UI๋กœ๋Š” ๋ถ€์ ํ•ฉํ•˜๋‹ค๊ณ  ํŒ๋‹จํ–ˆ๋‹ค. ๋ฉ”๋‰ด ๋ฐ” ๊ตฌํ˜„์— ์‹œ๊ฐ„์ด ๊ฑธ๋ฆด ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ด๋•Œ, Jetpack Compose์— ๊ธฐ๋ณธ ์ œ๊ณต๋˜๋Š” ModalNavigationDrawer์˜ ์กด์žฌ๋ฅผ ์•Œ์•˜๋‹ค. ์ด๋ฅผ ์ด์šฉํ•ด ์‰ฝ๊ณ  ๋น ๋ฅด๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

๊ด€๋ จ ๊ฐœ๋ฐœ ๋ฒ”์œ„

  • ์นดํ…Œ๊ณ ๋ฆฌ ์ฝ๊ธฐ ๋ฐ ์„ ํƒ์„ ์œ„ํ•œ UI
  • Compose ๊ธฐ๋ฐ˜ UI

 

์ฃผ์š” ๋ณ€๊ฒฝ์ 

  • :feature:test ๋ชจ๋“ˆ ๋งŒ๋“ค๊ณ  ํ•ด๋‹น ๋ชจ๋“ˆ๋กœ TestScreen() ์˜ฎ๊น€
  • ModalNavigationDrawer๋ฅผ ์ด์šฉํ•ด TestScreen()์— '๋ฉ”๋‰ด ๋ฐ”' ์ถ”๊ฐ€

 

์Šคํฌ๋ฆฐ์ƒท

 

TestScreen()์˜ ๊ตฌ์กฐ

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TestScreen() {
    ...
    ModalNavigationDrawer(
        drawerState = drawerState,
        drawerContent = {
            ...
        }
    ) {
        ...

        Column(
            modifier = Modifier.fillMaxSize()
        ) {
            TopAppBar(
                ...
                navigationIcon = {
                    IconButton(
                    ...
                },
                actions = {
                    IconButton(
                    ...
                }
            )
            Text(
                text = memosText,
                modifier = Modifier.fillMaxWidth()
            )
        }
    }
}

ModalNavigationDrawer๋Š” ํ‘œ์‹œํ•  ์ปดํฌ๋„ŒํŠธ์— '์”Œ์šฐ๋“ฏ์ด' ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. ์ง€๊ธˆ์€ Column์— ์”Œ์› ์ง€๋งŒ, ๋‚˜์ค‘์—๋Š” ์•„๋งˆ Scaffold์— ์”Œ์šฐ์ง€ ์•Š์„๊นŒ?

 

์ฐธ๊ณ 

 

#2-4 Iteration 1-4: Fake Catetory ๋ฐ ์ƒํ˜ธ์ž‘์šฉ

#2-2์™€ ์œ ์‚ฌํ•œ ๋ฐฉ์‹์œผ๋กœ, Category๋ผ๋Š” Data entity๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค. ์ด์— ๋งž์ถฐ MemeRository์™€ Memo ํด๋ž˜์Šค๋ฅผ ์ˆ˜์ •ํ–ˆ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ ์‚ฌ์šฉ์ž๊ฐ€ (UI์—์„œ) ์นดํ…Œ๊ณ ๋ฆฌ์™€ ์ƒํ˜ธ์ž‘์šฉ์„ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ–ˆ๋‹ค.

 

๊ด€๋ จ ๊ฐœ๋ฐœ ๋ฒ”์œ„

  • ๋ฉ”๋ชจ ์นดํ…Œ๊ณ ๋ฆฌ ์ฝ๊ธฐ ๊ธฐ๋Šฅ (์˜ˆ๋ฅผ ๋“ค๋ฉด ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก ๋ฆฌ์ŠคํŠธ ๋“ฑ)
  • ๋ฉ”๋ชจ ์นดํ…Œ๊ณ ๋ฆฌ '์„ ํƒ' ๊ธฐ๋Šฅ (= ํŠน์ • ๋ฉ”๋ชจ ์นดํ…Œ๊ณ ๋ฆฌ ์†์˜ ๋ฉ”๋ชจ๋งŒ ๋ชจ์•„ ์ฝ๋Š” ๊ธฐ๋Šฅ)

 

์ฃผ์š” ๋ณ€๊ฒฝ์ 

  • Data entity (Category) ์ถ”๊ฐ€
  • data class์ธ Memo์— ์นดํ…Œ๊ณ ๋ฆฌ ํ‘œ์‹œ๋ฅผ ์œ„ํ•œ ํ”„๋กœํผํ‹ฐ("categoryName") ์ถ”๊ฐ€
  • MemoRepository์˜ ๋ฉ”์†Œ๋“œ๋“ค์ด ์นดํ…Œ๊ณ ๋ฆฌ ๊ฐœ๋…์„ ํฌํ•จํ•˜๋„๋ก ์—…๋ฐ์ดํŠธ
  • TestScreen() ์† '๋ฉ”๋‰ด ๋ฐ”'(#2-3)์— ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก์„ ํ‘œ์‹œ, ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๊ฐ ํ•ญ๋ชฉ ํด๋ฆญ ์‹œ ํ•ด๋‹น ํ•ญ๋ชฉ์— ์†ํ•˜๋Š” ๋ฉ”๋ชจ๋งŒ ํ‘œ์‹œ

 

์Šคํฌ๋ฆฐ์ƒท

#2-3์— ์žˆ๋Š” ์Šคํฌ๋ฆฐ์ƒท๊ณผ ๋น„๊ตํ•˜๋ฉด TopAppBar()์˜ ์ƒ‰์ด ๋‹ค๋ฅธ๋ฐ, #2-3์€ ๋‹คํฌ ๋ชจ๋“œ์ผ ๋•Œ ์Šคํฌ๋ฆฐ์ƒท์„ ์ฐ์—ˆ๊ณ  ์—ฌ๊ธฐ์„œ๋Š” ๋ผ์ดํŠธ ๋ชจ๋“œ์ผ ๋•Œ ์ฐ์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์•ž์œผ๋กœ๋Š” ๋ชจ๋“œ์˜ ์ผ๊ด€์„ฑ์„ ์ง€์ผœ์„œ ์Šคํฌ๋ฆฐ์ƒท์„ ์ฐ์–ด์•ผ๊ฒ ๋‹ค.

 

์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๋ฉ”๋ชจ ๋ฐ์ดํ„ฐ ์กฐํšŒ (FakeMemoRepository)

package com.example.data

...

class FakeMemoRepository : MemoRepository {
    private val categoriesFlow = MutableStateFlow<List<Category>>(
        ...
    )

    private val memosFlow = MutableStateFlow<List<Memo>>(
        listOf(
            Memo(content = "Fake memo 1 (category 1)", categoryName = "category 1"),
            Memo(content = "Fake memo 2 (category 1)", categoryName = "category 1"),
            Memo(content = "Fake memo 3 (category 2)", categoryName = "category 2"),
            Memo(content = "Fake memo 4 (category 2)", categoryName = "category 2"),
            Memo(content = "Fake memo 5 (category 3)", categoryName = "category 3"),
            Memo(content = "Fake memo 6 (category 3)", categoryName = "category 3"),
        )
    )

    ...

    override fun getMemosByCategory(categoryName: String?): Flow<List<Memo>> {
        return memosFlow.map { memos ->
            if (categoryName == null) {
                memos
            } else {
                memos.filter { it.categoryName == categoryName }
            }
        }
    }

    ...
}

Test double์—์„œ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๋ฉ”๋ชจ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฉ”์†Œ๋“œ.

 

์ฐธ๊ณ