[Android ๊ธฐ์ดˆ] 19. Retrofit (HTTP, API, REST, JSON, GSON)
๋ฐ˜์‘ํ˜•



 

Retrofit์„ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด ์•Œ์•„์•ผ ํ•˜๋Š” ๊ฐœ๋…

  • ์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ
  • HTTP
  • API
  • REST
  • JSON
  • GSON

 


 

 

1. ์„œ๋ฒ„? ํด๋ผ์ด์–ธํŠธ?

 

  • ์„œ๋ฒ„ (Server)
    - ๋ฐ์ดํ„ฐ๋‚˜ ๋ฆฌ์†Œ์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ์‹œ์Šคํ…œ
    - ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์„ ๊ธฐ๋‹ค๋ฆฌ๊ณ , ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด ๊ทธ์— ๋งž๋Š” ์‘๋‹ต์„ ์ „์†ก
  • ํด๋ผ์ด์–ธํŠธ (Client)
    - ์‚ฌ์šฉ์ž๋ฅผ ๋Œ€ํ‘œํ•˜์—ฌ ์„œ๋ฒ„์— ์ •๋ณด๋‚˜ ์„œ๋น„์Šค๋ฅผ ์š”์ฒญํ•˜๋Š” ์‹œ์Šคํ…œ
    - ์›น ๋ธŒ๋ผ์šฐ์ €, ๋ชจ๋ฐ”์ผ ์•ฑ, ๋ฐ์Šคํฌํ†ฑ ์•ฑ ๋“ฑ ๋‹ค์–‘ํ•œ ํ˜•ํƒœ๋กœ ์กด์žฌ

 

์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ํ†ต์‹ ํ•˜๋Š” ๋ฐฉ์‹์€ ๋‹ค์–‘ํ•˜๋ฉฐ, ์‚ฌ์šฉํ•˜๋Š” ํ”„๋กœํ† ์ฝœ / ์šฉ๋„ / ์„ฑ๋Šฅ ์š”๊ตฌ์‚ฌํ•ญ์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ๊ฒƒ์„ ์„ ํƒ
ํ†ต์‹  ๋ฐฉ์‹์œผ๋กœ๋Š” HTTP/HTTPS, WebSockets, Socket(TCP/UDP), FTP, RPC, SOAP, GraphQL, MQTT ๋“ฑ์ด ์žˆ์Œ
ํ”„๋กœํ† ์ฝœ : ํ†ต์‹  ๊ทœ์•ฝ (= ์•ฝ์†, ์ปดํ“จํ„ฐ ๋˜๋Š” ์ „์ž๊ธฐ๊ธฐ ๊ฐ„์˜ ์›ํ™œํ•œ ํ†ต์‹ ์„ ์œ„ํ•ด ์ง€ํ‚ค๊ธฐ๋กœ ์•ฝ์†ํ•œ ๊ทœ์•ฝ)

 

๐Ÿ’ก 3-Tier ์•„ํ‚คํ…์ณ

(์ถœ์ฒ˜ : Three-tier architecture overview AWS Documentation)

  • ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๊ณ„์ธต (Web Server) : ๋ฆฌ์†Œ์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์•ฑ(ํด๋ผ์ด์–ธํŠธ)
  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ณ„์ธต (WAS) : ๋ฆฌ์†Œ์Šค๋ฅผ ์ „๋‹ฌํ•ด์ฃผ๋Š” ์•ฑ(์„œ๋ฒ„)
  • ๋ฐ์ดํ„ฐ ๊ณ„์ธต (DB) : ๋ฆฌ์†Œ์Šค ์ €์žฅ ๊ณต๊ฐ„(๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค)

 


 

2. HTTP? ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ”„๋กœํ† ์ฝœ

 

[Network] HTTP ํ”„๋กœํ† ์ฝœ์— ๋Œ€ํ•˜์—ฌ

HTTP ํ”„๋กœํ† ์ฝœ HTTP(Hypertext Transfer Protocol)๋Š” ์ธํ„ฐ๋„ท์ƒ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ  ๋ฐ›๊ธฐ ์œ„ํ•œ ์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ ๋ชจ๋ธ์„ ๋”ฐ๋ฅด๋Š” ํ”„๋กœํ† ์ฝœ ์ด๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ๋ฒจ์˜ ํ”„๋กœํ† ์ฝœ๋กœ TCP/IP์œ„์—์„œ ์ž‘๋™ํ•œ๋‹ค.

velog.io

  • ์›น ๊ธฐ๋ฐ˜์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ฃผ๋กœ ์‚ฌ์šฉ
  • REST API๋‚˜ SOAP์™€ ๊ฐ™์€ ์›น ์„œ๋น„์Šค ํ†ต์‹  ๋ฐฉ์‹์—์„œ ๊ธฐ๋ฐ˜
  • ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„๊ฐ€ ์„œ๋กœ HTTP ๋ผ๋Š” ํ”„๋กœํ† ์ฝœ์„ ์ด์šฉํ•ด์„œ ์„œ๋กœ ๋Œ€ํ™”๋ฅผ ๋‚˜๋ˆ”
  • HTTP๋ฅผ ์ด์šฉํ•ด ์ฃผ๊ณ ๋ฐ›๋Š” ๋ฉ”์‹œ์ง€๋Š” "HTTP ๋ฉ”์‹œ์ง€" ๋ผ๊ณ  ๋ถ€๋ฆ„

 

 


 

3. API? Application Programming Interface

https://rapidapi.com/blog/api-glossary/api/

  • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„์—๊ฒŒ ์š”์ฒญํ•˜๋ ค๋ฉด, ์ •ํ™•ํ•œ ์ฃผ๋ฌธ ๋ฐฉ๋ฒ•์— ๋”ฐ๋ผ ์š”์ฒญํ•ด์•ผ ํ•จ
  • ์ด์ฒ˜๋Ÿผ ์„œ๋ฒ„๊ฐ€ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฆฌ์†Œ์Šค๋ฅผ ์ž˜ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œ๊ณตํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ API๋ผ๊ณ  ์นญํ•จ
    ์—ฌ๊ธฐ์„œ ์ธํ„ฐํŽ˜์ด์Šค์˜ ๋œป์€ '์˜์‚ฌ์†Œํ†ต์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ๋งŒ๋“ค์–ด์ง„ ์ ‘์ ', ์ฆ‰ ๊ทœ๊ฒฉ/๋งค๋‰ด์–ผ ๋Š๋‚Œ

 

 


 

4. REST API? Representational State Transfer

 

API, REST API๋ž€?

์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์šด์˜ ์ฒด์ œ๋‚˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“  ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋œป ํ•จ. ์ฆ‰, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(์‘์šฉ ํ”„๋กœ๊ทธ๋žจ)์„ ํ”„๋กœ๊ทธ๋ž˜๋ฐํ•˜๋Š”๋ฐ ์“ฐ์ด๋Š”

velog.io

