[Android] Network Programming ์ •๋ฆฌ

Minjun Kimยท2023๋…„ 9์›” 16์ผ
1

Android

๋ชฉ๋ก ๋ณด๊ธฐ
42/47
post-thumbnail

๐Ÿ“ SeSAC์˜ 'JetPack๊ณผ Kotlin์„ ํ™œ์šฉํ•œ Android App ๊ฐœ๋ฐœ' ๊ฐ•์ขŒ๋ฅผ ์ •๋ฆฌํ•œ ๊ธ€ ์ž…๋‹ˆ๋‹ค.


๐Ÿ’ฌ ๋„คํŠธ์›Œํฌ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ฐœ์š”

โ• ํผ๋ฏธ์…˜

<uses-permission android:name="android.permission.INTERNET"/>
  • ํ•ด๋‹น ํผ๋ฏธ์…˜์ด ์žˆ์–ด์•ผ ์„œ๋ฒ„์™€ ๋„คํŠธ์›Œํ‚น์ด ๊ฐ€๋Šฅ.

๐Ÿ“Œ ํ†ต์‹  ๋ฐฉ๋ฒ•

์„œ๋ฒ„์™€ http ํ†ต์‹ ์„ ํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์—๋Š” ์—ฌ๋Ÿฌ ๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค.

๐Ÿ““ HttpURLConnection

  • java SE ์—์„œ ์ œ๊ณต๋˜๋Š” API.

  • ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, ์ดˆ๊ธฐ ๋ฒ„์ „๋ถ€ํ„ฐ ์ œ๊ณต.

์‚ฌ์šฉํ•  ์ˆ˜๋Š” ์žˆ์ง€๋งŒ ์ฝ”๋“œ ์ž‘์„ฑ ์‹œ ํ”„๋กœ๊ทธ๋žจ ์ฝ”๋“œ๊ฐ€ ๊ธธ์–ด์ง€๊ฑฐ๋‚˜ ๋ณต์žก, ์ค‘๋ณต๋˜๋Š” ์ธก๋ฉด์ด ์žˆ๋‹ค.

๊ทธ๋ž˜์„œ ์‚ฌ์šฉํ•˜๋‹ค๋ฉด HttpURLConnection ๋ฅผ ์ถ”์ƒํ™” ์‹œ์ผœ ๋ณ„๋„์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ์„œ ๊ฐœ๋ฐœ.

๐Ÿ““ HttpClient

  • apache http ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ.

  • ์•ˆ๋“œ๋กœ์ด๋“œ ์ดˆ๊ธฐ HttpURLConnection ๋ถ€๋ถ„์— ๋‚ด๋ถ€์ ์ธ ๋ฌธ์ œ๊ฐ€ ์žˆ์–ด ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ํฌํ•จ๋˜์–ด ๋งŽ์ด ์‚ฌ์šฉํ•˜๋˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ.

  • Android 6.0 ์—์„œ๋Š” Apache HTTP ํด๋ผ์ด์–ธํŠธ์— ๋Œ€ํ•œ ์ง€์›์ด ์ œ๊ฑฐ.

  • Android 9 ๋ถ€ํ„ฐ๋Š” ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ bootclasspath ์—์„œ ์ œ๊ฑฐ๋˜๊ณ  ๊ธฐ๋ณธ์ ์œผ๋กœ ์•ฑ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.

  • Android 9 ์ด์ƒ์„ ๋Œ€์ƒ์ด๋กœ ํ•˜๋Š” ์•ฑ์ด Apache HTTP ํด๋ผ์ด์–ธํŠธ๋ฅผ ๊ณ„์† ์‚ฌ์šฉํ•˜๋ ค๋ฉด AndroidManifest.xml ์— ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค.

์ดˆ๊ธฐ์— HttpURLConnection ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋ฌธ์ œ๊ฐ€ ์žˆ์–ด Apache ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž„์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ์•ˆ๋“œ๋กœ์ด๋“œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ํฌํ•จ ๋์—ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‹ค HttpURLConnection ์˜ ๋‚ด๋ถ€์  ๋ฌธ์ œ ํ•ด๊ฒฐ ๋ฐ ๋งŽ์€ http ๋„คํŠธ์›Œํ‚น ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๋“ฑ์žฅ์œผ๋กœ ์ธ๊ธฐ๊ฐ€ ์‚ฌ๊ทธ๋ผ๋“ค์—ˆ๋‹ค.

๊ทธ๋ž˜์„œ ํ˜„์žฌ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์•ฑ์—์„œ ์ด์šฉํ•  ์ˆ˜ ์—†๋Š” ์ƒํƒœ์ด๋‹ค. ๋งŒ์•ฝ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด Manifest ์— <uses-library> ๋กœ ์„ ์–ธํ•ด์•ผ ํ•œ๋‹ค. ๊ทธ ์™ธ์—๋„ ์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ๋„คํŠธ์›Œํ‚น ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ๋‚ด๋ถ€์ ์œผ๋กœ HttpClient ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์„ ์–ธํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ข…์ข… ์žˆ๋‹ค.