https://mannhowie.com/rest-api

  • API์—๋„ ์ฒด๊ณ„๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋Š” ๊ด€์ ์—์„œ ๋‚˜์˜จ ๋ฐฉ๋ฒ•์ด์ž, API ๊ทœ์น™๊ณผ ๋‚ด์šฉ์„ ๋” ์ง๊ด€์ ์ด๊ณ  ๊ฐ„๋‹จํ•˜๊ณ  ์‰ฝ๊ฒŒ ๋งŒ๋“  ๊ฒƒ
  • HTTP ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ ์–ด๋–ค URI์— ์–ด๋–ค Method๋ฅผ ์‚ฌ์šฉํ• ์ง€ ๊ฐœ๋ฐœ์ž ์‚ฌ์ด์—์„œ ๋„๋ฆฌ์ง€์ผœ์ง€๋Š” ์•ฝ์†
    ์ผ์ข…์˜ ๊ด€ํ–‰์ ์ธ ํ˜•์‹๊ณผ ์•ฝ์†์ด๋ฏ€๋กœ, ์•ฑ์„ ๋งŒ๋“ค๋“  ์›น์„ ๋งŒ๋“ค๋“  ์–ด๋–ค ์–ธ์–ด๋ฅผ ์จ์„œ ๊ตฌํ˜„ํ–ˆ๋“ , ๊ทธ ์•ˆ์— ์†Œํ”„ํŠธ์›จ์–ด ๊ฐ„ HTTP๋กœ ์ •๋ณด๋ฅผ ์ฃผ๊ณ  ๋ฐ›๋Š” ๋ถ€๋ถ„์ด ์žˆ๋‹ค๋ฉด ๊ทœ์น™์„ ์ค€์ˆ˜ํ•˜์—ฌ RESTfulํ•œ ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Œ
  • ์›น์˜ ์žฅ์ ์„ ์ตœ๋Œ€ํ•œ ํ™œ์šฉ
    ์›”๋“œ ์™€์ด๋“œ ์›น(WWW) : REST ์•„ํ‚คํ…์ฒ˜๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌ์„ฑ
  • REST API Method
    ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ๋•Œ HTTP URI๋กœ ์–ด๋–ค ์ž์›์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์–ป์„ ๊ฒƒ์ธ์ง€ ํ‘œ์‹œ
    - POST: Create(์ƒ์„ฑํ•˜๊ธฐ) ex. ์›น ํŽ˜์ด์ง€์— ์‚ฌ์ง„์„ ์˜ฌ๋ฆฌ๋Š” ์š”์ฒญ.
    - GET: Read(์ฝ์–ด์˜ค๊ธฐ) ex. ์›น ํŽ˜์ด์ง€์—์„œ ์‚ฌ์ง„์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์š”์ฒญ.
    - PUT(์ „์ฒด) / PATCH(์ผ๋ถ€): Update(๋ณ€๊ฒฝํ•˜๊ธฐ) ex. ์›น ํŽ˜์ด์ง€์˜ ์‚ฌ์ง„์„ ๋ฐ”๊พธ๋Š” ์š”์ฒญ.
    - DELETE: Delete(์‚ญ์ œํ•˜๊ธฐ) ex. ์›น ํŽ˜์ด์ง€์˜ ์‚ฌ์ง„์„ ์ง€์šฐ๋Š” ์š”์ฒญ.
  • RESTful API ์„œ๋ฒ„ ์‘๋‹ต: 2XX ์ฝ”๋“œ๋Š” ์„ฑ๊ณต์„ ๋‚˜ํƒ€๋‚ด๊ณ  4XX ๋ฐ 5XX ์ฝ”๋“œ๋Š” ์˜ค๋ฅ˜

 


 

5. JSON? :  JavaScript Object Notation

 

JSON

JSON์€ ์ผ๋ฐ˜์ ์œผ๋กœ ์„œ๋ฒ„์—์„œ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ผ ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ์–‘์‹์œผ๋กœ, '์ œ์ด์Šจ'์œผ๋กœ ์ฝ๋Š”๋‹ค. ํด๋ผ์ด์–ธํŠธ๊ฐ€

namu.wiki

  • ์„œ๋ฒ„์—์„œ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด๊ฑฐ๋‚˜ ์ €์žฅํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ ๊ตํ™˜ ํ˜•์‹
  • ๊ณผ๊ฑฐ ์›น ์ดˆ๊ธฐ ์‹œ์ ˆ๋ถ€ํ„ฐ ์‚ฌ์šฉ๋œ XML์€ ํ—ค๋”์™€ ํƒœ๊ทธ ๋“ฑ์˜ ์—ฌ๋Ÿฌ ์š”์†Œ๋กœ ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์ง€๊ณ , ์“ธ๋ฐ์—†์ด ์šฉ๋Ÿ‰์„ ์žก์•„๋จน๋Š”๋‹ค๋Š” ๋‹จ์ ์„ ํ•ญ์ƒ ์ง€์ ๋ฐ›์•˜๋‹ค. ์ด์— ๋Œ€์‘ํ•ด ๊ฐ„๊ฒฐํ•˜๊ณ  ํ†ต์ผ๋œ ์–‘์‹์œผ๋กœ ๊ฐ๊ด‘์„ ๋ฐ›๊ณ  ์žˆ๋Š” ๊ฒƒ์ด JSON์ด๋‹ค.
  • ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด์„œ ํ•˜๋‚˜์˜ ์•ฝ์†์œผ๋กœ JSON์ด๋ž€ ๋ฐ์ดํ„ฐํ˜•์‹์„ ์‚ฌ์šฉํ•˜์—ฌ ํ†ต์‹ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด๋œ๋‹ค.

 

{
  "์ด๋ฆ„๊ณต๊ฐ„(ํ‚ค)": "๊ฐ’",
  "๊ฐ’ ๊ตฌ๋ถ„์ž": "๊ฐ๊ฐ์˜ ๊ฐ’๋“ค์€ ',' (์ฝค๋งˆ)๋กœ ๊ตฌ๋ถ„๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.",
  "์ด์Šค์ผ€์ดํ”„": "ํ‚ค๋‚˜ ๊ฐ’์—์„œ ํฐ๋”ฐ์˜ดํ‘œ๋ฅผ ์“ฐ๊ณ  ์‹ถ์œผ๋ฉด-ํŠน์ • ๋ฌธ์ž๋ฅผ ์ด์Šค์ผ€์ดํ”„ ํ•˜๋ ค๋ฉด- \" ์ฒ˜๋Ÿผ ๋ฌธ์ž ์•ž์— ์—ญ์Šฌ๋ž˜์‹œ๋ฅผ ๋ถ™์ž…๋‹ˆ๋‹ค.",
  "์ž๋ฃŒํ˜•": "ํ‘œํ˜„ ๊ฐ€๋Šฅํ•œ ์ž๋ฃŒํ˜•์€ ๋ฌธ์ž์—ด, ์ˆซ์ž, ๋ถˆ๋ฆฌ์–ธ, ๋„, ๊ฐ์ฒด, ๋ฐฐ์—ด 6๊ฐœ์ž…๋‹ˆ๋‹ค.",
  "๋ฌธ์ž์—ด ๊ฐ’": "๋‚˜๋ฌด์œ„ํ‚ค, ์—ฌ๋Ÿฌ๋ถ„์ด ๊ฐ€๊พธ์–ด ๋‚˜๊ฐ€๋Š” ์ง€์‹์˜ ๋‚˜๋ฌด",
  "์ˆซ์ž ๊ฐ’": 19721121,
  "๋ถˆ๋ฆฌ์–ธ ๊ฐ’": true,
  "๋„ ๊ฐ’": null,
  "๊ฐ์ฒด ๊ฐ’": {
    "๊ฐ’1": 3.14159265358979323846264338,
    "๊ฐ’2": false,
    "๊ฐ’3": {
      "๊ฐ์ฒด ์•ˆ์—": "๊ฐ์ฒด๋ฅผ ๋„ฃ๋Š”๊ฒƒ๋„ ๊ฐ€๋Šฅํ•˜์ง€์š”",
      "๊ตฌ๋ถ„์ž": "๋˜ํ•œ ํ‚ค์™€ ๊ฐ’์€ ':' ๋กœ ๊ตฌ๋ถ„๋ฉ๋‹ˆ๋‹ค"
    }
  },
  "๋ฐฐ์—ด ๊ฐ’": [
    "์ด๊ฒƒ์€ ๋ฐฐ์—ด์ž…๋‹ˆ๋‹ค.",
    {
      "ํ˜„์žฌ ๊ฐ’์˜ ์ธ๋ฑ์Šค": 1,
      "์ด๋Ÿฐ ์‹์œผ๋กœ": "๋ฐฐ์—ด ์•ˆ์— ์—ฌ๋Ÿฌ ๊ฐ’์„ ๋„ฃ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."
    },
    [ "๋ฐฐ์—ด", "์•ˆ์—", "๋ฐฐ์—ด์„", "๋„ฃ๋Š”๊ฒƒ๋„", "๊ฐ€๋Šฅํ•˜์ง€์š”" ]
  ],
  "๊ฐ’์˜ ๊ฐœ์ˆ˜๊ฐ€ ์ ์„๋•Œ๋Š”": "๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•œ ์ค„๋กœ๋„ ๊ฐ์ฒด์™€ ๋ฐฐ์—ด ํ‘œํ˜„์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.",
  "ํ•œ ์ค„ ๊ฐ์ฒด": { "๊น€๋‘ํ•œ": "๋‚˜ ๊น€๋‘ํ•œ์ด๋‹ค", "์‹ฌ์˜": "๋‚ด๊ฐ€ ๊ณ ์ž๋ผ๋‹ˆ", "์˜์‚ฌ์–‘๋ฐ˜": "๋ณ‘์‹ ์„ ๋งŒ๋“ค์–ด์ฃผ๋งˆ" ,  "์ด๊ทผ": "4๋ฒˆ์€ ๊ฐœ์ธ์ฃผ์˜์•ผ" },
  "ํ•œ ์ค„ ๋ฐฐ์—ด": [ "๋‚˜๋ฌด์œ„ํ‚ค๋Š”", "๋ˆ„๊ตฌ๋‚˜", "๊ธฐ์—ฌํ• ", "์ˆ˜", "์žˆ๋Š”", "์œ„ํ‚ค์ž…๋‹ˆ๋‹ค." ]
}
  • JSON๋ฐ์ดํ„ฐ๋Š” ํ•˜๋‚˜์˜ NAME๊ณผ VALUE๋กœ ์ด๋ฃจ์–ด์ง ("๋ฐ์ดํ„ฐ์ด๋ฆ„" : ๊ฐ’)
    ๋ฐ์ดํ„ฐ์ด๋ฆ„(NAME) : Stringํƒ€์ž…
    ๊ฐ’(value) : ์ˆซ์ž, ๋ฌธ์ž์—ด, ๋ถˆ๋ฆฌ์–ธ(boolean), ๊ฐ์ฒด, ๋ฐฐ์—ด(array), NULL๊ฐ’ ๋“ฑ
  • JSON๋ฐฐ์—ด์€ ์ค‘๊ด„ํ˜ธ{}๊ฐ€ ์•„๋‹Œ ๋Œ€๊ด„ํ˜ธ[]๋กœ ๋‘˜๋Ÿฌ์Œ“์•„ ํ‘œํ˜„

 

 


 

6. GSON?

  • Google์—์„œ ์ œ๊ณตํ•˜๋Š” ์˜คํ”ˆ์†Œ์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ, Java์™€ Kotlin์—์„œ ์ฃผ๋กœ ์‚ฌ์šฉ
  • ๊ฐœ๋ฐœํ•˜๋‹ค ๋ณด๋ฉด, ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ  ๋ฐ›์„ ๋•Œ ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ๋ฐ์ดํ„ฐ ํฌ๋งท์ธ JSON ๋ฐ์ดํ„ฐ๋ฅผ ์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์˜ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•ด์•ผ ํ•  ๋•Œ๊ฐ€ ์žˆ์Œ (๋ฐ˜๋Œ€๋กœ ๊ฐ์ฒด๋ฅผ JSON ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•ด์•ผ ํ•  ๋•Œ๋„ ์กด์žฌ)
    → ์ด๋Ÿฌํ•œ ์ž‘์—…์„  '์ง๋ ฌํ™”(Serialization)'์™€ '์—ญ์ง๋ ฌํ™”(Deserialization)'๋ผ๊ณ  ํ•จ
  • Gson ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์ด๋Ÿฌํ•œ ์ง๋ ฌํ™” / ์—ญ์ง๋ ฌํ™” ์ž‘์—…์„ ๋งค์šฐ ๊ฐ„๋‹จํ•˜๊ฒŒ ํ•ด์คŒ

 

val gson = Gson()
val jsonString = gson.toJson(someObject)
  • Kotlin ๊ฐ์ฒด๋ฅผ JSON์œผ๋กœ ๋ณ€ํ™˜

 

val myClassInstance: MyClass = gson.fromJson(jsonString, MyClass::class.java)
  • JSON์„ Kotlin ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜

 

data class Person(
    @SerializedName("person_name")
    val name: String
)
  • @SerializedName("์›๋ž˜ JSONํ‚ค ์ด๋ฆ„") ์–ด๋…ธํ…Œ์ด์…˜
  • Kotlin ํ•„๋“œ์™€ JSON ํ‚ค ์ด๋ฆ„์ด ๋‹ค๋ฅผ ๊ฒฝ์šฐ ๋งคํ•‘

 