๐Ÿ““ volley

  • 2013๋…„ Google I/O ์—์„œ ๋ฐœํ‘œํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค.

  • http ํ†ต์‹ ์„ ์œ„ํ•œ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค.

  • 1.0.0 ๋ฒ„์ „์—์„œ๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ apache http ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ–ˆ์œผ๋ฉฐ 1.1.1 ๋ฒ„์ „์—์„œ๋Š” apache http ์ข…์†์„ฑ์ด ์ œ๊ฑฐ๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค.

  • dependency ๋ฅผ ์ถ”๊ฐ€ํ•ด ์ฃผ์–ด์•ผ ํ•œ๋‹ค.

์ด์šฉ ๋น„์œจ์ด ๋†’์ง€๋Š” ์•Š์ง€๋งŒ ๊ฐ€๋” volley ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ๊ฐœ๋ฐœํ•˜๋Š” ๊ฐœ๋ฐœ์ž๋„ ์žˆ๋‹ค.

๐Ÿ““ okHttp

  • square ๋ผ๋Š” ํšŒ์‚ฌ์—์„œ ๋งŒ๋“  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค.

  • ์•„์ฃผ ์œ ๋ช…ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ.

๐Ÿ““ retrofit

  • ์ด๊ฒƒ๋„ square ์—์„œ ๋งŒ๋“ค์—ˆ๋‹ค.

  • 2013๋…„ 1.0 ๋ฐœํ‘œ ๋˜์—ˆ๊ณ  2016๋…„ 2.0 ๋ฐœํ‘œ๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค.

  • ๋‚ด๋ถ€์ ์œผ๋กœ okHttp ๋ฅผ ์ด์šฉํ•œ๋‹ค.

ํ˜„์žฌ ๊ฐ€์žฅ ์œ ๋ช…ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ. ํ˜„ ์‹œ์  ๊ฐ€์žฅ ์ด์šฉ ๋น„์œจ์ด ๋†’๊ณ  ์œ ๋ช…ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค.
2.0 ๋ฒ„์ „์ด ๋‚˜์˜ค๋ฉด์„œ ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„์ด ์žˆ๊ณ  retrofit ์ด๋ผ๊ณ  ํ•˜๋ฉด ํ”ํžˆ 2.0 ๋ฒ„์ „์„ ์ง€์นญํ•œ๋‹ค.

๐Ÿ”‘ ๋ณด์•ˆ ์ •์ฑ…์˜ ๋ณ€๊ฒฝ

โ• useCleartextTraffic

<application
	android:usesCleartextTraffic="true">
  • api level 28 ๋ถ€ํ„ฐ ๋„คํŠธ์›Œํฌ ๋ณด์•ˆ ์ •์ฑ…๋ณ€๊ฒฝ.

  • https ๋Š” ๋ฌธ์ œ ์—†์œผ๋ฉฐ http ์ธ ๊ฒฝ์šฐ๋Š” ์„ค์ •์ด ํ•„์š”

  • useCleartextTraffic ์„ค์ •

  • ์ด์ „์—๋„ ์žˆ์—ˆ๋˜ ์†์„ฑ. api level 28 ๋ถ€ํ„ฐ๋Š” ๊ธฐ๋ณธ๊ฐ’์ด false ๋กœ ๋ณ€๊ฒฝ. ๋ช…์‹œ์ ์œผ๋กœ true ๋กœ ์„ค์ •ํ•ด ์ฃผ์–ด์•ผ ํ•œ๋‹ค.

โ• network_security_config

<network-security-config>
	<domain-config cleartextTrafficPermitted="true">
    	<domain includeSubdomains="true">192.168.1.2</domain>
	</domain-config>
</network-security-config>
<application
	android:networkSecurityConfig="@xml/network_security_config">
  • network_security_config ์ด์šฉ

xml ๋””๋ ‰ํ† ๋ฆฌ์— ๊ฐœ๋ฐœ์ž ์ž„์˜์˜ xml ํŒŒ์ผ์„ ์ƒ์„ฑํ•œ๋‹ค. <network-security-config> ๋ฅผ ๋ฃจํŠธ ํƒœ๊ทธ๋กœ ์ž‘์„ฑํ•˜๊ณ  ์•ˆ ์ชฝ์˜ <domain> ์— http ํ†ต์‹ ์„ ํ—ˆ์šฉํ•˜๋Š” ๋„๋ฉ”์ธ ํ˜น์€ IP ์ฃผ์†๋ฅผ ๋“ฑ๋กํ•œ๋‹ค.


๐Ÿ’ฌ Network ์ •๋ณด ํ™•์ธ

๐Ÿ““ ConnectivityManager

โ• ํผ๋ฏธ์…˜

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  • ConnectivityManager ์„ ์ด์šฉํ•ด ๋„คํŠธ์›Œํฌ ์ƒํƒœ๋ฅผ ํŒŒ์•…

  • ์‹œ์Šคํ…œ ์„œ๋น„์Šค์ด๋‹ค.

๋„คํŠธ์›Œํฌ ์ƒํ™ฉ ํŒŒ์•…์˜ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ํด๋ž˜์Šค. ๋„คํŠธ์›Œํฌ ์ •๋ณด ํŒŒ์•…์„ ์œ„ํ•ด์„œ๋Š” ํผ๋ฏธ์…˜์ด ์š”๊ตฌ๋œ๋‹ค.

๐Ÿ“Œ getActiveNetwork()

val nw = connectivityManager.activeNetwork
val acNw = connectivityManager.getNetworkCapabilities(nw)
  • ConnectivityManager ์˜ getActiveNetwork() ์„ ์ด์šฉํ•ด Network ๊ฐ์ฒด๋ฅผ ํš๋“

  • Network ๊ฐ์ฒด๋ฅผ getNetworkCapabilities() ํ•จ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ง€์ •ํ•˜๋ฉด ํ˜„์žฌ ์ ‘์†๋œ ๋„คํŠธ์›Œํฌ ๋ง ์ •๋ณด๋ฅผ ํš๋“ ๊ฐ€๋Šฅ.

์‹ค์ œ ๋„คํŠธ์›Œํฌ๋ฅผ ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๋„คํŠธ์›Œํฌ ์ •๋ณด๊ฐ€ ๋‹ด๊ธด ๊ฐ์ฒด๋ฅผ ํš๋“ํ•ด getter ํ•จ์ˆ˜๋กœ ํ˜„์žฌ ๋„คํŠธ์›Œํฌ ์ƒํƒœ๋ฅผ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ“Œ hasTransport()

actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> {
	return "wifi online"
}
actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> {
	return "cellular available"
}
  • hasTransport() ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด ํ˜„์žฌ ํฐ์— ์™€์ดํŒŒ์ด์— ์ ‘์†๋œ ์ƒํƒœ์ธ์ง€ ์•„๋‹ˆ๋ฉด ์ดํ†ต์‚ฌ ๋ง์— ์ ‘์†๋œ ์ƒํƒœ์ธ์ง€๋ฅผ ํŒŒ์•…

๐Ÿงฉ ์‹ค์Šต ์˜ˆ์ œ

โ• ํผ๋ฏธ์…˜ ์ถ”๊ฐ€

  • Manifest.xml
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

๐ŸŽจ ์•กํ‹ฐ๋น„ํ‹ฐ ๋ ˆ์ด์•„์›ƒ

  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/resultView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20dp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

๐Ÿ“‚ ๋ฉ”์ธ ์†Œ์Šค ์ฝ”๋“œ

  • MainActivity.kt
package com.kotdev99.android.c85

class MainActivity : AppCompatActivity() {
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_main)

		val resultView = findViewById<TextView>(R.id.resultView)
		resultView.text = isNetworkAvailable()
	}

	private fun isNetworkAvailable(): String {
		val manager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
			val nw = manager.activeNetwork ?: return "offline"
			val actNw = manager.getNetworkCapabilities((nw)) ?: return "offline"
			return when {
				actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> {
					return "wifi online"
				}

				actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> {
					return "cellular online"
				}

				else -> "offline"
			}
		} else {
			if (manager.activeNetworkInfo?.isConnected ?: false) {
				return "online"
			} else {
				return "offline"
			}
		}
	}
}

๐Ÿ“ฒ ๊ฒฐ๊ณผ


๐Ÿ’ฌ Retrofit

๐Ÿ›  Retrofit ๊ตฌ์กฐ

Retrofit2 ๋Š” Square ์‚ฌ์—์„œ ๋งŒ๋“  HTTP ํ†ต์‹ ์„ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ˜น์€ ํ”„๋ ˆ์ž„์›Œํฌ

  • ๋‚ด๋ถ€์ ์œผ๋กœ okHttp ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  okHttp ๋ฅผ ์ด์šฉํ•˜์—ฌ ์„ค์ • ์ •๋ณด๋ฅผ ์ค„ ์ˆ˜ ์žˆ๋‹ค.