์ด์™ธ์—๋„ Custom Serializer/Deserializer (ํŠน์ • ํƒ€์ž…์— ๋Œ€ํ•ด ์‚ฌ์šฉ์ž ์ง€์ • ์ง๋ ฌํ™” ๋ฐ ์—ญ์ง๋ ฌํ™” ๋กœ์ง์„ ์ •์˜)
Exclusion Strategies (ํŠน์ • ํ•„๋“œ์˜ ์ง๋ ฌํ™” ๋˜๋Š” ์—ญ์ง๋ ฌํ™”๋ฅผ ์ œ์™ธํ•˜๊ธฐ ์œ„ํ•œ ์ „๋žต ์ •์˜)๊ฐ€ ์žˆ์Œ

 

 

์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™” , Gson vs Moshi

# ์ง๋ ฌํ™”/ ์—ญ์ง๋ ฌํ™”

medium.com

๋‹ค๋งŒ 2024๋…„ ํ˜„์žฌ ์‹œ์ ์—์„œ Gson๋ณด๋‹ค Moshi ์‚ฌ์šฉ์„ ์ง€ํ–ฅํ•˜๋Š” ํŽธ
(Gson์€ ์ฝ”ํ‹€๋ฆฐ์˜ ๋„์„ธ์ดํ”„ํ‹ฐ๋ฅผ ์ค€์ˆ˜ํ•˜์ง€ ์•Š์œผ๋ฉฐ, ์ฝ”ํ‹€๋ฆฐ์˜ ๋””ํดํŠธ ๋ฌธ๋ฒ•์„ ๋ฌด์‹œํ•ด๋ฒ„๋ฆผ
๋ฐ˜๋ฉด Moshi๋Š” Kotlin๋„ ํฌํ•จ๋œ ์ปจ๋ฒ„ํ„ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ, ๋„ ์„ธ์ดํ”„ํ‹ฐ ๊ฐ™์€ ์žฅ์ ์„ ์‚ด๋ฆด ์ˆ˜ ์žˆ๊ณ  ์—…๋ฐ์ดํŠธ ํ™œ๋ฐœ)

 

 


 

7. Retrofit?

 

Retrofit์ด๋ž€? (์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ์•Œ์•„์•ผ ํ•  ๊ฒƒ๋“ค)

1. Retrofit 1-1. Retrofit ์ด๋ž€? 1-2. TMI 2. ๊ธฐ๋ณธ ๊ฐœ๋… 2-1. Request URL 2-2. JSON๊ณผ HTML 2-3. GET๊ณผ POST 2-4. parameter 3. ์‚ฌ์šฉ ์ „ ์„ธํŒ… 3-1. gradle 3-2. AndroidManifest.xml 3-3. data class ์ƒ์„ฑ 1. Retrofit 1-1. Retrofit ์ด๋ž€? Retrofit๋Š”

todaycode.tistory.com

 

  • ์•ˆ๋“œ๋กœ์ด๋“œ ๋ฐ ์ž๋ฐ”๋ฅผ ์œ„ํ•œ ํƒ€์ž… ์„ธ์ดํ”„ํ•œ HTTP ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • ์•ˆ๋“œ๋กœ์ด๋“œ ๊ฐœ๋ฐœ ์‹œ ์„œ๋ฒ„ํ†ต์‹ ์— ์‚ฌ์šฉ๋˜๋Š” HTTP API๋ฅผ ์ž๋ฐ”, ์ฝ”ํ‹€๋ฆฐ์˜ ์ธํ„ฐํŽ˜์ด์Šค ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•ด API๋ฅผ ์‰ฝ๊ฒŒ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • ์žฅ์ 
    1. ์ฝ”๋“œ์˜ ๊ฐ„๊ฒฐ์„ฑ
        - ๋ณต์žกํ•œ HTTP API ์š”์ฒญ์„ ์‰ฝ๊ณ  ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๋งŒ๋“ฆ
        - ๊ฐ„๋‹จํ•œ ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด ์š”์ฒญ ๋ฉ”์„œ๋“œ์™€ URL์„ ์ •์˜ ๊ฐ€๋Šฅ
    2. ์•ˆ์ •์„ฑ๊ณผ ํ™•์žฅ์„ฑ
        - ๋‚ด๋ถ€์ ์œผ๋กœ OkHttp ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ†ต์‹ , ์ด๋ฅผ ํ†ตํ•ด ์•ˆ์ •์ ์ธ ํ†ต์‹ ์ด ๊ฐ€๋Šฅ
        - ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ/์‘๋‹ต ํ”„๋กœ์„ธ์Šค๋ฅผ ํ™•์žฅํ•˜๊ฑฐ๋‚˜ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Œ
    3. ๋‹ค์–‘ํ•œ ํ”Œ๋Ÿฌ๊ทธ์ธ๊ณผ ์ปจ๋ฒ„ํ„ฐ ์ง€์›
        - ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ ํ˜•์‹(JSON, XML ๋“ฑ)์— ๋Œ€ํ•ด ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ์ปจ๋ฒ„ํ„ฐ๋ฅผ ์ œ๊ณต
        - RxJava, Coroutines์™€ ๊ฐ™์€ ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ์—ฐ๋™ ๊ฐ€๋Šฅ

 

์žฅ์ 

1. ์ฝ”๋“œ์˜ ๊ฐ„๊ฒฐ์„ฑ
    - ๋ณต์žกํ•œ HTTP API ์š”์ฒญ์„ ์‰ฝ๊ณ  ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Œ
    - ๊ฐ„๋‹จํ•œ ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด ์š”์ฒญ ๋ฉ”์„œ๋“œ์™€ URL์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Œ
2. ์•ˆ์ •์„ฑ๊ณผ ํ™•์žฅ์„ฑ
    - ๋‚ด๋ถ€์ ์œผ๋กœ OkHttp ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ†ต์‹ , ์ด๋ฅผ ํ†ตํ•ด ์•ˆ์ •์ ์ธ ํ†ต์‹ ์ด ๊ฐ€๋Šฅ
    - ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ/์‘๋‹ต ํ”„๋กœ์„ธ์Šค๋ฅผ ํ™•์žฅํ•˜๊ฑฐ๋‚˜ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Œ
3. ๋‹ค์–‘ํ•œ ํ”Œ๋Ÿฌ๊ทธ์ธ๊ณผ ์ปจ๋ฒ„ํ„ฐ ์ง€์›
    - ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ ํ˜•์‹(JSON, XML ๋“ฑ)์— ๋Œ€ํ•ด ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ์ปจ๋ฒ„ํ„ฐ๋ฅผ ์ œ๊ณต
    - RxJava, Coroutines์™€ ๊ฐ™์€ ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ์—ฐ๋™ ๊ฐ€๋Šฅ

 

 


 

1. ์•ฑ ์ˆ˜์ค€ build.gradle์— Retrofit ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”๊ฐ€ํ•˜๊ธฐ

// build.gradle (Module: app)
dependencies {
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")
}

 

2. API ์ธํ„ฐํŽ˜์ด์Šค ์ •์˜ํ•˜๊ธฐ