Retrofit ์˜ ๊ฐœ๋…์€ ๋„คํŠธ์›Œํ‚น ํ”„๋กœ๊ทธ๋žจ ์ฝ”๋“œ๋ฅผ ์ž๋™์œผ๋กœ ์ž‘์„ฑํ•ด ์ฃผ๋Š” ๊ฒƒ์ด๋‹ค.
๋„คํŠธ์›Œํ‚น์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋„คํŠธ์›Œํฌ ๊ด€๋ จ ์ฝ”๋“œ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค. ํ•˜์ง€๋งŒ Retrofit ์€ ์ด๋Ÿฌํ•œ ๊ธฐ๋ณธ ๋„คํŠธ์›Œํ‚น ์ฝ”๋“œ๋ฅผ ์ž๋™์œผ๋กœ ์ž‘์„ฑ ํ•ด์ค€๋‹ค. ๊ฐœ๋ฐœ์ž๋Š” ๋„คํŠธ์›Œํฌ์— ํ•„์š”ํ•œ ์ •๋ณด (URL/request/response ๋“ฑ) ๋งŒ Retrofit ์— ๋„˜๊ฒจ์ฃผ๋ฉด ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

โ“ ๊ทธ๋Ÿผ ์–ด๋–ป๊ฒŒ ์ •๋ณด๋ฅผ ์ „๋‹ฌํ• ๊นŒ?

๋„คํŠธ์›Œํฌ ์ •๋ณด๋Š” ์–ด๋…ธํ…Œ์ด์…˜ ์œผ๋กœ ์ „๋‹ฌํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ๊ฐœ๋ฐœ์ž๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋งŒ ๋งŒ๋“ค์–ด์„œ Retrofit ์— ๋“ฑ๋ก์‹œ์ผœ ๋ฒ„๋ฆฌ๋ฉด ๋์ด๋‹ค. ๊ทธ๋Ÿฌ๋ฉด Retrofit ์—์„œ ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„์ฒด๋ฅผ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด ์ค€๋‹ค.

๐Ÿงท dependency

implementation ("com.squareup.retrofit2:retrofit:2.9.0")
// Retrofit ํ•„์ˆ˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

implementation ("com.google.code.gson:gson:2.10.1")
// JSON ํ˜น์€ XML ํŒŒ์‹ฑ์„ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ (optional)

implementation ("com.squareup.retrofit2:converter-gson:2.9.0")
// ํŒŒ์‹ฑํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋ธ(VO ํด๋ž˜์Šค) ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ (optional)

์•ˆ๋“œ๋กœ์ด๋“œ ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— XML parser ํ˜น์€ JSON parser ๋ฅผ ์‚ฌ์šฉํ•ด๋„ ๋œ๋‹ค. ๊ทธ๋Ÿด ๊ฒฝ์šฐ ํŒŒ์‹ฑํ•œ ๋ฐ์ดํ„ฐ๋ฅผ VO ํด๋ž˜์Šค ๊ฐ์ฒด๋กœ ๋งŒ๋“ค์–ด์ฃผ๋Š” ์ฝ”๋“œ๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•ด ์ฃผ์–ด์•ผ ํ•œ๋‹ค.

์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŒŒ์‹ฑ๊ณผ VO ํด๋ž˜์Šค๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” ์ž‘์—…์„ ๊ฐ„์†Œํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค. ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๊ฐœ๋ฐœ์ž ์ทจํ–ฅ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. ์œ„์—์„œ๋Š” gson ์„ ์˜ˆ๋กœ ๋“ค์—ˆ๋‹ค.

๐Ÿ“‹ Parser ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

Gson : com.squareup.retrofit2:converter-gson

Jackson : com.squareup.retrofit2:converter-jackson

Moshi : com.squareup.retrofit2:converter-moshi

Protobuf : com.squareup.retrofit2:converter-protobuf

Wire : com.squareup.retrofit2:converter-wire

Simple XML : com.squareup.retrofit2:converter-simplexml

JAXB : com.squareup.retrofit2:converter-jaxb

Scalars (primitives, boxed, and String) : com.squareup.retrofit2:converter-scalars

๐Ÿ“‚ ๋ชจ๋ธ ํด๋ž˜์Šค

// ์„œ๋ฒ„ ์‚ฌ์ด๋“œ์—์„œ ๋„˜์–ด์˜ค๋Š” JSON

{
	"id": 3,
    "email": "kotdev.coder@reqres.in",
    "first_name": "kotdev",
    "last_name": "Lawson",
    "avatar": "https://reqres.in/img/faces/3-image.jpg"
}
// ๋ชจ๋ธ ํด๋ž˜์Šค

data class UserModel(

	var id: String,						// JSON ์˜ Key์™€ ๋™์ผํ•˜๊ฒŒ ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•ด์•ผ ํ•œ๋‹ค
    @SerializedName("first_name")		// ๋งŒ์•ฝ ์ž„์˜์˜ ๋ณ€์ˆ˜๋ช…์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด annotation ์ž‘์„ฑ
    var firstName: String,
    @SerializedName("last_name")
    var lastName: String,
    var avatar: String,
    
    var avatarBitmap: Bitmap
}
  • ๋ชจ๋ธ ํด๋ž˜์Šค๋ž€ ์„œ๋ฒ„์™€ ์ฃผ๊ณ  ๋ฐ›๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œํ˜„ํ•˜๋Š” ํด๋ž˜์Šค
    (๋ชจ๋ธ/VO/DTO ํด๋ž˜์Šค = ๋ญ๋ผ ๋ถ€๋ฅธ๋˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด๊ธฐ ์œ„ํ•œ ํด๋ž˜์Šค๋ฅผ ์˜๋ฏธ)

  • JSON, XML ๋ฐ์ดํ„ฐ๋ฅผ ํŒŒ์‹ฑํ•ด ๋ชจ๋ธ ํด๋ž˜์Šค ๊ฐ์ฒด์— ๋‹ด์•„์ฃผ๋Š” ๊ฒƒ์„ ์ž๋™ํ™”


๐Ÿ’ฌ Retrofit ํ™œ์šฉ

๐Ÿ““ ์„œ๋น„์Šค ์ธํ„ฐํŽ˜์ด์Šค

interface INetworkService {
	@GET("api/users")	// http ํ”„๋กœํ† ์ฝœ๊ณผ ์—ฐ๋™ํ•  URL ํ˜น์€ path ๋ฅผ ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ์ž‘์„ฑ
    fun doGetUserList(@Query("page") page: String): Call<UserListModel>	
    // ์„œ๋ฒ„์— ๋„˜๊ฒจ์—ฌํ•  ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋‹ค๋ฉด ๋งค๊ฐœ๋ณ€์ˆ˜์— Query ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ๋„˜๊ฒจ์ฃผ๋ฉด ๋œ๋‹ค
    // "page" ๊ฐ€ Key ๊ฐ’, page: String ๊ฐ€ Value ๊ฐ’
}
  • ๋„คํŠธ์›Œํฌ ํ†ต์‹ ์— ์ด์šฉ๋  ์ธํ„ฐํŽ˜์ด์Šค

  • ๋„คํŠธ์›Œํฌ ํ†ต์‹ ์ด ํ•„์š”ํ•œ ์ˆœ๊ฐ„ ํ˜ธ์ถœํ•  ํ•จ์ˆ˜๋ฅผ ์„ ์–ธ

โ“ Call ๊ฐ์ฒด๋ž€?

์‹ค์ œ ๋„คํŠธ์›Œํ‚น์ด ๊ฐ€๋Šฅํ•œ ๊ฐ์ฒด. ์ด Call ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„ enqueue() ํ•จ์ˆ˜๋กœ ๋„คํŠธ์›Œํ‚น์„ ํ•œ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ œ๋„ค๋ฆญ์œผ๋กœ ๋ฆฌํ„ด ํƒ€์ž…์œผ๋กœ์„œ ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  DTO ๊ฐ์ฒด๋ฅผ ๋ช…์‹œํ•ด ์ค€๋‹ค. ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์„œ๋ฒ„์—์„œ ๋„˜์–ด์˜จ ๋ฐ์ดํ„ฐ๋ฅผ ์ž๋™์œผ๋กœ parsing ๋ฐ converting ํ•˜์—ฌ DTO ๊ฐ์ฒด๋กœ ์ƒ์„ฑ ํ•ด์ฃผ๋ฉด, ์šฐ๋ฆฌ๋Š” ์–ด๋–ค DTO ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ธ์ง€๋งŒ ์ œ๋„ค๋ฆญ ํƒ€์ž…์œผ๋กœ ์•Œ๋ ค์ฃผ๋ฉด ๋œ๋‹ค.

๐Ÿ““ Retrofit ๊ฐ์ฒด

val retrofit: Retrofit
	get() = Retrofit.Builder()
    	.baseUrl("https://reqres.in/")	// ์„œ๋น„์Šค ์ธํ„ฐํŽ˜์ด์Šค์˜ ์–ด๋…ธํ…Œ์ด์…˜์— ์ž‘์„ฑํ•œ URL ์•ž ๋ฌธ์ž์—ด์„ ์ง€์ •
        .addConverterFactory(GsonConverterFactory.create())
        .build()
  • Retrofit ์˜ ์ดˆ๊ธฐ ์„ค์ •์„ ๋ชฉ์ 

  • baseUrl() ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด ์„œ๋ฒ„ ์—ฐ๋™์„ ์œ„ํ•œ URL ์„ ์„ค์ •
    ์ฆ‰, ์„œ๋น„์Šค ์ธํ„ฐํŽ˜์ด์Šค์˜ ์–ด๋…ธํ…Œ์ด์…˜ ์ฃผ์†Œ๋ฅผ path ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
    ๋งค๋ฒˆ ํ’€ ๋„๋ฉ”์ธ ์“ฐ๋Š” ๊ฑด ๊ท€์ฐฎ์œผ๋‹ˆ๊นŒ!

    baseUrl + @GET = "https://reqres.in/" + "api/users" = "https://reqres.in/api/users"

  • addConverterFactory() ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด converter ์ง€์ •