interface ApiService {
    @GET("users/{id}")
    fun getUser(@Path("id") id: Int): Call<User> // ์—ฌ๊ธฐ์„œ User๋Š” ์„œ๋ฒ„ ์‘๋‹ต์œผ๋กœ ๋ฐ›์•„์˜ฌ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ ํด๋ž˜์Šค
}

 

3. Retrofit ์ธ์Šคํ„ด์Šค ์ƒ์„ฑํ•˜๊ธฐ

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .build()

val apiService = retrofit.create(ApiService::class.java)
// ์ด์ œ apiService ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ์ •์˜๋œ API ์š”์ฒญ์„ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

 

4-1. ์‘๋‹ต ์ฒ˜๋ฆฌํ•˜๊ธฐ (๋™๊ธฐ์‹ vs ๋น„๋™๊ธฐ์‹ ์š”์ฒญ)

// ๋™๊ธฐ์‹ ์š”์ฒญ: ํ˜„์žฌ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰๋˜๋ฉฐ, ์‘๋‹ต์ด ์˜ฌ ๋•Œ๊นŒ์ง€ ๋‹ค์Œ ์ฝ”๋“œ์˜ ์‹คํ–‰์ด ์ค‘๋‹จ
val response: Response<User> = apiService.getUser(id).execute()



// ๋น„๋™๊ธฐ์‹ ์š”์ฒญ: ์ฝœ๋ฐฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰๋˜๋ฉฐ, ์‘๋‹ต์ด ์˜ค๋ฉด ํ•ด๋‹น ์ฝœ๋ฐฑ์ด ํ˜ธ์ถœ
apiService.getUser(id).enqueue(object: Callback<User> {
    override fun onResponse(call: Call<User>, response: Response<User>) {
        // ์ฒ˜๋ฆฌ
    }

    override fun onFailure(call: Call<User>, t: Throwable) {
        // ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ
    }
})

 

4-2. ์‘๋‹ต ์ฒ˜๋ฆฌํ•˜๊ธฐ (์‘๋‹ต ๊ฐ์ฒด ์‚ฌ์šฉํ•˜๊ธฐ)

// Response ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด HTTP ์‘๋‹ต์˜ ์—ฌ๋Ÿฌ ์ •๋ณด์— ์ ‘๊ทผ ๊ฐ€๋Šฅ
if (response.isSuccessful) { 
    val user: User? = response.body()
} else {
    // ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ
    val error: String = response.errorBody()?.string() ?: "Unknown error"
}

 

4-3. ์‘๋‹ต ์ฒ˜๋ฆฌํ•˜๊ธฐ (์˜ค๋ฅ˜ ์ฒ˜๋ฆฌํ•˜๊ธฐ)

// Retrofit์˜ onFailure ์ฝœ๋ฐฑ์€ ๋„คํŠธ์›Œํฌ ์˜ค๋ฅ˜๋‚˜ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ์˜ค๋ฅ˜ ๋“ฑ์—์„œ ํ˜ธ์ถœ

override fun onFailure(call: Call<User>, t: Throwable) {
    // ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
    Log.e("API_ERROR", t.message ?: "Unknown error")
}

 

 


 

+ (์ถ”๊ฐ€) ๋ณด๋‹ค ์ž์„ธํ•œ Retrofit ์‚ฌ์šฉ๋ฒ•

 

ํ™˜๊ฒฝ ์„ธํŒ…

Gradle ์„ค์ • (app ์ˆ˜์ค€ build.gradle.kts ์ข…์†์„ฑ ์ถ”๊ฐ€)

implementation(libs.retrofit)
 		
implementation(platform(libs.okHttpBom)) // Bom - ํ•˜๋‚˜์˜ ๋ญ‰์น˜ (๋นŒ๋ฆฌ์–ธ์Šค ์˜ค๋ธŒ ๋จธํŠธ๋ฆฌ์–ผ?)
implementation(libs.logging.interceptor) // ๋„คํŠธ์›Œํฌ ์†ก์ˆ˜์‹ ์ด ์–ด๋–ป๊ฒŒ ์ด๋ฃจ์–ด์ง€๋Š”์ง€ ๋ณด๊ธฐ์œ„ํ•จ

// moshi
implementation(libs.moshi.kotlin) // ์ฝ”ํ‹€๋ฆฐ์—์„œ ์“ฐ๋Š” moshi
implementation(libs.converter.moshi)
implementation(libs.moshi.adapters)

 

libs.versions.toml (๋ฒ„์ „ ๋ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”๊ฐ€)

[versions]
retrofitVersion = "2.11.0"
okHttpBomVersion = "4.12.0"
moshiVersion = "1.15.1"
moshiAdapters = "1.15.1"

[libraries]
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofitVersion" }
okHttpBom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okHttpBomVersion" }
logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okHttpBomVersion" }
converter-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofitVersion" }
moshi-kotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshiVersion" }
moshi-adapters = { module = "com.squareup.moshi:moshi-adapters", version.ref = "moshiAdapters" }

 

  • ๊ธฐ์กด ๋ฐฉ์‹๊ณผ ๋น„๊ตํ•˜์—ฌ ๋ฒ„์ „๊ด€๋ฆฌ๋ฅผ ๋ฒ„์ „ ์นดํƒˆ๋กœ๊ทธ ๋ฐฉ์‹์œผ๋กœ ํ•˜๋Š” ๊ฒƒ์˜ ์žฅ์ ?
    : ๋ฉ€ํ‹ฐ ๋ชจ๋“ˆ ํ”„๋กœ์ ํŠธ์˜ ๊ฒฝ์šฐ ๋ชจ๋“ˆ๋งˆ๋‹ค gradle์ด ์„ค์ •๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ๊ฐ ์ž‘์„ฑํ•˜๊ธฐ์— ๋ฒˆ๊ฑฐ๋กญ๊ณ  ๋ฌธ์ œ ์ƒ๊น€

 

์‚ฌ์šฉํ•˜๊ณ ์ž ํ•˜๋Š” API์˜ Key๋ฅผ local.properties์— ์ถ”๊ฐ€ (ํ‚ค๋Š” ์ž์‹ ์ด ๋ฐœ๊ธ‰๋ฐ›์€ ํ‚ค)

REST_API_KEY=abcdefghijklmnopqrstuvwxyz
KAKAO_BASE_URL = https://dapi.kakao.com/
  • ๋ฒ ์ด์Šค url: dapi.kakao.com
  • ์—”๋“œ ํฌ์ธํŠธ: ๊ทธ ์ดํ›„ url 
    ์˜ˆ: @POST("v2/search/app") ์ฒ˜๋Ÿผ ๋‚˜์™€์žˆ๋Š” ๋ฉ”์„œ๋“œ์™€ ๊ทธ์— ๋งž๊ฒŒ ์—”๋“œํฌ์ธํŠธ ๋„ฃ์–ด์คŒ

 

build.gradle.kts์— ์•„๋ž˜์™€ ๊ฐ™์ด ๊ฐ’ ์ถ”๊ฐ€