์ดˆ๊ธฐ ์„ค์ •์ด๋‹ˆ ์•ฑ์ด ์‹คํ–‰๋˜๋ฉด์„œ ๋”ฑ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰๋˜๋Š” ๊ณณ์— ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ด๋‹ค.

๐Ÿ““ ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„ ๊ฐ์ฒด ํš๋“

var networkService: INetworkService = retrofit.create(INetworkService::class.java)
  • Retrofit ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•ด ์„œ๋น„์Šค ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค์˜ ๊ฐ์ฒด๋ฅผ ํš๋“

์ด์ œ ์‹ค์ œ ๋„คํŠธ์›Œํ‚น์„ ํ•ด์•ผ ํ•œ๋‹ค. ๋„คํŠธ์›Œํ‚น์„ ์œ„ํ•ด์„œ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ๊ฐ์ฒด๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

Retrofit์˜ create() ํ•จ์ˆ˜์— ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋„ฃ์–ด์ค€๋‹ค. ๋ถ™์—ฌ์ง„ ์–ด๋…ธํ…Œ์ด์…˜์— ๋”ฐ๋ผ ๋„คํŠธ์›Œํ‚น ์ฝ”๋“œ๊ฐ€ ์ž‘์„ฑ๋œ ๊ฐ์ฒด๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค. ์ด์ œ ์šฐ๋ฆฐ ์ธํ„ฐํŽ˜์ด์Šค์— ์ž‘์„ฑํ•œ ํ•จ์ˆ˜๋งŒ ํ˜ธ์ถœํ•˜๋ฉด ๋œ๋‹ค.

๐Ÿ““ ๋„คํŠธ์›Œํ‚น

val userListCall = networkService.doGetUserList("1")	// ๊ตฌํ˜„์ฒด์˜ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ด์„œ Call ๊ฐ์ฒด ์ƒ์„ฑ

// Call ๊ฐ์ฒด.enqueue ํ•˜๋Š” ์ˆœ๊ฐ„ ๋„คํŠธ์›Œํ‚น์ด ๋œ๋‹ค
// ๋งค๊ฐœ๋ณ€์ˆ˜์— ์ฝœ๋ฐฑ์„ ์ „๋‹ฌ
userListCall.enqueue(object : Callback<UserListModel> {

	// ๋„คํŠธ์›Œํ‚น์ด ์„ฑ๊ณตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ •์ƒ์ ์œผ๋กœ ๋ฐ›์€ ์ˆœ๊ฐ„ ์ž๋™ ์ฝœ
    // ๋งค๊ฐœ๋ณ€์ˆ˜ response ๋กœ ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ๊ฐ€ ๋„˜์–ด์˜จ๋‹ค
	override fun onResponse(call: Call<UserListModel>, response: Response<UserListModel>) {
    
    	val userList = response.body()

}
    // ๋„คํŠธ์›Œํ‚น์ด ์‹คํŒจํ–ˆ์„ ๊ฒฝ์šฐ ์ž๋™ ์ฝœ
    // ์˜ค๋ฅ˜๊ฐ€ ๋‚ฌ๋˜ ๊ฐ์ฒด๋„ ๋งค๊ฐœ๋ณ€์ˆ˜ t ๋กœ ์ „๋‹ฌ์„ ํ•˜๊ธด ํ•œ๋‹ค
	override fun onFailure(call: Call<UserListModel>, t: Throwable) {	
		call.cancel()
	}
})

๐Ÿงฉ ์‹ค์Šต ์˜ˆ์ œ

๋ฆฌ์ŠคํŠธ๋ทฐ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„์—์„œ ๊ฐ€์ ธ์˜ค์ž!

๐Ÿงท dependency

  • build.gradle.kts
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.google.code.gson:gson:2.10.1")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")

โ• ํผ๋ฏธ์…˜ ์ถ”๊ฐ€

  • Manifest.xml
<uses-permission android:name="android.permission.INTERNET" />

๐ŸŽจ ์•กํ‹ฐ๋น„ํ‹ฐ ๋ ˆ์ด์•„์›ƒ

  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/listView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

๐Ÿงฎ ๋ชจ๋ธ ํด๋ž˜์Šค

ํ•˜๋‚˜์˜ ์œ ์ € ์ •๋ณด๋ฅผ ๋‹ด๋Š” DTO ํด๋ž˜์Šค

  • UserModel.kt
package com.kotdev99.android.c87

data class UserModel(
	@SerializedName("first_name")
	var firstName: String,
	@SerializedName("last_name")
	var lastName: String
)

JSON ์•ˆ์˜ ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด๋Š” VO ํด๋ž˜์Šค

  • UserListModel.kt
package com.kotdev99.android.c87

data class UserListModel(
	var data: List<UserModel>?
	// total ํŽ˜์ด์ง€ ์ˆ˜ ๋“ฑ์˜ ์ •๋ณด๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค
)

๐Ÿ“€ ์„œ๋น„์Šค ์ธํ„ฐํŽ˜์ด์Šค

๋„คํŠธ์›Œํ‚น ์‹œ ํ˜ธ์ถœํ•  ํ•จ์ˆ˜๋ฅผ ๊ฐ–๋Š” ์ธํ„ฐํŽ˜์ด์Šค

  • INetworkService.kt
package com.kotdev99.android.c87

interface INetworkService {
	@GET("api/users")
	fun doGetUserList(@Query("page") page: String): Call<UserListModel>
}

๐Ÿ“‚ ๋ฉ”์ธ ์†Œ์Šค ์ฝ”๋“œ

  • MainActivity.kt
package com.kotdev99.android.c87

class MainActivity : AppCompatActivity() {

	// Retrofit ์ดˆ๊ธฐํ™”
	private val retrofit: Retrofit
		get() = Retrofit.Builder()
			.baseUrl("https://reqres.in/")
			.addConverterFactory(GsonConverterFactory.create())
			.build()

	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_main)

		val listView = findViewById<ListView>(R.id.listView)

		// Retrofit ์— ์ธํ„ฐํŽ˜์ด์Šค ๋“ฑ๋ก
		var networkService = retrofit.create(INetworkService::class.java)
		// Call ๊ฐ์ฒด ํš๋“
		val call = networkService.doGetUserList("1")

		// ์ฝœ๋ฐฑ์œผ๋กœ ๊ฒฐ๊ณผ๊ฐ’์„ ๋ฐ›๋Š”๋‹ค
		call.enqueue(object : Callback<UserListModel> {
			override fun onResponse(call: Call<UserListModel>, response: Response<UserListModel>) {
				val userList = response.body()
				val mutableList = mutableListOf<Map<String, String>>()
				userList?.data?.forEach {
					val map = mapOf("firstName" to it.firstName, "lastName" to it.lastName)
					mutableList.add(map)
				}
				val adapter = SimpleAdapter(
					this@MainActivity,
					mutableList,
					android.R.layout.simple_expandable_list_item_2,
					arrayOf("firstName", "lastName"),
					intArrayOf(android.R.id.text1, android.R.id.text2)
				)
				listView.adapter = adapter
			}

			override fun onFailure(call: Call<UserListModel>, t: Throwable) {
				call.cancel()
			}
		})
	}
}

๐Ÿ“ฒ ๊ฒฐ๊ณผ


๐Ÿ’ฌ Glide

โ“ Glide ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ 

์„œ๋ฒ„์—์„œ JSON ํ˜น์€ XML ๋งŒํผ ๋งŽ์ด ๋„˜์–ด์˜ค๋Š” ๊ฒƒ์ด ์ด๋ฏธ์ง€์ด๋‹ค.

์ด๋ฏธ์ง€๋Š” ๋ฐ”์ดํŠธ ๋ฐ์ดํ„ฐ์ด๋‹ค. ๊ทธ๋ž˜์„œ ํ”ํžˆ ์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ๋ผ๊ณ  ์ด์•ผ๊ธฐ ํ•œ๋‹ค.

โ“ ๊ทธ๋Ÿผ ์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ๋ฅผ ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ• ๊นŒ?

Java ์˜ ์ฝ”์–ด API ๋กœ ๊ฐ€๋Šฅํ•˜๋‹ค. Retrofit ์œผ๋กœ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค. ํ•˜์ง€๋งŒ ์ด๋ฏธ์ง€๋งŒ์„ ์œ„ํ•œ ์ „๋ฌธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์ด ์กด์žฌํ•œ๋‹ค. ๋งŒ์•ฝ ์ด๋ฏธ์ง€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ์ƒํ™ฉ์— ๋”ฐ๋ฅธ ์ฒ˜๋ฆฌ๋ฅผ ์ผ์ผ์ด ์ž‘์„ฑํ•ด ์ฃผ์–ด์•ผ ํ•œ๋‹ค.

์ด๋ฏธ์ง€๊ฐ€ ํŒŒ์ผ or ๋ฆฌ์†Œ์Šค or ๋‹ค์šด๋กœ๋“œ?
์‚ฌ์ด์ฆˆ๋กœ ์ธํ•œ OOM.
์ด๋ฏธ์ง€ ๋กœ๋”ฉ ์‹œ๊ฐ„์ด ๊ธธ ๊ฒฝ์šฐ placeholder, ๊ทธ๋ฆฌ๊ณ  ๋กœ๋”ฉ์ด ๋๋‚ฌ์„ ๋•Œ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ.
์ด๋ฏธ์ง€ ๋กœ๋”ฉ ์‹คํŒจ ์‹œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ.
etc...