buildTypes {
        val gradleLocalProperties = gradleLocalProperties(
            projectRootDir = rootDir,
            providers = providers
        )
        val apiKey = gradleLocalProperties.getProperty("REST_API_KEY")
        val baseUrl = gradleLocalProperties.getProperty("KAKAO_BASE_URL")
        
        debug {
            buildConfigField("String", "REST_API_KEY", "\"$apiKey\"")
            buildConfigField("String", "KAKAO_BASE_URL", "\"$baseUrl\"")
            isDebuggable = true
            isMinifyEnabled = false
        }
        
        release {
            buildConfigField("String", "REST_API_KEY", "\"$apiKey\"")
            buildConfigField("String", "KAKAO_BASE_URL", "\"$baseUrl\"")
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }

buildFeatures {
    // ...
    buildConfig = true
}
  • ๋ณดํ†ต release๋งŒ ์žˆ๋Š”๋ฐ, debug ์ถ”๊ฐ€
  • buildConfig = true๋กœ ์ง€์ •ํ•˜๋ฉด key๊ฐ’์„ ์–ด๋””์„œ๋‚˜(์ฝ”๋“œ๋‹จ)์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
    ex. BuildConfig.REST_API_KEY ์ฒ˜๋Ÿผ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

 

 

์ฝ”๋“œ ์„ธํŒ…

์ฝ”๋“œ ์„ธํŒ…

<uses-permission android:name="android.permission.INTERNET"/>
  • ๋„คํŠธ์›Œํฌ ์ž‘์—…์€ Presentation, Domain, Data Layer ์ค‘์—์„œ Data Layer์— ์†ํ•จ 
  • ๋„คํŠธ์›Œํฌ ์ž‘์—…์ด๋ฏ€๋กœ ํ•ญ์ƒ ์•„๋ž˜ ๊ถŒํ•œ์„ AndroidManifest.xml ์— ๋„ฃ์–ด์ฃผ์ž!

 

 

NetworkModule ๋งŒ๋“ค๊ธฐ

import com.spartabasic.www.data.network.api.KakaoApiService
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import java.util.Date
import java.util.concurrent.TimeUnit


// ์•„๋ž˜ ๊ฑฐ ๋ณต๋ถ™ํ•ด์„œ ์–ด๋””์„œ๋‚˜ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
object NetworkModule {

    val retrofit by lazy { // ์–ด๋Š ์œ„์น˜์—์„œ๋“  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ „์—ญ๋ณ€์ˆ˜๋กœ ์„ค์ •
        provideKakaoApiService()
    }

    private fun provideMoshi(): Moshi { // ๋ชจ์‹œ ๋นŒ๋“œ
        return Moshi.Builder()
            .add(KotlinJsonAdapterFactory())
            .add(Date::class.java, Rfc3339DateJsonAdapter()) // ๋‚ ์งœ ํ˜•์‹ ๋ณ€ํ™˜ : (2017-) ์–ด์ฉŒ๊ตฌ๋ฅผ ์ž๋ฐ”๋กœ ๋ณ€ํ™˜
            .build()
    }

    private fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor { // ์ธํ„ฐ์…‰ํ„ฐ
        val interceptor = HttpLoggingInterceptor()
        if (BuildConfig.DEBUG) {
            interceptor.level = HttpLoggingInterceptor.Level.BODY
        } else { // ๋””๋ฒ„๊ทธ๊ฐ€ ์•„๋‹ ๋•Œ๋Š” ๋กœ๊ทธ๊ฐ€ ์ฐํžˆ์ง€ ์•Š๋„๋ก ์„ค์ •. ์ถœ์‹œํ•  ๋• ๋กœ๊ทธ ๋ชจ๋‘ ์‚ญ์ œํ•ด์•ผ ํ•จ!
            interceptor.level = HttpLoggingInterceptor.Level.NONE
        }
        return interceptor
    }

    private fun provideOkHttpClient(
        httpLoggingInterceptor: HttpLoggingInterceptor = provideHttpLoggingInterceptor(),
    ): OkHttpClient {
        return OkHttpClient
            .Builder() // ๋นŒ๋” ๋””์ž์ธํŒจํ„ด - ์ž๋ฐ” ๋ฐฉ์‹ (์›๋ž˜ ์ฝ”ํ‹€๋ฆฐ์—์„  ํ•„์š” ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ž๋ฐ” ๋ฐฉ์‹์ž„์„ ์ถ”๋ก )
            .connectTimeout(timeout = 20, unit = TimeUnit.SECONDS)
            .readTimeout(timeout = 20, unit = TimeUnit.SECONDS)
            .writeTimeout(timeout = 20, unit = TimeUnit.SECONDS)
            .addInterceptor(interceptor = httpLoggingInterceptor) // ์ธํ„ฐ์…‰ํ„ฐ : ๋„คํŠธ์›Œํฌ ์†ก์ˆ˜์‹ ์ด ๋กœ๊น…๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ถ”๊ฐ€
            .build()
    }

    private fun provideRetrofitModule(moshi: Moshi = provideMoshi()): Retrofit.Builder {
        return Retrofit.Builder()
            .addConverterFactory(MoshiConverterFactory.create(moshi)) // ์ง๋ ฌํ™”์™€ ์—ญ์ง๋ ฌํ™”. Gson์ธ ๊ฒฝ์šฐ GsonConverterFactory
    }


// ์นด์นด์˜ค api ์ถ”๊ฐ€
    private fun provideKakaoApiService(
        client: OkHttpClient = provideOkHttpClient(),
        retrofit: Retrofit.Builder = provideRetrofitModule()
    ): KakaoApiService {
        return retrofit.baseUrl(BuildConfig.KAKAO_BASE_URL) // ๋ฒ ์ด์Šค url ์„ค์ •
            .client(client) // ํด๋ผ์ด์–ธํŠธ ์„ค์ •
            .build()
            .create(KakaoApiService::class.java) // ์–ด๋–ค api ์„œ๋น„์Šค๋ฅผ ๋ฐ”๋ผ๋ณด๊ฒŒ ํ•  ๊ฑฐ๋ƒ
    }
}
  • retrofit์„ ์„ค์ •ํ•˜๋Š” ํŒŒ์ผ์ด ํ•„์š”. ๋ฒ ์ด์Šค url๊ณผ ํด๋ผ์ด์–ธํŠธ ์„ค์ • ๋“ฑ

 

 

Response ํด๋ž˜์Šค ๋ฐ Dto ๋งŒ๋“ค๊ธฐ

1. KakaoApiResponse

import com.spartabasic.www.data.network.model.MetaDto

data class KakaoApiResponse<out T>( // T : ์ œ๋„ค๋ฆญ ํƒ€์ž…. ์ด๋ฏธ์ง€api๋ฅผ ์„ค์ •ํ•˜๋ฉด ์ด๋ฏธ์ง€ ๋ฆฌ์Šคํฐ์Šค, ๋น„๋””์˜คapi๋ฅผ ์„ค์ •ํ•˜๋ฉด ๋น„๋””์˜ค ๋ฆฌ์Šคํฐ์Šค
// out T๋Š” Tํƒ€์ž…์ด๋ฉด ๋‹ค ๋œ๋‹ค๋Š” ๊ฒƒ์ธ๋ฐ out๊ณผ in ๊ตฌ๋ถ„ํ•˜๊ธฐ. ์ง‘์–ด๋„ฃ๋Š” ๊ฒฝ์šฐ ํ•จ์ˆ˜๋ช…(T)<>๋ฉด in
    val meta: MetaDto,
    val documents: T,
)

 

2. ImageSearchDto

import com.squareup.moshi.Json
import java.util.Date

data class ImageSearchDto(
    val collection: String,
    @Json(name = "thumbnail_url")
    val thumbnailUrl: String,
    @Json(name = "image_url")
    val imageUrl: String,
    val width: Int,
    val height: Int,
    @Json(name = "display_sitename")
    val displaySiteName: String,
    @Json(name = "doc_url")
    val docUrl: String,
    @Json(name = "datetime")
    val dateTime: Date
)
  • DTO๋ž€?
    = Data transfer object : ๋ฐ์ดํ„ฐ๋ฅผ ์˜ฎ๊ฒจ์ฃผ๋Š”(์†ก์ˆ˜์‹ ์„ ๋‹ด๋‹นํ•˜๋Š”) ๊ฐ์ฒด, DT์— ํ•„์š”ํ•œ ๊ฐ์ฒด
    ๊ฐ€๋” DTO๊ฐ€ ์•„๋‹ˆ๋ผ Entity๋ผ๊ณ  ์ ๋Š” ์‚ฌ๋žŒ๋„ ์žˆ์Œ. ๊ทผ๋ฐ ์ด๊ฑด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ž‘ ํ—ท๊ฐˆ๋ ค์„œ ๋„คํŠธ์›Œํฌ ์ž‘์—…์€ DTO๊ฐ€ ๋‚˜์Œ
    cf. DT (Data Transfer) ๋ฅผ ํ•˜๋Š” ๊ฒƒ์€ Retrofit
  • @Json ์–ด๋…ธํ…Œ์ด์…˜ : moshi์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ด๋ฆ„ ๋ณ€๊ฒฝ ๋ฐฉ์‹ (gson์—์„  @Serializedname)

 

3. VideoSearchDto

import com.squareup.moshi.Json
import java.util.Date

data class VideoSearchDto(
    val title: String,
    val url: String,
    @Json(name = "datetime")
    val dateTime: Date,
    @Json(name = "play_time")
    val playTime: Int,
    val thumbnail: String,
    val author: String
)

 

 

Api Service ์ธํ„ฐํŽ˜์ด์Šค ๋งŒ๋“ค๊ธฐ

import com.spartabasic.www.data.network.model.ImageSearchDto
import com.spartabasic.www.data.network.model.VideoSearchDto
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Query

interface KakaoApiService { // Retrofit ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ์‚ฌ์šฉ๋ฒ•์— ๋”ฐ๋ผ interface๋กœ ์‚ฌ์šฉ, ๋‚˜๋จธ์ง„ Retrofit์ด ๋‹ค ํ•ด์ค˜!
    @GET("v2/search/image")
    suspend fun getSearchedImages(
        @Header("Authorization") header: String = "KakaoAK ${BuildConfig.REST_API_KEY}",
        @Query("query") query: String,
        @Query("sort") sort: String,
        @Query("page") page: Int,
        @Query("size") size: Int,
    ): Response<KakaoApiResponse<List<ImageSearchDto>>> // Response๋Š” ์„ฑ๊ณตํ–ˆ๋Š”์ง€ ์•„๋‹Œ์ง€??

    @GET("v2/search/vclip")
    suspend fun getSearchedVideos(
        @Header("Authorization") header: String = "KakaoAK ${BuildConfig.REST_API_KEY}",
        @Query("query") query: String,
        @Query("sort") sort: String,
        @Query("page") page: Int,
        @Query("size") size: Int,
    ): Response<KakaoApiResponse<List<VideoSearchDto>>>
}
  • ๋กœ๊ทธ์—์„œ response๊ฐ’ ๋ณด๋ฉด 2xx๋ฉด ๋ชจ๋‘ ์„ฑ๊ณต, 3xx์€ ๋ฆฌ๋‹ค์ด๋ ‰์…˜, 4xx๋Š” ํด๋ผ์ด์–ธํŠธ ์—๋Ÿฌ, 5xx๋Š” ์„œ๋ฒ„ ์—๋Ÿฌ
    404๋Š” not found ์—๋Ÿฌ (์˜ˆ: ์ž…๋ ฅํ•œ ์—”๋“œ ํฌ์ธํŠธ๊ฐ€ ์‹ค์ œ์™€ ๋‹ค๋ฅธ ๊ฒฝ์šฐ)
    400์€ Bad Request ์—๋Ÿฌ๋กœ ๊ฐ’์„ ๋ญ”๊ฐ€ ์ž˜๋ชป ๋ณด๋ƒˆ์„ ๋•Œ (์˜ˆ: ํ•œ ๋ฒˆ์— 10๋งŒ๊ฐœ ์š”์ฒญํ•˜๋Š” ๊ฒฝ์šฐ)
    401์€ Unauthorized ์—๋Ÿฌ๋กœ ๊ถŒํ•œ ์˜ค๋ฅ˜ (์˜ˆ: ํ‚ค ๊ฐ’์„ ์•ˆ ๋„ฃ์—ˆ์„ ๋•Œ)
  • ๋กœ๊ทธ ๋ณด๋ฉด ๊ฐ€์ ธ์˜จ ๊ฐ’๋“ค๋„ ๋‹ค ๋ณผ ์ˆ˜ ์žˆ์Œ

 

 

์ผ๋ฐ˜์ ์ธ ์‚ฌ์šฉ๋ฒ•

์ผ๋ฐ˜์ ์ธ ์‚ฌ์šฉ๋ฒ• (์˜ˆ์‹œ: SearchFragment.kt)

1. Retrofit์˜ Call ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•

private fun fetchVideos() {
        NetworkModule.retrofit.getSearchedVideos(
            query = "test",
            page = 1,
            size = 10, 
            sort = "recency"
        ).enqueue(object : Callback<KakaoApiResponse<List<VideoSearchDto>>> { 
		// ์ฝœ๋ฐฑ : ๊ณ„์† ๊ธฐ๋‹ค๋ฆด ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ, ์‹คํ–‰์ด ์„ฑ๊ณต์ ์œผ๋กœ ๋๋‚˜๋ฉด ์•„๋ž˜ ๋ธ”๋Ÿญ ์‹คํ–‰
            override fun onResponse(
                call: Call<KakaoApiResponse<List<VideoSearchDto>>>,
                response: Response<KakaoApiResponse<List<VideoSearchDto>>>
            ) {
                if (response.isSuccessful) {
                    Log.i(TAG, "Successful! ${response.code()}")
                    val body = response.body()
                    if (body != null) {
                        val meta = body.meta
                        val documents = body.documents
                        binding.tvMeta.text = "$meta"
                        binding.tvDocuments.text = "$documents"

                    } else {
                        Log.e(TAG, "Something went wrong!")
                    }
                }
            }

            override fun onFailure( // ์‹คํŒจํ–ˆ์„ ๋•Œ
                p0: Call<KakaoApiResponse<List<VideoSearchDto>>>,
                p1: Throwable
            ) {
                Log.e(TAG, "Error: ${p1.message}")
            }

        })
    }
  • call์€ retrofit์—์„œ ์ œ๊ณตํ•ด์ฃผ๋Š” ์ธํ„ฐํŽ˜์ด์Šค
  • call์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๊ธฐ ๋•Œ๋ฌธ์— ๋ฌด์กฐ๊ฑด enqueue(Retrofit์—์„œ ์ง€์›ํ•˜๋Š” ํ•จ์ˆ˜) ๋ถ€๋ถ„ ๋“ฑ์ด ํ•„์š”ํ•จ
    -> ์ฝ”๋“œ๊ฐ€ ๊ธธ์–ด์ง€๊ณ  ๋ณด์ผ๋Ÿฌ ํ”Œ๋ ˆ์ดํŠธ, ์ฝœ๋ฐฑ ์ง€์˜ฅ(callback hell) ๊ฐ€๋Šฅ์„ฑ...

 

 

2. Coroutine์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•

   private fun fetchImages() {
        lifecycleScope.launch {
            val response = NetworkModule.retrofit.getSearchedImages(
                query = "test",
                page = 1,
                size = 10,
                sort = "recency"
            )
            if (response.isSuccessful) {
                Log.i(TAG, "Successful! ${response.code()}")
                val body = response.body()
                if (body != null) {
                    val meta = body.meta
                    val documents = body.documents
                    binding.tvMeta.text = "$meta"
                    binding.tvDocuments.text = "$documents"

                } else {
                    Log.e(TAG, "Something went wrong!")
                }
            } else {
                Log.e(TAG, "Error: ${response.errorBody()}")
            }
        }
    }
  • ๋ณต์žกํ•œ ์ž‘์—…์„ ํ•˜๊ฑฐ๋‚˜, ์ˆœ์„œ๋ฅผ ๋ณด์žฅํ•ด์•ผ ๋  ๋•Œ ์‚ฌ์šฉ
  • suspend๋ผ๊ณ  ์จ ์žˆ๋Š” ํ•จ์ˆ˜๋“ค์„ ์ˆœ์ฐจ์ ์œผ๋กœ (์œ„->์•„๋ž˜) ์ง„ํ–‰ํ•ด ์คŒ. ๋‹ค๋งŒ!!!
    1) ํ•จ์ˆ˜๊ฐ€ suspend๋กœ ๋˜์–ด ์žˆ๋Š”์ง€ ํ•ญ์ƒ ํ™•์ธํ•ด์ค˜์•ผ ํ•จ
    2) ์ฒซ ๋ฒˆ์งธ suspend๊ฐ€ ์‹คํŒจํ•˜๋ฉด ๋‘ ๋ฒˆ์งธ suspend ์‹คํ–‰ ์•ˆ ๋จ (Supervisorjob ์„ค์ •ํ•˜๋ฉด ๊ดœ์ฐฎ์•„์ง)
  • ์ฝ”๋ฃจํ‹ด์˜ ๊ฒฝ์šฐ job.cancel()๋กœ ์—„์ฒญ ์‰ฝ๊ฒŒ ์ทจ์†Œ ๊ฐ€๋Šฅ -> call ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์–ด๋–ป๊ฒŒ ์ทจ์†Œํ•˜๋Š”์ง€ ์ž˜ ๋ชจ๋ฆ„

 

 

 