๊ทธ๋ƒฅ ์ด๋ฏธ์ง€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉํ•˜์ž.

๐Ÿงท dependency

implementation ("com.github.bumptech.glide:glide:4.16.0")
  • ๊ตฌ๊ธ€์—์„œ ๋งŒ๋“  ์ด๋ฏธ์ง€ ํ•ธ๋“ค๋ง์„ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

  • ๋ฆฌ์†Œ์Šค ์ด๋ฏธ์ง€, ํŒŒ์ผ ์ด๋ฏธ์ง€, ๋„คํŠธ์›Œํฌ ์ด๋ฏธ์ง€ ํš๋“

  • ์ด๋ฏธ์ง€์˜ ํฌ๊ธฐ ์กฐ์ •

  • ๋กœ๋”ฉ ์ด๋ฏธ์ง€ ํ‘œ์‹œ

  • ์—๋Ÿฌ ์ด๋ฏธ์ง€ ํ‘œ์‹œ

๐Ÿ“‚ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•

// ์ด๋ฏธ์ง€๋ฅผ ImageView ์— ์ถœ๋ ฅ

Glide.with(this)
	.load(R.drawable.seoul)		// ๋ฆฌ์†Œ์Šค ์ด๋ฏธ์ง€
    .into(resultView)			// ์ด๋ฏธ์ง€๋ฅผ ์ถœ๋ ฅํ•˜๋Š” View


Glide.with(this)
	.load(url)			// ๋„คํŠธ์›Œํฌ ์ด๋ฏธ์ง€
    .into(resultView)	// ์ด๋ฏธ์ง€๋ฅผ ์ถœ๋ ฅํ•˜๋Š” View
    
// File์˜ ๊ฒฝ์šฐ์—๋„ .load ์— ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ์ฃผ๋ฉด ๋œ๋‹ค.
// ํŠน์ • ํฌ๊ธฐ๋กœ ์ด๋ฏธ์ง€ ๋กœ๋”ฉ (OOM)

Glide.with(this)
	.load(R.drawable.seoul)		
    .override(200, 200)			// ์ด๋ฏธ์ง€ ์‚ฌ์ด์ฆˆ
    .into(resultView)			
// ํŠน์ • ํฌ๊ธฐ๋กœ ์ด๋ฏธ์ง€ ๋กœ๋”ฉ (OOM)

Glide.with(this)
	.load(R.drawable.seoul)		
    .override(200, 200)			// ์ด๋ฏธ์ง€ ์‚ฌ์ด์ฆˆ
    .into(resultView)			
// ๋กœ๋”ฉ, ์—๋Ÿฌ ์ด๋ฏธ์ง€ ์ถœ๋ ฅ

Glide.with(this)
	.load(R.drawable.seoul)		
    .override(200, 200)
    .placeholder(R.drawable.loading)	// ๋กœ๋”ฉ ์ด๋ฏธ์ง€ (์ฃผ๋กœ GIF ์‚ฌ์šฉ)
    .error(R.drawable.error)			// ์—๋Ÿฌ ์ด๋ฏธ์ง€
    .into(resultView)			
  • ๋ณด๋‹ค์‹œํ”ผ ๊ทธ๋ƒฅ ์‚ฌ์šฉํ•˜๊ธฐ๊ฐ€ ํŽธํ•˜๋‹ค. ๊ทธ๋ž˜์„œ ์ธ๊ธฐ๊ฐ€ ๋งŽ๋‹ค ๐Ÿ‘

๐Ÿงฉ ์‹ค์Šต ์˜ˆ์ œ

๐Ÿงท dependency

  • build.gradle.kts
implementation ("com.github.bumptech.glide:glide:4.16.0")

โ• ํผ๋ฏธ์…˜ ์ถ”๊ฐ€

  • Manifest.xml
<uses-permission android:name="android.permission.INTERNET" />

๐ŸŽจ ์•กํ‹ฐ๋น„ํ‹ฐ ๋ ˆ์ด์•„์›ƒ

  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/resultView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

๐Ÿ“‚ ๋ฉ”์ธ ์ฝ”๋“œ

  • MainActivity.kt
package com.kotdev99.android.c88

class MainActivity : AppCompatActivity() {
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_main)

		val imageView = findViewById<ImageView>(R.id.resultView)

		Glide.with(this)
			.load("https://www.google.co.kr/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png")
			.override(200, 200)
			.placeholder(R.drawable.loading)
			.error(R.drawable.error)
			.into(imageView)
	}
}

๐Ÿ“ฒ ๊ฒฐ๊ณผ

profile
์‘์•  ๋‚˜ ์•„๊ธฐ ๋‰ด๋น„

0๊ฐœ์˜ ๋Œ“๊ธ€