3. Rx๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•

  • ์ž˜์€ ๋ชจ๋ฅด์ง€๋งŒ, ๋ณด์ผ๋Ÿฌ ํ”Œ๋ ˆ์ดํŠธ ๊ฐ€๋Šฅ์„ฑ์ด ๋งŽ์Œ

 

 

 

๊ณ ๋ฏผ ์‚ฌํ•ญ

1. ๊ตณ์ด ViewModel์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š”๊ฐ€?  

  • ์ƒํ™ฉ์„ ๊ณ ๋ ค ํ–ˆ์„ ๋•Œ ๋ฐ์ดํ„ฐ ์ƒํƒœ๋ฅผ ๋ณด์™„ํ•˜๊ธฐ ์œ„ํ•จ
  • ํ™”๋ฉด์ด ์ „ํ™˜๋˜๊ฑฐ๋‚˜, Fragment ๋ฆฌํ”Œ๋ ˆ์ด์Šค ๋  ๋•Œ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด Livedata ์‚ฌ์šฉ
  • ์ƒํ™ฉ์— ๋งž๊ฒŒ ํŒ๋‹จ ํ•˜์— ์ž˜ ์“ฐ๋ฉด ๋จ
  • ๋ณดํ†ต ๊ทธ๋Ÿฐ๋ฐ ์ด๋Ÿฐ ํ™˜๊ฒฝ/์ƒํ™ฉ์ด ๋งŽ๊ธฐ ๋•Œ๋ฌธ์— ๋ทฐ๋ชจ๋ธ์„ ๋งŽ์ด ์“ฐ๋Š” ๊ฒƒ
  • ํ•œ๋‘๋ฒˆ ์“ฐ๋Š” ๊ฒŒ ์•„๋‹Œ ์ด์ƒ ๋ทฐ์— ํ‘œ์‹œ๋˜๋Š” ๋ฐ์ดํ„ฐ (๋ฐ์ดํ„ฐ ๊ฐฑ์‹  ์‹œ์  ๋“ฑ ์ž‘์—…) ์ž‘์—…์€ ๋ชจ๋‘ ๋ทฐ๋ชจ๋ธ์—์„œ ํ•  ๊ฒƒ

2. ์™œ ๋„คํŠธ์›Œํฌ ๋ชจ๋“ˆ์€ object(Singleton)์ด์–ด์•ผ ํ• ๊นŒ? 

  • ์ƒˆ๋กœ์šด ๊ฐ์ฒด๊ฐ€ ๊ณ„์† ์ƒ์„ฑ๋˜๋ฉด ์•ˆ ๋จ → ๋˜‘๊ฐ™์€ ์ž‘์—…์€ ํ•œ ๋ฒˆ๋งŒ ํ•ด์•ผ ๋„คํŠธ์›Œํฌ๊ฐ€ ์•ˆ ํ„ฐ์ง
  • ๊ฐ™์€ ๋„คํŠธ์›Œํฌ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•จ

 

 

 

 

๋ฐ˜์‘ํ˜•
 ๐Ÿ’ฌ C O M M E N T